From 108d38d4ea2907c1f123b795ddd9c56a42446042 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Loren=20=E2=98=BA=EF=B8=8F?= <251288+lorensr@users.noreply.github.com> Date: Fri, 15 Apr 2022 13:59:38 -0500 Subject: [PATCH] =?UTF-8?q?=F0=9F=A4=96=20Merge=20PR=20#59453=20Add=20addT?= =?UTF-8?q?ype=20to=20ejson=20by=20@lorensr?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add addType to ejson * Update version in header * Fix TODO --- types/ejson/ejson-tests.ts | 101 ++++++++++++++++++++++++------------- types/ejson/index.d.ts | 56 ++++++++++++++++++-- 2 files changed, 118 insertions(+), 39 deletions(-) diff --git a/types/ejson/ejson-tests.ts b/types/ejson/ejson-tests.ts index 2a9663acb2..f50874eb5e 100644 --- a/types/ejson/ejson-tests.ts +++ b/types/ejson/ejson-tests.ts @@ -1,70 +1,101 @@ import { - clone as importedClone, - parse as importedParse, - stringify as importedStringify, - toJSONValue as importedToJSONValue, - fromJSONValue as importedFromJSONValue, - isBinary as importedIsBinary, - newBinary as importedNewBinary, - equals as importedEquals -} from "ejson"; + addType as importedAddType, + clone as importedClone, + CustomType, + equals as importedEquals, + fromJSONValue as importedFromJSONValue, + isBinary as importedIsBinary, + newBinary as importedNewBinary, + parse as importedParse, + stringify as importedStringify, + toJSONValue as importedToJSONValue, +} from 'ejson'; function testImportedClone() { - var obj: Object = { - a: "a" - }; - var retval: Object = importedClone(obj); + var obj: Object = { + a: 'a', + }; + var retval: Object = importedClone(obj); - var str: string = "as"; - var retval2: string = importedClone(str); + var str: string = 'as'; + var retval2: string = importedClone(str); } function testParse() { - var str: string = '{a:"a"}'; - importedParse(str); + var str: string = '{a:"a"}'; + importedParse(str); } function testStringify() { - var obj: any = {a:"a"}; - var retval: string = importedStringify(obj); + var obj: any = { a: 'a' }; + var retval: string = importedStringify(obj); } function testStringifyIndent() { - var obj: any = {a: "a"}; - var retval: string = importedStringify(obj, { indent: true }); + var obj: any = { a: 'a' }; + var retval: string = importedStringify(obj, { indent: true }); } function testStringifyCanonical() { - var obj: any = {a: "a"}; - var retval: string = importedStringify(obj, { canonical: true }); + var obj: any = { a: 'a' }; + var retval: string = importedStringify(obj, { canonical: true }); } function testStringifyOptions() { - var obj: any = {a: "a"}; - var retval: string = importedStringify(obj, { canonical: true, indent: 'hello' }); + var obj: any = { a: 'a' }; + var retval: string = importedStringify(obj, { canonical: true, indent: 'hello' }); } function testToJSONValue() { - var obj: any = {a:"a"}; - var retval: string = importedToJSONValue(obj); + var obj: any = { a: 'a' }; + var retval: string = importedToJSONValue(obj); } function testFromJSONValue() { - var str: string = '{a:"a"}'; - importedFromJSONValue(str); + var str: string = '{a:"a"}'; + importedFromJSONValue(str); } function testIsBinary() { - var val: any = 'sasda'; - var retval: boolean = importedIsBinary(val); + var val: any = 'sasda'; + var retval: boolean = importedIsBinary(val); } function testNewBinary() { - var retval: Uint8Array = importedNewBinary(3); + var retval: Uint8Array = importedNewBinary(3); } function testEquals() { - var a: any; - var b: any; - var retval: boolean = importedEquals(a,b); + var a: any; + var b: any; + var retval: boolean = importedEquals(a, b); +} + +class MyInt implements CustomType { + constructor(public value: number) {} + + typeName() { + return 'MyInt'; + } + + toJSONValue() { + return this.value.toString(); + } + + static fromJSONValue(jsonValue: unknown) { + const value = parseInt(jsonValue as string, 10); + return new MyInt(value); + } + + clone(): CustomType { + return new MyInt(this.value); + } + + equals(other: CustomType): boolean { + return this.value === (other as MyInt).value; + } +} + +function testAddType() { + importedAddType('MyInt', MyInt.fromJSONValue); } diff --git a/types/ejson/index.d.ts b/types/ejson/index.d.ts index 3a35636095..6755717534 100644 --- a/types/ejson/index.d.ts +++ b/types/ejson/index.d.ts @@ -1,15 +1,15 @@ -// Type definitions for ejson v2.1.2 +// Type definitions for ejson v2.2.2 // Project: https://www.npmjs.com/package/ejson // Definitions by: Shantanu Bhadoria // Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped interface StringifyOptions { - canonical?: boolean; - indent?: boolean|number|string; + canonical?: boolean; + indent?: boolean | number | string; } interface CloneOptions { - keyOrderSensitive: boolean; + keyOrderSensitive: boolean; } export function clone(obj: T): T; @@ -21,3 +21,51 @@ export function fromJSONValue(obj: string): any; export function isBinary(value: any): boolean; export function newBinary(len: number): Uint8Array; export function equals(a: any, b: any, options?: CloneOptions): boolean; + +/** + * The interface that a class must satisfy to be able to become an + * EJSON custom type via EJSON.addType. + */ +interface CustomType { + /** + * Return the tag used to identify this type. This must match the + * tag used to register this type. + */ + typeName: () => string; + + /* Serialize this instance into a JSON-compatible value. */ + toJSONValue: () => any; + + /** + * Return a value `val` such that `this.equals(val)` is true, and + * modifications to `val` do not affect `this` and vice versa. + */ + clone?: () => CustomType; + + /* Return `true` if `other` has a value equal to `this`; `false` otherwise. */ + equals?: (other: CustomType) => boolean; +} + +/** + * Add a custom type, using a method of your choice to get to and + * from a basic JSON-able representation. The factory argument + * is a function of JSON-able --> your object + * The type you add must have: + * - A toJSONValue() method, so that Meteor can serialize it + * - a typeName() method, to show how to look it up in our type table. + * It is okay if these methods are monkey-patched on. + * EJSON.clone will use toJSONValue and the given factory to produce + * a clone, but you may specify a method clone() that will be + * used instead. + * Similarly, EJSON.equals will use toJSONValue to make comparisons, + * but you may provide a method equals() instead. + * + * @param name A tag for your custom type; must be unique among + * custom data types defined in your project, and must + * match the result of your type's `typeName` method. + * @param factory A function that deserializes a JSON-compatible + * value into an instance of your type. This should + * match the serialization performed by your + * type's `toJSONValue` method. + */ +export function addType(name: string, factory: (jsonValue: unknown) => CustomType): void;