diff --git a/package.json b/package.json index fdec79f5d0..6dd771b068 100644 --- a/package.json +++ b/package.json @@ -114,11 +114,6 @@ "upgrade:interactive": "npx npm-check-updates@16.14 --interactive" }, "peerDependencies": { - "@jsonjoy.com/base64": "^1.1.2", - "@jsonjoy.com/json-expression": "^1.0.0", - "@jsonjoy.com/json-pack": "^1.0.4", - "@jsonjoy.com/json-pointer": "^1.0.0", - "@jsonjoy.com/util": "^1.3.0", "rxjs": "7", "tslib": "2" }, @@ -128,6 +123,12 @@ } }, "dependencies": { + "@jsonjoy.com/base64": "^1.1.2", + "@jsonjoy.com/json-expression": "^1.0.0", + "@jsonjoy.com/json-pack": "^1.1.0", + "@jsonjoy.com/json-pointer": "^1.0.0", + "@jsonjoy.com/json-type": "^1.0.0", + "@jsonjoy.com/util": "^1.4.0", "arg": "^5.0.2", "hyperdyperid": "^1.2.0", "sonic-forest": "^1.0.3", @@ -135,11 +136,6 @@ "tree-dump": "^1.0.2" }, "devDependencies": { - "@jsonjoy.com/base64": "^1.1.2", - "@jsonjoy.com/json-expression": "^1.0.0", - "@jsonjoy.com/json-pack": "^1.0.4", - "@jsonjoy.com/json-pointer": "^1.0.0", - "@jsonjoy.com/util": "^1.3.0", "@types/benchmark": "^2.1.5", "@types/jest": "^29.5.12", "benchmark": "^2.1.4", @@ -190,7 +186,6 @@ "", "demo", "json-cli", - "json-clone", "json-crdt-patch", "json-crdt-extensions", "json-crdt-peritext-ui", @@ -199,12 +194,8 @@ "json-ot", "json-patch-ot", "json-patch", - "json-schema", - "json-size", "json-stable", "json-text", - "json-type", - "json-type-value", "json-walk", "util" ] diff --git a/src/index.ts b/src/index.ts index ab2584d75f..3fa92a8370 100644 --- a/src/index.ts +++ b/src/index.ts @@ -12,4 +12,3 @@ export type * from './json-crdt'; export type * from './json-crdt-patch'; export type * from './json-crdt-extensions'; export type * from './json-patch/types'; -export type * from './json-schema/types'; diff --git a/src/json-binary/index.ts b/src/json-binary/index.ts deleted file mode 100644 index 4b173b2b67..0000000000 --- a/src/json-binary/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from '@jsonjoy.com/json-pack/lib/json-binary'; diff --git a/src/json-cli/json-patch-test.ts b/src/json-cli/json-patch-test.ts index 78a92b312f..3fbe72bc4d 100644 --- a/src/json-cli/json-patch-test.ts +++ b/src/json-cli/json-patch-test.ts @@ -2,7 +2,7 @@ import {spawnSync} from 'child_process'; import {validateOperation} from '../json-patch'; -import {deepEqual} from '../json-equal/deepEqual'; +import {deepEqual} from '@jsonjoy.com/util/lib/json-equal/deepEqual'; import {testSuites} from './test/suites'; const bin = String(process.argv[2]); diff --git a/src/json-cli/json-pointer-test.ts b/src/json-cli/json-pointer-test.ts index df9cadc351..302ec05a32 100644 --- a/src/json-cli/json-pointer-test.ts +++ b/src/json-cli/json-pointer-test.ts @@ -2,7 +2,7 @@ import {spawnSync} from 'child_process'; import {testCases} from './json-pointer-testCases'; -import {deepEqual} from '../json-equal/deepEqual'; +import {deepEqual} from '@jsonjoy.com/util/lib/json-equal/deepEqual'; const bin = String(process.argv[2]); diff --git a/src/json-cli/json-unpack.ts b/src/json-cli/json-unpack.ts index 5cba8c0159..86b92adf67 100644 --- a/src/json-cli/json-unpack.ts +++ b/src/json-cli/json-unpack.ts @@ -1,7 +1,7 @@ import {readFileSync} from 'fs'; import {MsgPackDecoderFast} from '@jsonjoy.com/json-pack/lib/msgpack'; import {CborDecoder} from '@jsonjoy.com/json-pack/lib/cbor/CborDecoder'; -import * as JSONB from '../json-binary'; +import * as JSONB from '@jsonjoy.com/json-pack/lib/json-binary'; import arg from 'arg'; const args = arg( diff --git a/src/json-cli/test/op.replace.tests.json.ts b/src/json-cli/test/op.replace.tests.json.ts index 451eb7036e..5c12f2cb81 100644 --- a/src/json-cli/test/op.replace.tests.json.ts +++ b/src/json-cli/test/op.replace.tests.json.ts @@ -1,4 +1,4 @@ -import {clone as deepClone} from '../../json-clone/clone'; +import {clone as deepClone} from '@jsonjoy.com/util/lib/json-clone/clone'; import {TestCase} from './types'; const values: [string, unknown][] = [ diff --git a/src/json-clone/README.md b/src/json-clone/README.md deleted file mode 100644 index 853d971d53..0000000000 --- a/src/json-clone/README.md +++ /dev/null @@ -1,28 +0,0 @@ -# json-clone - -Provides small and fast deep cloning functions. - -- `clone()` — deeply clones a JSON-like value. -- `cloneBinary()` — same as `clone()` but also supports `Uint8Array` objects. - -```ts -import {cloneBinary} from 'json-joy/lib/json-clone'; - -const obj = {foo: new Uint8Array([1, 2, 3])}; -const cloned = cloneBinary(obj); - -isDeepEqual(obj, cloned); // true -obj === cloned; // false -obj.foo === cloned.foo; // false -``` - -## Benchmarks - -``` -node benchmarks/json-clone/main.js -json-joy/json-clone clone() x 2,015,507 ops/sec ±1.52% (100 runs sampled) -JSON.parse(JSON.stringify()) x 410,189 ops/sec ±0.94% (98 runs sampled) -v8.deserialize(v8.serialize(obj)) x 146,059 ops/sec ±2.16% (79 runs sampled) -lodash x 582,504 ops/sec ±0.68% (97 runs sampled) -Fastest is json-joy/json-clone clone() -``` diff --git a/src/json-clone/__bench__/main.ts b/src/json-clone/__bench__/main.ts deleted file mode 100644 index 2881bc6a6b..0000000000 --- a/src/json-clone/__bench__/main.ts +++ /dev/null @@ -1,41 +0,0 @@ -/* tslint:disable no-console */ - -import * as Benchmark from 'benchmark'; -import {clone} from '..'; -import {cloneBinary} from '..'; -const v8 = require('v8'); -const lodashClone = require('lodash/cloneDeep'); - -const patch = [ - {op: 'add', path: '/foo/baz', value: 666}, - {op: 'add', path: '/foo/bx', value: 666}, - {op: 'add', path: '/asdf', value: 'asdfadf asdf'}, - {op: 'move', path: '/arr/0', from: '/arr/1'}, - {op: 'replace', path: '/foo/baz', value: 'lorem ipsum'}, -]; - -const suite = new Benchmark.Suite(); - -suite - .add(`json-joy/json-clone clone()`, () => { - clone(patch); - }) - .add(`json-joy/json-clone cloneBinary()`, () => { - cloneBinary(patch); - }) - .add(`JSON.parse(JSON.stringify())`, () => { - JSON.parse(JSON.stringify(patch)); - }) - .add(`v8.deserialize(v8.serialize(obj))`, () => { - v8.deserialize(v8.serialize(patch)); - }) - .add(`lodash/cloneDeep`, () => { - lodashClone(patch); - }) - .on('cycle', (event: any) => { - console.log(String(event.target)); - }) - .on('complete', () => { - console.log('Fastest is ' + suite.filter('fastest').map('name')); - }) - .run(); diff --git a/src/json-clone/__tests__/clone.spec.ts b/src/json-clone/__tests__/clone.spec.ts deleted file mode 100644 index 00754000df..0000000000 --- a/src/json-clone/__tests__/clone.spec.ts +++ /dev/null @@ -1,10 +0,0 @@ -import {documents} from '../../__tests__/json-documents'; -import {clone} from '../clone'; - -for (const {name, json, only} of [...documents]) { - (only ? test.only : test)(name, () => { - const cloned = clone(json); - if (cloned && typeof cloned === 'object') expect(cloned).not.toBe(json); - expect(cloned).toStrictEqual(json); - }); -} diff --git a/src/json-clone/__tests__/cloneBinary.spec.ts b/src/json-clone/__tests__/cloneBinary.spec.ts deleted file mode 100644 index 974195d968..0000000000 --- a/src/json-clone/__tests__/cloneBinary.spec.ts +++ /dev/null @@ -1,24 +0,0 @@ -import {documents} from '../../__tests__/json-documents'; -import {binaryDocuments} from '../../__tests__/binary-documents'; -import {cloneBinary} from '../cloneBinary'; - -describe('automated', () => { - for (const {name, json, only} of [...documents, ...binaryDocuments]) { - (only ? test.only : test)(name, () => { - const cloned = cloneBinary(json); - if (cloned && typeof cloned === 'object') expect(cloned).not.toBe(json); - expect(cloned).toStrictEqual(json); - }); - } -}); - -test('deep copies binary contents', () => { - const buf = new Uint8Array([1, 2, 3]); - const obj = {foo: buf}; - const cloned = cloneBinary(obj); - expect(cloned).toStrictEqual(obj); - expect((cloned as any).foo).not.toBe(obj.foo); - obj.foo[1] = 5; - expect(obj.foo[1]).toBe(5); - expect((cloned as any).foo[1]).toBe(2); -}); diff --git a/src/json-clone/clone.ts b/src/json-clone/clone.ts deleted file mode 100644 index 2e95c1f16b..0000000000 --- a/src/json-clone/clone.ts +++ /dev/null @@ -1,28 +0,0 @@ -const {isArray} = Array; -const objectKeys = Object.keys; - -/** - * Creates a deep clone of any JSON-like object. - * - * @param obj Any plain POJO object. - * @returns A deep copy of the object. - */ -export const clone = (obj: T): T => { - if (!obj) return obj; - if (isArray(obj)) { - const arr: unknown[] = []; - const length = obj.length; - for (let i = 0; i < length; i++) arr.push(clone(obj[i])); - return arr as unknown as T; - } else if (typeof obj === 'object') { - const keys = objectKeys(obj!); - const length = keys.length; - const newObject: any = {}; - for (let i = 0; i < length; i++) { - const key = keys[i]; - newObject[key] = clone((obj as any)[key]); - } - return newObject; - } - return obj; -}; diff --git a/src/json-clone/cloneBinary.ts b/src/json-clone/cloneBinary.ts deleted file mode 100644 index 4d5725902c..0000000000 --- a/src/json-clone/cloneBinary.ts +++ /dev/null @@ -1,31 +0,0 @@ -import {isUint8Array} from '@jsonjoy.com/util/lib/buffers/isUint8Array'; - -const {isArray} = Array; -const objectKeys = Object.keys; - -/** - * Creates a deep clone of any JSON-like object. - * - * @param obj Any plain POJO object. - * @returns A deep copy of the object. - */ -export const cloneBinary = (obj: T): T => { - if (!obj) return obj; - if (isArray(obj)) { - const arr: unknown[] = []; - const length = obj.length; - for (let i = 0; i < length; i++) arr.push(cloneBinary(obj[i])); - return arr as unknown as T; - } else if (typeof obj === 'object') { - if (isUint8Array(obj)) return new Uint8Array(obj) as unknown as T; - const keys = objectKeys(obj!); - const length = keys.length; - const newObject: any = {}; - for (let i = 0; i < length; i++) { - const key = keys[i]; - newObject[key] = cloneBinary((obj as any)[key]); - } - return newObject; - } - return obj; -}; diff --git a/src/json-clone/index.ts b/src/json-clone/index.ts deleted file mode 100644 index 76660194a3..0000000000 --- a/src/json-clone/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './clone'; -export * from './cloneBinary'; diff --git a/src/json-crdt-extensions/quill-delta/QuillDeltaNode.ts b/src/json-crdt-extensions/quill-delta/QuillDeltaNode.ts index 897f722f93..9d1164250e 100644 --- a/src/json-crdt-extensions/quill-delta/QuillDeltaNode.ts +++ b/src/json-crdt-extensions/quill-delta/QuillDeltaNode.ts @@ -1,5 +1,5 @@ import {isEmpty} from '@jsonjoy.com/util/lib/isEmpty'; -import {deepEqual} from '../../json-equal/deepEqual'; +import {deepEqual} from '@jsonjoy.com/util/lib/json-equal/deepEqual'; import {StrNode} from '../../json-crdt/nodes/str/StrNode'; import {ArrNode} from '../../json-crdt/nodes/arr/ArrNode'; import {Peritext} from '../peritext'; diff --git a/src/json-crdt-extensions/quill-delta/__tests__/QuillDeltaFuzzer.ts b/src/json-crdt-extensions/quill-delta/__tests__/QuillDeltaFuzzer.ts index 3a9c406021..a4141fd689 100644 --- a/src/json-crdt-extensions/quill-delta/__tests__/QuillDeltaFuzzer.ts +++ b/src/json-crdt-extensions/quill-delta/__tests__/QuillDeltaFuzzer.ts @@ -3,7 +3,7 @@ import {randomU32} from 'hyperdyperid/lib/randomU32'; import {Fuzzer} from '@jsonjoy.com/util/lib/Fuzzer'; import {isEmpty} from '@jsonjoy.com/util/lib/isEmpty'; import {QuillDeltaAttributes, QuillDeltaOp, QuillDeltaOpInsert, QuillDeltaOpRetain, QuillTrace} from '../types'; -import {RandomJson} from '../../../json-random'; +import {RandomJson} from '@jsonjoy.com/util/lib/json-random'; import {removeErasures} from '../util'; export interface QuillDeltaFuzzerOptions { diff --git a/src/json-crdt-extensions/quill-delta/__tests__/fuzzing.yjs.spec.ts b/src/json-crdt-extensions/quill-delta/__tests__/fuzzing.yjs.spec.ts index 103236d4e7..83cb44abd7 100644 --- a/src/json-crdt-extensions/quill-delta/__tests__/fuzzing.yjs.spec.ts +++ b/src/json-crdt-extensions/quill-delta/__tests__/fuzzing.yjs.spec.ts @@ -1,8 +1,8 @@ import {QuillDeltaFuzzer} from './QuillDeltaFuzzer'; import {Doc as YDoc} from 'yjs'; import {QuillDeltaOp, QuillDeltaOpDelete} from '../types'; -import {deepEqual} from '../../../json-equal/deepEqual'; -import {clone} from '../../../json-clone'; +import {deepEqual} from '@jsonjoy.com/util/lib/json-equal/deepEqual'; +import {clone} from '@jsonjoy.com/util/lib/json-clone'; const normalizeDelta = (delta: QuillDeltaOp[]): QuillDeltaOp[] => { const length = delta.length; diff --git a/src/json-crdt-patch/codec/__tests__/PatchFuzzer.ts b/src/json-crdt-patch/codec/__tests__/PatchFuzzer.ts index 9bfe94c1bc..f0ba698ce4 100644 --- a/src/json-crdt-patch/codec/__tests__/PatchFuzzer.ts +++ b/src/json-crdt-patch/codec/__tests__/PatchFuzzer.ts @@ -1,4 +1,4 @@ -import {RandomJson} from '../../../json-random'; +import {RandomJson} from '@jsonjoy.com/util/lib/json-random'; import {Fuzzer} from '@jsonjoy.com/util/lib/Fuzzer'; import {interval, ITimestampStruct, Timespan, ClockVector, ServerClockVector, ts} from '../../clock'; import {SESSION} from '../../constants'; diff --git a/src/json-crdt/__tests__/fuzzer/JsonCrdtFuzzer.ts b/src/json-crdt/__tests__/fuzzer/JsonCrdtFuzzer.ts index ff1b5a26d7..7ee35514c6 100644 --- a/src/json-crdt/__tests__/fuzzer/JsonCrdtFuzzer.ts +++ b/src/json-crdt/__tests__/fuzzer/JsonCrdtFuzzer.ts @@ -2,7 +2,7 @@ import {Model} from '../../model/Model'; import {SessionLogical} from './SessionLogical'; import {Picker} from './Picker'; import {FuzzerOptions} from './types'; -import {RandomJson} from '../../../json-random/RandomJson'; +import {RandomJson} from '@jsonjoy.com/util/lib/json-random/RandomJson'; import {generateInteger} from './util'; import {PatchBuilder} from '../../../json-crdt-patch/PatchBuilder'; import {Patch} from '../../../json-crdt-patch'; diff --git a/src/json-crdt/__tests__/fuzzer/Picker.ts b/src/json-crdt/__tests__/fuzzer/Picker.ts index e8415d7fb7..0fe744b184 100644 --- a/src/json-crdt/__tests__/fuzzer/Picker.ts +++ b/src/json-crdt/__tests__/fuzzer/Picker.ts @@ -1,5 +1,5 @@ import {DelOp, InsObjOp, InsStrOp, InsBinOp, InsArrOp} from '../../../json-crdt-patch/operations'; -import {RandomJson} from '../../../json-random'; +import {RandomJson} from '@jsonjoy.com/util/lib/json-random'; import {JsonNode, ObjNode, ArrNode, BinNode, StrNode} from '../../nodes'; import {Model} from '../../model/Model'; import {Fuzzer} from '@jsonjoy.com/util/lib/Fuzzer'; diff --git a/src/json-crdt/__tests__/fuzzer/SessionLogical.ts b/src/json-crdt/__tests__/fuzzer/SessionLogical.ts index b1c219305c..fd715ef335 100644 --- a/src/json-crdt/__tests__/fuzzer/SessionLogical.ts +++ b/src/json-crdt/__tests__/fuzzer/SessionLogical.ts @@ -16,7 +16,7 @@ import {generateInteger} from './util'; import {Model} from '../..'; import {Patch} from '../../../json-crdt-patch/Patch'; import {PatchBuilder} from '../../../json-crdt-patch/PatchBuilder'; -import {RandomJson} from '../../../json-random/RandomJson'; +import {RandomJson} from '@jsonjoy.com/util/lib/json-random/RandomJson'; import {randomU32} from 'hyperdyperid/lib/randomU32'; import {StrNode, ValNode, ObjNode, ArrNode, BinNode} from '../../nodes'; import {interval} from '../../../json-crdt-patch/clock'; diff --git a/src/json-crdt/__tests__/hash.spec.ts b/src/json-crdt/__tests__/hash.spec.ts index f119f6338d..de6e93b30d 100644 --- a/src/json-crdt/__tests__/hash.spec.ts +++ b/src/json-crdt/__tests__/hash.spec.ts @@ -1,4 +1,4 @@ -import {RandomJson} from '../../json-random'; +import {RandomJson} from '@jsonjoy.com/util/lib/json-random'; import {hashNode} from '../hash'; import {Model} from '../model'; diff --git a/src/json-crdt/json-patch/JsonPatch.ts b/src/json-crdt/json-patch/JsonPatch.ts index 87654d72f3..9f84b6099c 100644 --- a/src/json-crdt/json-patch/JsonPatch.ts +++ b/src/json-crdt/json-patch/JsonPatch.ts @@ -1,4 +1,4 @@ -import {deepEqual} from '../../json-equal/deepEqual'; +import {deepEqual} from '@jsonjoy.com/util/lib/json-equal/deepEqual'; import {ObjNode, ArrNode, JsonNode, ConNode} from '../nodes'; import {toPath, isChild} from '@jsonjoy.com/json-pointer/lib/util'; import {interval} from '../../json-crdt-patch/clock'; diff --git a/src/json-crdt/nodes/bin/__tests__/BinNode.fuzzing.spec.ts b/src/json-crdt/nodes/bin/__tests__/BinNode.fuzzing.spec.ts index 69344286a0..4edf96f900 100644 --- a/src/json-crdt/nodes/bin/__tests__/BinNode.fuzzing.spec.ts +++ b/src/json-crdt/nodes/bin/__tests__/BinNode.fuzzing.spec.ts @@ -4,7 +4,7 @@ import {ITimespanStruct, ITimestampStruct, ts} from '../../../../json-crdt-patch import {Fuzzer} from '@jsonjoy.com/util/lib/Fuzzer'; import {BinNode} from '../BinNode'; import {randomU32} from 'hyperdyperid/lib/randomU32'; -import {RandomJson} from '../../../../json-random'; +import {RandomJson} from '@jsonjoy.com/util/lib/json-random'; import * as path from 'path'; import * as fs from 'fs'; diff --git a/src/json-crdt/nodes/str/__tests__/StrNode.fuzzing.spec.ts b/src/json-crdt/nodes/str/__tests__/StrNode.fuzzing.spec.ts index 7432aa6ce4..4a33f2859f 100644 --- a/src/json-crdt/nodes/str/__tests__/StrNode.fuzzing.spec.ts +++ b/src/json-crdt/nodes/str/__tests__/StrNode.fuzzing.spec.ts @@ -4,7 +4,7 @@ import {ITimespanStruct, ITimestampStruct, ts} from '../../../../json-crdt-patch import {Fuzzer} from '@jsonjoy.com/util/lib/Fuzzer'; import {StrNode} from '../StrNode'; import {randomU32} from 'hyperdyperid/lib/randomU32'; -import {RandomJson} from '../../../../json-random'; +import {RandomJson} from '@jsonjoy.com/util/lib/json-random'; import * as path from 'path'; import * as fs from 'fs'; diff --git a/src/json-crdt/schema/__tests__/toSchema.spec.ts b/src/json-crdt/schema/__tests__/toSchema.spec.ts index 0683c8c867..1e0c7759b3 100644 --- a/src/json-crdt/schema/__tests__/toSchema.spec.ts +++ b/src/json-crdt/schema/__tests__/toSchema.spec.ts @@ -1,5 +1,5 @@ import {NodeBuilder, s, nodes} from '../../../json-crdt-patch'; -import {deepEqual} from '../../../json-equal/deepEqual'; +import {deepEqual} from '@jsonjoy.com/util/lib/json-equal/deepEqual'; import {cmpUint8Array} from '@jsonjoy.com/util/lib/buffers/cmpUint8Array'; import {Model} from '../../model'; import {toSchema} from '../toSchema'; diff --git a/src/json-equal/$$deepEqual/__tests__/deepEqual.fuzzing.spec.ts b/src/json-equal/$$deepEqual/__tests__/deepEqual.fuzzing.spec.ts deleted file mode 100644 index c089cb8a32..0000000000 --- a/src/json-equal/$$deepEqual/__tests__/deepEqual.fuzzing.spec.ts +++ /dev/null @@ -1,22 +0,0 @@ -import {$$deepEqual} from '..'; -import {RandomJson} from '../../../json-random/RandomJson'; - -const deepEqual = (a: unknown, b: unknown) => { - const js = $$deepEqual(a); - const fn = eval(js); // tslint:disable-line - return fn(b); -}; - -for (let i = 0; i < 100; i++) { - const json1 = RandomJson.generate(); - const json2 = JSON.parse(JSON.stringify(json1)); - - test('iteration ' + (i + 1), () => { - const res1 = deepEqual(json1, json1); - const res2 = deepEqual(json1, json2); - const res3 = deepEqual(json2, json1); - expect(res1).toBe(true); - expect(res2).toBe(true); - expect(res3).toBe(true); - }); -} diff --git a/src/json-equal/$$deepEqual/__tests__/deepEqual.spec.ts b/src/json-equal/$$deepEqual/__tests__/deepEqual.spec.ts deleted file mode 100644 index 4f40619ae4..0000000000 --- a/src/json-equal/$$deepEqual/__tests__/deepEqual.spec.ts +++ /dev/null @@ -1,10 +0,0 @@ -import {$$deepEqual} from '..'; -import {runDeepEqualTestSuite} from '../../deepEqual/__tests__/runDeepEqualTestSuite'; - -const deepEqual = (a: unknown, b: unknown) => { - const js = $$deepEqual(a); - const fn = eval(js); // tslint:disable-line - return fn(b); -}; - -runDeepEqualTestSuite(deepEqual); diff --git a/src/json-equal/$$deepEqual/__tests__/deepEqualCodegen.spec.ts b/src/json-equal/$$deepEqual/__tests__/deepEqualCodegen.spec.ts deleted file mode 100644 index ce48b47211..0000000000 --- a/src/json-equal/$$deepEqual/__tests__/deepEqualCodegen.spec.ts +++ /dev/null @@ -1,55 +0,0 @@ -import {$$deepEqual} from '..'; - -test('generates a deep equal comparator', () => { - const js = $$deepEqual([1, true, false, 'sdf', {foo: 123, null: null}, [null, true, 'asdf'], 3, {}]); - const deepEqual = eval(js); // tslint:disable-line - - const res1 = deepEqual([1, true, false, 'sdf', {foo: 123, null: null}, [null, true, 'asdf'], 3, {}]); - const res2 = deepEqual([2, true, false, 'sdf', {foo: 123, null: null}, [null, true, 'asdf'], 3, {}]); - const res3 = deepEqual([1, true, false, 'sdf', {foox: 123, null: null}, [null, true, 'asdf'], 3, {}]); - const res4 = deepEqual([1, true, false, 'sdf', {foo: 123, null: null}, [null, true, 'asdf'], 3, {a: 1}]); - - expect(res1).toBe(true); - expect(res2).toBe(false); - expect(res3).toBe(false); - expect(res4).toBe(false); -}); - -test('generates a deep equal comparator for primitives', () => { - /* tslint:disable */ - const equal1 = eval($$deepEqual('asdf')); - const equal2 = eval($$deepEqual(123)); - const equal3 = eval($$deepEqual(true)); - const equal4 = eval($$deepEqual(null)); - const equal5 = eval($$deepEqual(false)); - const equal6 = eval($$deepEqual(4.4)); - /* tslint:enable */ - - expect(equal1('asdf')).toBe(true); - expect(equal1('asdf2')).toBe(false); - - expect(equal2(123)).toBe(true); - expect(equal2(1234)).toBe(false); - - expect(equal3(true)).toBe(true); - expect(equal3(false)).toBe(false); - expect(equal3(null)).toBe(false); - - expect(equal4(true)).toBe(false); - expect(equal4(false)).toBe(false); - expect(equal4(null)).toBe(true); - - expect(equal5(true)).toBe(false); - expect(equal5(false)).toBe(true); - expect(equal5(null)).toBe(false); - - expect(equal6(4.4)).toBe(true); - expect(equal6(4)).toBe(false); -}); - -test('undefined is not an empty object', () => { - const js = $$deepEqual(undefined); - const deepEqual = eval(js); // tslint:disable-line - const res = deepEqual({}); - expect(res).toBe(false); -}); diff --git a/src/json-equal/$$deepEqual/index.ts b/src/json-equal/$$deepEqual/index.ts deleted file mode 100644 index 5b98253d93..0000000000 --- a/src/json-equal/$$deepEqual/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './v1'; diff --git a/src/json-equal/$$deepEqual/v1.ts b/src/json-equal/$$deepEqual/v1.ts deleted file mode 100644 index c775924f41..0000000000 --- a/src/json-equal/$$deepEqual/v1.ts +++ /dev/null @@ -1,62 +0,0 @@ -import {JavaScript} from '@jsonjoy.com/util/lib/codegen'; - -const codegenValue = (doc: unknown, code: string[], r: number): number => { - let rr = r; - const type = typeof doc; - const isPrimitive = doc === null || type === 'boolean' || type === 'string' || type === 'number'; - - // Primitives - if (isPrimitive) { - if (doc === Infinity) { - code.push(`if(r${r} !== Infinity)return false;`); - } else if (doc === -Infinity) { - code.push(`if(r${r} !== -Infinity)return false;`); - } else { - code.push(`if(r${r} !== ${JSON.stringify(doc)})return false;`); - } - return rr; - } - - // Arrays - if (Array.isArray(doc)) { - code.push(`if(!Array.isArray(r${r}) || r${r}.length !== ${doc.length})return false;`); - for (let i = 0; i < doc.length; i++) { - rr++; - code.push(`var r${rr}=r${r}[${i}];`); - rr = codegenValue(doc[i], code, rr); - } - return rr; - } - - // Objects - if (type === 'object' && doc) { - const obj = doc as Record; - const keys = Object.keys(obj); - code.push( - `if(!r${r} || typeof r${r} !== "object" || Array.isArray(r${r}) || Object.keys(r${r}).length !== ${keys.length})return false;`, - ); - for (const key of keys) { - rr++; - code.push(`var r${rr}=r${r}[${JSON.stringify(key)}];`); - rr = codegenValue(obj[key], code, rr); - } - } - - // Undefined - if (doc === undefined) { - code.push(`if(r${r} !== undefined)return false;`); - return rr; - } - - return rr; -}; - -export const $$deepEqual = (a: unknown): JavaScript<(b: unknown) => boolean> => { - const code: string[] = []; - codegenValue(a, code, 0); - - const fn = ['(function(r0){', ...code, 'return true;', '})']; - - // return fn.join('\n') as JavaScript<(b: unknown) => boolean>; - return fn.join('') as JavaScript<(b: unknown) => boolean>; -}; diff --git a/src/json-equal/README.md b/src/json-equal/README.md deleted file mode 100644 index f7bb500b8b..0000000000 --- a/src/json-equal/README.md +++ /dev/null @@ -1,45 +0,0 @@ -# json-equal - -This library contains the fastest JSON deep comparison algorithms. - -- `deepEqual` — deep comparison of JSON objects. Faster than `fast-deep-equal` and - `fast-equals` packages. -- `$$deepEqual` — if the comparison JSON object is known in advance, this function - can pre-compile a javascript function for comparison, which is about an order of magnitude - faster than `deepEqual`. - -## Reference - -### `deepEqual` - -```ts -import {deepEqual} from 'json-joy/lib/json-equal/deepEqual'; - -deepEqual(a, b); // true/false -``` - -### `$$deepEqual` - -```ts -import {$$deepEqual} from 'json-joy/lib/json-equal/$$deepEqual'; - -const js = $$deepEqual(a); -const fn = eval(js); - -fn(b); // true/false -``` - -## Benchmarks - -``` -node benchmarks/json-equal/bench.deepEqual.js -json-joy/json-equal (v1) x 873,303 ops/sec ±0.34% (96 runs sampled), 1145 ns/op -json-joy/json-equal (v2) x 664,673 ops/sec ±0.44% (97 runs sampled), 1504 ns/op -json-joy/json-equal (v3) x 710,572 ops/sec ±0.15% (100 runs sampled), 1407 ns/op -fast-deep-equal x 620,740 ops/sec ±0.34% (101 runs sampled), 1611 ns/op -fast-equals x 812,390 ops/sec ±0.11% (101 runs sampled), 1231 ns/op -lodash.isEqual x 182,440 ops/sec ±0.18% (98 runs sampled), 5481 ns/op -json-joy/json-equal/deepEqualCodegen x 6,161,316 ops/sec ±0.30% (101 runs sampled), 162 ns/op -json-joy/json-equal/deepEqualCodegen (with codegen) x 47,583 ops/sec ±0.11% (100 runs sampled), 21016 ns/op -Fastest is json-joy/json-equal/deepEqualCodegen -``` diff --git a/src/json-equal/__bench__/bench.deepEqual.ts b/src/json-equal/__bench__/bench.deepEqual.ts deleted file mode 100644 index 03e32d1181..0000000000 --- a/src/json-equal/__bench__/bench.deepEqual.ts +++ /dev/null @@ -1,59 +0,0 @@ -// npx ts-node src/json-equal/__bench__/bench.deepEqual.ts - -/* tslint:disable no-console */ - -import * as Benchmark from 'benchmark'; -import {deepEqual as deepEqualV1} from '../deepEqual/v1'; -import {deepEqual as deepEqualV2} from '../deepEqual/v2'; -import {deepEqual as deepEqualV3} from '../deepEqual/v3'; -import {deepEqual as deepEqualV4} from '../deepEqual/v4'; -import {deepEqual as deepEqualV5} from '../deepEqual/v5'; -import {$$deepEqual} from '../$$deepEqual'; - -const json1 = { - foo: 'bar', - ff: 123, - gg: [4, 3, 'f'], -}; -const json2 = { - foo: 'bar', - ff: 123, - gg: [4, 3, 'f.'], -}; - -// tslint:disable-next-line no-eval eval ban -const equalGenerated1 = eval($$deepEqual(json1)); - -const suite = new Benchmark.Suite(); - -suite - .add(`json-joy/json-equal (v1)`, () => { - deepEqualV1(json1, json2); - }) - .add(`json-joy/json-equal (v2)`, () => { - deepEqualV2(json1, json2); - }) - .add(`json-joy/json-equal (v3)`, () => { - deepEqualV3(json1, json2); - }) - .add(`json-joy/json-equal (v4)`, () => { - deepEqualV4(json1, json2); - }) - .add(`json-joy/json-equal (v5)`, () => { - deepEqualV5(json1, json2); - }) - .add(`json-joy/json-equal/$$deepEqual`, () => { - equalGenerated1(json2); - }) - .add(`json-joy/json-equal/$$deepEqual (with codegen)`, () => { - // tslint:disable-next-line no-eval eval ban - const equalGenerated1 = eval($$deepEqual(json1)); - equalGenerated1(json2); - }) - .on('cycle', (event: any) => { - console.log(String(event.target) + `, ${Math.round(1000000000 / event.target.hz)} ns/op`); - }) - .on('complete', () => { - console.log('Fastest is ' + suite.filter('fastest').map('name')); - }) - .run(); diff --git a/src/json-equal/deepEqual/__tests__/deepEqual.fuzzing.spec.ts b/src/json-equal/deepEqual/__tests__/deepEqual.fuzzing.spec.ts deleted file mode 100644 index ea02d4ebc7..0000000000 --- a/src/json-equal/deepEqual/__tests__/deepEqual.fuzzing.spec.ts +++ /dev/null @@ -1,22 +0,0 @@ -import {deepEqual} from '../../deepEqual'; -import {RandomJson} from '../../../json-random/RandomJson'; - -for (let i = 0; i < 100; i++) { - const json1 = RandomJson.generate(); - const json2 = JSON.parse(JSON.stringify(json1)); - - test('iteration ' + (i + 1), () => { - const res1 = deepEqual(json1, json1); - const res2 = deepEqual(json1, json2); - const res3 = deepEqual(json2, json1); - try { - expect(res1).toBe(true); - expect(res2).toBe(true); - expect(res3).toBe(true); - } catch (error) { - // tslint:disable-next-line no-console - console.log({json1, json2}); - throw error; - } - }); -} diff --git a/src/json-equal/deepEqual/__tests__/deepEqual.spec.ts b/src/json-equal/deepEqual/__tests__/deepEqual.spec.ts deleted file mode 100644 index 4f15b991ee..0000000000 --- a/src/json-equal/deepEqual/__tests__/deepEqual.spec.ts +++ /dev/null @@ -1,4 +0,0 @@ -import {deepEqual} from '../../deepEqual'; -import {runDeepEqualTestSuite} from './runDeepEqualTestSuite'; - -runDeepEqualTestSuite(deepEqual); diff --git a/src/json-equal/deepEqual/__tests__/runDeepEqualTestSuite.ts b/src/json-equal/deepEqual/__tests__/runDeepEqualTestSuite.ts deleted file mode 100644 index c05fa562f5..0000000000 --- a/src/json-equal/deepEqual/__tests__/runDeepEqualTestSuite.ts +++ /dev/null @@ -1,20 +0,0 @@ -import {tests} from './tests'; - -export const runDeepEqualTestSuite = (deepEqual: (a: unknown, b: unknown) => boolean) => { - for (const s of tests) { - describe(s.description, () => { - for (const t of s.tests) { - test(t.description, () => { - const res1 = deepEqual(t.value1, t.value2); - const res2 = deepEqual(t.value2, t.value1); - try { - expect(res1).toBe(t.equal); - expect(res2).toBe(t.equal); - } catch (error) { - throw error; - } - }); - } - }); - } -}; diff --git a/src/json-equal/deepEqual/__tests__/tests.ts b/src/json-equal/deepEqual/__tests__/tests.ts deleted file mode 100644 index 5db01fe4ca..0000000000 --- a/src/json-equal/deepEqual/__tests__/tests.ts +++ /dev/null @@ -1,289 +0,0 @@ -interface Test { - description: string; - value1: unknown; - value2: unknown; - equal: boolean; -} - -interface Suite { - description: string; - tests: Test[]; -} - -export const tests: Suite[] = [ - { - description: 'scalars', - tests: [ - { - description: 'equal numbers', - value1: 1, - value2: 1, - equal: true, - }, - { - description: 'not equal numbers', - value1: 1, - value2: 2, - equal: false, - }, - { - description: 'number and array are not equal', - value1: 1, - value2: [], - equal: false, - }, - { - description: '0 and null are not equal', - value1: 0, - value2: null, - equal: false, - }, - { - description: 'equal strings', - value1: 'a', - value2: 'a', - equal: true, - }, - { - description: 'not equal strings', - value1: 'a', - value2: 'b', - equal: false, - }, - { - description: 'empty string and null are not equal', - value1: '', - value2: null, - equal: false, - }, - { - description: 'null is equal to null', - value1: null, - value2: null, - equal: true, - }, - { - description: 'equal booleans (true)', - value1: true, - value2: true, - equal: true, - }, - { - description: 'equal booleans (false)', - value1: false, - value2: false, - equal: true, - }, - { - description: 'not equal booleans', - value1: true, - value2: false, - equal: false, - }, - { - description: '1 and true are not equal', - value1: 1, - value2: true, - equal: false, - }, - { - description: '0 and false are not equal', - value1: 0, - value2: false, - equal: false, - }, - { - description: '0 and -0 are equal', - value1: 0, - value2: -0, - equal: true, - }, - { - description: 'Infinity and Infinity are equal', - value1: Infinity, - value2: Infinity, - equal: true, - }, - { - description: 'Infinity and -Infinity are not equal', - value1: Infinity, - value2: -Infinity, - equal: false, - }, - ], - }, - - { - description: 'objects', - tests: [ - { - description: 'empty objects are equal', - value1: {}, - value2: {}, - equal: true, - }, - { - description: 'equal objects (same properties "order")', - value1: {a: 1, b: '2'}, - value2: {a: 1, b: '2'}, - equal: true, - }, - { - description: 'equal objects (different properties "order")', - value1: {a: 1, b: '2'}, - value2: {b: '2', a: 1}, - equal: true, - }, - { - description: 'not equal objects (extra property)', - value1: {a: 1, b: '2'}, - value2: {a: 1, b: '2', c: []}, - equal: false, - }, - { - description: 'not equal objects (different property values)', - value1: {a: 1, b: '2', c: 3}, - value2: {a: 1, b: '2', c: 4}, - equal: false, - }, - { - description: 'not equal objects (different properties)', - value1: {a: 1, b: '2', c: 3}, - value2: {a: 1, b: '2', d: 3}, - equal: false, - }, - { - description: 'equal objects (same sub-properties)', - value1: {a: [{b: 'c'}]}, - value2: {a: [{b: 'c'}]}, - equal: true, - }, - { - description: 'not equal objects (different sub-property value)', - value1: {a: [{b: 'c'}]}, - value2: {a: [{b: 'd'}]}, - equal: false, - }, - { - description: 'not equal objects (different sub-property)', - value1: {a: [{b: 'c'}]}, - value2: {a: [{c: 'c'}]}, - equal: false, - }, - { - description: 'empty array and empty object are not equal', - value1: {}, - value2: [], - equal: false, - }, - { - description: 'nulls are equal', - value1: null, - value2: null, - equal: true, - }, - { - description: 'null and undefined are not equal', - value1: null, - value2: undefined, - equal: false, - }, - { - description: 'null and empty object are not equal', - value1: null, - value2: {}, - equal: false, - }, - { - description: 'undefined and empty object are not equal', - value1: undefined, - value2: {}, - equal: false, - }, - ], - }, - - { - description: 'arrays', - tests: [ - { - description: 'two empty arrays are equal', - value1: [], - value2: [], - equal: true, - }, - { - description: 'equal arrays', - value1: [1, 2, 3], - value2: [1, 2, 3], - equal: true, - }, - { - description: 'not equal arrays (different item)', - value1: [1, 2, 3], - value2: [1, 2, 4], - equal: false, - }, - { - description: 'not equal arrays (different length)', - value1: [1, 2, 3], - value2: [1, 2], - equal: false, - }, - { - description: 'equal arrays of objects', - value1: [{a: 'a'}, {b: 'b'}], - value2: [{a: 'a'}, {b: 'b'}], - equal: true, - }, - { - description: 'not equal arrays of objects', - value1: [{a: 'a'}, {b: 'b'}], - value2: [{a: 'a'}, {b: 'c'}], - equal: false, - }, - { - description: 'pseudo array and equivalent array are not equal', - value1: {'0': 0, '1': 1, length: 2}, - value2: [0, 1], - equal: false, - }, - ], - }, - - { - description: 'sample objects', - tests: [ - { - description: 'big object', - value1: { - prop1: 'value1', - prop2: 'value2', - prop3: 'value3', - prop4: { - subProp1: 'sub value1', - subProp2: { - subSubProp1: 'sub sub value1', - subSubProp2: [1, 2, {prop2: 1, prop: 2}, 4, 5], - }, - }, - prop5: 1000, - }, - value2: { - prop5: 1000, - prop3: 'value3', - prop1: 'value1', - prop2: 'value2', - prop4: { - subProp2: { - subSubProp1: 'sub sub value1', - subSubProp2: [1, 2, {prop2: 1, prop: 2}, 4, 5], - }, - subProp1: 'sub value1', - }, - }, - equal: true, - }, - ], - }, -]; diff --git a/src/json-equal/deepEqual/index.ts b/src/json-equal/deepEqual/index.ts deleted file mode 100644 index 7f687e9066..0000000000 --- a/src/json-equal/deepEqual/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './v5'; diff --git a/src/json-equal/deepEqual/v1.ts b/src/json-equal/deepEqual/v1.ts deleted file mode 100644 index ec7b604a4a..0000000000 --- a/src/json-equal/deepEqual/v1.ts +++ /dev/null @@ -1,28 +0,0 @@ -export const deepEqual = (a: unknown, b: unknown): boolean => { - // Primitives - if (a === b) return true; - - if (a && b && typeof a === 'object' && typeof b === 'object') { - // Arrays - if (a.constructor !== b.constructor) return false; - let length, i, keys; - if (Array.isArray(a)) { - length = a.length; - if (length !== (b as Array).length) return false; - for (i = length; i-- !== 0; ) if (!deepEqual(a[i], (b as Array)[i])) return false; - return true; - } - - // Objects - keys = Object.keys(a); - length = keys.length; - if (length !== Object.keys(b).length) return false; - for (i = length; i-- !== 0; ) { - const key = keys[i]; - if (!deepEqual((a as Record)[key], (b as Record)[key])) return false; - } - return true; - } - - return false; -}; diff --git a/src/json-equal/deepEqual/v2.ts b/src/json-equal/deepEqual/v2.ts deleted file mode 100644 index 1a5da799c9..0000000000 --- a/src/json-equal/deepEqual/v2.ts +++ /dev/null @@ -1,29 +0,0 @@ -export const deepEqual = (a: unknown, b: unknown): boolean => { - // Primitives - if (a === b) return true; - - if (a && b && typeof a === 'object' && typeof b === 'object') { - // Arrays - if (a.constructor !== b.constructor) return false; - let length, i, keys; - if (Array.isArray(a)) { - length = a.length; - if (length !== (b as Array).length) return false; - for (i = length; i-- !== 0; ) if (!deepEqual(a[i], (b as Array)[i])) return false; - return true; - } - - // Objects - keys = Object.keys(a); - length = keys.length; - if (length !== Object.keys(b).length) return false; - for (i = length; i-- !== 0; ) if ((b as Record)[keys[i]] === undefined) return false; - for (i = length; i-- !== 0; ) { - const key = keys[i]; - if (!deepEqual((a as Record)[key], (b as Record)[key])) return false; - } - return true; - } - - return false; -}; diff --git a/src/json-equal/deepEqual/v3.ts b/src/json-equal/deepEqual/v3.ts deleted file mode 100644 index 46684e7a00..0000000000 --- a/src/json-equal/deepEqual/v3.ts +++ /dev/null @@ -1,29 +0,0 @@ -export const deepEqual = (a: unknown, b: unknown): boolean => { - // Primitives - if (a === b) return true; - - if (a && b && typeof a === 'object' && typeof b === 'object') { - // Arrays - if (a.constructor !== b.constructor) return false; - let length, i, keys; - if (Array.isArray(a)) { - length = a.length; - if (length !== (b as Array).length) return false; - for (i = length; i-- !== 0; ) if (!deepEqual(a[i], (b as Array)[i])) return false; - return true; - } - - // Objects - keys = Object.keys(a); - length = keys.length; - if (length !== Object.keys(b).length) return false; - for (i = length; i-- !== 0; ) if (!Object.prototype.hasOwnProperty.call(b, keys[i])) return false; - for (i = length; i-- !== 0; ) { - const key = keys[i]; - if (!deepEqual((a as Record)[key], (b as Record)[key])) return false; - } - return true; - } - - return false; -}; diff --git a/src/json-equal/deepEqual/v4.ts b/src/json-equal/deepEqual/v4.ts deleted file mode 100644 index a115da1ec9..0000000000 --- a/src/json-equal/deepEqual/v4.ts +++ /dev/null @@ -1,29 +0,0 @@ -export const deepEqual = (a: unknown, b: unknown): boolean => { - // Primitives - if (a === b) return true; - - if (a && b && typeof a === 'object' && typeof b === 'object') { - // Arrays - if (a.constructor !== b.constructor) return false; - let length, i, keys; - if (a.constructor === Array) { - // V4: Array.isArray(a) - length = (a as unknown[]).length; - if (length !== (b as Array).length) return false; - for (i = length; i-- !== 0; ) if (!deepEqual(a[i], (b as Array)[i])) return false; - return true; - } - - // Objects - keys = Object.keys(a); - length = keys.length; - if (length !== Object.keys(b).length) return false; - for (i = length; i-- !== 0; ) { - const key = keys[i]; - if (!deepEqual((a as Record)[key], (b as Record)[key])) return false; - } - return true; - } - - return false; -}; diff --git a/src/json-equal/deepEqual/v5.ts b/src/json-equal/deepEqual/v5.ts deleted file mode 100644 index 3c0f8f81e5..0000000000 --- a/src/json-equal/deepEqual/v5.ts +++ /dev/null @@ -1,31 +0,0 @@ -const isArray = Array.isArray; - -export const deepEqual = (a: unknown, b: unknown): boolean => { - // Primitives - if (a === b) return true; - - // Arrays - let length, i, keys; - if (isArray(a)) { - if (!isArray(b)) return false; - length = a.length; - if (length !== (b as Array).length) return false; - for (i = length; i-- !== 0; ) if (!deepEqual(a[i], (b as Array)[i])) return false; - return true; - } - - // Objects - if (a && b && typeof a === 'object' && typeof b === 'object') { - keys = Object.keys(a); - length = keys.length; - if (length !== Object.keys(b).length) return false; - if (isArray(b)) return false; - for (i = length; i-- !== 0; ) { - const key = keys[i]; - if (!deepEqual((a as Record)[key], (b as Record)[key])) return false; - } - return true; - } - - return false; -}; diff --git a/src/json-hash/__tests__/index.spec.ts b/src/json-hash/__tests__/index.spec.ts index ffd1537acc..14d5029244 100644 --- a/src/json-hash/__tests__/index.spec.ts +++ b/src/json-hash/__tests__/index.spec.ts @@ -1,5 +1,5 @@ import {hash} from '..'; -import {RandomJson} from '../../json-random'; +import {RandomJson} from '@jsonjoy.com/util/lib/json-random'; test('returns the same hash for empty objects', () => { const res1 = hash({}); @@ -45,8 +45,8 @@ test('returns the same hash for array with values', () => { test('returns different hash for random JSON values', () => { for (let i = 0; i < 100; i++) { - const res1 = hash(RandomJson.generate()); - const res2 = hash(RandomJson.generate()); + const res1 = hash(RandomJson.generate() as any); + const res2 = hash(RandomJson.generate() as any); expect(res1).not.toBe(res2); } }); diff --git a/src/json-ot/types/ot-binary-irreversible/__tests__/BinaryOtFuzzer.ts b/src/json-ot/types/ot-binary-irreversible/__tests__/BinaryOtFuzzer.ts index aec4767320..f06a4a1221 100644 --- a/src/json-ot/types/ot-binary-irreversible/__tests__/BinaryOtFuzzer.ts +++ b/src/json-ot/types/ot-binary-irreversible/__tests__/BinaryOtFuzzer.ts @@ -1,4 +1,4 @@ -import {RandomJson} from '../../../../json-random'; +import {RandomJson} from '@jsonjoy.com/util/lib/json-random'; import {Fuzzer} from '@jsonjoy.com/util/lib/Fuzzer'; import {append, normalize} from '../util'; import {BinaryOp} from '../types'; diff --git a/src/json-ot/types/ot-json/__tests__/fuzzer/JsonOtFuzzer.ts b/src/json-ot/types/ot-json/__tests__/fuzzer/JsonOtFuzzer.ts index c5662377c5..6305e1be47 100644 --- a/src/json-ot/types/ot-json/__tests__/fuzzer/JsonOtFuzzer.ts +++ b/src/json-ot/types/ot-json/__tests__/fuzzer/JsonOtFuzzer.ts @@ -1,6 +1,6 @@ -import {clone} from '../../../../../json-clone'; +import {clone} from '@jsonjoy.com/util/lib/json-clone'; import {find, isArrayReference, isObjectReference, Path} from '@jsonjoy.com/json-pointer'; -import {RandomJson} from '../../../../../json-random'; +import {RandomJson} from '@jsonjoy.com/util/lib/json-random'; import {Fuzzer} from '@jsonjoy.com/util/lib/Fuzzer'; import type {JsonOp, JsonOpDataComponent, JsonOpDropComponent, JsonOpPickComponent} from '../../types'; import {applyPatch} from '../../../../../json-patch/applyPatch'; diff --git a/src/json-ot/types/ot-json/__tests__/fuzzer/compose.fuzzer.spec.ts b/src/json-ot/types/ot-json/__tests__/fuzzer/compose.fuzzer.spec.ts index 47b6ec37a9..70d978e079 100644 --- a/src/json-ot/types/ot-json/__tests__/fuzzer/compose.fuzzer.spec.ts +++ b/src/json-ot/types/ot-json/__tests__/fuzzer/compose.fuzzer.spec.ts @@ -1,4 +1,4 @@ -import {clone} from '../../../../../json-clone'; +import {clone} from '@jsonjoy.com/util/lib/json-clone'; import {apply} from '../../apply'; import {OpTree} from '../../tree'; import {JsonOtFuzzer} from './JsonOtFuzzer'; diff --git a/src/json-ot/types/ot-json/__tests__/tree.compose.spec.ts b/src/json-ot/types/ot-json/__tests__/tree.compose.spec.ts index 3f61ab9ad1..2ae774cb5d 100644 --- a/src/json-ot/types/ot-json/__tests__/tree.compose.spec.ts +++ b/src/json-ot/types/ot-json/__tests__/tree.compose.spec.ts @@ -1,4 +1,4 @@ -import {clone} from '../../../../json-clone'; +import {clone} from '@jsonjoy.com/util/lib/json-clone'; import {apply} from '../apply'; import {OpTree} from '../tree'; import {JsonOp} from '../types'; diff --git a/src/json-ot/types/ot-string-irreversible/__tests__/StringOtFuzzer.ts b/src/json-ot/types/ot-string-irreversible/__tests__/StringOtFuzzer.ts index c7da7b47e6..3f4735044f 100644 --- a/src/json-ot/types/ot-string-irreversible/__tests__/StringOtFuzzer.ts +++ b/src/json-ot/types/ot-string-irreversible/__tests__/StringOtFuzzer.ts @@ -1,4 +1,4 @@ -import {RandomJson} from '../../../../json-random'; +import {RandomJson} from '@jsonjoy.com/util/lib/json-random'; import {Fuzzer} from '@jsonjoy.com/util/lib/Fuzzer'; import {append, normalize} from '../util'; import {StringOp} from '../types'; diff --git a/src/json-ot/types/ot-string/__tests__/StringOtFuzzer.ts b/src/json-ot/types/ot-string/__tests__/StringOtFuzzer.ts index 1d87f12af1..647235d3f3 100644 --- a/src/json-ot/types/ot-string/__tests__/StringOtFuzzer.ts +++ b/src/json-ot/types/ot-string/__tests__/StringOtFuzzer.ts @@ -1,4 +1,4 @@ -import {RandomJson} from '../../../../json-random'; +import {RandomJson} from '@jsonjoy.com/util/lib/json-random'; import {Fuzzer} from '@jsonjoy.com/util/lib/Fuzzer'; import {append, normalize} from '../util'; import {StringOp} from '../types'; diff --git a/src/json-patch/applyPatch/__tests__/testApplyPatchAutomated.ts b/src/json-patch/applyPatch/__tests__/testApplyPatchAutomated.ts index dd798e3898..4c7a58e36f 100644 --- a/src/json-patch/applyPatch/__tests__/testApplyPatchAutomated.ts +++ b/src/json-patch/applyPatch/__tests__/testApplyPatchAutomated.ts @@ -1,7 +1,7 @@ import tests_json from '../../__tests__/tests.json'; import spec_json from '../../__tests__/spec.json'; import {validateOperation} from '../../validate'; -import {clone} from '../../../json-clone/clone'; +import {clone} from '@jsonjoy.com/util/lib/json-clone/clone'; import {ApplyPatch} from '../types'; const testSuites = [ diff --git a/src/json-patch/applyPatch/v1.ts b/src/json-patch/applyPatch/v1.ts index 5122bf7176..14d57eefe7 100644 --- a/src/json-patch/applyPatch/v1.ts +++ b/src/json-patch/applyPatch/v1.ts @@ -1,4 +1,4 @@ -import {clone as deepClone} from '../../json-clone/clone'; +import {clone as deepClone} from '@jsonjoy.com/util/lib/json-clone/clone'; import {Operation} from '../types'; import {Op} from '../op'; import {decode} from '../codec/json'; diff --git a/src/json-patch/applyPatch/v2.ts b/src/json-patch/applyPatch/v2.ts index 53d6044810..d218b4f8f9 100644 --- a/src/json-patch/applyPatch/v2.ts +++ b/src/json-patch/applyPatch/v2.ts @@ -1,4 +1,4 @@ -import {clone as deepClone} from '../../json-clone/clone'; +import {clone as deepClone} from '@jsonjoy.com/util/lib/json-clone/clone'; import {Operation} from '../types'; import {findByPointer} from '@jsonjoy.com/json-pointer/lib/findByPointer/v6'; import type {ApplyPatchOptions, OpResult, PatchResult} from './types'; diff --git a/src/json-patch/applyPatch/v3.ts b/src/json-patch/applyPatch/v3.ts index 6687fe2821..a8cfe390d4 100644 --- a/src/json-patch/applyPatch/v3.ts +++ b/src/json-patch/applyPatch/v3.ts @@ -1,7 +1,7 @@ -import {clone as deepClone} from '../../json-clone/clone'; +import {clone as deepClone} from '@jsonjoy.com/util/lib/json-clone/clone'; import {Operation} from '../types'; import {findByPointer, unescapeComponent} from '@jsonjoy.com/json-pointer'; -import {deepEqual} from '../../json-equal/deepEqual'; +import {deepEqual} from '@jsonjoy.com/util/lib/json-equal/deepEqual'; import type {ApplyPatchOptions, OpResult, PatchResult} from './types'; import {hasOwnProperty} from '@jsonjoy.com/util/lib/hasOwnProperty'; diff --git a/src/json-patch/applyPatch/v4.ts b/src/json-patch/applyPatch/v4.ts index 9cba640b30..6926e50b89 100644 --- a/src/json-patch/applyPatch/v4.ts +++ b/src/json-patch/applyPatch/v4.ts @@ -1,4 +1,4 @@ -import {clone as deepClone} from '../../json-clone/clone'; +import {clone as deepClone} from '@jsonjoy.com/util/lib/json-clone/clone'; import {Operation} from '../types'; import {Op} from '../op'; import {operationToOp} from '../codec/json'; diff --git a/src/json-patch/codegen/apply.ts b/src/json-patch/codegen/apply.ts index 5c32aa7c9c..7b1a8e1bfc 100644 --- a/src/json-patch/codegen/apply.ts +++ b/src/json-patch/codegen/apply.ts @@ -1,4 +1,4 @@ -import {clone as deepClone} from '../../json-clone/clone'; +import {clone as deepClone} from '@jsonjoy.com/util/lib/json-clone/clone'; import {Operation} from '../types'; import {operationToOp} from '../codec/json'; import {AbstractPredicateOp} from '../op'; diff --git a/src/json-patch/codegen/ops/test.ts b/src/json-patch/codegen/ops/test.ts index d1f4a26ea8..e26b4f2ace 100644 --- a/src/json-patch/codegen/ops/test.ts +++ b/src/json-patch/codegen/ops/test.ts @@ -1,6 +1,6 @@ import {OpTest} from '../../op'; import {$$find} from '@jsonjoy.com/json-pointer/lib/codegen/find'; -import {$$deepEqual} from '../../../json-equal/$$deepEqual'; +import {$$deepEqual} from '@jsonjoy.com/util/lib/json-equal/$$deepEqual'; import {JavaScriptLinked, compileClosure, JavaScript} from '@jsonjoy.com/util/lib/codegen'; import {predicateOpWrapper} from '../util'; import type {ApplyFn} from '../types'; diff --git a/src/json-patch/exec/test.ts b/src/json-patch/exec/test.ts index a29dd889e7..3d7500f755 100644 --- a/src/json-patch/exec/test.ts +++ b/src/json-patch/exec/test.ts @@ -1,4 +1,4 @@ -import {deepEqual} from '../../json-equal/deepEqual'; +import {deepEqual} from '@jsonjoy.com/util/lib/json-equal/deepEqual'; import {find, Path} from '@jsonjoy.com/json-pointer'; export const execTest = (path: Path, value: unknown, not: boolean, doc: unknown) => { diff --git a/src/json-patch/op/OpAdd.ts b/src/json-patch/op/OpAdd.ts index 765d2c6e85..11274e6381 100644 --- a/src/json-patch/op/OpAdd.ts +++ b/src/json-patch/op/OpAdd.ts @@ -3,7 +3,7 @@ import {AbstractOp} from './AbstractOp'; import {OperationAdd} from '../types'; import {find, Path, formatJsonPointer} from '@jsonjoy.com/json-pointer'; import {OPCODE} from '../constants'; -import {clone as deepClone} from '../../json-clone/clone'; +import {clone as deepClone} from '@jsonjoy.com/util/lib/json-clone/clone'; import type {IMessagePackEncoder} from '@jsonjoy.com/json-pack/lib/msgpack'; /** diff --git a/src/json-patch/op/OpCopy.ts b/src/json-patch/op/OpCopy.ts index d6715f789b..2f5fb82583 100644 --- a/src/json-patch/op/OpCopy.ts +++ b/src/json-patch/op/OpCopy.ts @@ -3,7 +3,7 @@ import {AbstractOp} from './AbstractOp'; import {OperationCopy} from '../types'; import {Path, find, formatJsonPointer} from '@jsonjoy.com/json-pointer'; import {OpAdd} from './OpAdd'; -import {clone as deepClone} from '../../json-clone/clone'; +import {clone as deepClone} from '@jsonjoy.com/util/lib/json-clone/clone'; import {OPCODE} from '../constants'; import type {IMessagePackEncoder} from '@jsonjoy.com/json-pack/lib/msgpack'; diff --git a/src/json-patch/op/OpIn.ts b/src/json-patch/op/OpIn.ts index 76504a4969..2d34252766 100644 --- a/src/json-patch/op/OpIn.ts +++ b/src/json-patch/op/OpIn.ts @@ -4,7 +4,7 @@ import {find, Path, formatJsonPointer} from '@jsonjoy.com/json-pointer'; import {AbstractPredicateOp} from './AbstractPredicateOp'; import {OPCODE} from '../constants'; import {AbstractOp} from './AbstractOp'; -import {deepEqual} from '../../json-equal/deepEqual'; +import {deepEqual} from '@jsonjoy.com/util/lib/json-equal/deepEqual'; import type {IMessagePackEncoder} from '@jsonjoy.com/json-pack/lib/msgpack'; /** diff --git a/src/json-patch/op/OpTest.ts b/src/json-patch/op/OpTest.ts index 3d1f872271..6cc27b6fee 100644 --- a/src/json-patch/op/OpTest.ts +++ b/src/json-patch/op/OpTest.ts @@ -4,7 +4,7 @@ import {find, Path, formatJsonPointer} from '@jsonjoy.com/json-pointer'; import {AbstractPredicateOp} from './AbstractPredicateOp'; import {OPCODE} from '../constants'; import {AbstractOp} from './AbstractOp'; -import {deepEqual} from '../../json-equal/deepEqual'; +import {deepEqual} from '@jsonjoy.com/util/lib/json-equal/deepEqual'; import type {IMessagePackEncoder} from '@jsonjoy.com/json-pack/lib/msgpack'; /** diff --git a/src/json-random/README.md b/src/json-random/README.md deleted file mode 100644 index 0fb6a31c6a..0000000000 --- a/src/json-random/README.md +++ /dev/null @@ -1,140 +0,0 @@ -# json-random - -The `json-random` library lets you generate random JSON values. - -## Usage - -Generate a random JSON object. - -```ts -import {RandomJson} from 'json-joy/lib/json-random'; - -const json1 = RandomJson.generate(); -console.log(json1); -// { -// '38': "'@9_nO'Mr2kNsk", -// ']Io': 'ek_(3hS_voW|4', -// O55y3: 381613794.8379983, -// 'nWiO8W2hkQ(': false, -// 'r,5^0K!?c': true, -// '믊㶋搀焟㰏䶨⃷쎨躡': 124288683.18213326, -// 'G;l{VueC(#\\': 90848872.89389054, -// '%dP|': 172689822.92919666, -// 'Z``>?7.(0': '鿞虠制�紉蓊澡඾嘍皽퀌࠻ꏙ۽', -// '9#zw;1Grn=95Csj|': { -// '4r::`32,': 606175517.8053282, -// '#vp': 833875564.9460341, -// ']bSg2%Pnh>': 916851127.8107322, -// ',a}I,XOTJo}sxp6': true, -// '?D[f': 218903673.91954625 -// }, -// yM: ',b7`wZ m9u', -// 'f3G!vM-': 856162337.7339423 -// } -``` - -You can provide options ot the `.generate()` function. For example, -you can decrease the node count in the JSON to five, using the `nodeCount` -option: - -```ts -const json2 = RandomJson.generate({ - nodeCount: 5, -}); -console.log(json2); -// { -// '?)DClmRrUZAg8z>8': [ null, 596640662.4073832, 82241937.12592442 ], -// '}geJx8\\u_s': 27895 -// } -``` - -You can set the root node of your JSON value to be an array, using -the `rootNode` option: - -```ts -const json3 = RandomJson.generate({ - nodeCount: 5, - rootNode: 'array', -}); -console.log(json3); -// [ -// 421841709.15660113, -// 641343038.74181, -// { 'SQ6QQ': 'Q{Zi', -// 'GPo/.@': 623441950.4015203, -// 'uvUNV+a0Vj': [] -// } - -const json8 = RandomJson.genArray(); -console.log(json8); -// [ 'BYTvAq+k', [], [ '&XT93Y', '{LN\\!P5SQ}0>&rZ%' ], null ] -``` - -## Demo - -See demo [here](../demo/json-random.ts). Run it with: - -``` -npx ts-node src/demo/json-random.ts -``` diff --git a/src/json-random/RandomJson.ts b/src/json-random/RandomJson.ts deleted file mode 100644 index aa4c00fee9..0000000000 --- a/src/json-random/RandomJson.ts +++ /dev/null @@ -1,321 +0,0 @@ -import type {JsonValue} from '@jsonjoy.com/json-pack/lib/types'; - -/** @ignore */ -export type NodeType = 'null' | 'boolean' | 'number' | 'string' | 'binary' | 'array' | 'object'; - -export interface NodeOdds { - null: number; - boolean: number; - number: number; - string: number; - binary: number; - array: number; - object: number; -} - -export interface RandomJsonOptions { - rootNode: 'object' | 'array' | undefined; - nodeCount: number; - odds: NodeOdds; -} - -const defaultOpts: RandomJsonOptions = { - rootNode: 'object', - nodeCount: 32, - odds: { - null: 1, - boolean: 2, - number: 10, - string: 8, - binary: 0, - array: 2, - object: 2, - }, -}; - -type ContainerNode = unknown[] | object; - -const ascii = (): string => { - return String.fromCharCode(Math.floor(32 + Math.random() * (126 - 32))); -}; - -const alphabet = [ - 'a', - 'b', - 'c', - 'd', - 'e', - 'f', - 'g', - 'h', - 'i', - 'j', - 'k', - 'l', - 'm', - 'n', - 'o', - 'p', - 'q', - 'r', - 's', - 't', - 'u', - 'v', - 'w', - 'x', - 'y', - 'z', - 'A', - 'B', - 'C', - 'D', - 'E', - 'F', - 'G', - 'H', - 'I', - 'J', - 'K', - 'L', - 'M', - 'N', - 'O', - 'P', - 'Q', - 'R', - 'S', - 'T', - 'U', - 'V', - 'W', - 'X', - 'Y', - 'Z', - '0', - '1', - '2', - '3', - '4', - '5', - '6', - '7', - '8', - '9', - '-', - '_', - '.', - ',', - ';', - '!', - '@', - '#', - '$', - '%', - '^', - '&', - '*', - '\\', - '/', - '(', - ')', - '+', - '=', - '\n', - '👍', - '🏻', - '😛', - 'ä', - 'ö', - 'ü', - 'ß', - 'а', - 'б', - 'в', - 'г', - '诶', - '必', - '西', -]; -const utf16 = (): string => { - return alphabet[Math.floor(Math.random() * alphabet.length)]; -}; - -/** - * Create a random JSON value. - * - * ```ts - * RandomJson.generate() - * ``` - */ -export class RandomJson { - public static generate(opts?: Partial): JsonValue { - const rnd = new RandomJson(opts); - return rnd.create(); - } - - public static genBoolean(): boolean { - return Math.random() > 0.5; - } - - public static genNumber(): number { - const num = - Math.random() > 0.2 - ? Math.random() * 1e9 - : Math.random() < 0.2 - ? Math.round(0xff * (2 * Math.random() - 1)) - : Math.random() < 0.2 - ? Math.round(0xffff * (2 * Math.random() - 1)) - : Math.round(Number.MAX_SAFE_INTEGER * (2 * Math.random() - 1)); - if (num === -0) return 0; - return num; - } - - public static genString(length = Math.ceil(Math.random() * 16)): string { - let str: string = ''; - if (Math.random() < 0.1) for (let i = 0; i < length; i++) str += utf16(); - else for (let i = 0; i < length; i++) str += ascii(); - if (str.length !== length) return ascii().repeat(length); - return str; - } - - public static genBinary(length = Math.ceil(Math.random() * 16)): Uint8Array { - const buf = new Uint8Array(length); - for (let i = 0; i < length; i++) buf[i] = Math.floor(Math.random() * 256); - return buf; - } - - public static genArray(options: Partial> = {odds: defaultOpts.odds}): unknown[] { - return RandomJson.generate({ - nodeCount: 6, - ...options, - rootNode: 'array', - }) as unknown[]; - } - - public static genObject(options: Partial> = {odds: defaultOpts.odds}): object { - return RandomJson.generate({ - nodeCount: 6, - ...options, - rootNode: 'object', - }) as object; - } - - /** @ignore */ - public opts: RandomJsonOptions; - /** @ignore */ - private totalOdds: number; - /** @ignore */ - private oddTotals: NodeOdds; - /** @ignore */ - public root: JsonValue; - /** @ignore */ - private containers: ContainerNode[] = []; - - /** - * @ignore - */ - public constructor(opts: Partial = {}) { - this.opts = {...defaultOpts, ...opts}; - this.oddTotals = {} as any; - this.oddTotals.null = this.opts.odds.null; - this.oddTotals.boolean = this.oddTotals.null + this.opts.odds.boolean; - this.oddTotals.number = this.oddTotals.boolean + this.opts.odds.number; - this.oddTotals.string = this.oddTotals.number + this.opts.odds.string; - this.oddTotals.binary = this.oddTotals.string + this.opts.odds.binary; - this.oddTotals.array = this.oddTotals.string + this.opts.odds.array; - this.oddTotals.object = this.oddTotals.array + this.opts.odds.object; - this.totalOdds = - this.opts.odds.null + - this.opts.odds.boolean + - this.opts.odds.number + - this.opts.odds.string + - this.opts.odds.binary + - this.opts.odds.array + - this.opts.odds.object; - this.root = - this.opts.rootNode === 'object' - ? {} - : this.opts.rootNode === 'array' - ? [] - : this.pickContainerType() === 'object' - ? {} - : []; - this.containers.push(this.root); - } - - /** - * @ignore - */ - public create(): JsonValue { - for (let i = 0; i < this.opts.nodeCount; i++) this.addNode(); - return this.root; - } - - /** - * @ignore - */ - public addNode(): void { - const container = this.pickContainer(); - const newNodeType = this.pickNodeType(); - const node = this.generate(newNodeType); - if (node && typeof node === 'object') this.containers.push(node as any); - if (Array.isArray(container)) { - const index = Math.floor(Math.random() * (container.length + 1)); - container.splice(index, 0, node); - } else { - const key = RandomJson.genString(); - (container as any)[key] = node; - } - } - - /** - * @ignore - */ - protected generate(type: NodeType): unknown { - switch (type) { - case 'null': - return null; - case 'boolean': - return RandomJson.genBoolean(); - case 'number': - return RandomJson.genNumber(); - case 'string': - return RandomJson.genString(); - case 'binary': - return RandomJson.genBinary(); - case 'array': - return []; - case 'object': - return {}; - } - } - - /** @ignore */ - public pickNodeType(): NodeType { - const odd = Math.random() * this.totalOdds; - if (odd <= this.oddTotals.null) return 'null'; - if (odd <= this.oddTotals.boolean) return 'boolean'; - if (odd <= this.oddTotals.number) return 'number'; - if (odd <= this.oddTotals.string) return 'string'; - if (odd <= this.oddTotals.binary) return 'binary'; - if (odd <= this.oddTotals.array) return 'array'; - return 'object'; - } - - /** - * @ignore - */ - protected pickContainerType(): 'array' | 'object' { - const sum = this.opts.odds.array + this.opts.odds.object; - if (Math.random() < this.opts.odds.array / sum) return 'array'; - return 'object'; - } - - /** - * @ignore - */ - protected pickContainer(): ContainerNode { - return this.containers[Math.floor(Math.random() * this.containers.length)]; - } -} diff --git a/src/json-random/__demos__/json-random.ts b/src/json-random/__demos__/json-random.ts deleted file mode 100644 index 81ce30f51c..0000000000 --- a/src/json-random/__demos__/json-random.ts +++ /dev/null @@ -1,107 +0,0 @@ -/** - * Run with: - * - * npx ts-node src/json-random/__demos__/json-random.ts - */ - -import {RandomJson} from '../../json-random'; - -const json1 = RandomJson.generate(); -console.log(json1); // tslint:disable-line no-console -// { -// '38': "'@9_nO'Mr2kNsk", -// ']Io': 'ek_(3hS_voW|4', -// O55y3: 381613794.8379983, -// 'nWiO8W2hkQ(': false, -// 'r,5^0K!?c': true, -// '믊㶋搀焟㰏䶨⃷쎨躡': 124288683.18213326, -// 'G;l{VueC(#\\': 90848872.89389054, -// '%dP|': 172689822.92919666, -// 'Z``>?7.(0': '鿞虠制�紉蓊澡඾嘍皽퀌࠻ꏙ۽', -// '9#zw;1Grn=95Csj|': { -// '4r::`32,': 606175517.8053282, -// '#vp': 833875564.9460341, -// ']bSg2%Pnh>': 916851127.8107322, -// ',a}I,XOTJo}sxp6': true, -// '?D[f': 218903673.91954625 -// }, -// yM: ',b7`wZ m9u', -// 'f3G!vM-': 856162337.7339423 -// } - -const json2 = RandomJson.generate({ - nodeCount: 5, -}); -console.log(json2); // tslint:disable-line no-console -// { -// '?)DClmRrUZAg8z>8': [ null, 596640662.4073832, 82241937.12592442 ], -// '}geJx8\\u_s': 27895 -// } - -const json3 = RandomJson.generate({ - nodeCount: 5, - rootNode: 'array', -}); -console.log(json3); // tslint:disable-line no-console -// [ -// 421841709.15660113, -// 641343038.74181, -// { 'SQ6QQ': 'Q{Zi', -// 'GPo/.@': 623441950.4015203, -// 'uvUNV+a0Vj': [] -// } - -const json8 = RandomJson.genArray(); -console.log(json8); // tslint:disable-line no-console -// [ 'BYTvAq+k', [], [ '&XT93Y', '{LN\\!P5SQ}0>&rZ%' ], null ] diff --git a/src/json-random/__tests__/RandomJson.spec.ts b/src/json-random/__tests__/RandomJson.spec.ts deleted file mode 100644 index fd13f23970..0000000000 --- a/src/json-random/__tests__/RandomJson.spec.ts +++ /dev/null @@ -1,122 +0,0 @@ -import {RandomJson} from '../RandomJson'; - -test('generates random JSON', () => { - const mathRandom = Math.random; - let i = 0.0; - Math.random = () => { - i += 0.0379; - if (i >= 1) i -= 1; - return i; - }; - const rj = new RandomJson(); - const json = rj.create(); - const str = JSON.stringify(json); - expect(str.length > 5).toBe(true); - expect(JSON.parse(str)).toEqual(json); - expect(json).toMatchInlineSnapshot(` - { - ""%),047;>BEILPTW": [ - "]\`dgknrvy}", - "aehlpswz #", - "knruy|"&)-04", - "vy}#&*-148;?CF", - 378700000.0000067, - 399200000.0000046, - 483700000.0000056, - 568200000.0000067, - 422500000.00000507, - 466300000.0000035, - 588700000.0000046, - "imptw{ $'+/2", - [ - "bfimqtx{!$(", - "jnquy|"%),03", - "hlosvz}#'*.1", - "jnqux|!%),03", - "lpswz $'+.25", - "adhkosvz}#", - {}, - ], - ], - "58<": false, - "6:=": false, - "8;?": false, - "AEHLO": 244800000.0000021, - "DHLOSV": 279600000.0000062, - "FJNQUX": -3601078262045373, - "GKNRUY": -3494793310839458, - "ORVY]\`d": 387700000.000001, - "PTW[_bfi": 405100000.00000304, - "UY\\\`cgjn": 454799999.99999994, - "i": "\\_cfjmqtx|", - } - `); - Math.random = mathRandom; -}); - -test('can enforce root node to be object', () => { - const rj = new RandomJson({rootNode: 'object'}); - const json = rj.create(); - expect(!!json).toBe(true); - expect(typeof json).toBe('object'); - expect(Array.isArray(json)).toBe(false); -}); - -test('can enforce root node to be array', () => { - const json = RandomJson.generate({rootNode: 'array'}); - expect(Array.isArray(json)).toBe(true); -}); - -describe('exact root type', () => { - describe('.genString()', () => { - test('can generate a string', () => { - const json = RandomJson.genString(); - expect(typeof json).toBe('string'); - }); - }); - - describe('.genNumber()', () => { - test('can generate a number', () => { - const json = RandomJson.genNumber(); - expect(typeof json).toBe('number'); - }); - }); - - describe('.genBoolean()', () => { - test('can generate a boolean', () => { - const json = RandomJson.genBoolean(); - expect(typeof json).toBe('boolean'); - }); - }); - - describe('.genArray()', () => { - test('can generate a array', () => { - const json = RandomJson.genArray(); - expect(json instanceof Array).toBe(true); - }); - }); - - describe('.genObject()', () => { - test('can generate a object', () => { - const json = RandomJson.genObject(); - expect(typeof json).toBe('object'); - expect(!!json).toBe(true); - }); - }); -}); - -test('emoji strings can be converted to UTF-8', () => { - for (let i = 0; i < 100; i++) { - const str = '👍🏻😛' + '👍🏻😛'; - const test = Buffer.from(str).toString('utf8'); - expect(test).toBe(str); - } -}); - -test('random strings can be converted to UTF-8', () => { - for (let i = 0; i < 1000; i++) { - const str = RandomJson.genString(10); - const test = Buffer.from(str).toString('utf8'); - expect(test).toBe(str); - } -}); diff --git a/src/json-random/index.ts b/src/json-random/index.ts deleted file mode 100644 index 553584b66e..0000000000 --- a/src/json-random/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './RandomJson'; diff --git a/src/json-schema/index.ts b/src/json-schema/index.ts deleted file mode 100644 index fcb073fefc..0000000000 --- a/src/json-schema/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './types'; diff --git a/src/json-schema/types.ts b/src/json-schema/types.ts deleted file mode 100644 index a19bef7db2..0000000000 --- a/src/json-schema/types.ts +++ /dev/null @@ -1,88 +0,0 @@ -export interface JsonSchemaGenericKeywords { - type?: string | string[]; - title?: string; - description?: string; - default?: unknown; - examples?: unknown[]; - deprecated?: boolean; - readOnly?: boolean; - writeOnly?: boolean; - $id?: string; - $ref?: string; - $defs?: {[name: string]: JsonSchemaValueNode}; -} - -export interface JsonSchemaString extends JsonSchemaGenericKeywords { - type: 'string'; - const?: string; - format?: string; - minLength?: number; - maxLength?: number; -} - -export interface JsonSchemaNumber extends JsonSchemaGenericKeywords { - type: 'number' | 'integer'; - const?: number; - minimum?: number; - exclusiveMinimum?: number; - maximum?: number; - exclusiveMaximum?: number; -} - -export interface JsonSchemaObject extends JsonSchemaGenericKeywords { - type: 'object'; - properties?: { - [key: string]: JsonSchemaNode; - }; - required?: string[]; - additionalProperties?: boolean | JsonSchemaNode; - patternProperties?: { - [key: string]: JsonSchemaNode; - }; - const?: object; -} - -export interface JsonSchemaArray extends JsonSchemaGenericKeywords { - type: 'array'; - items: JsonSchemaNode | false; - minItems?: number; - maxItems?: number; - const?: unknown[]; - prefixItems?: JsonSchemaNode[]; -} - -export interface JsonSchemaBoolean extends JsonSchemaGenericKeywords { - type: 'boolean'; - const?: boolean; -} - -export interface JsonSchemaNull extends JsonSchemaGenericKeywords { - type: 'null'; -} - -export interface JsonSchemaBinary extends JsonSchemaGenericKeywords { - type: 'binary'; -} - -export interface JsonSchemaAny extends JsonSchemaGenericKeywords { - type: Array<'string' | 'number' | 'boolean' | 'null' | 'array' | 'object'>; -} - -export interface JsonSchemaRef { - $ref: string; -} - -export interface JsonSchemaOr { - anyOf: JsonSchemaNode[]; -} - -export type JsonSchemaValueNode = - | JsonSchemaAny - | JsonSchemaNull - | JsonSchemaBoolean - | JsonSchemaNumber - | JsonSchemaString - | JsonSchemaArray - | JsonSchemaObject; - -export type JsonSchemaNode = JsonSchemaGenericKeywords | JsonSchemaValueNode | JsonSchemaRef | JsonSchemaOr; diff --git a/src/json-size/README.md b/src/json-size/README.md deleted file mode 100644 index 186820229c..0000000000 --- a/src/json-size/README.md +++ /dev/null @@ -1,38 +0,0 @@ -# `json-size` - -This library implements methods to calculate the size of JSON objects. -It calculates the size of bytes necessary to store the final serialized JSON -in UTF-8 encoding. - -## Usage - -```ts -import {jsonSize} from 'json-joy/{lib,es6}/json-size'; - -jsonSize({1: 2, foo: 'bar'}); // 19 -``` - -## Reference - -- `jsonSize` — calculates exact JSON size, as `JSON.stringify()` would return. -- `jsonSizeApprox` — a faster version, which uses string nominal length for calculation. -- `jsonSizeFast` — the fastest version, which uses nominal values for all JSON types. See - source code for description. -- `msgpackSizeFast` — same as `jsonSizeFast`, but for MessagePack values. In addition - to regular JSON values it also supports binary data (by `Buffer` or `Uint8Array`), - `JsonPackExtension`, and `JsonPackValue`. - -## Performance - -In most cases `json-size` will be faster than `JSON.stringify`. - -``` -node benchmarks/json-size.js -json-joy/json-size jsonSize() x 377,980 ops/sec ±0.12% (100 runs sampled), 2646 ns/op -json-joy/json-size jsonSizeApprox() x 377,841 ops/sec ±0.09% (98 runs sampled), 2647 ns/op -json-joy/json-size jsonSizeFast() x 2,229,344 ops/sec ±0.30% (101 runs sampled), 449 ns/op -json-joy/json-size msgpackSizeFast() x 1,260,284 ops/sec ±0.10% (96 runs sampled), 793 ns/op -JSON.stringify x 349,696 ops/sec ±0.08% (100 runs sampled), 2860 ns/op -JSON.stringify + utf8Count x 182,977 ops/sec ±0.10% (100 runs sampled), 5465 ns/op -Fastest is json-joy/json-size jsonSizeFast() -``` diff --git a/src/json-size/__bench__/json-size.ts b/src/json-size/__bench__/json-size.ts deleted file mode 100644 index 845a2d0fdf..0000000000 --- a/src/json-size/__bench__/json-size.ts +++ /dev/null @@ -1,80 +0,0 @@ -/* tslint:disable no-console */ - -// npx ts-node src/json-size/__bench__/json-size.ts - -import * as Benchmark from 'benchmark'; -import {utf8Size} from '@jsonjoy.com/util/lib/strings/utf8'; -import {jsonSize, jsonSizeApprox} from '../json'; -import {jsonSizeFast} from '../jsonSizeFast'; -import {msgpackSizeFast} from '../msgpackSizeFast'; - -const json = [ - {op: 'add', path: '/foo/baz', value: 666}, - {op: 'add', path: '/foo/bx', value: 666}, - {op: 'add', path: '/asdf', value: 'asdfadf asdf'}, - {op: 'move', path: '/arr/0', from: '/arr/1'}, - {op: 'replace', path: '/foo/baz', value: 'lorem ipsum'}, - { - op: 'add', - path: '/docs/latest', - value: { - name: 'blog post', - json: { - id: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx', - longString: - 'lorem ipsum dolorem, alamorem colomorem, ipsum pipsum, lorem ipsum dolorem, alamorem colomorem, ipsum pipsum, lorem ipsum dolorem, alamorem colomorem, ipsum pipsum, lorem ipsum dolorem, alamorem colomorem, ipsum pipsum, lorem ipsum dolorem, alamorem colomorem, ipsum pipsum', - author: { - name: 'John 💪', - handle: '@johny', - }, - lastSeen: -12345, - tags: [null, 'Sports 🏀', 'Personal', 'Travel'], - pins: [ - { - id: 1239494, - }, - ], - marks: [ - { - x: 1, - y: 1.234545, - w: 0.23494, - h: 0, - }, - ], - hasRetweets: false, - approved: true, - '👍': 33, - }, - }, - }, -]; - -const suite = new Benchmark.Suite(); - -suite - .add(`json-joy/json-size jsonSize()`, () => { - jsonSize(json); - }) - .add(`json-joy/json-size jsonSizeApprox()`, () => { - jsonSizeApprox(json); - }) - .add(`json-joy/json-size jsonSizeFast()`, () => { - jsonSizeFast(json); - }) - .add(`json-joy/json-size msgpackSizeFast()`, () => { - msgpackSizeFast(json); - }) - .add(`JSON.stringify`, () => { - JSON.stringify(json).length; - }) - .add(`JSON.stringify + utf8Count`, () => { - utf8Size(JSON.stringify(json)); - }) - .on('cycle', (event: any) => { - console.log(String(event.target) + `, ${Math.round(1000000000 / event.target.hz)} ns/op`); - }) - .on('complete', () => { - console.log('Fastest is ' + suite.filter('fastest').map('name')); - }) - .run(); diff --git a/src/json-size/__tests__/fuzz.spec.ts b/src/json-size/__tests__/fuzz.spec.ts deleted file mode 100644 index 41d4dd8560..0000000000 --- a/src/json-size/__tests__/fuzz.spec.ts +++ /dev/null @@ -1,16 +0,0 @@ -import {jsonSize} from '..'; -import {RandomJson} from '../../json-random/RandomJson'; -import {utf8Size} from '@jsonjoy.com/util/lib/strings/utf8'; - -const random = new RandomJson(); -const iterations = 100; - -for (let i = 0; i < iterations; i++) { - test(`calculates json size - ${i + 1}`, () => { - const json = random.create(); - // console.log(json); - const size1 = jsonSize(json); - const size2 = utf8Size(JSON.stringify(json)); - expect(size1).toBe(size2); - }); -} diff --git a/src/json-size/__tests__/json.spec.ts b/src/json-size/__tests__/json.spec.ts deleted file mode 100644 index 938229553c..0000000000 --- a/src/json-size/__tests__/json.spec.ts +++ /dev/null @@ -1,10 +0,0 @@ -import {jsonSize, jsonSizeApprox} from '../json'; -import {testJsonSize} from './testJsonSize'; - -describe('jsonSize', () => { - testJsonSize(jsonSize); -}); - -describe('jsonSizeApprox', () => { - testJsonSize(jsonSizeApprox, {simpleStringsOnly: true}); -}); diff --git a/src/json-size/__tests__/jsonSizeFast.spec.ts b/src/json-size/__tests__/jsonSizeFast.spec.ts deleted file mode 100644 index 784427ad6b..0000000000 --- a/src/json-size/__tests__/jsonSizeFast.spec.ts +++ /dev/null @@ -1,61 +0,0 @@ -import {jsonSizeFast} from '../jsonSizeFast'; - -test('computes size of single values', () => { - expect(jsonSizeFast(null)).toBe(1); - expect(jsonSizeFast(true)).toBe(1); - expect(jsonSizeFast(false)).toBe(1); - expect(jsonSizeFast(1)).toBe(9); - expect(jsonSizeFast(1.1)).toBe(9); - expect(jsonSizeFast('123')).toBe(7); - expect(jsonSizeFast('')).toBe(4); - expect(jsonSizeFast('A')).toBe(5); - expect(jsonSizeFast([])).toBe(2); - expect(jsonSizeFast({})).toBe(2); -}); - -test('computes size complex object', () => { - // prettier-ignore - const json = { // 2 - a: 1, // 2 + 1 + 9 - b: true, // 2 + 1 + 1 - c: false, // 2 + 1 + 1 - d: null, // 2 + 1 + 1 - 'e.e': 2.2, // 2 + 3 + 9 - f: '', // 2 + 1 + 4 + 0 - g: 'asdf', // 2 + 1 + 4 + 4 - h: {}, // 2 + 1 + 2 - i: [ // 2 + 1 + 2 - 1, // 9 - true, // 1 - false, // 1 - null, // 1 - 2.2, // 9 - '', // 4 + 0 - 'asdf', // 4 + 4 - {}, // 2 - ], - }; - const size = jsonSizeFast(json); - - // prettier-ignore - expect(size).toBe( - 2 + - 2 + 1 + 9 + - 2 + 1 + 1 + - 2 + 1 + 1 + - 2 + 1 + 1 + - 2 + 3 + 9 + - 2 + 1 + 4 + 0 + - 2 + 1 + 4 + 4 + - 2 + 1 + 2 + - 2 + 1 + 2 + - 9 + - 1 + - 1 + - 1 + - 9 + - 4 + 0 + - 4 + 4 + - 2 - ); -}); diff --git a/src/json-size/__tests__/maxEncodingCapacity.spec.ts b/src/json-size/__tests__/maxEncodingCapacity.spec.ts deleted file mode 100644 index f34021ad10..0000000000 --- a/src/json-size/__tests__/maxEncodingCapacity.spec.ts +++ /dev/null @@ -1,48 +0,0 @@ -import {documents} from '../../__tests__/json-documents'; -import {maxEncodingCapacity} from '../maxEncodingCapacity'; - -test('computes size of single values', () => { - expect(maxEncodingCapacity(null)).toBe(4); - expect(maxEncodingCapacity(true)).toBe(5); - expect(maxEncodingCapacity(false)).toBe(5); - expect(maxEncodingCapacity(1)).toBe(22); - expect(maxEncodingCapacity(1.1)).toBe(22); - expect(maxEncodingCapacity('123')).toBe(20); - expect(maxEncodingCapacity('')).toBe(5); - expect(maxEncodingCapacity('A')).toBe(10); - expect(maxEncodingCapacity([])).toBe(5); - expect(maxEncodingCapacity({})).toBe(5); - expect(maxEncodingCapacity({foo: 1})).toBe(49); - expect(maxEncodingCapacity({foo: [1]})).toBe(55); -}); - -test('a larger value', () => { - expect( - maxEncodingCapacity({ - name: 'cooking receipt', - json: { - id: '0001', - type: 'donut', - name: 'Cake', - ppu: 0.55, - batters: { - batter: [ - {id: '1001', type: 'Regular'}, - {id: '1002', type: 'Chocolate'}, - {id: '1003', type: 'Blueberry'}, - {id: '1004', type: "Devil's Food"}, - ], - }, - topping: [ - {id: '5001', type: 'None'}, - {id: '5002', type: 'Glazed'}, - {id: '5005', type: 'Sugar'}, - {id: '5007', type: 'Powdered Sugar'}, - {id: '5006', type: 'Chocolate with Sprinkles'}, - {id: '5003', type: 'Chocolate'}, - {id: '5004', type: 'Maple'}, - ], - }, - }), - ).toBe(1875); -}); diff --git a/src/json-size/__tests__/testJsonSize.ts b/src/json-size/__tests__/testJsonSize.ts deleted file mode 100644 index ae04fdfeb8..0000000000 --- a/src/json-size/__tests__/testJsonSize.ts +++ /dev/null @@ -1,66 +0,0 @@ -import {utf8Size} from '@jsonjoy.com/util/lib/strings/utf8'; - -export const testJsonSize = ( - jsonSize: (val: unknown) => number, - {simpleStringsOnly = false}: {simpleStringsOnly?: boolean} = {}, -) => { - test('calculates null size', () => { - expect(jsonSize(null)).toBe(4); - }); - - test('calculates boolean sizes', () => { - expect(jsonSize(true)).toBe(4); - expect(jsonSize(false)).toBe(5); - }); - - test('calculates number sizes', () => { - expect(jsonSize(1)).toBe(1); - expect(jsonSize(1.1)).toBe(3); - expect(jsonSize(0)).toBe(1); - expect(jsonSize(1.123)).toBe(5); - expect(jsonSize(-1.123)).toBe(6); - }); - - if (!simpleStringsOnly) { - test('calculates string sizes', () => { - expect(jsonSize('')).toBe(2); - expect(jsonSize('a')).toBe(3); - expect(jsonSize('abc')).toBe(5); - expect(jsonSize('👨‍👩‍👦‍👦')).toBe(27); - expect(jsonSize('büro')).toBe(7); - expect(jsonSize('office')).toBe(8); - }); - } - - if (!simpleStringsOnly) { - test('calculates string sizes with escaped characters', () => { - expect(jsonSize('\\')).toBe(4); - expect(jsonSize('"')).toBe(4); - expect(jsonSize('\b')).toBe(4); - expect(jsonSize('\f')).toBe(4); - expect(jsonSize('\n')).toBe(4); - expect(jsonSize('\r')).toBe(4); - expect(jsonSize('\t')).toBe(4); - }); - } - - test('calculates array sizes', () => { - expect(jsonSize([])).toBe(2); - expect(jsonSize([1])).toBe(3); - expect(jsonSize([1, 2, 3])).toBe(7); - expect(jsonSize([1, 'büro', 3])).toBe(13); - }); - - test('calculates object sizes', () => { - expect(jsonSize({})).toBe(2); - expect(jsonSize({a: 1})).toBe(2 + 3 + 1 + 1); - expect(jsonSize({1: 2, foo: 'bar'})).toBe(2 + 3 + 1 + 1 + 1 + 5 + 1 + 5); - }); - - test('calculates size of array of length 2 that begins with empty string', () => { - const json = ['', -1]; - const size1 = jsonSize(json); - const size2 = utf8Size(JSON.stringify(json)); - expect(size1).toBe(size2); - }); -}; diff --git a/src/json-size/index.ts b/src/json-size/index.ts deleted file mode 100644 index 94f9f7620f..0000000000 --- a/src/json-size/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export * from './json'; -export * from './jsonSizeFast'; -export * from './msgpackSizeFast'; -export * from './maxEncodingCapacity'; diff --git a/src/json-size/json.ts b/src/json-size/json.ts deleted file mode 100644 index 7b2c3bfefd..0000000000 --- a/src/json-size/json.ts +++ /dev/null @@ -1,94 +0,0 @@ -import {utf8Size} from '@jsonjoy.com/util/lib/strings/utf8'; - -const numberSize = (num: number) => { - const isInteger = num === Math.round(num); - if (isInteger) return Math.max(Math.floor(Math.log10(Math.abs(num))), 0) + 1 + (num < 0 ? 1 : 0); - return JSON.stringify(num).length; -}; - -const stringSize = (str: string) => { - const strLength = str.length; - let byteLength = strLength; - let pos = 0; - while (pos < strLength) { - const value = str.charCodeAt(pos++); - if (value < 128) { - switch (value) { - case 8: // \b - case 9: // \t - case 10: // \n - case 12: // \f - case 13: // \r - case 34: // \" - case 92: // \\ - byteLength += 1; - break; - } - continue; - } else return utf8Size(JSON.stringify(str)); - } - return byteLength + 2; -}; - -const booleanSize = (bool: boolean) => (bool ? 4 : 5); - -const arraySize = (arr: unknown[]) => { - let size = 0; - const length = arr.length; - for (let i = 0; i < length; i++) size += jsonSize(arr[i]); - return size + 2 + (length > 1 ? length - 1 : 0); -}; - -const objectSize = (obj: Record) => { - let size = 2; - let length = 0; - for (const key in obj) - if (obj.hasOwnProperty(key)) { - length++; - size += stringSize(key) + jsonSize(obj[key]); - } - const colonSize = length; - const commaSize = length > 1 ? length - 1 : 0; - return size + colonSize + commaSize; -}; - -/** - * Computes exact prices JSON size as would be output from JSON.stringify(). - * - * @param value JSON value to approximate size of - * @returns Size in bytes of JSON value - */ -export const jsonSize = (value: unknown): number => { - if (value === null) return 4; - switch (typeof value) { - case 'number': - return numberSize(value); - case 'string': - return stringSize(value); - case 'boolean': - return booleanSize(value); - } - if (value instanceof Array) return arraySize(value); - return objectSize(value as Record); -}; - -/** - * Same as `jsonSize` function, but approximates the size of strings to improve performance. - * Uses `.length` property of strings to approximate their size. - * - * @param value JSON value to approximate size of - * @returns Size in bytes of JSON value - */ -export const jsonSizeApprox = (value: unknown): number => { - if (value === null) return 4; - switch (typeof value) { - case 'number': - return numberSize(value); - case 'string': - return value.length; - case 'boolean': - return booleanSize(value); - } - if (value instanceof Array) return arraySize(value); - return objectSize(value as Record); -}; diff --git a/src/json-size/jsonSizeFast.ts b/src/json-size/jsonSizeFast.ts deleted file mode 100644 index 1717661dfb..0000000000 --- a/src/json-size/jsonSizeFast.ts +++ /dev/null @@ -1,56 +0,0 @@ -const arraySize = (arr: unknown[]): number => { - let size = 2; - for (let i = arr.length - 1; i >= 0; i--) size += jsonSizeFast(arr[i]); - return size; -}; - -const objectSize = (obj: Record): number => { - let size = 2; - for (const key in obj) if (obj.hasOwnProperty(key)) size += 2 + key.length + jsonSizeFast(obj[key]); - return size; -}; - -/** - * This function is the fastest way to approximate size of JSON object in bytes. - * - * It uses the following heuristics: - * - * - Boolean: 1 byte. - * - Null: 1 byte. - * - Number: 9 bytes (1 byte to store the number type, 8 bytes to store the number). - * - String: 4 bytes + string length. String length is encoded in UTF-8, so it is not - * exactly the same as the number of bytes in the string. - * - Array: 2 bytes + sum of sizes of elements. - * - Object: 2 bytes + 2 bytes for each key + length of each key + sum of sizes of values. - * - * Rationale: - * - * - Booleans and `null` are stored as one byte in MessagePack. - * - Maximum size of a number in MessagePack is 9 bytes (1 byte for the type, - * 8 bytes for the number). - * - Maximum overhead for string storage is 4 bytes in MessagePack. We use that, especially - * because we approximate the size of strings in UTF-8, which can consume more bytes if - * non-ASCII characters are present. - * - Maximum overhead for arrays is 4 bytes in MessagePack, but we use 2 bytes for the - * array length, as we don't expect most arrays to be longer than 65,535 elements. - * - Maximum overhead for objects is 4 bytes in MessagePack, but we use 2 bytes for the - * object length, as we don't expect most objects to have more than 65,535 keys. - * - For object keys we use 2 bytes overhead for each key, as we don't expect most - * keys to be longer than 65,535 characters. - * - * @param value JSON value to calculate approximate size of - * @returns Number of bytes required to store the JSON value - */ -export const jsonSizeFast = (value: unknown): number => { - if (value === null) return 1; - switch (typeof value) { - case 'number': - return 9; - case 'string': - return 4 + value.length; - case 'boolean': - return 1; - } - if (value instanceof Array) return arraySize(value); - return objectSize(value as Record); -}; diff --git a/src/json-size/maxEncodingCapacity.ts b/src/json-size/maxEncodingCapacity.ts deleted file mode 100644 index 5ad7f900f6..0000000000 --- a/src/json-size/maxEncodingCapacity.ts +++ /dev/null @@ -1,55 +0,0 @@ -export const enum MaxEncodingOverhead { - Null = 4, // Literal "null" - Boolean = 5, // Literal "false" - Number = 22, // Literal "1.1111111111111111e+21" = JSON.stringify(1111111111111111111112) - String = 1 + 4, // As per TLV: 1 byte for type, 4 bytes for length. - StringLengthMultiplier = 5, // 4x UTF-8 overhead + 1.3x Base64 overhead, plus, 1 byte for each non-ASCII character. - Binary = 2 + 37 + 2, // 2 for two quotes, 37 for "data:application/octet-stream;base64,'" literal, 2 bytes for Base64 padding. - BinaryLengthMultiplier = 2, // 1.3x Base64 overhead. - Array = 1 + 4, // As per TLV: 1 byte for type, 4 bytes for length. - ArrayElement = 1, // Separator "," literal. - Object = 1 + 4, // As per TLV: 1 byte for type, 4 bytes for length. - ObjectElement = 1 + 1, // 1 byte for Key-value separator ":" literal, and 1 byte for separator "," literal. - Undefined = Binary + BinaryLengthMultiplier * 2, -} - -export const maxEncodingCapacity = (value: unknown): number => { - switch (typeof value) { - case 'number': - return MaxEncodingOverhead.Number; - case 'string': - return MaxEncodingOverhead.String + value.length * MaxEncodingOverhead.StringLengthMultiplier; - case 'boolean': - return MaxEncodingOverhead.Boolean; - case 'object': { - if (!value) return MaxEncodingOverhead.Null; - const constructor = value.constructor; - switch (constructor) { - case Array: { - const arr = value as unknown[]; - const length = arr.length; - let size = MaxEncodingOverhead.Array + length * MaxEncodingOverhead.ArrayElement; - for (let i = arr.length - 1; i >= 0; i--) size += maxEncodingCapacity(arr[i]); - return size; - } - case Uint8Array: { - return MaxEncodingOverhead.Binary + (value as Uint8Array).length * MaxEncodingOverhead.BinaryLengthMultiplier; - } - case Object: { - let size = MaxEncodingOverhead.Object; - const obj = value as Record; - for (const key in obj) - if (obj.hasOwnProperty(key)) - size += MaxEncodingOverhead.ObjectElement + maxEncodingCapacity(key) + maxEncodingCapacity(obj[key]); - return size; - } - default: - return MaxEncodingOverhead.Undefined; - } - } - case 'bigint': - return MaxEncodingOverhead.Number; - default: - return MaxEncodingOverhead.Undefined; - } -}; diff --git a/src/json-type-value/ObjectValue.ts b/src/json-type-value/ObjectValue.ts deleted file mode 100644 index 894eb2decc..0000000000 --- a/src/json-type-value/ObjectValue.ts +++ /dev/null @@ -1,164 +0,0 @@ -import {Value} from './Value'; -import {toText} from '../json-type/typescript/toText'; -import {TypeSystem} from '../json-type/system/TypeSystem'; -import {printTree} from 'tree-dump/lib/printTree'; -import type {ResolveType} from '../json-type'; -import type * as classes from '../json-type/type'; -import type * as ts from '../json-type/typescript/types'; -import type {TypeBuilder} from '../json-type/type/TypeBuilder'; -import type {Printable} from 'tree-dump/lib/types'; - -export type UnObjectType = T extends classes.ObjectType ? U : never; -export type UnObjectValue = T extends ObjectValue ? U : never; -export type UnObjectFieldTypeVal = T extends classes.ObjectFieldType ? U : never; -export type ObjectFieldToTuple = F extends classes.ObjectFieldType ? [K, V] : never; -export type ToObject = T extends [string, unknown][] ? {[K in T[number] as K[0]]: K[1]} : never; -export type ObjectValueToTypeMap = ToObject<{[K in keyof F]: ObjectFieldToTuple}>; -export type TuplesToFields = T extends PropDefinition[] ? classes.ObjectFieldType[] : never; - -// export type MergeObjectsTypes = -// A extends classes.ObjectType -// ? B extends classes.ObjectType -// ? classes.ObjectType<[...A2, ...B2]> : -// never : -// never; - -// export type MergeObjectValues = -// A extends ObjectValue -// ? B extends ObjectValue -// ? ObjectValue> : -// never : -// never; - -type PropDefinition = [key: K, val: V, data: ResolveType]; -type PropDef = (key: K, val: V, data: ResolveType) => PropDefinition; - -export class ObjectValue> extends Value implements Printable { - public static create = (system: TypeSystem = new TypeSystem()) => new ObjectValue(system.t.obj, {}); - - public get system(): TypeSystem { - return (this.type as T).getSystem(); - } - - public get t(): TypeBuilder { - return this.system.t; - } - - public keys(): string[] { - const type = this.type as T; - return type.fields.map((field: classes.ObjectFieldType) => field.key); - } - - public get>>( - key: K, - ): Value< - ObjectValueToTypeMap>[K] extends classes.Type - ? ObjectValueToTypeMap>[K] - : classes.Type - > { - const field = this.type.getField(key); - if (!field) throw new Error('NO_FIELD'); - const type = field.value; - const data = this.data[key]; - return new Value(type, data) as any; - } - - public field>( - field: F | ((t: TypeBuilder) => F), - data: ResolveType>, - ): ObjectValue, F]>> { - field = typeof field === 'function' ? field((this.type as classes.ObjectType).getSystem().t) : field; - const extendedData = {...this.data, [field.key]: data}; - const type = this.type; - const system = type.system; - if (!system) throw new Error('NO_SYSTEM'); - const extendedType = system.t.Object(...type.fields, field); - return new ObjectValue(extendedType, extendedData as any) as any; - } - - public prop( - key: K, - type: V | ((t: TypeBuilder) => V), - data: ResolveType, - ) { - const system = (this.type as classes.ObjectType).getSystem(); - const t = system.t; - type = typeof type === 'function' ? type(t) : type; - return this.field(t.prop(key, type), data); - } - - public merge>( - obj: O, - ): ObjectValue, ...UnObjectType]>> { - const extendedData = {...this.data, ...obj.data}; - const type = this.type; - const system = type.system; - if (!system) throw new Error('NO_SYSTEM'); - const extendedType = system.t.Object(...type.fields, ...obj.type.fields); - return new ObjectValue(extendedType, extendedData) as any; - } - - public extend[]>( - inp: (t: TypeBuilder, prop: PropDef, system: TypeSystem) => R, - ): ObjectValue, ...TuplesToFields]>> { - const system = this.type.getSystem(); - const r: PropDef = (key, val, data) => [key, val, data]; - const extension = inp(system.t, r, system); - const type = this.type; - const extendedFields: classes.ObjectFieldType[] = [...type.fields]; - const extendedData = {...this.data}; - for (const [key, val, data] of extension) { - extendedFields.push(system.t.prop(key, val)); - extendedData[key] = data; - } - const extendedType = system.t.Object(...extendedFields); - return new ObjectValue(extendedType, extendedData) as any; - } - - public toTypeScriptAst(): ts.TsTypeLiteral { - const node: ts.TsTypeLiteral = { - node: 'TypeLiteral', - members: [], - }; - const data = this.data as Record; - for (const [name, type] of Object.entries(data)) { - const schema = type.getSchema(); - const property: ts.TsPropertySignature = { - node: 'PropertySignature', - name, - type: type.toTypeScriptAst(), - }; - if (schema.title) property.comment = schema.title; - node.members.push(property); - } - return node; - } - - public toTypeScriptModuleAst(): ts.TsModuleDeclaration { - const node: ts.TsModuleDeclaration = { - node: 'ModuleDeclaration', - name: 'Router', - export: true, - statements: [ - { - node: 'TypeAliasDeclaration', - name: 'Routes', - type: this.toTypeScriptAst(), - export: true, - }, - ], - }; - const system = this.type.system; - if (!system) throw new Error('system is undefined'); - for (const alias of system.aliases.values()) node.statements.push({...alias.toTypeScriptAst(), export: true}); - return node; - } - - public toTypeScript(): string { - return toText(this.toTypeScriptModuleAst()); - } - - public toString(tab: string = ''): string { - return 'ObjectValue' + printTree(tab, [(tab) => this.type.toString(tab)]); - } -} diff --git a/src/json-type-value/Value.ts b/src/json-type-value/Value.ts deleted file mode 100644 index 3a66d3e93e..0000000000 --- a/src/json-type-value/Value.ts +++ /dev/null @@ -1,39 +0,0 @@ -import type {JsonValueCodec} from '@jsonjoy.com/json-pack/lib/codecs/types'; -import type {ResolveType, Type} from '../json-type'; - -export class Value { - constructor( - public type: T, - public data: ResolveType, - ) {} - - public encode(codec: JsonValueCodec): void { - const value = this.data; - const type = this.type; - if (value === undefined) return; - const encoder = codec.encoder; - if (!type) encoder.writeAny(value); - else type.encoder(codec.format)(value, encoder); - } -} - -if (process.env.NODE_ENV !== 'production') { - const encode = Value.prototype.encode; - Value.prototype.encode = function (codec: JsonValueCodec): void { - try { - encode.call(this, codec); - } catch (error) { - try { - // tslint:disable-next-line no-console - console.error(error); - const type = this.type; - if (type) { - const err = type.validator('object')(this.data); - // tslint:disable-next-line no-console - console.error(err); - } - } catch {} - throw error; - } - }; -} diff --git a/src/json-type-value/__tests__/ObjectValue.spec.ts b/src/json-type-value/__tests__/ObjectValue.spec.ts deleted file mode 100644 index 2af9d9a11b..0000000000 --- a/src/json-type-value/__tests__/ObjectValue.spec.ts +++ /dev/null @@ -1,18 +0,0 @@ -import {TypeSystem} from '../../json-type/system'; -import {ObjectValue} from '../ObjectValue'; - -test('can retrieve field as Value', () => { - const system = new TypeSystem(); - const {t} = system; - const obj = new ObjectValue(t.Object(t.prop('foo', t.str)), {foo: 'bar'}); - const foo = obj.get('foo'); - expect(foo.type.getTypeName()).toBe('str'); - expect(foo.data).toBe('bar'); -}); - -test('can print to string', () => { - const system = new TypeSystem(); - const {t} = system; - const obj = new ObjectValue(t.Object(t.prop('foo', t.str)), {foo: 'bar'}); - expect(obj + '').toMatchSnapshot(); -}); diff --git a/src/json-type-value/__tests__/__snapshots__/ObjectValue.spec.ts.snap b/src/json-type-value/__tests__/__snapshots__/ObjectValue.spec.ts.snap deleted file mode 100644 index ba1b31bc68..0000000000 --- a/src/json-type-value/__tests__/__snapshots__/ObjectValue.spec.ts.snap +++ /dev/null @@ -1,8 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`can print to string 1`] = ` -"ObjectValue -└─ obj - └─ "foo": - └─ str" -`; diff --git a/src/json-type-value/util.ts b/src/json-type-value/util.ts deleted file mode 100644 index a80e4f357f..0000000000 --- a/src/json-type-value/util.ts +++ /dev/null @@ -1,11 +0,0 @@ -import {Value} from './Value'; -import {ObjectValue} from './ObjectValue'; -import * as classes from '../json-type/type'; - -export const value: { - (type: T, data: unknown): ObjectValue; - (type: T, data: unknown): Value; -} = (type: any, data: any): any => { - if (type instanceof classes.ObjectType) return new ObjectValue(type as classes.ObjectType, data); - return new Value(type as classes.Type, data); -}; diff --git a/src/json-type/README.md b/src/json-type/README.md deleted file mode 100644 index 9228cb1d42..0000000000 --- a/src/json-type/README.md +++ /dev/null @@ -1,37 +0,0 @@ -# json-type - -Type builder for JSON and MessagePack. - -```ts -import {t} from 'json-joy/lib/json-type'; - -t.String(); // { kind: 'str' } -t.String({const: 'add'}); // { kind: 'str', const: 'add' } - -const type = t.Object([ - t.Field( - 'collection', - t.Object([ - t.Field('id', t.String({format: 'ascii', noJsonEscape: true})), - t.Field('ts', t.num, {format: 'u64'}), - t.Field('cid', t.String({format: 'ascii', noJsonEscape: true})), - t.Field('prid', t.String({format: 'ascii', noJsonEscape: true})), - t.Field('slug', t.String({format: 'ascii', noJsonEscape: true})), - t.Field('name', t.str, {isOptional: true}), - t.Field('src', t.str, {isOptional: true}), - t.Field('doc', t.str, {isOptional: true}), - t.Field('authz', t.str, {isOptional: true}), - t.Field('active', t.bool), - ]), - ), - t.Field( - 'block', - t.Object([ - t.Field('id', t.String({format: 'ascii', noJsonEscape: true})), - t.Field('ts', t.num, {format: 'u64'}), - t.Field('cid', t.String({format: 'ascii', noJsonEscape: true})), - t.Field('slug', t.String({format: 'ascii', noJsonEscape: true})), - ]), - ), -]); -``` diff --git a/src/json-type/__bench__/encode.ts b/src/json-type/__bench__/encode.ts deleted file mode 100644 index f165be6d44..0000000000 --- a/src/json-type/__bench__/encode.ts +++ /dev/null @@ -1,102 +0,0 @@ -/* tslint:disable no-console */ - -import {TypeSystem} from '..'; -import {CborEncoder} from '@jsonjoy.com/json-pack/lib/cbor/CborEncoder'; -import {JsonEncoder} from '@jsonjoy.com/json-pack/lib/json/JsonEncoder'; -import {CompiledBinaryEncoder} from '../codegen/types'; -import {EncodingFormat} from '@jsonjoy.com/json-pack/lib/constants'; -import {Writer} from '@jsonjoy.com/util/lib/buffers/Writer'; - -const system = new TypeSystem(); -const {t} = system; - -const response = system.alias( - 'Response', - t.Object( - t.prop( - 'collection', - t.Object( - t.prop('id', t.String({ascii: true, noJsonEscape: true})), - t.prop('ts', t.num.options({format: 'u64'})), - t.prop('cid', t.String({ascii: true, noJsonEscape: true})), - t.prop('prid', t.String({ascii: true, noJsonEscape: true})), - t.prop('slug', t.String({ascii: true, noJsonEscape: true})), - t.propOpt('name', t.str), - t.propOpt('src', t.str), - t.propOpt('doc', t.str), - t.propOpt('longText', t.str), - t.prop('active', t.bool), - t.prop('views', t.Array(t.num)), - ), - ), - t.prop( - 'block', - t.Object( - t.prop('id', t.String({ascii: true, noJsonEscape: true})), - t.prop('ts', t.num.options({format: 'u64'})), - t.prop('cid', t.String({ascii: true, noJsonEscape: true})), - t.prop('slug', t.String({ascii: true, noJsonEscape: true})), - ), - ), - ), -); - -const json = { - collection: { - id: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx', - ts: Date.now(), - cid: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx', - prid: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx', - slug: 'slug-name', - name: 'Super collection', - src: '{"foo": "bar"}', - longText: - 'After implementing a workaround for the first issue and merging the changes to another feature branch with some extra code and tests, the following error was printed in the stage’s log “JavaScript heap out of memory error.”', - active: true, - views: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], - }, - block: { - id: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx', - ts: Date.now(), - cid: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx', - slug: 'slug-name', - }, -}; - -const jsonTextEncoder = response.type.jsonTextEncoder(); -const jsonEncoderFn = response.type.encoder(EncodingFormat.Json) as CompiledBinaryEncoder; -const cborEncoderFn = response.type.encoder(EncodingFormat.Cbor) as CompiledBinaryEncoder; - -const jsonEncoder = new JsonEncoder(new Writer()); -const cborEncoder = new CborEncoder(); - -const {Suite} = require('benchmark'); -const suite = new Suite(); -suite - .add(`json-type "json" text encoder and Buffer.from()`, () => { - Buffer.from(jsonTextEncoder(json)); - }) - .add(`json-type "json" encoder`, () => { - jsonEncoderFn(json, jsonEncoder); - jsonEncoder.writer.flush(); - }) - .add(`json-type "cbor" encoder`, () => { - cborEncoderFn(json, cborEncoder); - cborEncoder.writer.flush(); - }) - .add(`json-pack CborEncoder`, () => { - cborEncoder.encode(json); - }) - .add(`Buffer.from(JSON.stringify())`, () => { - Buffer.from(JSON.stringify(json)); - }) - .on('cycle', (event: any) => { - console.log(String(event.target) + `, ${Math.round(1000000000 / event.target.hz)} ns/op`); - }) - .on('complete', () => { - console.log('Fastest is ' + suite.filter('fastest').map('name')); - }) - .run(); - -// console.log(response.encoder('json').toString()); -// console.log(response.encoder('cbor').toString()); diff --git a/src/json-type/__demos__/json-type.ts b/src/json-type/__demos__/json-type.ts deleted file mode 100644 index 6a416d75a1..0000000000 --- a/src/json-type/__demos__/json-type.ts +++ /dev/null @@ -1,142 +0,0 @@ -/** - * Run with: - * - * npx nodemon -q -x npx ts-node src/json-type/__demos__/json-type.ts - */ - -/* tslint:disable no-console */ - -import {EncodingFormat} from '@jsonjoy.com/json-pack/lib/constants'; -import {TypeSystem} from '../../json-type'; - -console.clear(); - -const system = new TypeSystem(); -const {t} = system; - -const type = t - .Object( - t.prop('id', t.str.options({ascii: true, min: 40, max: 80})).options({title: 'Every object has an ID'}), - t.propOpt('name', t.str), - t.prop('age', t.num.options({format: 'u8', gt: 16, lt: 100})), - t.prop('verified', t.bool), - t.prop('sex', t.Or(t.Const('male'), t.Const('female'), t.Const('other'), t.Const(null))), - ) - .options({ - title: 'My object', - }); - -console.log(); -console.log('Print to string:'); -console.log(); -console.log(type + ''); - -console.log(); -console.log('Generate random value:'); -console.log(); -console.log(type.random()); - -console.log(); -console.log('Can output JSON Type schema:'); -console.log(); -console.log(type.getSchema()); - -console.log(); -console.log('Can output JSON Schema schema:'); -console.log(); -console.log(type.toJsonSchema()); - -console.log(); -console.log('Can export and import the schema:'); -const type2 = t.import(type.getSchema()); -console.log(); -console.log(type2 + ''); - -console.log(); - -console.log('Can validate the schema.'); -type2.validateSchema(); - -console.log('Can validate data.'); -type2.validate({ - id: '1234567890123456789012345678901234567890', - name: 'John Doe', - age: 18, - verified: true, - sex: 'male', -}); - -console.log(); -console.log('Can serialize value to JSON:'); -console.log(); -console.log( - type2.toJson({ - id: '1234567890123456789012345678901234567890', - name: 'John Doe', - age: 18, - verified: true, - sex: 'male', - }), -); - -console.log(); -console.log('Can create a JSON Type schema out of a sample object:'); -const sample = { - id: '1234567890123456789012345678901234567890', - name: 'John Doe', - age: 18, -}; -const user = system.alias('User', t.from(sample)); -console.log(); -console.log(sample); -console.log(); -console.log(user.type + ''); - -console.log(); -console.log('Can generate TypeScript types for a schema:'); -console.log(); -console.log(user.toTypeScriptAst()); -console.log(); -console.log(user.toTypeScript()); - -console.log(); -console.log('Can compile a fast JSON serializer:'); -console.log(); -console.log(user.type.compileEncoder(EncodingFormat.Json).toString()); - -console.log(); -console.log('Can compile a fast CBOR serializer:'); -console.log(); -console.log(user.type.compileCborEncoder({system}).toString()); - -console.log(); -console.log('Can compile a fast MessagePack serializer:'); -console.log(); -console.log(user.type.compileMessagePackEncoder({system}).toString()); - -console.log(); -console.log('Can compile a fast validator, which returns booleans as errors:'); -console.log(); -const validator = user.type.compileValidator({ - errors: 'boolean', - skipObjectExtraFieldsCheck: true, -}); -console.log(validator.toString()); - -console.log(); -console.log('Can compile a fast validator, which returns JSON strings as errors:'); -console.log(); -const validator2 = user.type.compileValidator({ - errors: 'string', - skipObjectExtraFieldsCheck: true, -}); -console.log(validator2.toString()); - -console.log(); -console.log('Can compile a fast validator, which returns objects as errors:'); -console.log(); -const validator3 = user.type.compileValidator({ - errors: 'object', - skipObjectExtraFieldsCheck: true, -}); -console.log(validator3.toString()); diff --git a/src/json-type/__demos__/samples.ts b/src/json-type/__demos__/samples.ts deleted file mode 100644 index 83e77c8f36..0000000000 --- a/src/json-type/__demos__/samples.ts +++ /dev/null @@ -1,100 +0,0 @@ -import {CustomValidator, s} from '../../json-type'; - -export const customValidators: CustomValidator[] = [ - { - name: 'globalId', - fn: (id: string) => { - if (typeof id !== 'string') throw new Error('id must be string'); - if (id.length > 10) throw new Error('id too long'); - }, - }, -]; - -export const types = { - ID: s.String({ - title: 'Global resource ID', - description: 'Unique identifier for any resource in the system regardless of its type.', - validator: 'globalId', - }), - - User: s.Object( - [ - s.propOpt('type', s.Const<'User'>('User')), - s.propOpt('op', s.Const<-1>(-1)), - s.prop('gid', s.Ref('ID')), - s.prop('id', s.num, { - title: 'User ID', - description: 'Unique identifier for a user in the user table.', - }), - s.propOpt('name', s.str, { - title: 'User name', - description: 'Name of the user as entered during registration.', - }), - s.prop('email', s.str), - s.prop('timeCreated', s.Number({format: 'u'})), - s.prop('timeUpdated', s.Number({format: 'u'})), - s.prop('scores', s.Array(s.num)), - s.prop('isActive', s.bool), - s.prop('null', s.nil), - s.prop('unknown', s.any), - s.prop('isUser', s.Const(true)), - s.prop('isPost', s.Const(false)), - s.prop('tags', s.Array(s.Or(s.str, s.num))), - s.prop( - 'meta', - s.Object([], { - unknownFields: true, - }), - ), - ], - { - title: 'A user object', - description: - 'Users are entities in the system that represent a human. When user is created, automatically a User entity is assigned to that user.', - }, - ), - - CrateUserRequest: s.Object( - [s.prop('user', s.Object([s.propOpt('id', s.num), s.propOpt('name', s.str), s.prop('email', s.str)]))], - {title: 'The create user request'}, - ), - - CreateUserResponse: s.Object([s.prop('user', s.Ref('User'))], { - title: 'A response to a create user request', - description: 'The response to a create user request.', - }), - - UpdateUserResponse: s.Object( - [ - s.prop('user', s.Ref('User')), - s.prop('changes', s.Number({format: 'u'}), { - title: 'The number of fields that were changed', - description: 'The number of fields that were changed during the update user call.', - }), - ], - { - title: 'A response to a create user request', - description: 'The response to a create user request.', - }, - ), - - 'pubsub.channel.Channel': s.Object( - [ - s.prop('id', s.str, {title: 'ID of the user'}), - s.prop('payload', s.Ref('pubsub.channel.PayloadType'), {description: 'Yup, the payload.'}), - s.prop('meta', s.Object([s.Field('description', s.str)])), - ], - {description: 'A channel'}, - ), - - 'pubsub.channel.PayloadType': s.String({ - description: 'The type of payload that is sent to the channel.', - }), - - 'pubsub.channel.CreateChannelResponse': s.Object([ - s.prop('channel', s.Ref('pubsub.channel.Channel')), - s.prop('project', s.Ref('util.Project')), - ]), - - 'util.Project': s.Object([s.prop('id', s.String({})), s.prop('name', s.str)]), -}; diff --git a/src/json-type/codegen/WriteBlobStep.ts b/src/json-type/codegen/WriteBlobStep.ts deleted file mode 100644 index feb9792472..0000000000 --- a/src/json-type/codegen/WriteBlobStep.ts +++ /dev/null @@ -1,3 +0,0 @@ -export class WriteBlobStep { - constructor(public arr: Uint8Array) {} -} diff --git a/src/json-type/codegen/binary/BinaryEncoderCodegenContext.ts b/src/json-type/codegen/binary/BinaryEncoderCodegenContext.ts deleted file mode 100644 index e8a395d29b..0000000000 --- a/src/json-type/codegen/binary/BinaryEncoderCodegenContext.ts +++ /dev/null @@ -1,126 +0,0 @@ -import {Codegen, CodegenStepExecJs} from '@jsonjoy.com/util/lib/codegen'; -import {WriteBlobStep} from '../WriteBlobStep'; -import {concat} from '@jsonjoy.com/util/lib/buffers/concat'; -import {Value} from '../../../json-type-value/Value'; -import type {TypeSystem} from '../../system'; -import type {Type} from '../../type'; -import type {CompiledBinaryEncoder} from '../types'; -import type {BinaryJsonEncoder} from '@jsonjoy.com/json-pack/lib/types'; - -type Step = WriteBlobStep | CodegenStepExecJs; - -export interface BinaryEncoderCodegenContextOptions { - /** Type for which to generate the encoder. */ - type: Type; - - /** Encoder to generate inlined blobs. */ - encoder: Encoder; - - /** Type system to use for alias and validator resolution. */ - system?: TypeSystem; - - /** Name to concatenate to the end of the generated function. */ - name?: string; -} - -export class BinaryEncoderCodegenContext { - public readonly codegen: Codegen; - - constructor(public readonly options: BinaryEncoderCodegenContextOptions) { - this.codegen = new Codegen({ - name: 'toBinary' + (options.name ? '_' + options.name : ''), - args: ['r0', 'encoder'], - prologue: /* js */ ` -var writer = encoder.writer; -writer.ensureCapacity(capacityEstimator(r0)); -var uint8 = writer.uint8, view = writer.view;`, - epilogue: '', - linkable: { - Value, - }, - processSteps: (steps) => { - const stepsJoined: Step[] = []; - for (let i = 0; i < steps.length; i++) { - const step = steps[i]; - if (step instanceof CodegenStepExecJs) stepsJoined.push(step); - else if (step instanceof WriteBlobStep) { - const last = stepsJoined[stepsJoined.length - 1]; - if (last instanceof WriteBlobStep) last.arr = concat(last.arr, step.arr); - else stepsJoined.push(step); - } - } - const execSteps: CodegenStepExecJs[] = []; - for (const step of stepsJoined) { - if (step instanceof CodegenStepExecJs) { - execSteps.push(step); - } else if (step instanceof WriteBlobStep) { - execSteps.push(this.codegenBlob(step)); - } - } - return execSteps; - }, - }); - this.codegen.linkDependency(options.type.capacityEstimator(), 'capacityEstimator'); - } - - public getBigIntStr(arr: Uint8Array, offset: number): string { - const buf = new Uint8Array(8); - for (let i = 0; i < 8; i++) buf[i] = arr[offset + i]; - const view = new DataView(buf.buffer); - const bigint = view.getBigUint64(0); - return bigint.toString() + 'n'; - } - - private codegenBlob(step: WriteBlobStep) { - const lines: string[] = []; - const ro = this.codegen.getRegister(); - const length = step.arr.length; - if (length === 1) { - lines.push(/* js */ `uint8[writer.x++] = ${step.arr[0]};`); - } else { - lines.push(`var ${ro} = writer.x;`); - lines.push(`writer.x += ${step.arr.length};`); - let i = 0; - while (i < length) { - const remaining = length - i; - if (remaining >= 8) { - const value = this.getBigIntStr(step.arr, i); - lines.push(/* js */ `view.setBigUint64(${ro}${i ? ` + ${i}` : ''}, ${value});`); - i += 8; - } else if (remaining >= 4) { - const value = (step.arr[i] << 24) | (step.arr[i + 1] << 16) | (step.arr[i + 2] << 8) | step.arr[i + 3]; - lines.push(/* js */ `view.setInt32(${ro}${i ? ` + ${i}` : ''}, ${value});`); - i += 4; - } else if (remaining >= 2) { - const value = (step.arr[i] << 8) | step.arr[i + 1]; - lines.push(/* js */ `view.setInt16(${ro}${i ? ` + ${i}` : ''}, ${value});`); - i += 2; - } else { - lines.push(/* js */ `uint8[${ro}${i ? ` + ${i}` : ''}] = ${step.arr[i]};`); - i++; - } - } - } - const js = lines.join('\n'); - return new CodegenStepExecJs(js); - } - - public js(js: string): void { - this.codegen.js(js); - } - - public gen(callback: (encoder: Encoder) => void): Uint8Array { - const {encoder} = this.options; - encoder.writer.reset(); - callback(encoder); - return encoder.writer.flush(); - } - - public blob(arr: Uint8Array): void { - this.codegen.step(new WriteBlobStep(arr)); - } - - public compile() { - return this.codegen.compile(); - } -} diff --git a/src/json-type/codegen/binary/CborEncoderCodegenContext.ts b/src/json-type/codegen/binary/CborEncoderCodegenContext.ts deleted file mode 100644 index 94923f4412..0000000000 --- a/src/json-type/codegen/binary/CborEncoderCodegenContext.ts +++ /dev/null @@ -1,5 +0,0 @@ -import type {CborEncoder} from '@jsonjoy.com/json-pack/lib/cbor/CborEncoder'; -import {BinaryEncoderCodegenContext, type BinaryEncoderCodegenContextOptions} from './BinaryEncoderCodegenContext'; - -export interface CborEncoderCodegenContextOptions extends BinaryEncoderCodegenContextOptions {} -export class CborEncoderCodegenContext extends BinaryEncoderCodegenContext {} diff --git a/src/json-type/codegen/binary/JsonEncoderCodegenContext.ts b/src/json-type/codegen/binary/JsonEncoderCodegenContext.ts deleted file mode 100644 index bea35022d1..0000000000 --- a/src/json-type/codegen/binary/JsonEncoderCodegenContext.ts +++ /dev/null @@ -1,5 +0,0 @@ -import type {JsonEncoder} from '@jsonjoy.com/json-pack/lib/json/JsonEncoder'; -import {BinaryEncoderCodegenContext, type BinaryEncoderCodegenContextOptions} from './BinaryEncoderCodegenContext'; - -export interface JsonEncoderCodegenContextOptions extends BinaryEncoderCodegenContextOptions {} -export class JsonEncoderCodegenContext extends BinaryEncoderCodegenContext {} diff --git a/src/json-type/codegen/binary/MessagePackEncoderCodegenContext.ts b/src/json-type/codegen/binary/MessagePackEncoderCodegenContext.ts deleted file mode 100644 index 0583134e39..0000000000 --- a/src/json-type/codegen/binary/MessagePackEncoderCodegenContext.ts +++ /dev/null @@ -1,5 +0,0 @@ -import type {MsgPackEncoder} from '@jsonjoy.com/json-pack/lib/msgpack/MsgPackEncoder'; -import {BinaryEncoderCodegenContext, type BinaryEncoderCodegenContextOptions} from './BinaryEncoderCodegenContext'; - -export interface MessagePackEncoderCodegenContextOptions extends BinaryEncoderCodegenContextOptions {} -export class MessagePackEncoderCodegenContext extends BinaryEncoderCodegenContext {} diff --git a/src/json-type/codegen/binary/__tests__/CborEncoderCodegenContext.spec.ts b/src/json-type/codegen/binary/__tests__/CborEncoderCodegenContext.spec.ts deleted file mode 100644 index cfb2fc23d6..0000000000 --- a/src/json-type/codegen/binary/__tests__/CborEncoderCodegenContext.spec.ts +++ /dev/null @@ -1,24 +0,0 @@ -import {TypeSystem} from '../../../system'; -import {CborEncoder} from '@jsonjoy.com/json-pack/lib/cbor/CborEncoder'; -import {CborDecoder} from '@jsonjoy.com/json-pack/lib/cbor/CborDecoder'; -import {Type} from '../../../type'; -import {testBinaryCodegen} from './testBinaryCodegen'; -import {EncodingFormat} from '@jsonjoy.com/json-pack/lib/constants'; -import {Writer} from '@jsonjoy.com/util/lib/buffers/Writer'; - -const writer = new Writer(1); -const encoder = new CborEncoder(writer); -const decoder = new CborDecoder(); - -const transcode = (system: TypeSystem, type: Type, value: unknown) => { - const fn = type.encoder(EncodingFormat.Cbor); - // console.log(fn.toString()) - // console.log(fn.toString()); - encoder.writer.reset(); - fn(value, encoder); - const encoded = encoder.writer.flush(); - const decoded = decoder.decode(encoded); - return decoded; -}; - -testBinaryCodegen(transcode); diff --git a/src/json-type/codegen/binary/__tests__/JsonEncoderCodegenContext.spec.ts b/src/json-type/codegen/binary/__tests__/JsonEncoderCodegenContext.spec.ts deleted file mode 100644 index a5747828a2..0000000000 --- a/src/json-type/codegen/binary/__tests__/JsonEncoderCodegenContext.spec.ts +++ /dev/null @@ -1,22 +0,0 @@ -import {TypeSystem} from '../../../system'; -import {Type} from '../../../type'; -import {testBinaryCodegen} from './testBinaryCodegen'; -import {EncodingFormat} from '@jsonjoy.com/json-pack/lib/constants'; -import {JsonEncoder} from '@jsonjoy.com/json-pack/lib/json/JsonEncoder'; -import {Writer} from '@jsonjoy.com/util/lib/buffers/Writer'; -import {parse} from '../../../../json-binary'; - -const encoder = new JsonEncoder(new Writer(16)); - -const transcode = (system: TypeSystem, type: Type, value: unknown) => { - const fn = type.encoder(EncodingFormat.Json); - encoder.writer.reset(); - fn(value, encoder); - const encoded = encoder.writer.flush(); - const json = Buffer.from(encoded).toString('utf-8'); - // console.log(json); - const decoded = parse(json); - return decoded; -}; - -testBinaryCodegen(transcode); diff --git a/src/json-type/codegen/binary/__tests__/MessagePackEncoderCodegenContext.spec.ts b/src/json-type/codegen/binary/__tests__/MessagePackEncoderCodegenContext.spec.ts deleted file mode 100644 index 64cf21bf65..0000000000 --- a/src/json-type/codegen/binary/__tests__/MessagePackEncoderCodegenContext.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import {TypeSystem} from '../../../system'; -import {MsgPackEncoder} from '@jsonjoy.com/json-pack/lib/msgpack/MsgPackEncoder'; -import {MsgPackDecoder} from '@jsonjoy.com/json-pack/lib/msgpack/MsgPackDecoder'; -import {Type} from '../../../type'; -import {testBinaryCodegen} from './testBinaryCodegen'; -import {EncodingFormat} from '@jsonjoy.com/json-pack/lib/constants'; -import {Writer} from '@jsonjoy.com/util/lib/buffers/Writer'; - -const writer = new Writer(64); -const encoder = new MsgPackEncoder(writer); -const decoder = new MsgPackDecoder(); - -const transcode = (system: TypeSystem, type: Type, value: unknown) => { - const fn = type.encoder(EncodingFormat.MsgPack); - // console.log(fn.toString()); - encoder.writer.reset(); - fn(value, encoder); - const encoded = encoder.writer.flush(); - const decoded = decoder.decode(encoded); - return decoded; -}; - -testBinaryCodegen(transcode); diff --git a/src/json-type/codegen/binary/__tests__/testBinaryCodegen.ts b/src/json-type/codegen/binary/__tests__/testBinaryCodegen.ts deleted file mode 100644 index ce33ba0030..0000000000 --- a/src/json-type/codegen/binary/__tests__/testBinaryCodegen.ts +++ /dev/null @@ -1,589 +0,0 @@ -import {TypeSystem} from '../../../system'; -import {Type} from '../../../type'; - -export const testBinaryCodegen = (transcode: (system: TypeSystem, type: Type, value: unknown) => void) => { - describe('"any" type', () => { - test('can encode any value - 1', () => { - const system = new TypeSystem(); - const any = system.t.any; - const value = {foo: 'bar'}; - const decoded = transcode(system, any, value); - expect(decoded).toStrictEqual(value); - }); - - test('can encode any value - 2', () => { - const system = new TypeSystem(); - const any = system.t.any; - const value = 123; - const decoded = transcode(system, any, value); - expect(decoded).toStrictEqual(value); - }); - }); - - describe('"const" type', () => { - test('can encode number const', () => { - const system = new TypeSystem(); - const any = system.t.Const<123>(123); - const value = {foo: 'bar'}; - const decoded = transcode(system, any, value); - expect(decoded).toStrictEqual(123); - }); - - test('can encode array const', () => { - const system = new TypeSystem(); - const any = system.t.Const([1, 2, 3]); - const decoded = transcode(system, any, [false, true, null]); - expect(decoded).toStrictEqual([1, 2, 3]); - }); - }); - - describe('"bool" type', () => { - test('can encode booleans', () => { - const system = new TypeSystem(); - const any = system.t.bool; - expect(transcode(system, any, true)).toStrictEqual(true); - expect(transcode(system, any, false)).toStrictEqual(false); - expect(transcode(system, any, 1)).toStrictEqual(true); - expect(transcode(system, any, 0)).toStrictEqual(false); - }); - }); - - describe('"num" type', () => { - test('can encode any number', () => { - const system = new TypeSystem(); - const any = system.t.num; - expect(transcode(system, any, 0)).toBe(0); - expect(transcode(system, any, 1)).toBe(1); - expect(transcode(system, any, 123)).toBe(123); - expect(transcode(system, any, 0xfaffaf78273)).toBe(0xfaffaf78273); - expect(transcode(system, any, -234435)).toBe(-234435); - expect(transcode(system, any, 1.234)).toBe(1.234); - }); - - test('can encode an integer', () => { - const system = new TypeSystem(); - const any = system.t.num.options({format: 'i'}); - expect(transcode(system, any, 0)).toBe(0); - expect(transcode(system, any, 1)).toBe(1); - expect(transcode(system, any, 123)).toBe(123); - expect(transcode(system, any, 0xfaffa273)).toBe(0xfaffa273); - expect(transcode(system, any, 1.1)).toBe(1); - }); - - test('can encode an unsigned ints', () => { - const system = new TypeSystem(); - const any = system.t.num.options({format: 'u8'}); - expect(transcode(system, any, 0)).toBe(0); - expect(transcode(system, any, 1)).toBe(1); - expect(transcode(system, any, 123)).toBe(123); - expect(transcode(system, any, 1.1)).toBe(1); - }); - - test('can encode an floats', () => { - const system = new TypeSystem(); - const any = system.t.num.options({format: 'f'}); - expect(transcode(system, any, 0)).toBe(0); - expect(transcode(system, any, 1)).toBe(1); - expect(transcode(system, any, 123)).toBe(123); - expect(transcode(system, any, 1.1)).toBe(1.1); - expect(transcode(system, any, 123.456)).toBe(123.456); - }); - }); - - describe('"str" type', () => { - test('can encode regular strings', () => { - const system = new TypeSystem(); - const type = system.t.str; - let value = ''; - expect(transcode(system, type, value)).toBe(value); - value = '1'; - expect(transcode(system, type, value)).toBe(value); - value = 'asdfasdf'; - expect(transcode(system, type, value)).toBe(value); - value = 'asdfasdfasdfas98ahcas982h39zsdKJHH9823asd'; - expect(transcode(system, type, value)).toBe(value); - value = - '❌🏎asdfasdfasdfasdfo(*@()J_!JOICPA:KD:ZCLZSLDIJ)(!J@LKDVlkdsjalkjf;asdlfj;laskdjf;lkajsdf⏰as98ahca🎉s982h39zsdKJHH9🥳823asd'; - expect(transcode(system, type, value)).toBe(value); - }); - - test('can encode ascii strings', () => { - const system = new TypeSystem(); - const type = system.t.str.options({ascii: true}); - let value = ''; - expect(transcode(system, type, value)).toBe(value); - value = '1'; - expect(transcode(system, type, value)).toBe(value); - value = 'asdfasdf'; - expect(transcode(system, type, value)).toBe(value); - value = 'asdfasdfasdfas98ahcas982h39zsdKJHH9823asd'; - expect(transcode(system, type, value)).toBe(value); - value = - '❌🏎asdfasdfasdfasdfo(*@()J_!JOICPA:KD:ZCLZSLDIJ)(!J@LKDVlkdsjalkjf;asdlfj;laskdjf;lkajsdf⏰as98ahca🎉s982h39zsdKJHH9🥳823asd'; - expect(transcode(system, type, value)).not.toBe(value); - }); - }); - - describe('"bin" type', () => { - test('can encode binary data', () => { - const system = new TypeSystem(); - const type = system.t.bin; - let value = new Uint8Array(); - expect(transcode(system, type, value)).toStrictEqual(value); - value = new Uint8Array([1, 3, 3]); - expect(transcode(system, type, value)).toStrictEqual(value); - }); - }); - - describe('"arr" type', () => { - test('can encode simple arrays', () => { - const system = new TypeSystem(); - const type = system.t.arr; - let value: any[] = []; - expect(transcode(system, type, value)).toStrictEqual(value); - value = [1, 2, 3]; - expect(transcode(system, type, value)).toStrictEqual(value); - }); - - test('can encode array inside array', () => { - const system = new TypeSystem(); - const type = system.t.Array(system.t.arr); - const value: any[] = [ - [1, 2, 3], - [4, 5, 6], - [7, 8, 9], - ]; - expect(transcode(system, type, value)).toStrictEqual(value); - }); - - test('can encode array of strings', () => { - const system = new TypeSystem(); - const type = system.t.Array(system.t.str); - const value: any[] = ['1', '2', '3']; - expect(transcode(system, type, value)).toStrictEqual(value); - }); - }); - - describe('"tup" type', () => { - test('can encode a simple tuple', () => { - const system = new TypeSystem(); - const t = system.t; - const type = system.t.Tuple(t.str, t.num, t.bool); - const value: any[] = ['abc', 123, true]; - expect(transcode(system, type, value)).toStrictEqual(value); - }); - - test('can encode an empty tuple', () => { - const system = new TypeSystem(); - const t = system.t; - const type = system.t.Tuple(); - const value: any[] = []; - expect(transcode(system, type, value)).toStrictEqual(value); - }); - - test('can encode a tuple of arrays', () => { - const system = new TypeSystem(); - const t = system.t; - const type = system.t.Tuple(t.arr, t.arr); - const value: any[] = [[], [1, 'b', false]]; - expect(transcode(system, type, value)).toStrictEqual(value); - }); - }); - - describe('"obj" type', () => { - test('can encode empty object', () => { - const system = new TypeSystem(); - const t = system.t; - const type = t.obj; - const value: {} = {}; - expect(transcode(system, type, value)).toStrictEqual(value); - }); - - test('can encode empty object, which has optional fields', () => { - const system = new TypeSystem(); - const t = system.t; - const type = t.Object(t.propOpt('field1', t.str)); - const value1: {} = {}; - expect(transcode(system, type, value1)).toStrictEqual(value1); - const value2: {} = {field1: 'abc'}; - expect(transcode(system, type, value2)).toStrictEqual(value2); - }); - - test('can encode fixed size object', () => { - const system = new TypeSystem(); - const t = system.t; - const type = t.Object(t.prop('field1', t.str), t.prop('field2', t.num), t.prop('bool', t.bool)); - const value: {} = { - field1: 'abc', - field2: 123, - bool: true, - }; - expect(transcode(system, type, value)).toStrictEqual(value); - }); - - test('can encode object with an optional field', () => { - const system = new TypeSystem(); - const t = system.t; - const type = t.Object(t.prop('id', t.str), t.propOpt('name', t.str)); - const value: {} = { - id: 'xxxxx', - name: 'Go Lang', - }; - expect(transcode(system, type, value)).toStrictEqual(value); - }); - - test('can encode object with a couple of optional fields', () => { - const system = new TypeSystem(); - const t = system.t; - const type = t.Object( - t.prop('id', t.str), - t.propOpt('name', t.str), - t.prop('age', t.num), - t.propOpt('address', t.str), - ); - const value: {} = { - id: 'xxxxx', - name: 'Go Lang', - age: 30, - address: '123 Main St', - }; - expect(transcode(system, type, {...value, unknownField: 123})).toStrictEqual(value); - }); - - test('can encode object with unknown fields', () => { - const system = new TypeSystem(); - const t = system.t; - const type = t - .Object(t.prop('id', t.str), t.propOpt('name', t.str), t.prop('age', t.num), t.propOpt('address', t.str)) - .options({encodeUnknownFields: true}); - const value: {} = { - id: 'xxxxx', - name: 'Go Lang', - ____unknownField: 123, - age: 30, - address: '123 Main St', - }; - expect(transcode(system, type, value)).toStrictEqual(value); - }); - - test('can encode nested objects', () => { - const system = new TypeSystem(); - const t = system.t; - const type = t - .Object( - t.prop('id', t.str), - t.propOpt('name', t.str), - t.prop('addr', t.Object(t.prop('street', t.str))), - t.prop( - 'interests', - t.Object(t.propOpt('hobbies', t.Array(t.str)), t.propOpt('sports', t.Array(t.Tuple(t.num, t.str)))), - ), - ) - .options({encodeUnknownFields: true}); - const decoded = transcode(system, type, { - id: 'xxxxx', - name: 'Go Lang', - ____unknownField: 123, - addr: { - street: '123 Main St', - ____extra: true, - }, - interests: { - hobbies: ['hiking', 'biking'], - sports: [ - [1, 'football'], - [12333, 'skiing'], - ], - ______extraProp: 'abc', - }, - }); - expect(decoded).toStrictEqual({ - id: 'xxxxx', - name: 'Go Lang', - ____unknownField: 123, - addr: { - street: '123 Main St', - }, - interests: { - hobbies: ['hiking', 'biking'], - sports: [ - [1, 'football'], - [12333, 'skiing'], - ], - }, - }); - }); - - test('can encode object with only optional fields (encodeUnknownFields = true)', () => { - const system = new TypeSystem(); - const t = system.t; - const type = t - .Object(t.propOpt('id', t.str), t.propOpt('name', t.str), t.propOpt('address', t.str)) - .options({encodeUnknownFields: true}); - let value: {} = { - id: 'xxxxx', - name: 'Go Lang', - ____unknownField: 123, - age: 30, - address: '123 Main St', - }; - expect(transcode(system, type, value)).toStrictEqual(value); - value = { - ____unknownField: 123, - address: '123 Main St', - }; - expect(transcode(system, type, value)).toStrictEqual(value); - value = { - ____unknownField: 123, - }; - expect(transcode(system, type, value)).toStrictEqual(value); - value = {}; - expect(transcode(system, type, value)).toStrictEqual(value); - }); - - test('can encode object with only optional fields (encodeUnknownFields = false)', () => { - const system = new TypeSystem(); - const t = system.t; - const type = t - .Object(t.propOpt('id', t.str), t.propOpt('name', t.str), t.propOpt('address', t.str)) - .options({encodeUnknownFields: false}); - let value: {} = { - id: 'xxxxx', - name: 'Go Lang', - address: '123 Main St', - }; - expect(transcode(system, type, value)).toStrictEqual(value); - value = { - ____unknownField: 123, - address: '123 Main St', - }; - expect(transcode(system, type, value)).toStrictEqual({ - address: '123 Main St', - }); - value = { - ____unknownField: 123, - }; - expect(transcode(system, type, value)).toStrictEqual({}); - value = {}; - expect(transcode(system, type, value)).toStrictEqual({}); - }); - }); - - describe('"map" type', () => { - test('can encode empty map', () => { - const system = new TypeSystem(); - const t = system.t; - const type = t.map; - const value: {} = {}; - expect(transcode(system, type, value)).toStrictEqual(value); - }); - - test('can encode empty map with one key', () => { - const system = new TypeSystem(); - const t = system.t; - const type = t.map; - const value: {} = {a: 'asdf'}; - expect(transcode(system, type, value)).toStrictEqual(value); - }); - - test('can encode typed map with two keys', () => { - const system = new TypeSystem(); - const t = system.t; - const type = t.Map(t.bool); - const value: {} = {x: true, y: false}; - expect(transcode(system, type, value)).toStrictEqual(value); - }); - - test('can encode nested maps', () => { - const system = new TypeSystem(); - const t = system.t; - const type = t.Map(t.Map(t.bool)); - const value: {} = {a: {x: true, y: false}}; - expect(transcode(system, type, value)).toStrictEqual(value); - }); - }); - - describe('"ref" type', () => { - test('can encode a simple reference', () => { - const system = new TypeSystem(); - const t = system.t; - system.alias('Obj', t.Object(t.prop('foo', t.str))); - const type = t.Ref('Obj'); - expect(transcode(system, type, {foo: 'bar'})).toStrictEqual({foo: 'bar'}); - }); - }); - - describe('"or" type', () => { - test('can encode a simple union type', () => { - const system = new TypeSystem(); - const t = system.t; - const type = system.t.Or(t.str, t.num).options({ - discriminator: ['if', ['==', 'string', ['type', ['get', '']]], 0, 1], - }); - expect(transcode(system, type, 123)).toStrictEqual(123); - expect(transcode(system, type, 'asdf')).toStrictEqual('asdf'); - }); - }); - - describe('various', () => { - test('encodes benchmark example', () => { - const system = new TypeSystem(); - const t = system.t; - const response = system.alias( - 'Response', - t.Object( - t.prop( - 'collection', - t.Object( - t.prop('id', t.String({ascii: true, noJsonEscape: true})), - t.prop('ts', t.num.options({format: 'u64'})), - t.prop('cid', t.String({ascii: true, noJsonEscape: true})), - t.prop('prid', t.String({ascii: true, noJsonEscape: true})), - t.prop('slug', t.String({ascii: true, noJsonEscape: true})), - t.propOpt('name', t.str), - t.propOpt('src', t.str), - t.propOpt('doc', t.str), - t.propOpt('longText', t.str), - t.prop('active', t.bool), - t.prop('views', t.Array(t.num)), - ), - ), - t.prop( - 'block', - t.Object( - t.prop('id', t.String({ascii: true, noJsonEscape: true})), - t.prop('ts', t.num.options({format: 'u64'})), - t.prop('cid', t.String({ascii: true, noJsonEscape: true})), - t.prop('slug', t.String({ascii: true, noJsonEscape: true})), - ), - ), - ), - ); - const value = { - collection: { - id: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx', - ts: Date.now(), - cid: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx', - prid: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx', - slug: 'slug-name', - name: 'Super collection', - src: '{"foo": "bar"}', - longText: - 'After implementing a workaround for the first issue and merging the changes to another feature branch with some extra code and tests, the following error was printed in the stage’s log “JavaScript heap out of memory error.”', - active: true, - views: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], - }, - block: { - id: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx', - ts: Date.now(), - cid: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx', - slug: 'slug-name', - }, - }; - const decoded = transcode(system, response.type, value); - // console.log(decoded); - expect(decoded).toStrictEqual(value); - }); - - test('serializes according to schema a POJO object', () => { - const system = new TypeSystem(); - const t = system.t; - const type = t.Object( - t.prop('a', t.num), - t.prop('b', t.str), - t.prop('c', t.nil), - t.prop('d', t.bool), - t.prop('arr', t.Array(t.Object(t.prop('foo', t.Array(t.num)), t.prop('.!@#', t.str)))), - t.prop('bin', t.bin), - ); - const value = { - a: 1.1, - b: 'sdf', - c: null, - d: true, - arr: [ - {foo: [1], '.!@#': ''}, - {'.!@#': '......', foo: [4, 4, 4.4]}, - ], - bin: new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]), - }; - const decoded = transcode(system, type, value); - expect(decoded).toStrictEqual(value); - }); - - test('supports "encodeUnknownFields" property', () => { - const system = new TypeSystem(); - const t = system.t; - const type = t.Object(t.prop('a', t.Object().options({encodeUnknownFields: true}))); - const value = { - a: { - foo: 123, - }, - }; - const decoded = transcode(system, type, value); - expect(decoded).toStrictEqual(value); - }); - - test('supports "encodeUnknownFields" property', () => { - const system = new TypeSystem(); - const t = system.t; - const type = t.Object(t.prop('a', t.num), t.propOpt('b', t.num), t.prop('c', t.bool), t.propOpt('d', t.nil)); - const json1 = { - a: 1.1, - b: 3, - c: true, - d: null, - }; - const json2 = { - a: 1.1, - c: true, - }; - const json3 = { - a: 1.1, - c: true, - b: 0, - }; - const decoded1 = transcode(system, type, json1); - expect(decoded1).toStrictEqual(json1); - const decoded2 = transcode(system, type, json2); - expect(decoded2).toStrictEqual(json2); - const decoded = transcode(system, type, json3); - expect(decoded).toStrictEqual(json3); - }); - - test('supports "encodeUnknownFields" property', () => { - const system = new TypeSystem(); - const t = system.t; - const type = t.Object( - t.prop( - 'collection', - t.Object( - t.prop('id', t.str), - t.prop('ts', t.num), - t.prop('cid', t.str), - t.prop('prid', t.str), - t.prop('slug', t.str), - t.propOpt('name', t.str), - t.propOpt('src', t.str), - t.propOpt('doc', t.str), - t.propOpt('authz', t.str), - ), - ), - ); - const value = { - collection: { - id: '123', - ts: 123, - cid: '123', - prid: '123', - slug: 'slug', - name: 'name', - src: 'src', - authz: 'authz', - }, - }; - const decoded = transcode(system, type, value); - expect(decoded).toStrictEqual(value); - }); - }); -}; diff --git a/src/json-type/codegen/capacity/CapacityEstimatorCodegenContext.ts b/src/json-type/codegen/capacity/CapacityEstimatorCodegenContext.ts deleted file mode 100644 index 0e5fee9ef4..0000000000 --- a/src/json-type/codegen/capacity/CapacityEstimatorCodegenContext.ts +++ /dev/null @@ -1,58 +0,0 @@ -import {Codegen, CodegenStepExecJs} from '@jsonjoy.com/util/lib/codegen'; -import {maxEncodingCapacity} from '../../../json-size'; -import {Value} from '../../../json-type-value/Value'; -import type {TypeSystem} from '../../system'; -import type {Type} from '../../type'; - -export type CompiledCapacityEstimator = (value: any) => number; - -class IncrementSizeStep { - constructor(public readonly inc: number) {} -} - -export interface CapacityEstimatorCodegenContextOptions { - /** Type for which to generate the encoder. */ - type: Type; - - /** Type system to use for alias and validator resolution. */ - system?: TypeSystem; - - /** Name to concatenate to the end of the generated function. */ - name?: string; -} - -export class CapacityEstimatorCodegenContext { - public readonly codegen: Codegen; - - constructor(public readonly options: CapacityEstimatorCodegenContextOptions) { - this.codegen = new Codegen({ - name: 'approxSize' + (options.name ? '_' + options.name : ''), - args: ['r0'], - prologue: /* js */ `var size = 0;`, - epilogue: /* js */ `return size;`, - linkable: { - Value, - }, - processSteps: (steps) => { - const stepsJoined: CodegenStepExecJs[] = []; - for (let i = 0; i < steps.length; i++) { - const step = steps[i]; - if (step instanceof CodegenStepExecJs) stepsJoined.push(step); - else if (step instanceof IncrementSizeStep) { - stepsJoined.push(new CodegenStepExecJs(/* js */ `size += ${step.inc};`)); - } - } - return stepsJoined; - }, - }); - this.codegen.linkDependency(maxEncodingCapacity, 'maxEncodingCapacity'); - } - - public inc(inc: number): void { - this.codegen.step(new IncrementSizeStep(inc)); - } - - public compile(): CompiledCapacityEstimator { - return this.codegen.compile(); - } -} diff --git a/src/json-type/codegen/capacity/__tests__/CapacityEstimatorCodegenContext.spec.ts b/src/json-type/codegen/capacity/__tests__/CapacityEstimatorCodegenContext.spec.ts deleted file mode 100644 index ed7bfb5487..0000000000 --- a/src/json-type/codegen/capacity/__tests__/CapacityEstimatorCodegenContext.spec.ts +++ /dev/null @@ -1,217 +0,0 @@ -import {maxEncodingCapacity} from '../../../../json-size'; -import {TypeSystem} from '../../../system'; - -describe('"any" type', () => { - test('returns the same result as maxEncodingCapacity()', () => { - const system = new TypeSystem(); - const any = system.t.any; - const estimator = any.compileCapacityEstimator({}); - const values = [null, true, false, 1, 123.123, '', 'adsf', [], {}, {foo: 'bar'}, [{a: [{b: null}]}]]; - for (const value of values) expect(estimator(value)).toBe(maxEncodingCapacity(value)); - }); -}); - -describe('const', () => { - test('returns exactly the same size as maxEncodingCapacity()', () => { - const system = new TypeSystem(); - const type = system.t.Const({foo: [123]}); - const estimator = type.compileCapacityEstimator({}); - expect(estimator(null)).toBe(maxEncodingCapacity({foo: [123]})); - }); -}); - -describe('null', () => { - test('returns exactly the same size as maxEncodingCapacity()', () => { - const system = new TypeSystem(); - const type = system.t.nil; - const estimator = type.compileCapacityEstimator({}); - expect(estimator(null)).toBe(maxEncodingCapacity(null)); - }); -}); - -describe('boolean', () => { - test('returns 5', () => { - const system = new TypeSystem(); - const type = system.t.bool; - const estimator = type.compileCapacityEstimator({}); - expect(estimator(null)).toBe(5); - }); -}); - -describe('number', () => { - test('returns 22', () => { - const system = new TypeSystem(); - const type = system.t.num; - const estimator = type.compileCapacityEstimator({}); - expect(estimator(null)).toBe(22); - }); -}); - -describe('string', () => { - test('empty string', () => { - const system = new TypeSystem(); - const type = system.t.str; - const estimator = type.compileCapacityEstimator({}); - expect(estimator('')).toBe(maxEncodingCapacity('')); - }); - - test('short string', () => { - const system = new TypeSystem(); - const type = system.t.str; - const estimator = type.compileCapacityEstimator({}); - expect(estimator('asdf')).toBe(maxEncodingCapacity('asdf')); - }); -}); - -describe('binary', () => { - test('empty', () => { - const system = new TypeSystem(); - const type = system.t.bin; - const estimator = type.compileCapacityEstimator({}); - expect(estimator(new Uint8Array())).toBe(maxEncodingCapacity(new Uint8Array())); - }); - - test('small buffer', () => { - const system = new TypeSystem(); - const type = system.t.bin; - const estimator = type.compileCapacityEstimator({}); - expect(estimator(new Uint8Array([1, 2, 3]))).toBe(maxEncodingCapacity(new Uint8Array([1, 2, 3]))); - }); -}); - -describe('array', () => { - test('empty', () => { - const system = new TypeSystem(); - const type = system.t.arr; - const estimator = type.compileCapacityEstimator({}); - expect(estimator([])).toBe(maxEncodingCapacity([])); - }); - - test('simple elements', () => { - const system = new TypeSystem(); - const type = system.t.arr; - const estimator = type.compileCapacityEstimator({}); - expect(estimator([1, true, 'asdf'])).toBe(maxEncodingCapacity([1, true, 'asdf'])); - }); - - test('typed array, optimizes computation', () => { - const system = new TypeSystem(); - const type = system.t.Array(system.t.num); - const estimator = type.compileCapacityEstimator({}); - expect(estimator([1, 2, 3])).toBe(maxEncodingCapacity([1, 2, 3])); - }); - - test('array of strings', () => { - const system = new TypeSystem(); - const type = system.t.Array(system.t.str); - const estimator = type.compileCapacityEstimator({}); - expect(estimator(['a', 'asdf'])).toBe(maxEncodingCapacity(['a', 'asdf'])); - }); -}); - -describe('tuple', () => { - test('empty', () => { - const system = new TypeSystem(); - const type = system.t.Tuple(); - const estimator = type.compileCapacityEstimator({}); - expect(estimator([])).toBe(maxEncodingCapacity([])); - }); - - test('two elements', () => { - const system = new TypeSystem(); - const type = system.t.Tuple(system.t.num, system.t.str); - const estimator = type.compileCapacityEstimator({}); - expect(estimator([1, 'asdf'])).toBe(maxEncodingCapacity([1, 'asdf'])); - }); -}); - -describe('object', () => { - test('empty', () => { - const system = new TypeSystem(); - const type = system.t.obj; - const estimator = type.compileCapacityEstimator({}); - expect(estimator(123)).toBe(maxEncodingCapacity({})); - }); - - test('object with unknown fields', () => { - const system = new TypeSystem(); - const type = system.t.obj.options({encodeUnknownFields: true}); - const estimator = type.compileCapacityEstimator({}); - expect(estimator({foo: 'bar'})).toBe(maxEncodingCapacity({foo: 'bar'})); - }); - - test('one required key', () => { - const system = new TypeSystem(); - const type = system.t.Object(system.t.prop('abc', system.t.str)); - const estimator = type.compileCapacityEstimator({}); - expect(estimator({abc: 'foo'})).toBe(maxEncodingCapacity({abc: 'foo'})); - }); - - test('one required and one optional keys', () => { - const system = new TypeSystem(); - const type = system.t.Object(system.t.prop('abc', system.t.str), system.t.propOpt('key', system.t.num)); - const estimator = type.compileCapacityEstimator({}); - expect(estimator({abc: 'foo', key: 111})).toBe(maxEncodingCapacity({abc: 'foo', key: 111})); - }); -}); - -describe('map', () => { - test('empty', () => { - const system = new TypeSystem(); - const type = system.t.map; - const estimator = type.compileCapacityEstimator({}); - expect(estimator(123)).toBe(maxEncodingCapacity({})); - }); - - test('with one field', () => { - const system = new TypeSystem(); - const type = system.t.Map(system.t.bool); - const estimator = type.compileCapacityEstimator({}); - expect(estimator({foo: true})).toBe(maxEncodingCapacity({foo: true})); - }); - - test('three number fields', () => { - const system = new TypeSystem(); - const type = system.t.Map(system.t.num); - const estimator = type.compileCapacityEstimator({}); - const data = {foo: 1, bar: 2, baz: 3}; - expect(estimator(data)).toBe(maxEncodingCapacity(data)); - }); - - test('nested maps', () => { - const system = new TypeSystem(); - const type = system.t.Map(system.t.Map(system.t.str)); - const estimator = type.compileCapacityEstimator({}); - const data = {foo: {bar: 'baz'}, baz: {bar: 'foo'}}; - expect(estimator(data)).toBe(maxEncodingCapacity(data)); - }); -}); - -describe('ref', () => { - test('two hops', () => { - const system = new TypeSystem(); - system.alias('Id', system.t.str); - system.alias('User', system.t.Object(system.t.prop('id', system.t.Ref('Id')), system.t.prop('name', system.t.str))); - const type = system.t.Ref('User'); - const value = {id: 'asdf', name: 'foo'}; - const estimator = type.capacityEstimator(); - expect(estimator(value)).toBe(maxEncodingCapacity(value)); - }); -}); - -describe('or', () => { - test('empty', () => { - const system = new TypeSystem(); - const type = system.t.Or(system.t.str, system.t.arr).options({ - discriminator: [ - 'if', - ['==', 'string', ['type', ['get', '']]], - 0, - ['if', ['==', 'array', ['type', ['get', '']]], 1, -1], - ], - }); - const estimator = type.compileCapacityEstimator({}); - expect(estimator('asdf')).toBe(maxEncodingCapacity('asdf')); - expect(estimator([1, 2, 3])).toBe(maxEncodingCapacity([1, 2, 3])); - }); -}); diff --git a/src/json-type/codegen/json/JsonTextEncoderCodegenContext.ts b/src/json-type/codegen/json/JsonTextEncoderCodegenContext.ts deleted file mode 100644 index 2e500aad09..0000000000 --- a/src/json-type/codegen/json/JsonTextEncoderCodegenContext.ts +++ /dev/null @@ -1,78 +0,0 @@ -import {Codegen, CodegenStepExecJs} from '@jsonjoy.com/util/lib/codegen'; -import {asString} from '@jsonjoy.com/util/lib/strings/asString'; -import {toBase64} from '@jsonjoy.com/base64/lib/toBase64'; -import type {TypeSystem} from '../../system'; -import type {Type} from '../../type'; -import type {json_string} from '@jsonjoy.com/util/lib/json-brand'; - -export type JsonEncoderFn = (value: T) => json_string; - -class WriteTextStep { - constructor(public str: string) {} -} - -type Step = WriteTextStep | CodegenStepExecJs; - -export interface JsonTextEncoderCodegenContextOptions { - /** Type for which to generate the encoder. */ - type: Type; - - /** Type system to use for alias and validator resolution. */ - system?: TypeSystem; - - name?: string; -} - -export class JsonTextEncoderCodegenContext { - public readonly codegen: Codegen; - - constructor(public readonly options: JsonTextEncoderCodegenContextOptions) { - this.codegen = new Codegen({ - name: 'toJson' + (options.name ? '_' + options.name : ''), - prologue: `var s = '';`, - epilogue: `return s;`, - processSteps: (steps) => { - const stepsJoined: Step[] = []; - for (let i = 0; i < steps.length; i++) { - const step = steps[i]; - if (step instanceof CodegenStepExecJs) stepsJoined.push(step); - else if (step instanceof WriteTextStep) { - const last = stepsJoined[stepsJoined.length - 1]; - if (last instanceof WriteTextStep) last.str += step.str; - else stepsJoined.push(step); - } - } - const execSteps: CodegenStepExecJs[] = []; - for (const step of stepsJoined) { - if (step instanceof CodegenStepExecJs) { - execSteps.push(step); - } else if (step instanceof WriteTextStep) { - const js = /* js */ `s += ${JSON.stringify(step.str)};`; - execSteps.push(new CodegenStepExecJs(js)); - } - } - return execSteps; - }, - }); - this.codegen.linkDependency(asString, 'asString'); - this.codegen.linkDependency(JSON.stringify, 'stringify'); - } - - public js(js: string): void { - this.codegen.js(js); - } - - public writeText(str: string): void { - this.codegen.step(new WriteTextStep(str)); - } - - protected base64Linked = false; - public linkBase64() { - if (this.base64Linked) return; - this.codegen.linkDependency(toBase64, 'toBase64'); - } - - public compile() { - return this.codegen.compile(); - } -} diff --git a/src/json-type/codegen/json/__tests__/JsonEncoderCodegenContext.spec.ts b/src/json-type/codegen/json/__tests__/JsonEncoderCodegenContext.spec.ts deleted file mode 100644 index b7d63bfa6d..0000000000 --- a/src/json-type/codegen/json/__tests__/JsonEncoderCodegenContext.spec.ts +++ /dev/null @@ -1,101 +0,0 @@ -import {TypeSystem} from '../../../system'; - -test('encodes extra fields with "encodeUnknownFields" when referenced by ref', () => { - const system = new TypeSystem(); - const {t} = system; - const type = t.Object(t.prop('foo', t.str), t.propOpt('zzz', t.num)).options({encodeUnknownFields: true}); - system.alias('foo', type); - const type2 = system.t.Ref('foo'); - const encoder = type2.jsonTextEncoder(); - expect(encoder({foo: 'bar', zzz: 1, baz: 123})).toBe('{"foo":"bar","zzz":1,"baz":123}'); -}); - -test('add circular reference test', () => { - const system = new TypeSystem(); - const {t} = system; - const user = system.alias('User', t.Object(t.prop('id', t.str), t.propOpt('address', t.Ref('Address')))); - const address = system.alias('Address', t.Object(t.prop('id', t.str), t.propOpt('user', t.Ref('User')))); - const value1 = { - id: 'user-1', - address: { - id: 'address-1', - user: { - id: 'user-2', - address: { - id: 'address-2', - user: { - id: 'user-3', - }, - }, - }, - }, - }; - const encoded1 = user.type.jsonTextEncoder()(value1); - const res1 = JSON.parse(encoded1); - expect(res1).toStrictEqual(value1); - const value2 = { - id: 'address-1', - user: { - id: 'user-1', - address: { - id: 'address-2', - user: { - id: 'user-2', - address: { - id: 'address-3', - }, - }, - }, - }, - }; - const encoded2 = address.type.jsonTextEncoder()(value2); - const res2 = JSON.parse(encoded2); - expect(res2).toStrictEqual(value2); -}); - -test('add circular reference test with chain of refs', () => { - const system = new TypeSystem(); - const {t} = system; - system.alias('User0', t.Object(t.prop('id', t.str), t.propOpt('address', t.Ref('Address')))); - system.alias('User1', t.Ref('User0')); - const user = system.alias('User', t.Ref('User1')); - system.alias('Address0', t.Object(t.prop('id', t.str), t.propOpt('user', t.Ref('User')))); - system.alias('Address1', t.Ref('Address0')); - const address = system.alias('Address', t.Ref('Address1')); - const value1 = { - id: 'user-1', - address: { - id: 'address-1', - user: { - id: 'user-2', - address: { - id: 'address-2', - user: { - id: 'user-3', - }, - }, - }, - }, - }; - const encoded1 = user.type.jsonTextEncoder()(value1); - const res1 = JSON.parse(encoded1); - expect(res1).toStrictEqual(value1); - const value2 = { - id: 'address-1', - user: { - id: 'user-1', - address: { - id: 'address-2', - user: { - id: 'user-2', - address: { - id: 'address-3', - }, - }, - }, - }, - }; - const encoded2 = address.type.jsonTextEncoder()(value2); - const res2 = JSON.parse(encoded2); - expect(res2).toStrictEqual(value2); -}); diff --git a/src/json-type/codegen/json/__tests__/json.spec.ts b/src/json-type/codegen/json/__tests__/json.spec.ts deleted file mode 100644 index 25474f9e12..0000000000 --- a/src/json-type/codegen/json/__tests__/json.spec.ts +++ /dev/null @@ -1,218 +0,0 @@ -import {Schema, s} from '../../../schema'; -import {TypeSystem} from '../../../system'; - -const exec = (schema: Schema, json: unknown, expected: unknown = json) => { - const system = new TypeSystem(); - const type = system.t.import(schema); - const fn = type.jsonTextEncoder(); - // console.log(fn.toString()); - const serialized = fn(json); - // console.log('serialized', serialized); - const decoded = JSON.parse(serialized); - expect(decoded).toStrictEqual(expected); -}; - -describe('"str" type', () => { - test('serializes a plain short string', () => { - const type = s.str; - const json = 'asdf'; - exec(type, json); - }); - - test('serializes a long string', () => { - const type = s.str; - const json = - '0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789'; - exec(type, json); - }); - - test('serializes a const string', () => { - const type = s.Const<'asdf'>('asdf'); - const json = '555'; - exec(type, json, 'asdf'); - }); -}); - -describe('"num" type', () => { - test('serializes numbers', () => { - const type = s.num; - exec(type, 0); - exec(type, 1); - exec(type, -1); - exec(type, 4.234); - exec(type, -23.23); - }); - - test('serializes a const number', () => { - const type = s.Const<7>(7); - const json = 123; - exec(type, json, 7); - }); - - test('serializes integers', () => { - const type = s.Const<7>(7); - const json = 123; - exec(type, json, 7); - }); -}); - -describe('"nil" type', () => { - test('serializes null', () => { - const type = s.nil; - exec(type, null); - exec(type, 123, null); - }); -}); - -describe('"bool" type', () => { - test('serializes boolean', () => { - const type = s.bool; - exec(type, true); - exec(type, false); - exec(type, 123, true); - exec(type, 0, false); - }); -}); - -describe('"arr" type', () => { - test('serializes an array', () => { - const type = s.Array(s.num); - exec(type, [1, 2, 3]); - }); - - test('serializes an array in array', () => { - const type = s.Array(s.Array(s.num)); - exec(type, [[1, 2, 3]]); - }); -}); - -describe('"obj" type', () => { - test('serializes object with required fields', () => { - const type = s.Object([s.Field('a', s.num), s.Field('b', s.str)]); - exec(type, {a: 123, b: 'asdf'}); - }); - - test('serializes object with constant string with required fields', () => { - const type = s.Object([s.Field('a', s.num), s.Field('b', s.Const<'asdf'>('asdf'))]); - exec(type, {a: 123, b: 'asdf'}); - }); - - test('can serialize optional fields', () => { - const type = s.Object([ - s.Field('a', s.num), - s.Field('b', s.Const<'asdf'>('asdf')), - s.FieldOpt('c', s.str), - s.FieldOpt('d', s.num), - ]); - exec(type, {a: 123, b: 'asdf'}); - exec(type, {a: 123, b: 'asdf', c: 'qwerty'}); - exec(type, {a: 123, d: 4343.3, b: 'asdf', c: 'qwerty'}); - }); - - test('can serialize object with unknown fields', () => { - const type = s.Object( - [s.prop('a', s.num), s.prop('b', s.Const<'asdf'>('asdf')), s.propOpt('c', s.str), s.propOpt('d', s.num)], - {encodeUnknownFields: true}, - ); - exec(type, {a: 123, b: 'asdf'}); - exec(type, {a: 123, b: 'asdf', c: 'qwerty'}); - exec(type, {a: 123, d: 4343.3, b: 'asdf', c: 'qwerty', e: 'asdf'}); - exec(type, {a: 123, d: 4343.3, b: 'asdf', c: 'qwerty', e: 'asdf', z: true}); - }); -}); - -describe('"map" type', () => { - test('serializes a map', () => { - const type = s.Map(s.num); - exec(type, {a: 1, b: 2, c: 3}); - }); - - test('serializes empty map', () => { - const type = s.Map(s.num); - exec(type, {}); - }); - - test('serializes a map with a single key', () => { - const type = s.Map(s.num); - exec(type, {'0': 0}); - }); - - test('serializes a map in a map', () => { - const type = s.Map(s.Map(s.bool)); - exec(type, {a: {b: true}}); - }); -}); - -describe('general', () => { - test('serializes according to schema a POJO object', () => { - const type = s.Object({ - fields: [ - s.prop('a', s.num), - s.prop('b', s.str), - s.prop('c', s.nil), - s.prop('d', s.bool), - s.prop( - 'arr', - s.Array( - s.Object({ - fields: [s.prop('foo', s.Array(s.num)), s.prop('.!@#', s.str)], - }), - ), - ), - s.prop('bin', s.bin), - ], - }); - const json = { - a: 1.1, - b: 'sdf', - c: null, - d: true, - arr: [ - {foo: [1], '.!@#': ''}, - {'.!@#': '......', foo: [4, 4, 4.4]}, - ], - bin: new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]), - }; - - exec(type, json, { - a: 1.1, - b: 'sdf', - c: null, - d: true, - arr: [ - {foo: [1], '.!@#': ''}, - {'.!@#': '......', foo: [4, 4, 4.4]}, - ], - bin: 'data:application/octet-stream;base64,AQIDBAUGBwgJCg==', - }); - }); - - test('can encode binary', () => { - const type = s.Object([s.Field('bin', s.bin)]); - const json = { - bin: new Uint8Array([1, 2, 3]), - }; - - exec(type, json, { - bin: 'data:application/octet-stream;base64,AQID', - }); - }); -}); - -describe('"ref" type', () => { - test('can serialize reference by resolving to type', () => { - const system = new TypeSystem(); - system.alias('ID', system.t.str); - const schema = s.Object([s.prop('name', s.str), s.prop('id', s.Ref('ID')), s.prop('createdAt', s.num)]); - const type = system.t.import(schema); - const fn = type.jsonTextEncoder(); - const json = { - name: 'John', - id: '123', - createdAt: 123, - }; - const blob = fn(json); - const decoded = JSON.parse(blob); - expect(decoded).toStrictEqual(json); - }); -}); diff --git a/src/json-type/codegen/types.ts b/src/json-type/codegen/types.ts deleted file mode 100644 index ac8f6dd65a..0000000000 --- a/src/json-type/codegen/types.ts +++ /dev/null @@ -1,3 +0,0 @@ -import {BinaryJsonEncoder} from '@jsonjoy.com/json-pack/lib/types'; - -export type CompiledBinaryEncoder = (value: unknown, encoder: BinaryJsonEncoder) => void; diff --git a/src/json-type/codegen/validator/README.md b/src/json-type/codegen/validator/README.md deleted file mode 100644 index 08a76bbc05..0000000000 --- a/src/json-type/codegen/validator/README.md +++ /dev/null @@ -1,32 +0,0 @@ -# json-type Validator - -This library implements JSON validation according to `json-type` schema. It -generates the most efficient JavaScript code for validation given the schema. - -The generated validator functions return truthy value or error information on -validation failure. And `false` or a falsy value on success. - -## Usage - -```ts -const type = t.Object(t.prop('id', t.str), t.propOpt('name', t.str)); - -const json = { - id: '123', - name: 'John', -}; - -const validator = type.compileValidator('boolean'); - -const err1 = validator(json); // false -const err2 = validator({}); // true -``` - -To see insides of the validator function use `.toString()`. - -```ts -console.log(validator.toString()); -``` - -If you want your validator to return more info about the error, use -`string` or `object` validator types. diff --git a/src/json-type/codegen/validator/ValidatorCodegenContext.ts b/src/json-type/codegen/validator/ValidatorCodegenContext.ts deleted file mode 100644 index 065eeaa091..0000000000 --- a/src/json-type/codegen/validator/ValidatorCodegenContext.ts +++ /dev/null @@ -1,165 +0,0 @@ -import {Codegen} from '@jsonjoy.com/util/lib/codegen'; -import {ValidationError, ValidationErrorMessage} from '../../constants'; -import type {TypeSystem} from '../../system'; -import type {Type} from '../../type'; -import type {JsonTypeValidator, ValidationPath} from './types'; - -export interface ValidatorCodegenContextOptions { - /** Type for which to generate the validator. */ - type: Type; - - /** Type system to use for alias and validator resolution. */ - system?: TypeSystem; - - /** - * Specifies how errors should be reported. The validator always returns a truthy - * value on error, and falsy value on success. Depending on the value of this - * option, the validator will either return boolean, string, or object on error. - * - * - `"boolean"`: The validator will return `true` on error, and `false` on success. - * - `"string"`: The validator will return a string on error, and empty string `""` - * on success. The error string contains error code and path where error happened - * serialized as JSON. - * - `"object"`: The validator will return an object on error, and `null` on success. The - * error object contains error code and path where error happened as well as human readable - * description of the error. - * - * Use `"boolean"` for best performance. - */ - errors: 'boolean' | 'string' | 'object'; - - /** - * When an object type does not have "extraFields" set to true, the validator - * will check that there are not excess fields besides those explicitly - * defined. This settings removes this check. - * - * It may be useful when validating incoming data in RPC request as extra fields - * would not hurt, but removing this check may improve performance. In one - * micro-benchmark, this setting improves performance 5x. See json-type/validator.js benchmark. - */ - skipObjectExtraFieldsCheck?: boolean; - - /** - * In unsafe mode the validator will skip some checks which may result in - * error being thrown. When running validators in unsafe mode, it is assumed - * that the code is wrapped in a try-catch block. Micro-benchmarks DO NOT show - * that this setting improves performance much. - */ - unsafeMode?: boolean; -} - -export class ValidatorCodegenContext { - public readonly options: ValidatorCodegenContextOptions; - public readonly codegen: Codegen; - - constructor(options: ValidatorCodegenContextOptions) { - this.options = { - skipObjectExtraFieldsCheck: false, - unsafeMode: false, - ...options, - }; - const successResult = - this.options.errors === 'boolean' ? 'false' : this.options.errors === 'string' ? "''" : 'null'; - this.codegen = new Codegen({ - epilogue: `return ${successResult};`, - }); - } - - public js(js: string): void { - this.codegen.js(js); - } - - /** Generates an error message. The type of message form is dictated by `options.errors` setting. */ - public err( - code: ValidationError, - path: ValidationPath, - opts: {refId?: string; refError?: string; validator?: string} = {}, - ): string { - switch (this.options.errors) { - case 'boolean': - return 'true'; - case 'string': { - let out = "'[" + JSON.stringify(ValidationError[code]); - for (const step of path) { - if (typeof step === 'object') { - out += ",' + JSON.stringify(" + step.r + ") + '"; - } else { - out += ',' + JSON.stringify(step); - } - } - return out + "]'"; - } - case 'object': - default: { - let out = - '{code: ' + - JSON.stringify(ValidationError[code]) + - ', errno: ' + - JSON.stringify(code) + - ', message: ' + - JSON.stringify(ValidationErrorMessage[code]) + - ', path: ['; - let i = 0; - for (const step of path) { - if (i) out += ', '; - if (typeof step === 'object') { - out += step.r; - } else { - out += JSON.stringify(step); - } - i++; - } - out += ']'; - if (opts.refId) { - out += ', refId: ' + JSON.stringify(opts.refId); - } - if (opts.refError) { - out += ', ref: ' + opts.refError; - } - if (opts.validator) { - out += ', validator: ' + JSON.stringify(opts.validator); - } - return out + '}'; - } - } - } - - public emitCustomValidators(node: Type, path: ValidationPath, r: string): void { - const validatorNames = node.getValidatorNames(); - for (const validatorName of validatorNames) { - const codegen = this.codegen; - const v = this.linkValidator(validatorName); - const rerr = codegen.getRegister(); - const rc = codegen.getRegister(); - const err = this.err(ValidationError.VALIDATION, path, {validator: validatorName, refError: rerr}); - const errInCatch = this.err(ValidationError.VALIDATION, path, {validator: validatorName, refError: rc}); - const emitRc = this.options.errors === 'object'; - codegen.js(/* js */ `try { - var ${rerr} = ${v}(${r}); - if (${rerr}) return ${err}; -} catch (e) { - ${emitRc ? /* js */ `var ${rc} = e ? e : new Error('Validator ${JSON.stringify(validatorName)} failed.');` : ''} - return ${errInCatch}; -}`); - } - } - - private validatorCache = new Map(); - - protected linkValidator(name: string): string { - const cached = this.validatorCache.get(name); - if (cached) return cached; - const system = this.options.system; - if (!system) throw new Error('Type system not set.'); - const codegen = this.codegen; - const validator = system.getCustomValidator(name); - if (!validator) throw new Error(`Custom validator "${name}" not found.`); - const result = codegen.linkDependency(validator.fn); - this.validatorCache.set(name, result); - return result; - } - - public compile() { - return this.codegen.compile(); - } -} diff --git a/src/json-type/codegen/validator/__tests__/codegen.spec.ts b/src/json-type/codegen/validator/__tests__/codegen.spec.ts deleted file mode 100644 index 030f6b3dba..0000000000 --- a/src/json-type/codegen/validator/__tests__/codegen.spec.ts +++ /dev/null @@ -1,819 +0,0 @@ -import {ValidationError} from '../../../constants'; -import {OrSchema, s, Schema} from '../../../schema'; -import {TypeSystem} from '../../../system'; -import {ValidatorCodegenContextOptions} from '../ValidatorCodegenContext'; - -const exec = (schema: Schema, json: unknown, error: any, options: Partial = {}) => { - const system = new TypeSystem(); - const type = system.t.import(schema); - - const fn1 = type.compileValidator({errors: 'boolean', ...options}); - const fn2 = type.compileValidator({errors: 'string', ...options}); - const fn3 = type.compileValidator({errors: 'object', ...options}); - - // console.log(fn1.toString()); - // console.log(fn2.toString()); - // console.log(fn3.toString()); - - const result1 = fn1(json); - const result2 = fn2(json); - const result3 = fn3(json); - - // console.log('result1', result1); - // console.log('result2', result2); - // console.log('result3', result3); - - expect(result3).toStrictEqual(error); - expect(result2).toStrictEqual(!error ? '' : JSON.stringify([error.code, ...error.path])); - expect(result1).toBe(!!error); -}; - -test('validates according to schema a POJO object', () => { - const type = s.Object({ - unknownFields: false, - fields: [ - s.prop( - 'collection', - s.Object({ - unknownFields: false, - fields: [ - s.prop('id', s.str), - s.prop('ts', s.num), - s.prop('cid', s.str), - s.prop('prid', s.str), - s.propOpt('slug', s.str), - s.propOpt('name', s.str), - s.propOpt('src', s.str), - s.propOpt('authz', s.str), - s.prop('tags', s.Array(s.str)), - ], - }), - ), - s.prop('bin.', s.bin), - ], - }); - const json = { - collection: { - id: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx', - ts: Date.now(), - cid: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx', - prid: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx', - slug: 'slug-name', - name: 'Super collection', - src: '{"foo": "bar"}', - authz: 'export const (ctx) => ctx.userId === "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx";', - tags: ['foo', 'bar'], - }, - 'bin.': new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]), - }; - exec(type, json, null); -}); - -test('can have array of unknown elements', () => { - const type = s.Array(s.any); - exec(type, [], null); - exec(type, [1], null); - exec(type, [1, 2, 3], null); - exec(type, [1, 'adsf'], null); - exec(type, [1, {}], null); - exec(type, {}, {code: 'ARR', errno: ValidationError.ARR, message: 'Not an array.', path: []}); - exec(type, null, {code: 'ARR', errno: ValidationError.ARR, message: 'Not an array.', path: []}); - exec(type, 123, {code: 'ARR', errno: ValidationError.ARR, message: 'Not an array.', path: []}); - exec(type, 'asdf', {code: 'ARR', errno: ValidationError.ARR, message: 'Not an array.', path: []}); -}); - -test('object can have a field of any type', () => { - const type = s.Object({ - fields: [s.Field('foo', s.any)], - }); - exec(type, {foo: 123}, null); - exec(type, {foo: null}, null); - exec(type, {foo: 'asdf'}, null); - exec(type, {}, {code: 'KEY', errno: ValidationError.KEY, message: 'Missing key.', path: ['foo']}); -}); - -test('can detect extra properties in object', () => { - const type = s.Object({ - fields: [s.prop('foo', s.any), s.propOpt('zup', s.any)], - }); - exec(type, {foo: 123}, null); - exec(type, {foo: 123, zup: 'asdf'}, null); - exec( - type, - {foo: 123, bar: 'asdf'}, - {code: 'KEYS', errno: ValidationError.KEYS, message: 'Too many or missing object keys.', path: ['bar']}, - undefined, - ); -}); - -test('can disable extra property check', () => { - const type = s.Object({ - fields: [s.Field('foo', s.any), s.FieldOpt('zup', s.any)], - }); - exec(type, {foo: 123}, null, {skipObjectExtraFieldsCheck: true}); - exec(type, {foo: 123, zup: 'asdf'}, null, {skipObjectExtraFieldsCheck: true}); - exec(type, {foo: 123, bar: 'asdf'}, null, {skipObjectExtraFieldsCheck: true}); - exec(type, {foo: 123, zup: '1', bar: 'asdf'}, null, {skipObjectExtraFieldsCheck: true}); -}); - -describe('"str" type', () => { - test('validates a basic string', () => { - const type = s.str; - exec(type, '', null); - exec(type, 'asdf', null); - exec(type, 123, {code: 'STR', errno: ValidationError.STR, message: 'Not a string.', path: []}); - exec(type, null, {code: 'STR', errno: ValidationError.STR, message: 'Not a string.', path: []}); - }); - - test('validates minLength', () => { - const type = s.String({min: 3}); - exec(type, 'asdf', null); - exec(type, '', { - code: 'STR_LEN', - errno: ValidationError.STR_LEN, - message: 'Invalid string length.', - path: [], - }); - exec(type, '12', { - code: 'STR_LEN', - errno: ValidationError.STR_LEN, - message: 'Invalid string length.', - path: [], - }); - }); - - test('validates maxLength', () => { - const type = s.String({max: 5}); - exec(type, '', null); - exec(type, 'asdf', null); - exec(type, 'asdfd', null); - exec(type, 'asdfdf', { - code: 'STR_LEN', - errno: ValidationError.STR_LEN, - message: 'Invalid string length.', - path: [], - }); - exec(type, 'aasdf sdfdf', { - code: 'STR_LEN', - errno: ValidationError.STR_LEN, - message: 'Invalid string length.', - path: [], - }); - }); - - test('validates minLength and maxLength', () => { - const type = s.String({min: 3, max: 5}); - exec(type, 'aaa', null); - exec(type, 'bbbb', null); - exec(type, 'vvvvv', null); - exec(type, '', { - code: 'STR_LEN', - errno: ValidationError.STR_LEN, - message: 'Invalid string length.', - path: [], - }); - exec(type, 'asdfdf', { - code: 'STR_LEN', - errno: ValidationError.STR_LEN, - message: 'Invalid string length.', - path: [], - }); - exec(type, 'aasdf sdfdf', { - code: 'STR_LEN', - errno: ValidationError.STR_LEN, - message: 'Invalid string length.', - path: [], - }); - }); - - test('validates minLength and maxLength of equal size', () => { - const type = s.String({min: 4, max: 4}); - exec(type, 'aaa', { - code: 'STR_LEN', - errno: ValidationError.STR_LEN, - message: 'Invalid string length.', - path: [], - }); - exec(type, 'bbbb', null); - exec(type, 'vvvvv', { - code: 'STR_LEN', - errno: ValidationError.STR_LEN, - message: 'Invalid string length.', - path: [], - }); - exec(type, '', { - code: 'STR_LEN', - errno: ValidationError.STR_LEN, - message: 'Invalid string length.', - path: [], - }); - exec(type, 'asdfdf', { - code: 'STR_LEN', - errno: ValidationError.STR_LEN, - message: 'Invalid string length.', - path: [], - }); - exec(type, 'aasdf sdfdf', { - code: 'STR_LEN', - errno: ValidationError.STR_LEN, - message: 'Invalid string length.', - path: [], - }); - }); -}); - -describe('"num" type', () => { - test('validates general number type', () => { - const type = s.num; - exec(type, 123, null); - exec(type, -123, null); - exec(type, 0, null); - exec(type, '123', {code: 'NUM', errno: ValidationError.NUM, message: 'Not a number.', path: []}); - exec(type, '-123', {code: 'NUM', errno: ValidationError.NUM, message: 'Not a number.', path: []}); - exec(type, '0', {code: 'NUM', errno: ValidationError.NUM, message: 'Not a number.', path: []}); - exec(type, '', {code: 'NUM', errno: ValidationError.NUM, message: 'Not a number.', path: []}); - exec(type, null, {code: 'NUM', errno: ValidationError.NUM, message: 'Not a number.', path: []}); - }); - - test('validates integer type', () => { - const type = s.Number({format: 'i'}); - exec(type, 123, null); - exec(type, -123, null); - exec(type, 0, null); - exec(type, 123.4, {code: 'INT', errno: ValidationError.INT, message: 'Not an integer.', path: []}); - exec(type, -1.1, {code: 'INT', errno: ValidationError.INT, message: 'Not an integer.', path: []}); - }); - - test('validates unsigned integer type', () => { - const type = s.Number({format: 'u'}); - exec(type, 123, null); - exec(type, 0, null); - exec(type, -123, {code: 'UINT', errno: ValidationError.UINT, message: 'Not an unsigned integer.', path: []}); - exec(type, 123.4, {code: 'INT', errno: ValidationError.INT, message: 'Not an integer.', path: []}); - exec(type, -1.1, {code: 'INT', errno: ValidationError.INT, message: 'Not an integer.', path: []}); - }); - - test('validates i8', () => { - const type = s.Number({format: 'i8'}); - exec(type, 123, null); - exec(type, 0, null); - exec(type, -12, null); - exec(type, 127, null); - exec(type, -127, null); - exec(type, -128, null); - exec(type, 128, {code: 'INT', errno: ValidationError.INT, message: 'Not an integer.', path: []}); - exec(type, -129, {code: 'INT', errno: ValidationError.INT, message: 'Not an integer.', path: []}); - }); - - test('validates u8', () => { - const type = s.Number({format: 'u8'}); - exec(type, 123, null); - exec(type, 0, null); - exec(type, -12, {code: 'UINT', errno: ValidationError.UINT, message: 'Not an unsigned integer.', path: []}); - exec(type, 127, null); - exec(type, 222, null); - exec(type, 255, null); - exec(type, 256, {code: 'UINT', errno: ValidationError.UINT, message: 'Not an unsigned integer.', path: []}); - exec(type, 333, {code: 'UINT', errno: ValidationError.UINT, message: 'Not an unsigned integer.', path: []}); - }); - - test('validates i16', () => { - const type = s.Number({format: 'i16'}); - exec(type, 123, null); - exec(type, 0x33, null); - exec(type, 0x3333, null); - exec(type, -0x33, null); - exec(type, -0x3333, null); - exec(type, 0, null); - exec(type, -44, null); - exec(type, 0x7fff - 1, null); - exec(type, 0x7fff, null); - exec(type, 0x7fff + 1, {code: 'INT', errno: ValidationError.INT, message: 'Not an integer.', path: []}); - exec(type, -0x8000 + 1, null); - exec(type, -0x8000, null); - exec(type, -0x8000 - 1, {code: 'INT', errno: ValidationError.INT, message: 'Not an integer.', path: []}); - }); - - test('validates u16', () => { - const type = s.Number({format: 'u16'}); - exec(type, 123, null); - exec(type, 0x33, null); - exec(type, 0x3333, null); - exec(type, -0x33, { - code: 'UINT', - errno: ValidationError.UINT, - message: 'Not an unsigned integer.', - path: [], - }); - exec(type, -0x3333, { - code: 'UINT', - errno: ValidationError.UINT, - message: 'Not an unsigned integer.', - path: [], - }); - exec(type, 0, null); - exec(type, -44, {code: 'UINT', errno: ValidationError.UINT, message: 'Not an unsigned integer.', path: []}); - exec(type, 0x7fff - 1, null); - exec(type, 0x7fff, null); - exec(type, 0xffff - 1, null); - exec(type, 0xffff, null); - exec(type, 0xffff + 1, { - code: 'UINT', - errno: ValidationError.UINT, - message: 'Not an unsigned integer.', - path: [], - }); - exec(type, -0x8000 + 1, { - code: 'UINT', - errno: ValidationError.UINT, - message: 'Not an unsigned integer.', - path: [], - }); - exec(type, -0x8000, { - code: 'UINT', - errno: ValidationError.UINT, - message: 'Not an unsigned integer.', - path: [], - }); - }); - - test('validates i32', () => { - const type = s.Number({format: 'i32'}); - exec(type, 123, null); - exec(type, 0x33, null); - exec(type, 0x3333, null); - exec(type, 0x333333, null); - exec(type, 0x33333333, null); - exec(type, -0x33, null); - exec(type, -0x3333, null); - exec(type, -0x333333, null); - exec(type, -0x33333333, null); - exec(type, 0, null); - exec(type, -44, null); - exec(type, 0x7fffffff - 1, null); - exec(type, 0x7fffffff, null); - exec(type, 0x7fffffff + 1, {code: 'INT', errno: ValidationError.INT, message: 'Not an integer.', path: []}); - exec(type, -0x80000000 + 1, null); - exec(type, -0x80000000, null); - exec(type, -0x80000000 - 1, {code: 'INT', errno: ValidationError.INT, message: 'Not an integer.', path: []}); - }); - - test('validates u32', () => { - const type = s.Number({format: 'u32'}); - exec(type, 123, null); - exec(type, 0x33, null); - exec(type, 0x3333, null); - exec(type, -0x33, { - code: 'UINT', - errno: ValidationError.UINT, - message: 'Not an unsigned integer.', - path: [], - }); - exec(type, -0x3333, { - code: 'UINT', - errno: ValidationError.UINT, - message: 'Not an unsigned integer.', - path: [], - }); - exec(type, 0, null); - exec(type, -44, {code: 'UINT', errno: ValidationError.UINT, message: 'Not an unsigned integer.', path: []}); - exec(type, 0x7fff - 1, null); - exec(type, 0x7fff, null); - exec(type, 0xffff - 1, null); - exec(type, 0xffff, null); - exec(type, 0xffffffff, null); - exec(type, 0xffffffff + 1, { - code: 'UINT', - errno: ValidationError.UINT, - message: 'Not an unsigned integer.', - path: [], - }); - exec(type, -0x8000 + 1, { - code: 'UINT', - errno: ValidationError.UINT, - message: 'Not an unsigned integer.', - path: [], - }); - exec(type, -0x8000, { - code: 'UINT', - errno: ValidationError.UINT, - message: 'Not an unsigned integer.', - path: [], - }); - }); - - test('validates i64', () => { - const type = s.Number({format: 'i64'}); - exec(type, 123, null); - exec(type, 0x33, null); - exec(type, 0x3333, null); - exec(type, 0x333333, null); - exec(type, 0x33333333, null); - exec(type, 0x3333333333, null); - exec(type, 0x333333333333, null); - exec(type, -0x33, null); - exec(type, -0x3333, null); - exec(type, -0x333333, null); - exec(type, -0x33333333, null); - exec(type, -0x3333333333, null); - exec(type, -0x333333333333, null); - exec(type, 0, null); - exec(type, -44.123, {code: 'INT', errno: ValidationError.INT, message: 'Not an integer.', path: []}); - exec(type, 1.1, {code: 'INT', errno: ValidationError.INT, message: 'Not an integer.', path: []}); - }); - - test('validates u64', () => { - const type = s.Number({format: 'u64'}); - exec(type, 123, null); - exec(type, 0x33, null); - exec(type, 0x3333, null); - exec(type, 0x333333, null); - exec(type, 0x33333333, null); - exec(type, 0x3333333333, null); - exec(type, 0x333333333333, null); - exec(type, -0x33, { - code: 'UINT', - errno: ValidationError.UINT, - message: 'Not an unsigned integer.', - path: [], - }); - exec(type, -0x3333, { - code: 'UINT', - errno: ValidationError.UINT, - message: 'Not an unsigned integer.', - path: [], - }); - exec(type, -0x333333, { - code: 'UINT', - errno: ValidationError.UINT, - message: 'Not an unsigned integer.', - path: [], - }); - exec(type, -0x33333333, { - code: 'UINT', - errno: ValidationError.UINT, - message: 'Not an unsigned integer.', - path: [], - }); - exec(type, -0x3333333333, { - code: 'UINT', - errno: ValidationError.UINT, - message: 'Not an unsigned integer.', - path: [], - }); - exec(type, -0x333333333333, { - code: 'UINT', - errno: ValidationError.UINT, - message: 'Not an unsigned integer.', - path: [], - }); - exec(type, 0, null); - exec(type, -44.123, {code: 'INT', errno: ValidationError.INT, message: 'Not an integer.', path: []}); - exec(type, 1.1, {code: 'INT', errno: ValidationError.INT, message: 'Not an integer.', path: []}); - }); -}); - -describe('"or" type', () => { - test('object key can be of multiple types', () => { - const type = s.Object({ - fields: [ - s.Field('foo', { - ...s.Or(s.num, s.str), - discriminator: [ - 'if', - ['==', 'number', ['type', ['get', '']]], - 0, - ['if', ['==', 'string', ['type', ['get', '']]], 1, -1], - ], - }), - ], - }); - exec(type, {foo: 123}, null); - exec(type, {foo: '123'}, null); - exec(type, {foo: false}, {code: 'OR', errno: ValidationError.OR, message: 'None of types matched.', path: ['foo']}); - }); - - test('array can be of multiple types', () => { - const type = s.Object({ - fields: [ - s.Field( - 'gg', - s.Array({ - ...s.Or(s.num, s.str), - discriminator: [ - 'if', - ['==', 'number', ['type', ['get', '']]], - 0, - ['if', ['==', 'string', ['type', ['get', '']]], 1, -1], - ], - }), - ), - ], - }); - exec(type, {gg: []}, null); - exec(type, {gg: [1]}, null); - exec(type, {gg: [1, 2]}, null); - exec(type, {gg: [1, '3', '']}, null); - exec( - type, - {gg: [1, '3', false]}, - {code: 'OR', errno: ValidationError.OR, message: 'None of types matched.', path: ['gg', 2]}, - ); - }); - - test('root value can be of multiple types', () => { - const type = { - ...s.Or(s.num, s.str, s.obj), - discriminator: [ - 'if', - ['==', 'number', ['type', ['get', '']]], - 0, - ['if', ['==', 'string', ['type', ['get', '']]], 1, ['if', ['==', 'object', ['type', ['get', '']]], 2, -1]], - ], - } as OrSchema; - exec(type, 123, null); - exec(type, 'asdf', null); - exec(type, {}, null); - exec(type, {foo: 'bar'}, null); - exec(type, [], {code: 'OR', errno: ValidationError.OR, message: 'None of types matched.', path: []}); - exec(type, null, {code: 'OR', errno: ValidationError.OR, message: 'None of types matched.', path: []}); - }); -}); - -describe('"obj" type', () => { - test('object can have unknown fields', () => { - const type = s.obj; - exec(type, {}, null); - exec(type, {a: 'b'}, null); - }); - - test('"null" is not of type "obj"', () => { - const type = s.obj; - exec(type, null, {code: 'OBJ', errno: ValidationError.OBJ, message: 'Not an object.', path: []}); - }); -}); - -describe('single root element', () => { - test('null', () => { - const type = s.nil; - exec(type, null, null); - exec(type, '123', {code: 'CONST', errno: ValidationError.CONST, message: 'Invalid constant.', path: []}); - }); - - test('number', () => { - const type = s.num; - exec(type, 123, null); - exec(type, 1.123, null); - exec(type, -123, null); - exec(type, -5.5, null); - exec(type, '123', {code: 'NUM', errno: ValidationError.NUM, message: 'Not a number.', path: []}); - }); - - test('const number', () => { - const type = s.Const<66>(66); - exec(type, 66, null); - exec(type, 67, { - code: 'CONST', - errno: ValidationError.CONST, - message: 'Invalid constant.', - path: [], - }); - }); - - test('falsy const number', () => { - const type = s.Const<0>(0); - exec(type, 0, null); - exec(type, 1, { - code: 'CONST', - errno: ValidationError.CONST, - message: 'Invalid constant.', - path: [], - }); - }); - - test('string', () => { - const type = s.str; - exec(type, '', null); - exec(type, 'a', null); - exec(type, 'asdf', null); - exec(type, 123, {code: 'STR', errno: ValidationError.STR, message: 'Not a string.', path: []}); - }); - - test('const string', () => { - const type = s.Const<'asdf'>('asdf'); - exec(type, 'asdf', null); - exec(type, '', { - code: 'CONST', - errno: ValidationError.CONST, - message: 'Invalid constant.', - path: [], - }); - exec(type, 123, { - code: 'CONST', - errno: ValidationError.CONST, - message: 'Invalid constant.', - path: [], - }); - }); - - test('falsy const string', () => { - const type = s.Const<''>(''); - exec(type, '', null); - exec(type, 'asdf', { - code: 'CONST', - errno: ValidationError.CONST, - message: 'Invalid constant.', - path: [], - }); - exec(type, 123, { - code: 'CONST', - errno: ValidationError.CONST, - message: 'Invalid constant.', - path: [], - }); - }); - - test('boolean', () => { - const type = s.bool; - exec(type, true, null); - exec(type, false, null); - exec(type, 123, {code: 'BOOL', errno: ValidationError.BOOL, message: 'Not a boolean.', path: []}); - }); - - test('const boolean', () => { - const type1 = s.Const(true); - const type2 = s.Const(false); - exec(type1, true, null); - exec(type1, false, { - code: 'CONST', - errno: ValidationError.CONST, - message: 'Invalid constant.', - path: [], - }); - exec(type1, '123', { - code: 'CONST', - errno: ValidationError.CONST, - message: 'Invalid constant.', - path: [], - }); - exec(type1, 123, { - code: 'CONST', - errno: ValidationError.CONST, - message: 'Invalid constant.', - path: [], - }); - exec(type2, false, null); - exec(type2, true, { - code: 'CONST', - errno: ValidationError.CONST, - message: 'Invalid constant.', - path: [], - }); - exec(type2, '123', { - code: 'CONST', - errno: ValidationError.CONST, - message: 'Invalid constant.', - path: [], - }); - exec(type2, 123, { - code: 'CONST', - errno: ValidationError.CONST, - message: 'Invalid constant.', - path: [], - }); - }); -}); - -describe('custom validators', () => { - test('can specify a custom validator for a string', () => { - const system = new TypeSystem(); - const type = system.t.String({ - validator: 'is-a', - }); - system.addCustomValidator({ - name: 'is-a', - fn: (value) => (value === 'a' ? false : true), - }); - const validator = type.validator('object'); - const res1 = validator('a'); - expect(res1).toStrictEqual(null); - const res2 = validator('b'); - expect(res2).toStrictEqual({ - code: 'VALIDATION', - errno: ValidationError.VALIDATION, - message: 'Custom validator failed.', - path: [], - validator: 'is-a', - ref: true, - }); - }); - - test('can specify multiple validators', () => { - const system = new TypeSystem(); - const type = system.t.String({ - validator: ['is-ab', 'is-a'], - }); - system.addCustomValidator({ - name: 'is-ab', - fn: (value) => (value === 'a' || value === 'b' ? false : true), - }); - system.addCustomValidator({ - name: 'is-a', - fn: (value) => (value === 'a' ? false : true), - }); - const validator = type.validator('object'); - const res1 = validator('a'); - const res2 = validator('b'); - const res3 = validator('c'); - expect(res1).toStrictEqual(null); - expect(res2).toStrictEqual({ - code: 'VALIDATION', - errno: ValidationError.VALIDATION, - message: 'Custom validator failed.', - path: [], - validator: 'is-a', - ref: true, - }); - expect(res3).toStrictEqual({ - code: 'VALIDATION', - errno: ValidationError.VALIDATION, - message: 'Custom validator failed.', - path: [], - validator: 'is-ab', - ref: true, - }); - }); - - test('throws if custom validator is not provided', () => { - const system = new TypeSystem(); - const type = system.t.Object( - system.t.prop( - 'id', - system.t.String({ - validator: ['assetId'], - }), - ), - ); - expect(() => type.compileValidator({errors: 'object'})).toThrow(new Error('Validator [name = assetId] not found.')); - }); - - test('returns the error, which validator throws', () => { - const system = new TypeSystem(); - const type = system.t.Object( - system.t.prop( - 'id', - system.t.String({ - validator: ['assetId'], - }), - ), - ); - system.addCustomValidator({ - name: 'assetId', - fn: (id: string): void => { - if (!/^[a-z]+$/.test(id)) throw new Error('Asset ID must be a string.'); - }, - }); - const validator = type.validator('object'); - expect(validator({id: 'xxxxxxx'})).toBe(null); - expect(validator({id: '123'})).toStrictEqual({ - code: 'VALIDATION', - errno: ValidationError.VALIDATION, - message: 'Custom validator failed.', - path: ['id'], - ref: new Error('Asset ID must be a string.'), - validator: 'assetId', - }); - }); - - test('returns the error, which validator throws, even inside a "ref" type', () => { - const system = new TypeSystem(); - system.alias('ID', system.t.String({validator: 'assetId'})); - const type = system.t.Object(system.t.prop('id', system.t.Ref('ID'))); - system.addCustomValidator({ - name: 'assetId', - fn: (id: string) => { - if (id === 'xxxxxxx') return; - if (id === 'y') return; - throw new Error('Asset ID must be a string.'); - }, - }); - const validator = type.validator('object'); - expect(validator({id: 'xxxxxxx'})).toBe(null); - expect(validator({id: 'y'})).toBe(null); - expect(validator({id: '123'})).toStrictEqual({ - code: 'REF', - errno: ValidationError.REF, - message: 'Validation error in referenced type.', - path: ['id'], - refId: 'ID', - ref: { - code: 'VALIDATION', - errno: ValidationError.VALIDATION, - message: 'Custom validator failed.', - path: [], - validator: 'assetId', - ref: new Error('Asset ID must be a string.'), - }, - }); - }); -}); diff --git a/src/json-type/codegen/validator/types.ts b/src/json-type/codegen/validator/types.ts deleted file mode 100644 index 851c9257e4..0000000000 --- a/src/json-type/codegen/validator/types.ts +++ /dev/null @@ -1,3 +0,0 @@ -export type JsonTypeValidator = (value: unknown) => unknown; - -export type ValidationPath = Array; diff --git a/src/json-type/codegen/validator/util.ts b/src/json-type/codegen/validator/util.ts deleted file mode 100644 index 776f7786a0..0000000000 --- a/src/json-type/codegen/validator/util.ts +++ /dev/null @@ -1,14 +0,0 @@ -export const canSkipObjectKeyUndefinedCheck = (type: string): boolean => { - switch (type) { - case 'const': - case 'bool': - case 'num': - case 'str': - case 'obj': - case 'arr': - case 'bin': - return true; - default: - return false; - } -}; diff --git a/src/json-type/constants.ts b/src/json-type/constants.ts deleted file mode 100644 index 49af3008b6..0000000000 --- a/src/json-type/constants.ts +++ /dev/null @@ -1,53 +0,0 @@ -/** Validation error codes. */ -export enum ValidationError { - STR = 0, - NUM, - BOOL, - ARR, - TUP, - OBJ, - MAP, - KEY, - KEYS, - BIN, - OR, - REF, - ENUM, - CONST, - VALIDATION, - INT, - UINT, - STR_LEN, - ARR_LEN, - GT, - GTE, - LT, - LTE, -} - -/** Human-readable error messages by error code. */ -export const ValidationErrorMessage = { - [ValidationError.STR]: 'Not a string.', - [ValidationError.NUM]: 'Not a number.', - [ValidationError.BOOL]: 'Not a boolean.', - [ValidationError.ARR]: 'Not an array.', - [ValidationError.TUP]: 'Not a tuple.', - [ValidationError.OBJ]: 'Not an object.', - [ValidationError.MAP]: 'Not a map.', - [ValidationError.KEY]: 'Missing key.', - [ValidationError.KEYS]: 'Too many or missing object keys.', - [ValidationError.BIN]: 'Not a binary.', - [ValidationError.OR]: 'None of types matched.', - [ValidationError.REF]: 'Validation error in referenced type.', - [ValidationError.ENUM]: 'Not an enum value.', - [ValidationError.CONST]: 'Invalid constant.', - [ValidationError.VALIDATION]: 'Custom validator failed.', - [ValidationError.INT]: 'Not an integer.', - [ValidationError.UINT]: 'Not an unsigned integer.', - [ValidationError.STR_LEN]: 'Invalid string length.', - [ValidationError.ARR_LEN]: 'Invalid array length.', - [ValidationError.GT]: 'Value is too small.', - [ValidationError.GTE]: 'Value is too small.', - [ValidationError.LT]: 'Value is too large.', - [ValidationError.LTE]: 'Value is too large.', -}; diff --git a/src/json-type/index.ts b/src/json-type/index.ts deleted file mode 100644 index a736a4f568..0000000000 --- a/src/json-type/index.ts +++ /dev/null @@ -1,53 +0,0 @@ -/** - * `json-type` - * - * Implements types and builder for JSON Type. - * - * Use {@link t} builder instance to build your JSON types. - * - * ```ts - * import {t} from 'json-joy/lib/json-type'; - * - * const userType = t.Object( - * t.prop('id', t.num), - * t.prop('name', t.str), - * ); - * ``` - * - * Define basic types, for example, a string: - * - * ```ts - * t.String(); // { kind: 'str' } - * ``` - * - * Define complex types: - * - * ```ts - * const type = t.Object( - * t.prop('collection', t.Object( - * t.prop('id', t.String({format: 'ascii', noJsonEscape: true})), - * t.prop('ts', t.num, {format: 'u64'}), - * t.prop('cid', t.String({format: 'ascii', noJsonEscape: true})), - * t.prop('prid', t.String({format: 'ascii', noJsonEscape: true})), - * t.prop('slug', t.String({format: 'ascii', noJsonEscape: true})), - * t.prop('name', t.str, {isOptional: true}), - * t.prop('src', t.str, {isOptional: true}), - * t.prop('doc', t.str, {isOptional: true}), - * t.prop('authz', t.str, {isOptional: true}), - * t.prop('active', t.bool), - * )), - * t.prop('block', t.Object( - * t.prop('id', t.String({format: 'ascii', noJsonEscape: true})), - * t.prop('ts', t.num, {format: 'u64'}), - * t.prop('cid', t.String({format: 'ascii', noJsonEscape: true})), - * t.prop('slug', t.String({format: 'ascii', noJsonEscape: true})), - * )), - * ); - * ``` - * - * @module - */ - -export * from './schema'; -export * from './type'; -export * from './system'; diff --git a/src/json-type/jtd/types.ts b/src/json-type/jtd/types.ts deleted file mode 100644 index 4379117276..0000000000 --- a/src/json-type/jtd/types.ts +++ /dev/null @@ -1,70 +0,0 @@ -// prettier-ignore -export type JtdForm = - | JtdEmptyForm - | JtdRefForm - | JtdTypeForm - | JtdEnumForm - | JtdElementsForm - | JtdPropertiesForm - | JtdValuesForm - | JtdDiscriminatorForm - ; - -export interface JtdFormBase { - metadata?: Record; -} - -export interface JtdEmptyForm extends JtdFormBase { - nullable?: boolean; -} - -export interface JtdRefForm extends JtdFormBase { - ref: string; -} - -export interface JtdTypeForm extends JtdFormBase { - type: JtdType; -} - -// prettier-ignore -export type JtdType = - | 'boolean' - | 'float32' - | 'float64' - | 'int8' - | 'uint8' - | 'int16' - | 'uint16' - | 'int32' - | 'uint32' - | 'string' - | 'timestamp' - ; - -export interface JtdEnumForm extends JtdFormBase { - enum: string[]; -} - -export interface JtdElementsForm extends JtdFormBase { - elements: JtdForm[]; -} - -export interface JtdPropertiesForm extends JtdFormBase { - properties?: Record; - optionalProperties?: Record; - additionalProperties?: boolean; -} - -export interface JtdValuesForm extends JtdFormBase { - values: JtdForm; -} - -export interface JtdDiscriminatorForm extends JtdFormBase { - discriminator: string; - mapping: Record; -} - -export interface JtdError { - instancePath: string; - schemaPath: string; -} diff --git a/src/json-type/schema/SchemaBuilder.ts b/src/json-type/schema/SchemaBuilder.ts deleted file mode 100644 index 5cbe9220a7..0000000000 --- a/src/json-type/schema/SchemaBuilder.ts +++ /dev/null @@ -1,266 +0,0 @@ -import { - BooleanSchema, - NumberSchema, - StringSchema, - ArraySchema, - ObjectSchema, - ObjectFieldSchema, - MapSchema, - NoT, - BinarySchema, - AnySchema, - RefSchema, - OrSchema, - Schema, - ObjectOptionalFieldSchema, - Optional, - ConstSchema, - TupleSchema, - FunctionSchema, - FunctionStreamingSchema, - TType, -} from '.'; - -export class SchemaBuilder { - get str() { - return this.String(); - } - - get num() { - return this.Number(); - } - - get bool() { - return this.Boolean(); - } - - get undef() { - return this.Const(undefined); - } - - get nil() { - return this.Const(null); - } - - get arr() { - return this.Array(this.any); - } - - get obj() { - return this.Object(); - } - - get map() { - return this.Map(this.any); - } - - get bin() { - return this.Binary(this.any); - } - - get any() { - return this.Any(); - } - - get fn() { - return this.Function(this.any, this.any); - } - - get fn$() { - return this.Function$(this.any, this.any); - } - - public Boolean(id: string, options?: Omit, 'id'>): BooleanSchema; - public Boolean(options?: NoT): BooleanSchema; - public Boolean(a?: string | NoT, b?: NoT | void): BooleanSchema { - if (typeof a === 'string') return this.Boolean({id: a, ...(b || {})}); - return {kind: 'bool', ...(a || {})}; - } - - public Number(options?: NoT): NumberSchema { - return {kind: 'num', ...options}; - } - - public String(id: string, options?: NoT): StringSchema; - public String(options?: NoT): StringSchema; - public String(a?: string | NoT, b?: NoT): StringSchema { - if (typeof a === 'string') return this.String({id: a, ...(b || {})}); - return {kind: 'str', ...(a || {})}; - } - - public Binary(type: T, options: Optional = {}): BinarySchema { - return { - kind: 'bin', - type, - ...options, - }; - } - - public Array( - id: string, - type: T, - options?: Omit>, 'id' | 'type'>, - ): ArraySchema; - public Array(type: T, options?: Omit>, 'type'>): ArraySchema; - public Array( - a: string | T, - b?: T | Omit>, 'type'>, - c?: Omit>, 'id' | 'type'>, - ): ArraySchema { - if (typeof a === 'string') return this.Array(b as T, {id: a, ...(c || {})}); - return {kind: 'arr', ...(b as Omit>, 'id' | 'type'>), type: a as T}; - } - - /** - * Use TypeScript const when defining a constant value. - * - * - * @example - * - * ```ts - * s.Const('foo' as const); - * ``` - */ - public Const( - value: V, - options?: Optional>, - ): ConstSchema< - string extends V ? never : number extends V ? never : boolean extends V ? never : any[] extends V ? never : V - > { - return {kind: 'const', value: value as any, ...options}; - } - - public Tuple(...types: T): TupleSchema { - return {kind: 'tup', types}; - } - - public fields[]>(...fields: ObjectSchema['fields']): F { - return fields; - } - - public Object[] | readonly ObjectFieldSchema[]>( - options: NoT>, - ): ObjectSchema; - public Object[] | readonly ObjectFieldSchema[]>( - fields: ObjectSchema['fields'], - options?: Optional>, - ): ObjectSchema; - public Object[] | readonly ObjectFieldSchema[]>( - ...fields: ObjectSchema['fields'] - ): ObjectSchema; - public Object[] | readonly ObjectFieldSchema[]>( - ...args: unknown[] - ): ObjectSchema { - const first = args[0]; - if ( - args.length === 1 && - first && - typeof first === 'object' && - (first as NoT>).fields instanceof Array - ) - return {kind: 'obj', ...(first as NoT>)}; - if (args.length >= 1 && args[0] instanceof Array) - return this.Object({fields: args[0] as F, ...(args[1] as Optional>)}); - return this.Object({fields: args as F}); - } - - /** @deprecated Use `.prop`. */ - public Field( - key: K, - type: V, - options: Omit>, 'key' | 'type' | 'optional'> = {}, - ): ObjectFieldSchema { - return { - kind: 'field', - key, - type, - ...options, - }; - } - - /** @deprecated Use `.propOpt`. */ - public FieldOpt( - key: K, - type: V, - options: Omit>, 'key' | 'type' | 'optional'> = {}, - ): ObjectOptionalFieldSchema { - return { - kind: 'field', - key, - type, - ...options, - optional: true, - }; - } - - /** Declares an object property. */ - public prop( - key: K, - type: V, - options: Omit>, 'key' | 'type' | 'optional'> = {}, - ): ObjectFieldSchema { - return { - kind: 'field', - key, - type, - ...options, - }; - } - - /** Declares an optional object property. */ - public propOpt( - key: K, - type: V, - options: Omit>, 'key' | 'type' | 'optional'> = {}, - ): ObjectOptionalFieldSchema { - return { - kind: 'field', - key, - type, - ...options, - optional: true, - }; - } - - public Map(type: T, options?: Omit>, 'type'>): MapSchema { - return {kind: 'map', type, ...options}; - } - - public Any(options: NoT = {}): AnySchema { - return { - kind: 'any', - ...options, - }; - } - - public Ref(ref: string): RefSchema { - return { - kind: 'ref', - ref: ref as string & T, - }; - } - - public Or(...types: T): OrSchema { - return { - kind: 'or', - types, - discriminator: ['num', -1], - }; - } - - public Function(req: Req, res: Res): FunctionSchema { - return { - kind: 'fn', - req, - res, - }; - } - - public Function$(req: Req, res: Res): FunctionStreamingSchema { - return { - kind: 'fn$', - req, - res, - }; - } -} diff --git a/src/json-type/schema/__tests__/SchemaBuilder.spec.ts b/src/json-type/schema/__tests__/SchemaBuilder.spec.ts deleted file mode 100644 index d657f9e1a9..0000000000 --- a/src/json-type/schema/__tests__/SchemaBuilder.spec.ts +++ /dev/null @@ -1,81 +0,0 @@ -import {s} from '..'; - -describe('string', () => { - test('can create a string type', () => { - expect(s.String()).toEqual({kind: 'str'}); - }); - - test('can create a named a string type', () => { - expect(s.String('UserName')).toEqual({ - kind: 'str', - id: 'UserName', - }); - }); -}); - -describe('object', () => { - test('can create an empty object using shorthand', () => { - expect(s.obj).toEqual({kind: 'obj', fields: []}); - }); - - test('can create an empty object using default syntax', () => { - expect(s.Object()).toEqual({kind: 'obj', fields: []}); - }); - - test('can create an empty object using fields-first syntax', () => { - expect(s.Object()).toEqual({kind: 'obj', fields: []}); - }); - - test('can create a named empty object using fields-first syntax', () => { - expect(s.Object([])).toEqual({kind: 'obj', fields: []}); - }); - - test('can create a named empty object using default syntax', () => { - expect(s.Object({fields: []})).toEqual({kind: 'obj', fields: []}); - }); - - test('can specify types', () => { - const type = s.Object([s.prop('id', s.String('UserId')), s.prop('name', s.str)]); - expect(type).toEqual({ - kind: 'obj', - fields: [ - { - kind: 'field', - key: 'id', - type: { - kind: 'str', - id: 'UserId', - }, - }, - { - kind: 'field', - key: 'name', - type: { - kind: 'str', - }, - }, - ], - }); - }); -}); - -describe('map', () => { - test('can create an simple object using shorthand', () => { - expect(s.map).toEqual({kind: 'map', type: {kind: 'any'}}); - }); - - test('can define a map', () => { - expect(s.Map(s.Boolean())).toEqual({kind: 'map', type: {kind: 'bool'}}); - }); -}); - -describe('or', () => { - test('can create an "or" type', () => { - const type = s.Or(s.str, s.num); - expect(type).toEqual({ - kind: 'or', - types: [{kind: 'str'}, {kind: 'num'}], - discriminator: ['num', -1], - }); - }); -}); diff --git a/src/json-type/schema/__tests__/TypeOf.spec.ts b/src/json-type/schema/__tests__/TypeOf.spec.ts deleted file mode 100644 index df64f57bb3..0000000000 --- a/src/json-type/schema/__tests__/TypeOf.spec.ts +++ /dev/null @@ -1,236 +0,0 @@ -import {EMPTY, map} from 'rxjs'; -import {s, TypeOf} from '..'; - -test('can infer a simple "any" type', () => { - const schema1 = s.any; - const schema2 = s.Any(); - const schema3 = s.Any({}); - type T1 = TypeOf; - type T2 = TypeOf; - type T3 = TypeOf; - const val1: T1 = 1; - const val2: T2 = 'adf'; - const val3: T3 = null; -}); - -test('can infer a simple "undefined" type', () => { - const schema1 = s.undef; - type T1 = TypeOf; - const nil1: T1 = undefined; -}); - -test('can infer a simple "null" type', () => { - const schema1 = s.nil; - const nil1: TypeOf = null; -}); - -test('can infer a simple "number" type', () => { - const schema1 = s.num; - const schema2 = s.Number(); - const schema3 = s.Number({}); - const num1: TypeOf = 1; - const num2: TypeOf = 2; - const num3: TypeOf = 3; -}); - -test('can infer a simple "string" type', () => { - const schema1 = s.str; - const schema2 = s.String(); - const schema3 = s.String({}); - const schema4 = s.String('id', {}); - const str1: TypeOf = 'foo'; - const str2: TypeOf = 'bar'; - const str3: TypeOf = 'baz'; - const str4: TypeOf = 'qux'; -}); - -test('can infer a simple "boolean" type', () => { - const schema1 = s.bool; - const schema2 = s.Boolean(); - const schema3 = s.Boolean({}); - const schema4 = s.Boolean('id', {}); - const bool1: TypeOf = true; - const bool2: TypeOf = false; - const bool3: TypeOf = true; - const bool4: TypeOf = false; -}); - -test('can infer a simple "binary" type', () => { - const schema1 = s.bin; - const schema2 = s.Binary(s.any); - const schema3 = s.Binary(s.any, {}); - type T1 = TypeOf; - type T2 = TypeOf; - type T3 = TypeOf; - const arr1: T1 = new Uint8Array(); - const arr2: T2 = new Uint8Array([1, 2, 3]); - const arr3: T3 = Buffer.allocUnsafe(0); -}); - -test('can infer a simple "array" type', () => { - const schema1 = s.arr; - const schema2 = s.Array(s.num); - const schema3 = s.Array(s.str, {}); - type T1 = TypeOf; - type T2 = TypeOf; - type T3 = TypeOf; - const arr1: T1 = [null]; - const arr2: T2 = [1, 2, 3]; - const arr3: T3 = ['foo', 'bar', 'baz']; -}); - -test('can infer a simple "const" type', () => { - const schema1 = s.Const(123 as const); - const schema2 = s.Const('replace' as const, {}); - const schema3 = s.Const(true as const, {}); - const schema4 = s.Const([1, 2] as const, {}); - const schema5 = s.Const(123); - const schema6 = s.Const('replace'); - const schema7 = s.Const(true); - const schema8 = s.Const([1, 2]); - type T1 = TypeOf; - type T2 = TypeOf; - type T3 = TypeOf; - const value1: T1 = 123; - const value2: T2 = 'replace'; - const value3: T3 = true; -}); - -test('can infer a simple "tuple" type', () => { - const schema1 = s.Tuple(s.Const('replace' as const), s.str, s.str); - type T1 = TypeOf; - const value1: T1 = ['replace', 'foo', 'bar']; -}); - -test('can infer a simple object type', () => { - const schema1 = s.obj; - const schema2 = s.Object(s.prop('foo', s.str), s.propOpt('bar', s.num)); - const schema3 = s.Object({ - fields: [s.prop('bar', s.bool)], - }); - const schema4 = s.Object([s.prop('baz', s.num), s.propOpt('bazOptional', s.bool), s.propOpt('z', s.str)], {}); - type T1 = TypeOf; - type T2 = TypeOf; - type T3 = TypeOf; - type T4 = TypeOf; - const obj1: Record = {}; - const obj2: T2 = {foo: 'bar'}; - const obj3: T3 = {bar: true}; - const obj4: T4 = {baz: 123, bazOptional: false}; -}); - -test('can infer a map type', () => { - const schema1 = s.map; - const schema2 = s.Map(s.str); - const schema3 = s.Map(s.Array(s.num)); - type T1 = TypeOf; - type T2 = TypeOf; - type T3 = TypeOf; - const obj1: Record = {}; - const obj2: T2 = {foo: 'bar'}; - const obj3: T3 = {bar: [1, 2, 3]}; -}); - -test('can infer a simple union type', () => { - const schema1 = s.Or(s.str, s.num); - const schema2 = s.Or(s.str, s.num, s.bool); - const schema3 = s.Or(s.str, s.num, s.bool, s.nil); - type T1 = TypeOf; - type T2 = TypeOf; - type T3 = TypeOf; - const val1: T1 = 'foo'; - const val2: T1 = 123; - const val3: T2 = 1; - const val4: T2 = 'a'; - const val5: T2 = true; - const val6: T3 = null; - const val7: T3 = false; - const val8: T3 = ''; - const val9: T3 = 0; -}); - -test('can infer a simple "ref" type', () => { - const schema1 = s.str; - const schema2 = s.Object(s.prop('foo', s.Ref('another-str'))); - type T1 = TypeOf; - type T2 = TypeOf; - const val1: T1 = 'foo'; - const val2: T2 = {foo: 'bar'}; -}); - -test('can infer a simple "fn" type', () => { - const req = s.str; - const res = s.num; - const schema1 = s.Function(req, res); - const schema2 = s.fn; - type T1 = TypeOf; - type T2 = TypeOf; - const val1: T1 = async (arg: string) => +arg; - const val2: T2 = async (arg: unknown) => arg; -}); - -test('can infer a simple "fn$" type', () => { - const req = s.str; - const res = s.num; - const schema1 = s.Function$(req, res); - const schema2 = s.fn$; - type T1 = TypeOf; - type T2 = TypeOf; - const val1: T1 = (arg) => arg.pipe(map((x: string) => +x)); - const val2: T2 = () => EMPTY; -}); - -test('can infer a complex "fn" type', () => { - const req = s.Object( - s.prop('id', s.str), - s.prop('age', s.num), - s.prop('patch', s.Object(s.prop('ops', s.Array(s.Object(s.prop('op', s.str), s.prop('path', s.str)))))), - ); - const res = s.Object(s.prop('id', s.String())); - const schema1 = s.Function(req, res); - type T1 = TypeOf; - const val1: T1 = async ({patch, id}) => { - const str = patch.ops[0].op + id; - return {id: str}; - }; -}); - -test('can infer a realistic schema', () => { - const schema = s.Object( - s.prop('id', s.str), - s.prop('age', s.num), - s.prop('tags', s.Array(s.Or(s.str, s.num))), - s.prop('data', s.Object(s.prop('foo', s.str), s.prop('bar', s.num))), - s.prop('approved', s.bool), - s.prop('meta', s.any), - ); - type T = TypeOf; - const val: T = { - id: 'foo', - age: 18, - tags: ['baz', 'qux', 5], - data: { - foo: 'bar', - bar: 123, - }, - approved: true, - meta: {anything: 'goes'}, - }; -}); - -test('can specify an optional fields', () => { - const schema = s.Object(s.propOpt('meta', s.Object(s.prop('foo', s.str), s.propOpt('bar', s.num)))); - type T = TypeOf; - const val0: T = {}; - const val1: T = { - meta: { - foo: 'str', - }, - }; - const val2: T = { - meta: { - foo: 'str', - bar: 123, - }, - }; -}); diff --git a/src/json-type/schema/__tests__/metadata.spec.ts b/src/json-type/schema/__tests__/metadata.spec.ts deleted file mode 100644 index 17ec6144a9..0000000000 --- a/src/json-type/schema/__tests__/metadata.spec.ts +++ /dev/null @@ -1,39 +0,0 @@ -import {s} from '..'; - -describe('metadata', () => { - test('can add custom metadata', () => { - expect(s.String('validator', {meta: {regex: true}})).toEqual({ - kind: 'str', - id: 'validator', - meta: {regex: true}, - }); - }); -}); - -describe('deprecations', () => { - test('can deprecate a type', () => { - const schema = s.String('validator', { - deprecated: {}, - }); - expect(schema).toEqual({ - kind: 'str', - id: 'validator', - deprecated: {}, - }); - }); - - test('can deprecate a type with a message', () => { - const schema = s.String('validator', { - deprecated: { - description: 'Use the new type', - }, - }); - expect(schema).toEqual({ - kind: 'str', - id: 'validator', - deprecated: { - description: 'Use the new type', - }, - }); - }); -}); diff --git a/src/json-type/schema/__tests__/type.spec.ts b/src/json-type/schema/__tests__/type.spec.ts deleted file mode 100644 index bf77e7b2a6..0000000000 --- a/src/json-type/schema/__tests__/type.spec.ts +++ /dev/null @@ -1,99 +0,0 @@ -import {ObjectSchema, s} from '..'; - -test('can generate any type', () => { - const address: ObjectSchema = { - kind: 'obj', - title: 'User address', - description: 'Various address fields for user', - fields: [...s.Object(s.prop('street', s.String()), s.prop('zip', s.String())).fields], - }; - const userType = s.Object( - s.prop('id', s.Number({format: 'i'})), - s.prop('alwaysOne', s.Const<1>(1)), - s.prop('name', s.String()), - s.prop('address', address), - s.prop('timeCreated', s.Number()), - s.prop('tags', s.Array(s.Or(s.Number(), s.String()))), - s.prop('elements', s.Map(s.str)), - ); - - expect(userType).toMatchObject({ - kind: 'obj', - fields: [ - { - key: 'id', - type: { - kind: 'num', - format: 'i', - }, - }, - { - key: 'alwaysOne', - type: { - kind: 'const', - value: 1, - }, - }, - { - key: 'name', - type: { - kind: 'str', - }, - }, - { - key: 'address', - type: { - kind: 'obj', - title: 'User address', - description: 'Various address fields for user', - fields: [ - { - key: 'street', - type: { - kind: 'str', - }, - }, - { - key: 'zip', - type: { - kind: 'str', - }, - }, - ], - }, - }, - { - key: 'timeCreated', - type: { - kind: 'num', - }, - }, - { - key: 'tags', - type: { - kind: 'arr', - type: { - kind: 'or', - types: [ - { - kind: 'num', - }, - { - kind: 'str', - }, - ], - }, - }, - }, - { - key: 'elements', - type: { - kind: 'map', - type: { - kind: 'str', - }, - }, - }, - ], - }); -}); diff --git a/src/json-type/schema/common.ts b/src/json-type/schema/common.ts deleted file mode 100644 index 16ae60aa45..0000000000 --- a/src/json-type/schema/common.ts +++ /dev/null @@ -1,30 +0,0 @@ -/** - * Properties that are used to display to the user. - */ -export interface Display { - /** - * Title of something, i.e. a heading. - */ - title?: string; - - /** - * An introductory short description. Could be in Markdown. - */ - intro?: string; - - /** - * A long form description of something. Could be in Markdown. - */ - description?: string; -} - -/** - * Something that can be identified by a string. So it can be registered - * in a registry and then referenced by ID. - */ -export interface Identifiable { - /** - * Unique ID of something, i.e. a name, symbol, etc. - */ - id: string; -} diff --git a/src/json-type/schema/index.ts b/src/json-type/schema/index.ts deleted file mode 100644 index 5092fba92c..0000000000 --- a/src/json-type/schema/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -import {SchemaBuilder} from './SchemaBuilder'; - -export * from './common'; -export * from './schema'; - -/** - * JSON Type AST builder. - */ -export const s = new SchemaBuilder(); diff --git a/src/json-type/schema/schema.ts b/src/json-type/schema/schema.ts deleted file mode 100644 index 216e46bab6..0000000000 --- a/src/json-type/schema/schema.ts +++ /dev/null @@ -1,375 +0,0 @@ -import type {Observable} from 'rxjs'; -import type {Mutable} from '@jsonjoy.com/util/lib/types'; -import type {Display, Identifiable} from './common'; -import type {Expr} from '@jsonjoy.com/json-expression'; - -export interface TType extends Display, Partial { - /** - * The type of the JSON Type node. - */ - kind: string; - - /** - * Custom metadata that can be attached to the type. This is useful for - * documentation generation, and for custom code generators. The `meta` field - * is not used by the JSON Type system itself. - */ - meta?: Record; - - /** - * List of example usages of this type. - */ - examples?: TExample[]; - - /** - * A flag that indicates that this type is deprecated. When a type is - * deprecated, it should not be used in new code, and existing code should be - * updated to use a non-deprecated type. - */ - deprecated?: { - /** - * A message that explains why the type is deprecated, and what to use - * instead. - */ - description?: string; - }; -} - -/** - * An example of how a value of a given type could look like. - */ -export interface TExample extends Display { - value: Value; -} - -/** - * A type for which an explicit validation function can be applied. - */ -export interface WithValidator { - /** Name of the validation to apply. */ - validator?: string | string[]; -} - -/** - * Represents something of which type is not known. - */ -export interface AnySchema extends TType, WithValidator { - kind: 'any'; - - /** - * Custom metadata that can be attached to the type. This is useful for - * documentation generation, and for custom code generators. - */ - metadata?: Record; -} - -/** - * Represents a JSON boolean. - */ -export interface BooleanSchema extends TType, WithValidator { - kind: 'bool'; -} - -/** - * Represents a JSON number. - */ -export interface NumberSchema extends TType, WithValidator { - kind: 'num'; - - /** - * A more specific format of the number. When this is set, faster compiled - * serialization functions can generate. "i" stands for signed integer, "u" - * for unsigned integer, and "f" for float. - * - * - "i" is signed integer. - * - "i8" is 8-bit signed integer. - * - "i16" is 16-bit signed integer. - * - "i32" is 32-bit signed integer. - * - "i64" is 64-bit signed integer. - * - "u" is unsigned integer. - * - "u8" is 8-bit unsigned integer. - * - "u16" is 16-bit unsigned integer. - * - "u32" is 32-bit unsigned integer. - * - "u64" is 64-bit unsigned integer. - * - "f" is float. - * - "f32" is 32-bit float. - * - "f64" is 64-bit float. - */ - format?: 'i' | 'u' | 'f' | 'i8' | 'i16' | 'i32' | 'i64' | 'u8' | 'u16' | 'u32' | 'u64' | 'f32' | 'f64'; - - /** Minimum value. */ - gt?: number; - - /** Minimum value, inclusive. */ - gte?: number; - - /** Maximum value. */ - lt?: number; - - /** Maximum value, inclusive. */ - lte?: number; -} - -/** - * Represents a JSON string. - */ -export interface StringSchema extends TType, WithValidator { - kind: 'str'; - - /** - * When set to true, means that the string can contain only ASCII characters. - * This enables a range of optimizations, such as using a faster JSON - * serialization, faster binary serialization. - */ - ascii?: boolean; - - /** - * When set to `true`, a faster JSON serialization function can be - * generated, which does not escape special JSON string characters. - * See: https://www.json.org/json-en.html - */ - noJsonEscape?: boolean; - - /** Minimum number of characters. */ - min?: number; - - /** Maximum number of characters. */ - max?: number; -} - -/** - * Represents a binary type. - */ -export interface BinarySchema extends TType, WithValidator { - kind: 'bin'; - /** Type of value encoded in the binary data. */ - type: T; - /** Codec used for encoding the binary data. */ - format?: 'json' | 'cbor' | 'msgpack' | 'resp3' | 'ion' | 'bson' | 'ubjson' | 'bencode'; -} - -/** - * Represents a JSON array. - */ -export interface ArraySchema extends TType>, WithValidator { - kind: 'arr'; - /** One or more "one-of" types that array contains. */ - type: T; - /** Minimum number of elements. */ - min?: number; - /** Maximum number of elements. */ - max?: number; -} - -/** - * Represents a constant value. - */ -export interface ConstSchema extends TType, WithValidator { - /** @todo Rename to "con". */ - kind: 'const'; - /** The value. */ - value: V; -} - -/** - * Represents a JSON array. - */ -export interface TupleSchema extends TType, WithValidator { - kind: 'tup'; - // types: any[] extends T ? never : T; - types: T; -} - -/** - * Represents a JSON object type, the "object" type excluding "null" in JavaScript, - * the "object" type in JSON Schema, and the "obj" type in MessagePack. - */ -export interface ObjectSchema< - Fields extends ObjectFieldSchema[] | readonly ObjectFieldSchema[] = any, -> extends TType, - WithValidator { - kind: 'obj'; - - /** - * Sorted list of fields this object contains. Although object fields in JSON - * are not guaranteed to be in any particular order, this list is sorted so - * that the order of fields is consistent when generating documentation or code. - */ - fields: Fields; - - /** - * Whether the object may have fields that are not explicitly defined in the - * "fields" list. This setting is similar to "additionalProperties" in JSON - * Schema. Defaults to false. - * - * To define an object with of unknown shape use the following annotation: - * - * ```json - * { - * "kind": "obj", - * "fields": [], - * "unknownFields": true - * } - * ``` - * - * @deprecated - * @todo Rename ot `decodeUnknownFields`. - */ - unknownFields?: boolean; - - encodeUnknownFields?: boolean; -} - -/** - * Represents a single field of an object. - */ -export interface ObjectFieldSchema extends TType<[K, V]>, Display { - kind: 'field'; - /** Key name of the field. */ - key: K; - /** One or more "one-of" types of the field. */ - type: V; - optional?: boolean; -} - -export interface ObjectOptionalFieldSchema - extends ObjectFieldSchema { - optional: true; -} - -/** - * Represents an object, which is treated as a map. All keys are strings and all - * values are of the same type. - */ -export interface MapSchema extends TType>, WithValidator { - kind: 'map'; - /** Type of all values in the map. */ - type: T; -} - -/** - * Reference to another type. - */ -export interface RefSchema extends TType { - kind: 'ref'; - - /** ID of the type it references. */ - ref: string & T; -} - -/** - * Represents a type that is one of a set of types. - */ -export interface OrSchema extends TType { - kind: 'or'; - - /** One or more "one-of" types. */ - types: T; - - discriminator: Expr; -} - -export type FunctionValue = (req: Req, ctx?: Ctx) => Res | Promise; - -export interface FunctionSchema extends TType { - kind: 'fn'; - req: Req; - res: Res; -} - -export type FunctionStreamingValue = (req: Observable, ctx?: Ctx) => Observable; - -export interface FunctionStreamingSchema extends TType { - kind: 'fn$'; - req: Req; - res: Res; -} - -export interface TypeSystemSchema { - types: { - // [alias: string]: - }; -} - -/** - * Any valid JSON type. - */ -export type JsonSchema = - | BooleanSchema - | NumberSchema - | StringSchema - | BinarySchema - | ArraySchema - | ConstSchema - | TupleSchema - | ObjectSchema - | ObjectFieldSchema - | ObjectOptionalFieldSchema - | MapSchema; - -export type Schema = JsonSchema | RefSchema | OrSchema | AnySchema | FunctionSchema | FunctionStreamingSchema; - -export type NoT = Omit; - -export type TypeOf = - T extends OrSchema - ? TypeOfValue - : T extends RefSchema - ? TypeOf - : T extends AnySchema - ? unknown - : TypeOfValue; - -export type TypeOfValue = T extends BooleanSchema - ? boolean - : T extends NumberSchema - ? number - : T extends StringSchema - ? string - : T extends ArraySchema - ? TypeOf[] - : T extends ConstSchema - ? U - : T extends TupleSchema - ? {[K in keyof U]: TypeOf} - : T extends ObjectSchema - ? NoEmptyInterface>> - : T extends MapSchema - ? Record> - : T extends BinarySchema - ? Uint8Array - : T extends FunctionSchema - ? (req: TypeOf, ctx?: unknown) => Promise> - : T extends FunctionStreamingSchema - ? (req$: Observable>, ctx?: unknown) => Observable> - : never; - -export type TypeOfMap> = {[K in keyof M]: TypeOf}; - -type TypeFields = TypeOfFieldMap}>>>; - -type ToObject = T extends [string, unknown][] ? {[K in T[number] as K[0]]: K[1]} : never; - -type ObjectFieldToTuple = F extends ObjectFieldSchema ? [K, F] : never; - -type NoEmptyInterface = keyof I extends never ? Record : I; - -type OptionalFields = {[K in keyof T]-?: T[K] extends ObjectOptionalFieldSchema ? K : never}[keyof T]; - -type RequiredFields = Exclude>; - -type FieldsAdjustedForOptional = Pick> & Partial>>; - -type TypeOfFieldMap = {[K in keyof T]: TypeOf>}; - -type FieldValue = F extends ObjectFieldSchema ? V : never; - -export type OptionalProps = Exclude< - { - [K in keyof T]: T extends Record ? never : K; - }[keyof T], - undefined ->; - -export type Optional = Pick>; -export type Required = Omit>; diff --git a/src/json-type/schema/validate.ts b/src/json-type/schema/validate.ts deleted file mode 100644 index 459006fbec..0000000000 --- a/src/json-type/schema/validate.ts +++ /dev/null @@ -1,46 +0,0 @@ -import type {Display} from './common'; -import type {TExample, TType, WithValidator} from './schema'; - -export const validateDisplay = ({title, description, intro}: Display): void => { - if (title !== undefined && typeof title !== 'string') throw new Error('INVALID_TITLE'); - if (description !== undefined && typeof description !== 'string') throw new Error('INVALID_DESCRIPTION'); - if (intro !== undefined && typeof intro !== 'string') throw new Error('INVALID_INTRO'); -}; - -export const validateTExample = (example: TExample): void => { - validateDisplay(example); -}; - -export const validateTType = (tType: TType, kind: string): void => { - validateDisplay(tType); - const {id} = tType; - if (id !== undefined && typeof id !== 'string') throw new Error('INVALID_ID'); - if (tType.kind !== kind) throw new Error('INVALID_TYPE'); - const {examples} = tType; - if (examples) { - if (!Array.isArray(examples)) throw new Error('INVALID_EXAMPLES'); - examples.forEach(validateTExample); - } -}; - -export const validateWithValidator = ({validator}: WithValidator): void => { - if (validator !== undefined) { - if (Array.isArray(validator)) { - for (const v of validator) if (typeof v !== 'string') throw new Error('INVALID_VALIDATOR'); - } else if (typeof validator !== 'string') throw new Error('INVALID_VALIDATOR'); - } -}; - -export const validateMinMax = (min: number | undefined, max: number | undefined) => { - if (min !== undefined) { - if (typeof min !== 'number') throw new Error('MIN_TYPE'); - if (min < 0) throw new Error('MIN_NEGATIVE'); - if (min % 1 !== 0) throw new Error('MIN_DECIMAL'); - } - if (max !== undefined) { - if (typeof max !== 'number') throw new Error('MAX_TYPE'); - if (max < 0) throw new Error('MAX_NEGATIVE'); - if (max % 1 !== 0) throw new Error('MAX_DECIMAL'); - } - if (min !== undefined && max !== undefined && min > max) throw new Error('MIN_MAX'); -}; diff --git a/src/json-type/system/Method.ts b/src/json-type/system/Method.ts deleted file mode 100644 index 7cf538a5be..0000000000 --- a/src/json-type/system/Method.ts +++ /dev/null @@ -1,21 +0,0 @@ -import type {TypeOf} from '../schema'; -import type {SchemaOf} from '../type'; -import type {TypeAlias, TypeOfAlias, TypeSystem} from '.'; - -export class Method< - ID extends string, - Req extends TypeAlias, - Res extends TypeAlias, - Ctx = unknown, -> { - constructor( - public readonly system: TypeSystem, - public readonly id: ID, - public readonly req: Req, - public readonly res: Res, - public readonly fn: ( - req: TypeOf>>, - ctx: Ctx, - ) => Promise>>>, - ) {} -} diff --git a/src/json-type/system/TypeAlias.ts b/src/json-type/system/TypeAlias.ts deleted file mode 100644 index 67d2c5a113..0000000000 --- a/src/json-type/system/TypeAlias.ts +++ /dev/null @@ -1,72 +0,0 @@ -import {printTree} from 'tree-dump/lib/printTree'; -import {ObjectType} from '../type/classes'; -import {toText} from '../typescript/toText'; -import {JsonSchemaGenericKeywords, JsonSchemaValueNode} from '../../json-schema'; -import {TypeExportContext} from './TypeExportContext'; -import type {TypeSystem} from '.'; -import type {Type} from '../type'; -import type {Printable} from 'tree-dump/lib/types'; -import type * as ts from '../typescript/types'; - -export class TypeAlias implements Printable { - public constructor( - public readonly system: TypeSystem, - public readonly id: K, - public readonly type: T, - ) {} - - public getType(): Type { - return this.type; - } - - public resolve(): TypeAlias { - return this.system.resolve(this.id); - } - - public toString(tab: string = '') { - return this.id + printTree(tab, [(tab) => this.type.toString(tab)]); - } - - public toTypeScriptAst(): ts.TsInterfaceDeclaration | ts.TsTypeAliasDeclaration { - const type = this.type; - if (type instanceof ObjectType) { - const ast = this.type.toTypeScriptAst() as ts.TsTypeLiteral; - const node: ts.TsInterfaceDeclaration = { - node: 'InterfaceDeclaration', - name: this.id, - members: ast.members, - }; - return node; - } else { - const node: ts.TsTypeAliasDeclaration = { - node: 'TypeAliasDeclaration', - name: this.id, - type: type.toTypeScriptAst(), - }; - // TODO: Figure out if this is still needed, and possibly bring it back. - // augmentWithComment(type, node); - return node; - } - } - - public toTypeScript(): string { - return toText(this.toTypeScriptAst()); - } - - public toJsonSchema(): JsonSchemaGenericKeywords { - const node: JsonSchemaGenericKeywords = { - $id: this.id, - $ref: '#/$defs/' + this.id, - $defs: {}, - }; - const ctx = new TypeExportContext(); - ctx.visitRef(this.id); - node.$defs![this.id] = this.type.toJsonSchema(ctx) as JsonSchemaValueNode; - let ref: string | undefined; - while ((ref = ctx.nextMentionedRef())) { - ctx.visitRef(ref); - node.$defs![ref] = this.system.resolve(ref).type.toJsonSchema(ctx) as JsonSchemaValueNode; - } - return node; - } -} diff --git a/src/json-type/system/TypeExportContext.ts b/src/json-type/system/TypeExportContext.ts deleted file mode 100644 index f5e0853c5d..0000000000 --- a/src/json-type/system/TypeExportContext.ts +++ /dev/null @@ -1,16 +0,0 @@ -export class TypeExportContext { - public readonly refs = new Map(); - - public mentionRef(ref: string): void { - if (!this.refs.has(ref)) this.refs.set(ref, 'mentioned'); - } - - public nextMentionedRef(): string | undefined { - for (const [ref, type] of this.refs) if (type === 'mentioned') return ref; - return undefined; - } - - public visitRef(ref: string): void { - this.refs.set(ref, 'visited'); - } -} diff --git a/src/json-type/system/TypeRouter.ts b/src/json-type/system/TypeRouter.ts deleted file mode 100644 index 733d3bc826..0000000000 --- a/src/json-type/system/TypeRouter.ts +++ /dev/null @@ -1,108 +0,0 @@ -import * as classes from '../type/classes'; -import {TypeSystem} from './TypeSystem'; -import {toText} from '../typescript/toText'; -import type * as ts from '../typescript/types'; -import type {TypeBuilder} from '../type/TypeBuilder'; - -export interface TypeRouterOptions { - system: TypeSystem; - routes: R; -} - -export class TypeRouter { - public static create = ( - routes?: (t: TypeRouter<{}>) => NewRoutes, - ): TypeRouter => { - const system = new TypeSystem(); - const router = new TypeRouter({ - system, - routes: {}, - }); - return routes ? router.extend(routes) : (router as any); - }; - - public system: TypeSystem; - public t: TypeBuilder; - public routes: Routes; - - constructor(options: TypeRouterOptions) { - this.system = options.system; - this.t = this.system.t; - this.routes = options.routes; - // this.system.importTypes(this.routes); - } - - protected merge>(router: Router): TypeRouter> { - return new TypeRouter({ - system: this.system, - routes: { - ...this.routes, - ...router.routes, - }, - }); - } - - public extend(routes: (t: this) => NewRoutes): TypeRouter { - const router = new TypeRouter({system: this.system, routes: routes(this)}); - return this.merge(router); - } - - public fn>( - name: K, - type: R, - ): TypeRouter { - this.routes[name] = type; - return this; - } - - public fn$>( - name: K, - type: R, - ): TypeRouter { - this.routes[name] = type; - return this; - } - - public toTypeScriptAst(): ts.TsTypeLiteral { - const node: ts.TsTypeLiteral = { - node: 'TypeLiteral', - members: [], - }; - for (const [name, type] of Object.entries(this.routes)) { - const schema = type.getSchema(); - const property: ts.TsPropertySignature = { - node: 'PropertySignature', - name, - type: type.toTypeScriptAst(), - }; - if (schema.title) property.comment = schema.title; - node.members.push(property); - } - return node; - } - - public toTypeScriptModuleAst(): ts.TsModuleDeclaration { - const node: ts.TsModuleDeclaration = { - node: 'ModuleDeclaration', - name: 'Router', - export: true, - statements: [ - { - node: 'TypeAliasDeclaration', - name: 'Routes', - type: this.toTypeScriptAst(), - export: true, - }, - ], - }; - for (const alias of this.system.aliases.values()) node.statements.push({...alias.toTypeScriptAst(), export: true}); - return node; - } - - public toTypeScript(): string { - return toText(this.toTypeScriptModuleAst()); - } -} - -export type RoutesBase = Record | classes.FunctionStreamingType>; -type TypeRouterRoutes> = R extends TypeRouter ? R2 : never; diff --git a/src/json-type/system/TypeSystem.ts b/src/json-type/system/TypeSystem.ts deleted file mode 100644 index c86ee49783..0000000000 --- a/src/json-type/system/TypeSystem.ts +++ /dev/null @@ -1,95 +0,0 @@ -import {TypeAlias} from './TypeAlias'; -import {TypeBuilder} from '../type/TypeBuilder'; -import {RefType} from '../type/classes'; -import {printTree} from 'tree-dump/lib/printTree'; -import type {CustomValidator} from './types'; -import type {Type, TypeMap} from '../type'; -import type {Printable} from 'tree-dump/lib/types'; - -export class TypeSystem implements Printable { - public readonly t = new TypeBuilder(this); - - public readonly aliases: Map> = new Map(); - - /** - * @todo Add ability fetch object of given type by its ID, analogous to - * GraphQL "nodes". - */ - public readonly alias = (id: K, type: T): TypeAlias => { - const existingAlias = this.aliases.get(id); - if (existingAlias) return existingAlias as TypeAlias; - const alias = new TypeAlias(this, id, type); - this.aliases.set(id, alias); - return alias; - }; - - public readonly unalias = (id: K): TypeAlias => { - const alias = this.aliases.get(id); - if (!alias) throw new Error(`Alias [id = ${id}] not found.`); - return >alias; - }; - - public readonly hasAlias = (id: string): boolean => this.aliases.has(id); - - public readonly resolve = (id: K): TypeAlias => { - const alias = this.unalias(id); - return alias.type instanceof RefType ? this.resolve(alias.type.getRef() as K) : alias; - }; - - protected readonly customValidators: Map = new Map(); - - public readonly addCustomValidator = (validator: CustomValidator): void => { - if (this.customValidators.has(validator.name)) - throw new Error(`Validator [name = ${validator.name}] already exists.`); - this.customValidators.set(validator.name, validator); - }; - - public readonly getCustomValidator = (name: string): CustomValidator => { - const validator = this.customValidators.get(name); - if (!validator) throw new Error(`Validator [name = ${name}] not found.`); - return validator; - }; - - public exportTypes() { - const result: Record = {}; - for (const [id, alias] of this.aliases.entries()) { - result[id] = alias.getType().getSchema(); - } - return result; - } - - public importTypes( - types: A, - ): { - readonly [K in keyof A]: TypeAlias< - K extends string ? K : never, - /** @todo Replace `any` by inferred type here. */ any - >; - } { - const result = {} as any; - for (const id in types) result[id] = this.alias(id, this.t.import(types[id])); - return result; - } - - public toString(tab: string = '') { - const nl = () => ''; - return ( - 'TypeSystem' + - printTree(tab, [ - (tab) => - 'aliases' + - printTree( - tab, - [...this.aliases.values()].map((alias) => (tab) => alias.toString(tab)), - ), - this.customValidators.size ? nl : null, - (tab) => - 'validators' + - printTree( - tab, - [...this.customValidators.keys()].map((validator) => (tab) => `"${validator}"`), - ), - ]) - ); - } -} diff --git a/src/json-type/system/__tests__/TypeOfAlias.spec.ts b/src/json-type/system/__tests__/TypeOfAlias.spec.ts deleted file mode 100644 index 7de5189fec..0000000000 --- a/src/json-type/system/__tests__/TypeOfAlias.spec.ts +++ /dev/null @@ -1,14 +0,0 @@ -import {TypeOf} from '../../schema'; -import {SchemaOf} from '../../type'; -import {TypeSystem} from '../TypeSystem'; -import {TypeOfAlias} from '../types'; - -test('can infer alias type', () => { - const system = new TypeSystem(); - const {t} = system; - const user = system.alias('User', t.Object(t.prop('id', t.str), t.propOpt('name', t.str))); - type T = TypeOf>>; - const value: T = { - id: 'string', - }; -}); diff --git a/src/json-type/system/__tests__/TypeSystem.spec.ts b/src/json-type/system/__tests__/TypeSystem.spec.ts deleted file mode 100644 index 032ed57d59..0000000000 --- a/src/json-type/system/__tests__/TypeSystem.spec.ts +++ /dev/null @@ -1,47 +0,0 @@ -import {TypeSystem} from '../TypeSystem'; - -describe('.toString()', () => { - test('prints type system with nested refs', () => { - const system = new TypeSystem(); - const {t} = system; - system.alias('User0', t.Object(t.prop('id', t.str), t.propOpt('address', t.Ref('Address')))); - system.alias('User1', t.Ref('User0')); - const user = system.alias('User', t.Ref('User1')); - system.alias('Address0', t.Object(t.prop('id', t.str), t.propOpt('user', t.Ref('User')))); - system.alias('Address1', t.Ref('Address0')); - const address = system.alias('Address', t.Ref('Address1')); - system.addCustomValidator({ - name: 'empty-string', - fn: (value: string) => { - if (!value) throw new Error('empty string'); - }, - }); - expect(system.toString()).toMatchInlineSnapshot(` -"TypeSystem -├─ aliases -│ ├─ User0 -│ │ └─ obj -│ │ ├─ "id": -│ │ │ └─ str -│ │ └─ "address"?: -│ │ └─ ref → [Address] -│ ├─ User1 -│ │ └─ ref → [User0] -│ ├─ User -│ │ └─ ref → [User1] -│ ├─ Address0 -│ │ └─ obj -│ │ ├─ "id": -│ │ │ └─ str -│ │ └─ "user"?: -│ │ └─ ref → [User] -│ ├─ Address1 -│ │ └─ ref → [Address0] -│ └─ Address -│ └─ ref → [Address1] -│ -└─ validators - └─ "empty-string"" -`); - }); -}); diff --git a/src/json-type/system/__tests__/demo.ts b/src/json-type/system/__tests__/demo.ts deleted file mode 100644 index 0b12c41afe..0000000000 --- a/src/json-type/system/__tests__/demo.ts +++ /dev/null @@ -1,57 +0,0 @@ -import {TypeSystem} from '../TypeSystem'; - -const createTypes = (system: TypeSystem) => { - const t = system.t; - - // prettier-ignore - const MuCollection = t.Object( - t.prop('id', t.str), - t.propOpt('name', t.str), - ); - - // prettier-ignore - const MuBlock = t.Object( - t.prop('id', t.str), - t.prop('data', t.any), - ); - - // prettier-ignore - const MuBlockCreateRequest = t.Object( - t.propOpt('id', t.str), - ); - const MuBlockCreateResponse = t.Object(t.prop('block', t.Ref('MuBlock'))); - const MuBlockNew = t.Function(MuBlockCreateRequest, MuBlockCreateResponse); - - const MuBlockGetRequest = t.Object(t.prop('id', t.str)); - const MuBlockGetResponse = t.Object(t.prop('block', t.Ref('block.Block'))); - const MuBlockGet = t.Function(MuBlockGetRequest, MuBlockGetResponse).options({ - title: 'Get Block', - description: 'Get a block by ID', - }); - - const MuBlockListen = t.Function$(MuBlockGetRequest, MuBlockGetResponse); - - return { - MuCollection, - 'aa.collection.create': t.Function( - t.Object(t.prop('name', t.str)), - t.Object(t.prop('collection', t.Ref('collection.Collection'))), - ), - - MuBlock, - MuBlockNew, - MuBlockCreateRequest, - MuBlockCreateResponse, - MuBlockGet, - MuBlockGetRequest, - MuBlockGetResponse, - MuBlockListen, - }; -}; - -const system = new TypeSystem(); -const types = createTypes(system); -system.importTypes(types); - -type Types = typeof types; -type Ctx = {ip?: string}; diff --git a/src/json-type/system/__tests__/toJsonSchema.spec.ts b/src/json-type/system/__tests__/toJsonSchema.spec.ts deleted file mode 100644 index 225ed2d987..0000000000 --- a/src/json-type/system/__tests__/toJsonSchema.spec.ts +++ /dev/null @@ -1,17 +0,0 @@ -import {TypeSystem} from '..'; - -test('can export recursive schema', () => { - const system = new TypeSystem(); - const {t} = system; - const post = system.alias('Post', t.Object(t.prop('id', t.str), t.propOpt('author', t.Ref('User')))); - const stream = system.alias('Stream', t.Object(t.prop('id', t.str), t.prop('posts', t.Array(t.Ref('Post'))))); - const user = system.alias( - 'User', - t.Object(t.prop('id', t.str), t.prop('name', t.str), t.prop('following', t.Ref('Stream'))), - ); - const schema = post.toJsonSchema() as any; - expect(schema.$ref).toBe('#/$defs/Post'); - expect(typeof schema.$defs.Post).toBe('object'); - expect(typeof schema.$defs.Stream).toBe('object'); - expect(typeof schema.$defs.User).toBe('object'); -}); diff --git a/src/json-type/system/__tests__/toTypeScript.spec.ts b/src/json-type/system/__tests__/toTypeScript.spec.ts deleted file mode 100644 index 1b443730ca..0000000000 --- a/src/json-type/system/__tests__/toTypeScript.spec.ts +++ /dev/null @@ -1,140 +0,0 @@ -import {TypeSystem} from '..'; -import {TypeRouter} from '../TypeRouter'; - -test('generates TypeScript source for simple string type', () => { - const system = new TypeSystem(); - const {t} = system; - const alias = system.alias('ID', t.str); - expect(alias.toTypeScript()).toMatchInlineSnapshot(` - "type ID = string; - " - `); -}); - -test('emit a simple type interface', () => { - const system = new TypeSystem(); - const {t} = system; - const alias = system.alias( - 'BlogPost', - t.Object(t.prop('id', t.str), t.prop('title', t.str), t.propOpt('body', t.str), t.propOpt('time', t.num)), - ); - // console.log(alias.toTypeScript()); - expect(alias.toTypeScript()).toMatchInlineSnapshot(` - "interface BlogPost { - id: string; - title: string; - body?: string; - time?: number; - } - " - `); -}); - -test('emit an interface with all type kinds', () => { - const system = new TypeSystem(); - const {t} = system; - const alias = system.alias( - 'BlogPost', - t.Object( - t.prop('id', t.str), - t.prop('title', t.bool), - t.propOpt('body', t.str), - t.propOpt('time', t.num), - t.prop('arr', t.Array(t.str)), - t.prop('arrOfObjects', t.Array(t.Object(t.prop('reg', t.str)))), - t.prop('obj', t.Object(t.prop('reg', t.str), t.prop('arr', t.Array(t.str)))), - t.prop('tuple', t.Tuple(t.str, t.num, t.bool)), - t.prop('bin', t.bin), - t.prop('const', t.Const<'hello'>('hello')), - ), - ); - // console.log(alias.toTypeScript()); - expect(alias.toTypeScript()).toMatchInlineSnapshot(` - "interface BlogPost { - id: string; - title: boolean; - body?: string; - time?: number; - arr: string[]; - arrOfObjects: Array<{ - reg: string; - }>; - obj: { - reg: string; - arr: string[]; - }; - tuple: [string, number, boolean]; - bin: Uint8Array; - "const": "hello"; - } - " - `); -}); - -test('type interface inside a tuple', () => { - const system = new TypeSystem(); - const {t} = system; - const alias = system.alias( - 'Alias', - t.Object(t.prop('tup', t.Tuple(t.str, t.Object(t.prop('id', t.str), t.prop('title', t.bool)), t.num))), - ); - expect(alias.toTypeScript()).toMatchInlineSnapshot(` - "interface Alias { - tup: [ - string, - { - id: string; - title: boolean; - }, - number - ]; - } - " - `); -}); - -test('can export whole router', () => { - const system = new TypeSystem(); - const {t} = system; - const router = new TypeRouter({system, routes: {}}).extend(() => ({ - callMe: t.Function(t.str, t.num), - 'block.subscribe': t.Function$(t.Object(t.prop('id', t.str)), t.obj), - })); - expect(router.toTypeScript()).toMatchInlineSnapshot(` - "export namespace Router { - export type Routes = { - callMe: (request: string) => Promise; - "block.subscribe": (request$: Observable<{ - id: string; - }>) => Observable<{}>; - }; - } - " - `); -}); - -test('can export whole router and aliases', () => { - const system = new TypeSystem(); - const {t} = system; - system.alias('Document', t.Object(t.prop('id', t.str), t.prop('title', t.str)).options({title: 'The document'})); - const router = new TypeRouter({system, routes: {}}).extend(() => ({ - callMe: t.Function(t.str, t.num), - 'block.subscribe': t.Function$(t.Object(t.prop('id', t.str)), t.Ref('Document')), - })); - expect(router.toTypeScript()).toMatchInlineSnapshot(` - "export namespace Router { - export type Routes = { - callMe: (request: string) => Promise; - "block.subscribe": (request$: Observable<{ - id: string; - }>) => Observable; - }; - - export interface Document { - id: string; - title: string; - } - } - " - `); -}); diff --git a/src/json-type/system/index.ts b/src/json-type/system/index.ts deleted file mode 100644 index 1165864d25..0000000000 --- a/src/json-type/system/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './types'; -export * from './TypeAlias'; -export * from './TypeSystem'; diff --git a/src/json-type/system/types.ts b/src/json-type/system/types.ts deleted file mode 100644 index 3e0dfc7c02..0000000000 --- a/src/json-type/system/types.ts +++ /dev/null @@ -1,26 +0,0 @@ -import type {Schema, TypeOf} from '../schema'; -import type {SchemaOf, Type} from '../type'; -import type {TypeAlias} from './TypeAlias'; - -/** A custom user provided validator. */ -export interface CustomValidator { - /** Name of the validator. */ - name: string; - - /** - * Receives the value that needs to be validated, returns a truthy value representing an error - * or throws an error when validation fails. When validation succeeds, returns a falsy value. - */ - fn: (value: any) => unknown; -} - -export type TypeOfAlias = T extends TypeAlias ? T : T extends Type ? T : never; - -export type ResolveType = - T extends TypeAlias - ? TypeOf> - : T extends Type - ? TypeOf> - : T extends Schema - ? TypeOf - : never; diff --git a/src/json-type/type/TypeBuilder.ts b/src/json-type/type/TypeBuilder.ts deleted file mode 100644 index 74885ae4e7..0000000000 --- a/src/json-type/type/TypeBuilder.ts +++ /dev/null @@ -1,246 +0,0 @@ -import * as schema from '../schema'; -import * as classes from './classes'; -import type {Type} from './types'; -import type {TypeSystem} from '../system/TypeSystem'; -import type {TypeAlias} from '../system/TypeAlias'; -import type {TypeOfAlias} from '../system/types'; - -const {s} = schema; - -export class TypeBuilder { - constructor(public system?: TypeSystem) {} - - get any() { - return this.Any(); - } - - get undef() { - return this.Const(undefined); - } - - get nil() { - return this.Const(null); - } - - get bool() { - return this.Boolean(); - } - - get num() { - return this.Number(); - } - - get str() { - return this.String(); - } - - get bin() { - return this.Binary(this.any); - } - - get arr() { - return this.Array(this.any); - } - - get obj() { - return this.Object(); - } - - get map() { - return this.Map(this.any); - } - - get fn() { - return this.Function(this.any, this.any); - } - - get fn$() { - return this.Function$(this.any, this.any); - } - - public Any(options?: schema.Optional) { - const type = new classes.AnyType(s.Any(options)); - type.system = this.system; - return type; - } - - public Const(value: V, options?: schema.Optional) { - type V2 = string extends V - ? never - : number extends V - ? never - : boolean extends V - ? never - : any[] extends V - ? never - : V; - const type = new classes.ConstType(schema.s.Const(value, options)); - type.system = this.system; - return type; - } - - public Boolean(options?: schema.Optional) { - const type = new classes.BooleanType(s.Boolean(options)); - type.system = this.system; - return type; - } - - public Number(options?: schema.Optional) { - const type = new classes.NumberType(s.Number(options)); - type.system = this.system; - return type; - } - - public String(options?: schema.Optional) { - const type = new classes.StringType(s.String(options)); - type.system = this.system; - return type; - } - - public Binary(type: T, options: schema.Optional = {}) { - const bin = new classes.BinaryType(type, options); - bin.system = this.system; - return bin; - } - - public Array(type: T, options?: schema.Optional) { - const arr = new classes.ArrayType(type, options); - arr.system = this.system; - return arr; - } - - public Tuple(...types: F) { - const tup = new classes.TupleType(types); - tup.system = this.system; - return tup; - } - - public Object[]>(...fields: F) { - const obj = new classes.ObjectType(fields); - obj.system = this.system; - return obj; - } - - public prop(key: K, value: V) { - const field = new classes.ObjectFieldType(key, value); - field.system = this.system; - return field; - } - - public propOpt(key: K, value: V) { - const field = new classes.ObjectOptionalFieldType(key, value); - field.system = this.system; - return field; - } - - public Map(type: T, options?: schema.Optional) { - const map = new classes.MapType(type, options); - map.system = this.system; - return map; - } - - public Or(...types: F) { - const or = new classes.OrType(types); - or.system = this.system; - return or; - } - - public Ref>(ref: string) { - const type = new classes.RefType>(ref); - type.system = this.system; - return type; - } - - /** @todo Shorten to `Func`. */ - public Function(req: Req, res: Res) { - const fn = new classes.FunctionType(req, res); - fn.system = this.system; - return fn; - } - - public Function$(req: Req, res: Res) { - const fn = new classes.FunctionStreamingType(req, res); - fn.system = this.system; - return fn; - } - - public import(node: schema.Schema): Type { - switch (node.kind) { - case 'any': - return this.Any(node); - case 'bool': - return this.Boolean(node); - case 'num': - return this.Number(node); - case 'str': - return this.String(node); - case 'bin': - return this.Binary(this.import(node.type), node); - case 'arr': - return this.Array(this.import(node.type), node); - case 'tup': - return this.Tuple(...node.types.map((t: schema.Schema) => this.import(t))).options(node); - case 'obj': { - return this.Object( - ...node.fields.map((f: any) => - f.optional - ? this.propOpt(f.key, this.import(f.type)).options(f) - : this.prop(f.key, this.import(f.type)).options(f), - ), - ).options(node); - } - case 'map': - return this.Map(this.import(node.type), node); - case 'const': - return this.Const(node.value).options(node); - case 'or': - return this.Or(...node.types.map((t) => this.import(t as schema.Schema))).options(node); - case 'ref': - return this.Ref(node.ref).options(node); - case 'fn': - return this.Function(this.import(node.req as schema.Schema), this.import(node.res as schema.Schema)).options( - node, - ); - case 'fn$': - return this.Function$(this.import(node.req as schema.Schema), this.import(node.res as schema.Schema)).options( - node, - ); - } - throw new Error(`UNKNOWN_NODE [${node.kind}]`); - } - - public from(value: unknown): Type { - switch (typeof value) { - case 'undefined': - return this.undef; - case 'boolean': - return this.bool; - case 'number': - return this.num; - case 'string': - return this.str; - case 'object': - if (value === null) return this.nil; - if (Array.isArray(value)) { - if (value.length === 0) return this.arr; - const getType = (v: unknown): string => { - switch (typeof v) { - case 'object': - if (v === null) return 'nil'; - if (Array.isArray(v)) return 'arr'; - return 'obj'; - default: - return typeof v; - } - }; - const allElementsOfTheSameType = value.every((v) => getType(v) === getType(value[0])); - return allElementsOfTheSameType - ? this.Array(this.from(value[0])) - : this.Tuple(...value.map((v) => this.from(v))); - } - return this.Object(...Object.entries(value).map(([key, value]) => this.prop(key, this.from(value)))); - default: - return this.any; - } - } -} diff --git a/src/json-type/type/__tests__/SchemaOf.spec.ts b/src/json-type/type/__tests__/SchemaOf.spec.ts deleted file mode 100644 index 41e88e69b3..0000000000 --- a/src/json-type/type/__tests__/SchemaOf.spec.ts +++ /dev/null @@ -1,110 +0,0 @@ -import {EMPTY} from 'rxjs'; -import {SchemaOf, t} from '..'; -import {TypeOf} from '../../schema'; - -test('const', () => { - const type = t.Const(42); - type S = SchemaOf; - type T = TypeOf; - const v: T = 42; -}); - -test('undefined', () => { - const type = t.undef; - type S = SchemaOf; - type T = TypeOf; - const v: T = undefined; -}); - -test('null', () => { - const type = t.nil; - type S = SchemaOf; - type T = TypeOf; - const v: T = null; -}); - -test('boolean', () => { - const type = t.bool; - type S = SchemaOf; - type T = TypeOf; - const v: T = true; -}); - -test('number', () => { - const type = t.num; - type S = SchemaOf; - type T = TypeOf; - const v: T = 123; -}); - -test('string', () => { - const type = t.str; - type S = SchemaOf; - type T = TypeOf; - const v: T = 'abc'; -}); - -test('array', () => { - const type = t.arr; - type S = SchemaOf; - type T = TypeOf; - const v: T = []; -}); - -test('array', () => { - const type = t.Tuple(t.num, t.str); - type S = SchemaOf; - type T = TypeOf; - const v: T = [123, 'abc']; -}); - -test('object', () => { - const type = t.Object(t.prop('a', t.num), t.prop('b', t.str)); - type S = SchemaOf; - type T = TypeOf; - const v: T = {a: 123, b: 'abc'}; -}); - -test('optional field', () => { - const type = t.Object(t.prop('a', t.num), t.propOpt('b', t.str)); - type S = SchemaOf; - type T = TypeOf; - const v: T = {a: 123}; -}); - -test('binary', () => { - const type = t.bin; - type S = SchemaOf; - type T = TypeOf; - const v: T = new Uint8Array(); -}); - -test('ref', () => { - const alias = t.bin; - const type = t.Ref('my-alias'); - type S = SchemaOf; - type T = TypeOf; - const v: T = new Uint8Array(); -}); - -test('or', () => { - const type = t.Or(t.num, t.str); - type S = SchemaOf; - type T = TypeOf; - const v1: T = 123; - const v2: T = 'abc'; -}); - -test('fn', () => { - const type = t.Function(t.num, t.str); - type S = SchemaOf; - type T = TypeOf; - const v: T = async (arg: number) => 'abc'; -}); - -test('fn$', () => { - const type = t.Function$(t.num, t.str); - type S = SchemaOf; - type T = TypeOf; - const v: T = (arg) => EMPTY; -}); diff --git a/src/json-type/type/__tests__/TypeBuilder-from.spec.ts b/src/json-type/type/__tests__/TypeBuilder-from.spec.ts deleted file mode 100644 index e4f8d3df3c..0000000000 --- a/src/json-type/type/__tests__/TypeBuilder-from.spec.ts +++ /dev/null @@ -1,54 +0,0 @@ -import {TypeSystem} from '../../system'; - -const system = new TypeSystem(); -const t = system.t; - -test('can create a schema for a deeply nested object', () => { - const type = t.from({ - id: 123, - foo: 'bar', - verified: true, - tags: ['a', 'b', 'c'], - emptyArr: [], - vectorClockIsTuple: ['site 1', 123], - tupleOfObjectsAndArrays: [[], {}, null], - nested: { - id: 456, - }, - nil: null, - undef: undefined, - }); - expect(type + '').toMatchInlineSnapshot(` - "obj - ├─ "id": - │ └─ num - ├─ "foo": - │ └─ str - ├─ "verified": - │ └─ bool - ├─ "tags": - │ └─ arr - │ └─ str - ├─ "emptyArr": - │ └─ arr - │ └─ any - ├─ "vectorClockIsTuple": - │ └─ tup - │ ├─ str - │ └─ num - ├─ "tupleOfObjectsAndArrays": - │ └─ tup - │ ├─ arr - │ │ └─ any - │ ├─ obj - │ └─ const → null - ├─ "nested": - │ └─ obj - │ └─ "id": - │ └─ num - ├─ "nil": - │ └─ const → null - └─ "undef": - └─ const → undefined" - `); -}); diff --git a/src/json-type/type/__tests__/TypeBuilder.spec.ts b/src/json-type/type/__tests__/TypeBuilder.spec.ts deleted file mode 100644 index fcd9413017..0000000000 --- a/src/json-type/type/__tests__/TypeBuilder.spec.ts +++ /dev/null @@ -1,223 +0,0 @@ -import {SchemaOf, t} from '..'; -import {TypeOf} from '../../schema'; -import {NumberType, ObjectFieldType, ObjectType, StringType} from '../classes'; - -test('number', () => { - const type = t.Number({ - description: 'A number', - format: 'i32', - }); - expect(type.getSchema()).toStrictEqual({ - kind: 'num', - description: 'A number', - format: 'i32', - }); -}); - -test('can construct a array type', () => { - const type = t.Array(t.Or(t.num, t.str.options({title: 'Just a string'}))); - expect(type.getSchema()).toStrictEqual({ - kind: 'arr', - type: { - kind: 'or', - types: [{kind: 'num'}, {kind: 'str', title: 'Just a string'}], - discriminator: expect.any(Array), - }, - }); -}); - -test('array of any with options', () => { - const type = t.Array(t.any.options({description: 'Any type'})).options({intro: 'An array of any type'}); - expect(type.getSchema()).toStrictEqual({ - kind: 'arr', - intro: 'An array of any type', - type: { - kind: 'any', - description: 'Any type', - }, - }); -}); - -test('can construct a realistic object', () => { - const type = t.Object( - t.prop('id', t.str), - t.propOpt('name', t.str), - t.propOpt('age', t.num), - t.prop('verified', t.bool), - ); - expect(type.getSchema()).toStrictEqual({ - kind: 'obj', - fields: [ - {kind: 'field', key: 'id', type: {kind: 'str'}}, - {kind: 'field', key: 'name', type: {kind: 'str'}, optional: true}, - {kind: 'field', key: 'age', type: {kind: 'num'}, optional: true}, - {kind: 'field', key: 'verified', type: {kind: 'bool'}}, - ], - }); - type T = TypeOf>; - const val: T = { - id: 'abc', - verified: true, - }; -}); - -describe('import()', () => { - test('can import a number schema', () => { - const type = t.import({ - kind: 'num', - description: 'A number', - format: 'i32', - }); - expect(type).toBeInstanceOf(NumberType); - expect(type.getTypeName()).toBe('num'); - expect(type.getSchema()).toStrictEqual({ - kind: 'num', - description: 'A number', - format: 'i32', - }); - }); - - test('can import an object schema', () => { - const type = t.import({ - kind: 'obj', - fields: [ - {kind: 'field', key: 'id', type: {kind: 'str'}}, - {kind: 'field', key: 'name', type: {kind: 'str'}, optional: true}, - {kind: 'field', key: 'age', type: {kind: 'num'}, optional: true}, - {kind: 'field', key: 'verified', type: {kind: 'bool'}}, - ], - }) as ObjectType; - expect(type).toBeInstanceOf(ObjectType); - expect(type.getTypeName()).toBe('obj'); - const id = type.getField('id')!; - expect(id).toBeInstanceOf(ObjectFieldType); - expect(id.getTypeName()).toBe('field'); - expect(id.value).toBeInstanceOf(StringType); - expect(id.value.getTypeName()).toBe('str'); - expect(type.getSchema()).toStrictEqual({ - kind: 'obj', - fields: [ - {kind: 'field', key: 'id', type: {kind: 'str'}}, - {kind: 'field', key: 'name', type: {kind: 'str'}, optional: true}, - {kind: 'field', key: 'age', type: {kind: 'num'}, optional: true}, - {kind: 'field', key: 'verified', type: {kind: 'bool'}}, - ], - }); - }); -}); - -describe('validateSchema()', () => { - test('can validate a number schema', () => { - const schema = { - kind: 'num', - description: 'A number', - format: 'i32', - }; - expect(t.import(schema as any).validateSchema()).toBeUndefined(); - expect(() => t.import({...schema, description: 123} as any).validateSchema()).toThrow( - new Error('INVALID_DESCRIPTION'), - ); - expect(() => t.import({...schema, title: 123} as any).validateSchema()).toThrow(new Error('INVALID_TITLE')); - expect(() => t.import({...schema, intro: null} as any).validateSchema()).toThrow(new Error('INVALID_INTRO')); - expect(() => t.import({...schema, gt: null} as any).validateSchema()).toThrow(new Error('GT_TYPE')); - expect(() => t.import({...schema, lt: null} as any).validateSchema()).toThrow(new Error('LT_TYPE')); - expect(() => t.import({...schema, gte: '334'} as any).validateSchema()).toThrow(new Error('GTE_TYPE')); - expect(() => t.import({...schema, lte: '334'} as any).validateSchema()).toThrow(new Error('LTE_TYPE')); - expect(() => t.import({...schema, lt: 1, gt: 2} as any).validateSchema()).toThrow(new Error('GT_LT')); - expect(() => t.import({...schema, format: 'int'} as any).validateSchema()).toThrow(new Error('FORMAT_INVALID')); - expect(() => t.import({...schema, validator: 123} as any).validateSchema()).toThrow(new Error('INVALID_VALIDATOR')); - }); - - test('can validate a string schema', () => { - const schema = { - kind: 'str', - description: 'A string', - }; - expect(t.import(schema as any).validateSchema()).toBeUndefined(); - expect(() => t.import({...schema, description: 123} as any).validateSchema()).toThrow( - new Error('INVALID_DESCRIPTION'), - ); - expect(() => t.import({...schema, title: 123} as any).validateSchema()).toThrow(new Error('INVALID_TITLE')); - expect(() => t.import({...schema, intro: null} as any).validateSchema()).toThrow(new Error('INVALID_INTRO')); - expect(() => t.import({...schema, min: null} as any).validateSchema()).toThrow(new Error('MIN_TYPE')); - expect(() => t.import({...schema, max: 'asdf'} as any).validateSchema()).toThrow(new Error('MAX_TYPE')); - expect(() => t.import({...schema, min: -1} as any).validateSchema()).toThrow(new Error('MIN_NEGATIVE')); - expect(() => t.import({...schema, max: -1} as any).validateSchema()).toThrow(new Error('MAX_NEGATIVE')); - expect(() => t.import({...schema, max: 0.5} as any).validateSchema()).toThrow(new Error('MAX_DECIMAL')); - expect(() => t.import({...schema, min: 1.2} as any).validateSchema()).toThrow(new Error('MIN_DECIMAL')); - expect(() => t.import({...schema, min: 5, max: 3} as any).validateSchema()).toThrow(new Error('MIN_MAX')); - expect(() => t.import({...schema, ascii: 123} as any).validateSchema()).toThrow(new Error('ASCII')); - expect(() => t.import({...schema, ascii: 'bytes'} as any).validateSchema()).toThrow(new Error('ASCII')); - }); - - test('validates an arbitrary self-constructed object', () => { - const type = t.Object( - t.prop('id', t.String()), - t.prop('name', t.String({title: 'Name'})), - t.prop('age', t.Number({format: 'u16'})), - ); - type.validateSchema(); - }); - - test('validates array elements', () => { - const type = t.import({ - kind: 'arr', - description: 'An array', - type: {kind: 'str', ascii: 'bytes'}, - }); - expect(() => type.validateSchema()).toThrow(new Error('ASCII')); - }); - - test('validates array elements', () => { - const type = t.import({ - kind: 'arr', - description: 'An array', - type: {kind: 'str', ascii: 'bytes'}, - }); - expect(() => type.validateSchema()).toThrow(new Error('ASCII')); - }); - - test('validates object', () => { - const type = t.import({ - kind: 'obj', - description: 'An object', - fields: [], - unknownFields: 123 as any, - }); - expect(() => type.validateSchema()).toThrow(new Error('UNKNOWN_FIELDS_TYPE')); - }); - - test('validates object fields', () => { - const type = t.import({ - kind: 'obj', - description: 'An object', - fields: [{kind: 'field', key: 'id', type: {kind: 'str', ascii: 'bytes'} as any}], - }); - expect(() => type.validateSchema()).toThrow(new Error('ASCII')); - }); - - test('validates object fields - 2', () => { - const type = t.import({ - kind: 'obj', - description: 'An object', - fields: [{kind: 'field', key: 'id', optional: 123, type: {kind: 'str'}} as any], - }); - expect(() => type.validateSchema()).toThrow(new Error('OPTIONAL_TYPE')); - }); - - test('validates ref', () => { - const type = t.import({ - kind: 'ref', - } as any); - expect(() => type.validateSchema()).toThrow(new Error('REF_TYPE')); - }); - - test('validates or', () => { - const type = t.import({ - kind: 'or', - types: [{kind: 'str', ascii: '123'} as any], - discriminator: ['!', 0], - }); - expect(() => type.validateSchema()).toThrow(new Error('ASCII')); - }); -}); diff --git a/src/json-type/type/__tests__/__snapshots__/toString.spec.ts.snap b/src/json-type/type/__tests__/__snapshots__/toString.spec.ts.snap deleted file mode 100644 index 2ddf6e9594..0000000000 --- a/src/json-type/type/__tests__/__snapshots__/toString.spec.ts.snap +++ /dev/null @@ -1,73 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`can print a type 1`] = ` -"obj -├─ "id": "The id of the object" -│ └─ str -├─ "tags": "Always use tags" -│ └─ arr "Tags" -│ └─ str -├─ "optional"?: -│ └─ any -├─ "booleanProperty": -│ └─ bool -├─ "numberProperty": -│ └─ num -├─ "binaryProperty": -│ └─ bin -│ └─ any -├─ "arrayProperty": -│ └─ arr -│ └─ any -├─ "objectProperty": -│ └─ obj -│ └─ "id": -│ └─ str -├─ "unionProperty": -│ └─ or -│ ├─ str -│ ├─ num -│ └─ const → null -├─ "enumAsConst"?: -│ └─ or -│ ├─ const → "a" -│ ├─ const → "b" -│ └─ const → "c" -├─ "refField"?: -│ └─ ref → [refId] -├─ "und"?: -│ └─ const → undefined -├─ "operation": -│ └─ obj -│ ├─ "type": -│ │ └─ const "Always use replace" → "replace" -│ ├─ "path": -│ │ └─ str -│ └─ "value": -│ └─ any -├─ "binaryOperation": -│ └─ bin -│ └─ tup "Should always have 3 elements" -│ ├─ const "7 is the magic number" → 7 -│ ├─ str -│ └─ any -├─ "map": -│ └─ map -│ └─ num -├─ "simpleFn1": -│ └─ fn -│ ├─ req: any -│ └─ res: any -├─ "simpleFn2": -│ └─ fn$ -│ ├─ req: any -│ └─ res: any -└─ "function": - └─ fn - ├─ req: obj - │ └─ "id": - │ └─ str - └─ res: obj - └─ "name": - └─ str" -`; diff --git a/src/json-type/type/__tests__/discriminator.spec.ts b/src/json-type/type/__tests__/discriminator.spec.ts deleted file mode 100644 index 3245913215..0000000000 --- a/src/json-type/type/__tests__/discriminator.spec.ts +++ /dev/null @@ -1,96 +0,0 @@ -import {t} from '..'; -import {Discriminator} from '../discriminator'; - -describe('Discriminator', () => { - test('can find const discriminator at root node', () => { - const t1 = t.Const('foo'); - const t2 = t.Const(123); - const t3 = t.Const([true, false]); - const d1 = Discriminator.find(t1); - const d2 = Discriminator.find(t2); - const d3 = Discriminator.find(t3); - expect(d1!.toSpecifier()).toBe('["","const","foo"]'); - expect(d2!.toSpecifier()).toBe('["","const",123]'); - expect(d3!.toSpecifier()).toBe('["","const",[true,false]]'); - }); - - test('can find const discriminator in a tuple', () => { - const t1 = t.Tuple(t.Const('foo')); - const t2 = t.Tuple(t.Const('add'), t.str, t.any); - const t3 = t.Tuple(t.map, t.obj, t.Const(null), t.num); - const d1 = Discriminator.find(t1); - const d2 = Discriminator.find(t2); - const d3 = Discriminator.find(t3); - expect(d1!.toSpecifier()).toBe('["/0","const","foo"]'); - expect(d2!.toSpecifier()).toBe('["/0","const","add"]'); - expect(d3!.toSpecifier()).toBe('["/2","const",null]'); - }); - - test('can find const discriminator in a object', () => { - const t1 = t.Object(t.prop('op', t.Const('replace')), t.prop('value', t.num), t.prop('path', t.str)); - const d1 = Discriminator.find(t1); - expect(d1!.toSpecifier()).toBe('["/op","const","replace"]'); - }); - - test('uses node type as discriminator, if not const', () => { - const t1 = t.Map(t.str); - const t2 = t.obj; - const t3 = t.str; - const d1 = Discriminator.find(t1); - const d2 = Discriminator.find(t2); - const d3 = Discriminator.find(t3); - expect(d1!.toSpecifier()).toBe('["","obj",0]'); - expect(d2!.toSpecifier()).toBe('["","obj",0]'); - expect(d3!.toSpecifier()).toBe('["","str",0]'); - }); - - test('can find const node in nested fields', () => { - const t1 = t.Tuple(t.str, t.Tuple(t.num, t.Const('foo'))); - const t2 = t.Object(t.prop('type', t.Tuple(t.Const(25), t.str, t.any)), t.prop('value', t.num)); - const d1 = Discriminator.find(t1); - const d2 = Discriminator.find(t2); - // const d3 = Discriminator.find(t3); - expect(d1!.toSpecifier()).toBe('["/1/1","const","foo"]'); - expect(d2!.toSpecifier()).toBe('["/type/0","const",25]'); - }); -}); - -describe('OrType', () => { - test('can automatically infer discriminator', () => { - const or = t.Or(t.str, t.num); - or.validate('str'); - or.validate(123); - expect(() => or.validate(true)).toThrow(); - expect(() => or.validate(false)).toThrow(); - expect(() => or.validate(null)).toThrow(); - expect(() => or.validate({})).toThrow(); - expect(() => or.validate([])).toThrow(); - }); - - test('can automatically infer discriminator in objects', () => { - const or = t.Or( - t.Object(t.prop('op', t.Const('replace')), t.prop('path', t.str), t.prop('value', t.any)), - t.Object(t.prop('op', t.Const('add')), t.prop('path', t.str), t.prop('value', t.any)), - t.Object(t.prop('op', t.Const('test')), t.prop('path', t.str), t.prop('value', t.any)), - t.Object(t.prop('op', t.Const('move')), t.prop('path', t.str), t.prop('from', t.str)), - t.Object(t.prop('op', t.Const('copy')), t.prop('path', t.str), t.prop('from', t.str)), - t.Object(t.prop('op', t.Const('remove')), t.prop('path', t.str)), - ); - // console.log(JSON.stringify(or.getSchema(), null, 2)); - or.validate({op: 'replace', path: '/foo', value: 123}); - or.validate({op: 'add', path: '/f/o/o', value: {foo: 'bar'}}); - or.validate({op: 'test', path: '/abc', value: []}); - or.validate({op: 'move', path: '/abc', from: '/xyz'}); - or.validate({op: 'copy', path: '/abc', from: '/xyz'}); - or.validate({op: 'remove', path: '/abc'}); - expect(() => or.validate({op: 'replace2', path: '/foo', value: 123})).toThrow(); - expect(() => or.validate({op: 'add', path: 123, value: {foo: 'bar'}})).toThrow(); - expect(() => or.validate({op: 'test', path: '/abc'})).toThrow(); - expect(() => or.validate({op: 'move', path: ['/abc'], from: '/xyz'})).toThrow(); - expect(() => or.validate({op: 'copy', path: '/abc', fromd: '/xyz'})).toThrow(); - expect(() => or.validate({op: 'remove', path: '/abc', from: '/sdf'})).toThrow(); - expect(() => or.validate([])).toThrow(); - expect(() => or.validate({})).toThrow(); - expect(() => or.validate(123)).toThrow(); - }); -}); diff --git a/src/json-type/type/__tests__/fixtures.ts b/src/json-type/type/__tests__/fixtures.ts deleted file mode 100644 index 8154c75a38..0000000000 --- a/src/json-type/type/__tests__/fixtures.ts +++ /dev/null @@ -1,51 +0,0 @@ -import {TypeOf} from '../../schema'; -import {SchemaOf, t} from '..'; - -export const everyType = t.Object( - // t.prop('id', t.str.options({noJsonEscape: true})), - // t.prop('bin', t.bin), - // t.prop('bool', t.bool), - // t.prop('nil', t.nil), - // t.prop('num', t.num), - // t.prop('str', t.str), - // t.prop('arr', t.arr), - // t.prop('obj', t.obj), - // t.prop('any', t.any), - t.prop('undef', t.undef), - // t.prop('const', t.Const('const')), - // t.prop('const2', t.Const(2)), - // t.prop('emptyArray', t.arr.options({max: 0})), - // t.prop('oneItemArray', t.arr.options({min: 1, max: 1})), - // t.prop('objWithArray', t.Object(t.propOpt('arr', t.arr), t.propOpt('arr2', t.arr))), - // t.prop('emptyMap', t.map), - // t.prop('mapWithOneNumField', t.Map(t.num)), - // t.prop('mapOfStr', t.Map(t.str)), -); - -export const everyTypeValue: TypeOf> = { - // id: 'asdf', - // bin: new Uint8Array([1, 2, 3]), - // bool: true, - // nil: null, - // num: 1, - // str: 'asdf', - // arr: [1, 2, 3], - // obj: {}, - // any: 1, - undef: undefined, - // const: 'const', - // const2: 2, - // emptyArray: [], - // oneItemArray: [1], - // objWithArray: { - // arr: [1, 2, 3], - // }, - // emptyMap: {}, - // mapWithOneNumField: { - // a: 1, - // }, - // mapOfStr: { - // a: 'a', - // b: 'b', - // }, -}; diff --git a/src/json-type/type/__tests__/getJsonSchema.spec.ts b/src/json-type/type/__tests__/getJsonSchema.spec.ts deleted file mode 100644 index 1fd7789e29..0000000000 --- a/src/json-type/type/__tests__/getJsonSchema.spec.ts +++ /dev/null @@ -1,203 +0,0 @@ -import {t} from '..'; -import {TypeSystem} from '../../system'; - -test('can print a type', () => { - const type = t - .Object( - t.prop('id', t.str.options({validator: ['id', 'uuid']})).options({ - description: 'The id of the object', - }), - t.prop('tags', t.Array(t.str).options({title: 'Tags'})).options({title: 'Always use tags'}), - t.propOpt('optional', t.any), - t.prop('booleanProperty', t.bool), - t.prop('numberProperty', t.num.options({format: 'f64', gt: 3.14})), - t.prop('binaryProperty', t.bin.options({format: 'cbor'})), - t.prop('arrayProperty', t.Array(t.any)), - t.prop('objectProperty', t.Object(t.prop('id', t.str.options({ascii: true, min: 3, max: 128})))), - t.prop('unionProperty', t.Or(t.str, t.num, t.nil.options({description: ''}))), - t.propOpt('enumAsConst', t.Or(t.Const('a' as const), t.Const('b' as const), t.Const('c' as const))), - t.propOpt('refField', t.Ref('refId')), - t.propOpt('und', t.undef), - t.prop( - 'operation', - t.Object( - t.prop('type', t.Const('replace' as const).options({title: 'Always use replace'})), - t.prop('path', t.str), - t.prop('value', t.any), - ), - ), - t.prop( - 'binaryOperation', - t - .Binary( - t - .Tuple(t.Const(7 as const).options({description: '7 is the magic number'}), t.str, t.any) - .options({description: 'Should always have 3 elements'}), - ) - .options({format: 'cbor'}), - ), - t.prop('map', t.Map(t.str)), - ) - .options({unknownFields: true}); - // console.log(JSON.stringify(type.toJsonSchema(), null, 2)); - expect(type.toJsonSchema()).toMatchInlineSnapshot(` - { - "properties": { - "arrayProperty": { - "items": { - "type": [ - "string", - "number", - "boolean", - "null", - "array", - "object", - ], - }, - "type": "array", - }, - "binaryOperation": { - "type": "binary", - }, - "binaryProperty": { - "type": "binary", - }, - "booleanProperty": { - "type": "boolean", - }, - "enumAsConst": { - "anyOf": [ - { - "const": "a", - "type": "string", - }, - { - "const": "b", - "type": "string", - }, - { - "const": "c", - "type": "string", - }, - ], - }, - "id": { - "type": "string", - }, - "map": { - "patternProperties": { - ".*": { - "type": "string", - }, - }, - "type": "object", - }, - "numberProperty": { - "exclusiveMinimum": 3.14, - "type": "number", - }, - "objectProperty": { - "properties": { - "id": { - "maxLength": 128, - "minLength": 3, - "type": "string", - }, - }, - "required": [ - "id", - ], - "type": "object", - }, - "operation": { - "properties": { - "path": { - "type": "string", - }, - "type": { - "const": "replace", - "title": "Always use replace", - "type": "string", - }, - "value": { - "type": [ - "string", - "number", - "boolean", - "null", - "array", - "object", - ], - }, - }, - "required": [ - "type", - "path", - "value", - ], - "type": "object", - }, - "optional": { - "type": [ - "string", - "number", - "boolean", - "null", - "array", - "object", - ], - }, - "refField": { - "$ref": "#/$defs/refId", - }, - "tags": { - "items": { - "type": "string", - }, - "title": "Tags", - "type": "array", - }, - "und": { - "const": undefined, - "type": "undefined", - }, - "unionProperty": { - "anyOf": [ - { - "type": "string", - }, - { - "type": "number", - }, - { - "const": null, - "type": "object", - }, - ], - }, - }, - "required": [ - "id", - "tags", - "booleanProperty", - "numberProperty", - "binaryProperty", - "arrayProperty", - "objectProperty", - "unionProperty", - "operation", - "binaryOperation", - "map", - ], - "type": "object", - } - `); -}); - -test('exports "ref" type to JSON Schema "$defs"', () => { - const system = new TypeSystem(); - const t = system.t; - const type = t.Object(t.prop('id', t.str), t.prop('user', t.Ref('User'))); - const schema = type.toJsonSchema() as any; - expect(schema.properties.user.$ref).toBe('#/$defs/User'); -}); diff --git a/src/json-type/type/__tests__/random.fuzzer.spec.ts b/src/json-type/type/__tests__/random.fuzzer.spec.ts deleted file mode 100644 index 51ea4e0059..0000000000 --- a/src/json-type/type/__tests__/random.fuzzer.spec.ts +++ /dev/null @@ -1,11 +0,0 @@ -import {everyType} from './fixtures'; - -test('generate random JSON values an validate them', () => { - for (let i = 0; i < 100; i++) { - const value = everyType.random(); - everyType.validate(value); - const validator = everyType.compileValidator({errors: 'object'}); - const error = validator(value); - expect(error).toBe(null); - } -}); diff --git a/src/json-type/type/__tests__/random.spec.ts b/src/json-type/type/__tests__/random.spec.ts deleted file mode 100644 index fb13ed3d67..0000000000 --- a/src/json-type/type/__tests__/random.spec.ts +++ /dev/null @@ -1,40 +0,0 @@ -import {t} from '..'; - -test('generates random JSON', () => { - const mathRandom = Math.random; - let i = 0.0; - Math.random = () => { - i += 0.0379; - if (i >= 1) i -= 1; - return i; - }; - const type = t.Object( - t.prop('id', t.str), - t.prop('name', t.str), - t.prop('tags', t.Array(t.str)), - t.propOpt('scores', t.Array(t.num)), - t.prop('refs', t.Map(t.str)), - ); - const json = type.random(); - expect(json).toMatchInlineSnapshot(` - { - "id": "", - "name": "1", - "refs": { - "259<@CGK": "UY\\\`c", - ";>BEILPT": "^beimp", - "HKORVY]\`": "korvy}#", - "LOSWZ^ae": "pswz #'*", - "_cfjmqtx": "fimqtxBEIM", - "knrvyCGJ": "MQTX", - "nquy|"%)": "4", - "w{ $'+/2": "=@", - }, - "tags": [ - "@CG", - "QUY\\\`", - ], - } - `); - Math.random = mathRandom; -}); diff --git a/src/json-type/type/__tests__/toJson.spec.ts b/src/json-type/type/__tests__/toJson.spec.ts deleted file mode 100644 index 9c7d3267ee..0000000000 --- a/src/json-type/type/__tests__/toJson.spec.ts +++ /dev/null @@ -1,49 +0,0 @@ -import {t} from '..'; -import {parse} from '../../../json-binary'; -import {TypeSystem} from '../../system/TypeSystem'; -import {everyType, everyTypeValue} from './fixtures'; - -test('can serialize an object', () => { - const type = t.Object( - t.prop('id', t.str.options({validator: ['id', 'uuid']})).options({ - description: 'The id of the object', - }), - t.prop('tags', t.Array(t.str).options({title: 'Tags'})).options({title: 'Always use tags'}), - ); - const value = { - id: 'string', - tags: ['string', 'string 2'], - }; - expect(type.toJson(value)).toBe('{"id":"string","tags":["string","string 2"]}'); -}); - -test('can serialize string with emojis', () => { - const type = t.Object(t.prop('👋', t.str)); - const value = { - '👋': '😇', - }; - expect(type.toJson(value)).toBe('{"👋":"😇"}'); -}); - -test('can serialize every type', () => { - const json = everyType.toJson(everyTypeValue); - const decoded = parse(json); - (decoded as any).undef = undefined; - expect(decoded).toStrictEqual(everyTypeValue); -}); - -test('can serialize with ref', () => { - const system = new TypeSystem(); - system.alias('User', t.Object(t.prop('id', t.str), t.prop('name', t.str))); - const type = t.Object(t.prop('user', t.Ref('User'))); - const value = { - user: { - id: '!id', - name: '!name', - }, - }; - const json1 = type.toJson(value); - const json2 = type.toJson(value, system); - expect(json1).toBe('{"user":null}'); - expect(json2).toBe('{"user":{"id":"!id","name":"!name"}}'); -}); diff --git a/src/json-type/type/__tests__/toString.spec.ts b/src/json-type/type/__tests__/toString.spec.ts deleted file mode 100644 index c7b58f60e7..0000000000 --- a/src/json-type/type/__tests__/toString.spec.ts +++ /dev/null @@ -1,46 +0,0 @@ -import {t} from '..'; - -test('can print a type', () => { - const type = t - .Object( - t.prop('id', t.str.options({validator: ['id', 'uuid']})).options({ - description: 'The id of the object', - }), - t.prop('tags', t.Array(t.str).options({title: 'Tags'})).options({title: 'Always use tags'}), - t.propOpt('optional', t.any), - t.prop('booleanProperty', t.bool), - t.prop('numberProperty', t.num.options({format: 'f64', gt: 3.14})), - t.prop('binaryProperty', t.bin.options({format: 'cbor'})), - t.prop('arrayProperty', t.Array(t.any)), - t.prop('objectProperty', t.Object(t.prop('id', t.str.options({ascii: true, min: 3, max: 128})))), - t.prop('unionProperty', t.Or(t.str, t.num, t.nil.options({description: ''}))), - t.propOpt('enumAsConst', t.Or(t.Const('a' as const), t.Const('b' as const), t.Const('c' as const))), - t.propOpt('refField', t.Ref('refId')), - t.propOpt('und', t.undef), - t.prop( - 'operation', - t.Object( - t.prop('type', t.Const('replace' as const).options({title: 'Always use replace'})), - t.prop('path', t.str), - t.prop('value', t.any), - ), - ), - t.prop( - 'binaryOperation', - t - .Binary( - t - .Tuple(t.Const(7 as const).options({description: '7 is the magic number'}), t.str, t.any) - .options({description: 'Should always have 3 elements'}), - ) - .options({format: 'cbor'}), - ), - t.prop('map', t.Map(t.num)), - t.prop('simpleFn1', t.fn), - t.prop('simpleFn2', t.fn$), - t.prop('function', t.Function(t.Object(t.prop('id', t.str)), t.Object(t.prop('name', t.str)))), - ) - .options({unknownFields: true}); - // console.log(type + ''); - expect(type + '').toMatchSnapshot(); -}); diff --git a/src/json-type/type/__tests__/toTypeScriptAst.spec.ts b/src/json-type/type/__tests__/toTypeScriptAst.spec.ts deleted file mode 100644 index 9a567e4e54..0000000000 --- a/src/json-type/type/__tests__/toTypeScriptAst.spec.ts +++ /dev/null @@ -1,354 +0,0 @@ -import {TypeSystem} from '../../system'; - -describe('any', () => { - test('can encode "any" type', () => { - const system = new TypeSystem(); - const type = system.t.any; - expect(type.toTypeScriptAst()).toEqual({ - node: 'AnyKeyword', - }); - }); -}); - -describe('const', () => { - test('can handle number const', () => { - const system = new TypeSystem(); - const type = system.t.Const<123>(123); - expect(type.toTypeScriptAst()).toEqual({ - node: 'NumericLiteral', - text: '123', - }); - }); - - test('can handle null', () => { - const system = new TypeSystem(); - const type = system.t.Const(null); - expect(type.toTypeScriptAst()).toEqual({ - node: 'NullKeyword', - }); - }); - - test('can handle "true"', () => { - const system = new TypeSystem(); - const type = system.t.Const(true); - expect(type.toTypeScriptAst()).toEqual({ - node: 'TrueKeyword', - }); - }); - - test('can handle "false"', () => { - const system = new TypeSystem(); - const type = system.t.Const(false); - expect(type.toTypeScriptAst()).toEqual({ - node: 'FalseKeyword', - }); - }); - - test('can handle string', () => { - const system = new TypeSystem(); - const type = system.t.Const<'asdf'>('asdf'); - expect(type.toTypeScriptAst()).toEqual({ - node: 'StringLiteral', - text: 'asdf', - }); - }); - - test('complex objects', () => { - const system = new TypeSystem(); - const type = system.t.Const({foo: 'bar'} as const); - expect(type.toTypeScriptAst()).toEqual({ - node: 'ObjectKeyword', - }); - }); -}); - -describe('bool', () => { - test('can emit boolean AST', () => { - const system = new TypeSystem(); - const type = system.t.bool; - expect(type.toTypeScriptAst()).toEqual({ - node: 'BooleanKeyword', - }); - }); -}); - -describe('num', () => { - test('can emit number AST', () => { - const system = new TypeSystem(); - const type = system.t.num; - expect(type.toTypeScriptAst()).toEqual({ - node: 'NumberKeyword', - }); - }); -}); - -describe('str', () => { - test('can emit string AST', () => { - const system = new TypeSystem(); - const type = system.t.str; - expect(type.toTypeScriptAst()).toEqual({ - node: 'StringKeyword', - }); - }); -}); - -describe('bin', () => { - test('can emit binary AST', () => { - const system = new TypeSystem(); - const type = system.t.bin; - expect(type.toTypeScriptAst()).toMatchInlineSnapshot(` - { - "id": { - "name": "Uint8Array", - "node": "Identifier", - }, - "node": "GenericTypeAnnotation", - } - `); - }); -}); - -describe('arr', () => { - test('can emit array of "any" AST', () => { - const system = new TypeSystem(); - const type = system.t.arr; - expect(type.toTypeScriptAst()).toMatchInlineSnapshot(` - { - "elementType": { - "node": "AnyKeyword", - }, - "node": "ArrayType", - } - `); - }); - - test('can emit array of "string" AST', () => { - const system = new TypeSystem(); - const type = system.t.Array(system.t.str); - expect(type.toTypeScriptAst()).toMatchInlineSnapshot(` - { - "elementType": { - "node": "StringKeyword", - }, - "node": "ArrayType", - } - `); - }); -}); - -describe('tup', () => { - test('can emit tuple AST', () => { - const system = new TypeSystem(); - const {t} = system; - const type = system.t.Tuple(t.str, t.num, t.bool); - expect(type.toTypeScriptAst()).toMatchInlineSnapshot(` - { - "elements": [ - { - "node": "StringKeyword", - }, - { - "node": "NumberKeyword", - }, - { - "node": "BooleanKeyword", - }, - ], - "node": "TupleType", - } - `); - }); -}); - -describe('obj', () => { - test('can emit tuple AST', () => { - const system = new TypeSystem(); - const {t} = system; - const type = system.t - .Object( - t.prop('id', t.str).options({ - title: 'title-x', - description: 'description-x', - }), - t.propOpt('id', t.num), - ) - .options({ - title: 'title', - description: 'description', - }); - expect(type.toTypeScriptAst()).toMatchInlineSnapshot(` - { - "comment": "# title - - description", - "members": [ - { - "comment": "# title-x - - description-x", - "name": "id", - "node": "PropertySignature", - "type": { - "node": "StringKeyword", - }, - }, - { - "name": "id", - "node": "PropertySignature", - "optional": true, - "type": { - "node": "NumberKeyword", - }, - }, - ], - "node": "TypeLiteral", - } - `); - }); -}); - -describe('map', () => { - test('can emit tuple AST', () => { - const system = new TypeSystem(); - const {t} = system; - const type = system.t.Map(t.num).options({ - title: 'title', - description: 'description', - }); - expect(type.toTypeScriptAst()).toMatchInlineSnapshot(` - { - "node": "TypeReference", - "typeArguments": [ - { - "node": "StringKeyword", - }, - { - "node": "NumberKeyword", - }, - ], - "typeName": "Record", - } - `); - }); -}); - -describe('ref', () => { - test('can emit reference AST', () => { - const system = new TypeSystem(); - const {t} = system; - const type = system.t.Ref('Foo'); - expect(type.toTypeScriptAst()).toMatchInlineSnapshot(` - { - "id": { - "name": "Foo", - "node": "Identifier", - }, - "node": "GenericTypeAnnotation", - } - `); - }); -}); - -describe('or', () => { - test('can emit reference AST', () => { - const system = new TypeSystem(); - const {t} = system; - const type = system.t.Or(t.str, t.num); - expect(type.toTypeScriptAst()).toMatchInlineSnapshot(` - { - "node": "UnionType", - "types": [ - { - "node": "StringKeyword", - }, - { - "node": "NumberKeyword", - }, - ], - } - `); - }); -}); - -describe('fn', () => { - test('can emit reference AST', () => { - const system = new TypeSystem(); - const {t} = system; - const type = system.t.Function(t.str, t.num); - expect(type.toTypeScriptAst()).toMatchInlineSnapshot(` - { - "node": "FunctionType", - "parameters": [ - { - "name": { - "name": "request", - "node": "Identifier", - }, - "node": "Parameter", - "type": { - "node": "StringKeyword", - }, - }, - ], - "type": { - "node": "TypeReference", - "typeArguments": [ - { - "node": "NumberKeyword", - }, - ], - "typeName": { - "name": "Promise", - "node": "Identifier", - }, - }, - } - `); - }); -}); - -describe('fn$', () => { - test('can emit reference AST', () => { - const system = new TypeSystem(); - const {t} = system; - const type = system.t.Function$(t.str, t.num); - expect(type.toTypeScriptAst()).toMatchInlineSnapshot(` - { - "node": "FunctionType", - "parameters": [ - { - "name": { - "name": "request$", - "node": "Identifier", - }, - "node": "Parameter", - "type": { - "node": "TypeReference", - "typeArguments": [ - { - "node": "StringKeyword", - }, - ], - "typeName": { - "name": "Observable", - "node": "Identifier", - }, - }, - }, - ], - "type": { - "node": "TypeReference", - "typeArguments": [ - { - "node": "NumberKeyword", - }, - ], - "typeName": { - "name": "Observable", - "node": "Identifier", - }, - }, - } - `); - }); -}); diff --git a/src/json-type/type/__tests__/validate.spec.ts b/src/json-type/type/__tests__/validate.spec.ts deleted file mode 100644 index 2febc79adf..0000000000 --- a/src/json-type/type/__tests__/validate.spec.ts +++ /dev/null @@ -1,22 +0,0 @@ -import {Type, t} from '..'; -import {validateTestSuite} from './validateTestSuite'; - -const validate = (type: Type, value: unknown) => { - type.validate(value); -}; - -const validateCodegen = (type: Type, value: unknown) => { - const validator = type.validator('string'); - const err = validator(value); - if (err) { - throw new Error(JSON.parse(err as string)[0]); - } -}; - -describe('.validate()', () => { - validateTestSuite(validate); -}); - -describe('.codegenValidator()', () => { - validateTestSuite(validateCodegen); -}); diff --git a/src/json-type/type/__tests__/validateTestSuite.ts b/src/json-type/type/__tests__/validateTestSuite.ts deleted file mode 100644 index eeadfc0f13..0000000000 --- a/src/json-type/type/__tests__/validateTestSuite.ts +++ /dev/null @@ -1,586 +0,0 @@ -import {Type, t} from '..'; -import {TypeSystem} from '../../system/TypeSystem'; - -export const validateTestSuite = (validate: (type: Type, value: unknown) => void) => { - const system = new TypeSystem(); - system.addCustomValidator({ - name: 'bang', - fn: (value: unknown) => { - if (value !== '!') throw new Error('NOT_BANG'); - }, - }); - - describe('any', () => { - test('validates any value', () => { - const type = t.any; - validate(type, 123); - validate(type, false); - validate(type, null); - validate(type, {}); - validate(type, [1, 2, 4]); - }); - }); - - describe('const', () => { - test('exact value of primitives', () => { - const num = t.Const(123 as const); - const str = t.Const('asdf' as const); - const truthy = t.Const(true as const); - const nil = t.Const(null); - validate(num, 123); - expect(() => validate(num, 1234)).toThrowErrorMatchingInlineSnapshot(`"CONST"`); - validate(str, 'asdf'); - expect(() => validate(str, 'asdf_')).toThrowErrorMatchingInlineSnapshot(`"CONST"`); - validate(truthy, true); - expect(() => validate(truthy, 1)).toThrowErrorMatchingInlineSnapshot(`"CONST"`); - expect(() => validate(truthy, false)).toThrowErrorMatchingInlineSnapshot(`"CONST"`); - validate(nil, null); - expect(() => validate(nil, undefined)).toThrowErrorMatchingInlineSnapshot(`"CONST"`); - expect(() => validate(nil, false)).toThrowErrorMatchingInlineSnapshot(`"CONST"`); - }); - - test('exact value of primitives', () => { - const arr = t.Const([1, 2, 3] as const); - const obj = t.Const({foo: 'bar'} as const); - validate(arr, [1, 2, 3]); - expect(() => validate(arr, [1, 2, 3, 4])).toThrowErrorMatchingInlineSnapshot(`"CONST"`); - validate(obj, {foo: 'bar'}); - expect(() => validate(obj, {foo: 'bar', baz: 'bar'})).toThrowErrorMatchingInlineSnapshot(`"CONST"`); - }); - }); - - describe('undefined', () => { - test('validates online "undefined" value', () => { - const type = t.undef; - validate(type, undefined); - expect(() => validate(type, false)).toThrowErrorMatchingInlineSnapshot(`"CONST"`); - expect(() => validate(type, null)).toThrowErrorMatchingInlineSnapshot(`"CONST"`); - expect(() => validate(type, 123)).toThrowErrorMatchingInlineSnapshot(`"CONST"`); - }); - }); - - describe('null', () => { - test('validates "null" value', () => { - const type = t.nil; - validate(type, null); - expect(() => validate(type, false)).toThrowErrorMatchingInlineSnapshot(`"CONST"`); - expect(() => validate(type, undefined)).toThrowErrorMatchingInlineSnapshot(`"CONST"`); - expect(() => validate(type, 123)).toThrowErrorMatchingInlineSnapshot(`"CONST"`); - }); - }); - - describe('boolean', () => { - test('validates "boolean" value', () => { - const type = t.bool; - validate(type, true); - validate(type, false); - expect(() => validate(type, null)).toThrowErrorMatchingInlineSnapshot(`"BOOL"`); - expect(() => validate(type, undefined)).toThrowErrorMatchingInlineSnapshot(`"BOOL"`); - expect(() => validate(type, 123)).toThrowErrorMatchingInlineSnapshot(`"BOOL"`); - }); - }); - - describe('number', () => { - test('validates simple "number" value', () => { - const type = t.num; - validate(type, 123); - validate(type, 456); - validate(type, 0); - validate(type, 3.14); - expect(() => validate(type, null)).toThrowErrorMatchingInlineSnapshot(`"NUM"`); - expect(() => validate(type, undefined)).toThrowErrorMatchingInlineSnapshot(`"NUM"`); - expect(() => validate(type, 'asdf')).toThrowErrorMatchingInlineSnapshot(`"NUM"`); - }); - - describe('validates formats', () => { - describe('i', () => { - test('cannot be float', () => { - const type = t.Number({format: 'i'}); - validate(type, 123); - validate(type, 456); - validate(type, 0); - expect(() => validate(type, 3.14)).toThrowErrorMatchingInlineSnapshot(`"INT"`); - }); - - test('cannot be Infinity', () => { - const type = t.Number({format: 'i'}); - expect(() => validate(type, Infinity)).toThrowErrorMatchingInlineSnapshot(`"INT"`); - expect(() => validate(type, -Infinity)).toThrowErrorMatchingInlineSnapshot(`"INT"`); - }); - }); - - describe('u', () => { - test('cannot be float', () => { - const type = t.Number({format: 'u'}); - validate(type, 123); - validate(type, 456); - validate(type, 0); - expect(() => validate(type, 3.14)).toThrowErrorMatchingInlineSnapshot(`"INT"`); - }); - - test('cannot be Infinity', () => { - const type = t.Number({format: 'u'}); - expect(() => validate(type, Infinity)).toThrowErrorMatchingInlineSnapshot(`"INT"`); - expect(() => validate(type, -Infinity)).toThrowErrorMatchingInlineSnapshot(`"INT"`); - }); - - test('cannot be negative', () => { - const type = t.Number({format: 'u'}); - expect(() => validate(type, -1)).toThrowErrorMatchingInlineSnapshot(`"UINT"`); - }); - }); - - describe('f', () => { - test('cannot be Infinity', () => { - const type = t.Number({format: 'f'}); - expect(() => validate(type, Infinity)).toThrowErrorMatchingInlineSnapshot(`"NUM"`); - expect(() => validate(type, -Infinity)).toThrowErrorMatchingInlineSnapshot(`"NUM"`); - }); - }); - - describe('i8', () => { - test('should be within bounds', () => { - const type = t.Number({format: 'i8'}); - validate(type, 123); - expect(() => validate(type, Infinity)).toThrowErrorMatchingInlineSnapshot(`"INT"`); - expect(() => validate(type, -Infinity)).toThrowErrorMatchingInlineSnapshot(`"INT"`); - expect(() => validate(type, 128)).toThrowErrorMatchingInlineSnapshot(`"INT"`); - expect(() => validate(type, -129)).toThrowErrorMatchingInlineSnapshot(`"INT"`); - }); - - test('cannot be float', () => { - const type = t.Number({format: 'i8'}); - validate(type, 123); - expect(() => validate(type, 1.1)).toThrowErrorMatchingInlineSnapshot(`"INT"`); - }); - }); - - describe('i16', () => { - test('should be within bounds', () => { - const type = t.Number({format: 'i16'}); - validate(type, 123); - expect(() => validate(type, Infinity)).toThrowErrorMatchingInlineSnapshot(`"INT"`); - expect(() => validate(type, -Infinity)).toThrowErrorMatchingInlineSnapshot(`"INT"`); - expect(() => validate(type, 33333)).toThrowErrorMatchingInlineSnapshot(`"INT"`); - expect(() => validate(type, -33333)).toThrowErrorMatchingInlineSnapshot(`"INT"`); - }); - - test('cannot be float', () => { - const type = t.Number({format: 'i16'}); - validate(type, 123); - expect(() => validate(type, 1.1)).toThrowErrorMatchingInlineSnapshot(`"INT"`); - }); - }); - - describe('i32', () => { - test('should be within bounds', () => { - const type = t.Number({format: 'i32'}); - validate(type, 0xffff); - expect(() => validate(type, Infinity)).toThrowErrorMatchingInlineSnapshot(`"INT"`); - expect(() => validate(type, -Infinity)).toThrowErrorMatchingInlineSnapshot(`"INT"`); - expect(() => validate(type, 0xffffffaa)).toThrowErrorMatchingInlineSnapshot(`"INT"`); - expect(() => validate(type, -0xffffffab)).toThrowErrorMatchingInlineSnapshot(`"INT"`); - }); - - test('cannot be float', () => { - const type = t.Number({format: 'i32'}); - validate(type, 123); - expect(() => validate(type, 1.1)).toThrowErrorMatchingInlineSnapshot(`"INT"`); - }); - }); - - describe('i64', () => { - test('should be within bounds', () => { - const type = t.Number({format: 'i64'}); - validate(type, 0xffffdfdf); - expect(() => validate(type, Infinity)).toThrowErrorMatchingInlineSnapshot(`"INT"`); - expect(() => validate(type, -Infinity)).toThrowErrorMatchingInlineSnapshot(`"INT"`); - }); - - test('cannot be float', () => { - const type = t.Number({format: 'i64'}); - expect(() => validate(type, 1.1)).toThrowErrorMatchingInlineSnapshot(`"INT"`); - }); - }); - - describe('u8', () => { - test('should be within bounds', () => { - const type = t.Number({format: 'u8'}); - validate(type, 255); - expect(() => validate(type, Infinity)).toThrowErrorMatchingInlineSnapshot(`"INT"`); - expect(() => validate(type, -Infinity)).toThrowErrorMatchingInlineSnapshot(`"INT"`); - expect(() => validate(type, 256)).toThrowErrorMatchingInlineSnapshot(`"UINT"`); - expect(() => validate(type, -1)).toThrowErrorMatchingInlineSnapshot(`"UINT"`); - }); - - test('cannot be float', () => { - const type = t.Number({format: 'u8'}); - expect(() => validate(type, 1.1)).toThrowErrorMatchingInlineSnapshot(`"INT"`); - }); - }); - - describe('u16', () => { - test('should be within bounds', () => { - const type = t.Number({format: 'u16'}); - validate(type, 0xffff); - expect(() => validate(type, Infinity)).toThrowErrorMatchingInlineSnapshot(`"INT"`); - expect(() => validate(type, -Infinity)).toThrowErrorMatchingInlineSnapshot(`"INT"`); - expect(() => validate(type, 0xffff + 1)).toThrowErrorMatchingInlineSnapshot(`"UINT"`); - expect(() => validate(type, -1)).toThrowErrorMatchingInlineSnapshot(`"UINT"`); - }); - - test('cannot be float', () => { - const type = t.Number({format: 'u16'}); - expect(() => validate(type, 1.1)).toThrowErrorMatchingInlineSnapshot(`"INT"`); - }); - }); - - describe('u32', () => { - test('should be within bounds', () => { - const type = t.Number({format: 'u32'}); - validate(type, 0xffffffff); - expect(() => validate(type, Infinity)).toThrowErrorMatchingInlineSnapshot(`"INT"`); - expect(() => validate(type, -Infinity)).toThrowErrorMatchingInlineSnapshot(`"INT"`); - expect(() => validate(type, 0xffffffff + 1)).toThrowErrorMatchingInlineSnapshot(`"UINT"`); - expect(() => validate(type, -1)).toThrowErrorMatchingInlineSnapshot(`"UINT"`); - }); - - test('cannot be float', () => { - const type = t.Number({format: 'u32'}); - expect(() => validate(type, 1.1)).toThrowErrorMatchingInlineSnapshot(`"INT"`); - }); - }); - - describe('u64', () => { - test('should be within bounds', () => { - const type = t.Number({format: 'u64'}); - validate(type, 0xffffffffff); - expect(() => validate(type, Infinity)).toThrowErrorMatchingInlineSnapshot(`"INT"`); - expect(() => validate(type, -Infinity)).toThrowErrorMatchingInlineSnapshot(`"INT"`); - expect(() => validate(type, -1)).toThrowErrorMatchingInlineSnapshot(`"UINT"`); - }); - - test('cannot be float', () => { - const type = t.Number({format: 'u64'}); - expect(() => validate(type, 1.1)).toThrowErrorMatchingInlineSnapshot(`"INT"`); - }); - }); - - describe('f32', () => { - test('should be within bounds', () => { - const type = t.Number({format: 'f32'}); - validate(type, 1.123); - expect(() => validate(type, Infinity)).toThrowErrorMatchingInlineSnapshot(`"NUM"`); - expect(() => validate(type, -Infinity)).toThrowErrorMatchingInlineSnapshot(`"NUM"`); - }); - }); - - describe('f64', () => { - test('should be within bounds', () => { - const type = t.Number({format: 'f64'}); - validate(type, 1.123); - expect(() => validate(type, Infinity)).toThrowErrorMatchingInlineSnapshot(`"NUM"`); - expect(() => validate(type, -Infinity)).toThrowErrorMatchingInlineSnapshot(`"NUM"`); - }); - }); - }); - - describe('bounds', () => { - test('gt', () => { - const type = t.Number({gt: 10}); - validate(type, 11); - expect(() => validate(type, 10)).toThrowErrorMatchingInlineSnapshot(`"GT"`); - }); - - test('lt', () => { - const type = t.Number({lt: 10}); - validate(type, 9); - expect(() => validate(type, 10)).toThrowErrorMatchingInlineSnapshot(`"LT"`); - }); - - test('gte', () => { - const type = t.Number({gte: 10}); - validate(type, 10); - expect(() => validate(type, 9)).toThrowErrorMatchingInlineSnapshot(`"GTE"`); - }); - - test('lte', () => { - const type = t.Number({lte: 10}); - validate(type, 10); - expect(() => validate(type, 11)).toThrowErrorMatchingInlineSnapshot(`"LTE"`); - }); - - test('gt and lt', () => { - const type = t.Number({gt: 10, lt: 20}); - validate(type, 11); - expect(() => validate(type, 10)).toThrowErrorMatchingInlineSnapshot(`"GT"`); - expect(() => validate(type, 20)).toThrowErrorMatchingInlineSnapshot(`"LT"`); - }); - }); - }); - - describe('string', () => { - test('should be a string', () => { - const type = t.String(); - validate(type, 'foo'); - expect(() => validate(type, 1)).toThrowErrorMatchingInlineSnapshot(`"STR"`); - }); - - describe('size bounds', () => { - test('respects min and max', () => { - const type = t.String({min: 2, max: 4}); - validate(type, 'foo'); - expect(() => validate(type, 'f')).toThrowErrorMatchingInlineSnapshot(`"STR_LEN"`); - expect(() => validate(type, 'foooo')).toThrowErrorMatchingInlineSnapshot(`"STR_LEN"`); - }); - - test('respects min', () => { - const type = t.String({min: 2}); - validate(type, 'foo'); - expect(() => validate(type, 'f')).toThrowErrorMatchingInlineSnapshot(`"STR_LEN"`); - }); - }); - - describe('custom validators', () => { - test('throws if custom validator fails', () => { - const type = system.t.str.options({validator: ['bang']}); - validate(type, '!'); - expect(() => validate(type, 'foo')).toThrowErrorMatchingInlineSnapshot(`"VALIDATION"`); - }); - }); - }); - - describe('binary', () => { - test('accepts Uint8Array and Buffer', () => { - const type = t.bin; - validate(type, new Uint8Array()); - validate(type, Buffer.from('')); - }); - - test('throws on Uint16Array', () => { - const type = t.bin; - expect(() => validate(type, new Uint16Array())).toThrowErrorMatchingInlineSnapshot(`"BIN"`); - }); - }); - - describe('array', () => { - test('accepts array of "any"', () => { - const type = t.arr; - validate(type, []); - }); - - test('validates contained type', () => { - const type = t.Array(t.str); - validate(type, []); - validate(type, ['']); - validate(type, ['asdf']); - expect(() => validate(type, [1])).toThrowErrorMatchingInlineSnapshot(`"STR"`); - }); - - describe('size bounds', () => { - test('respects min and max', () => { - const type = t.arr.options({min: 2, max: 4}); - validate(type, [1, 2]); - expect(() => validate(type, [1])).toThrowErrorMatchingInlineSnapshot(`"ARR_LEN"`); - expect(() => validate(type, [1, 2, 3, 4, 5])).toThrowErrorMatchingInlineSnapshot(`"ARR_LEN"`); - }); - - test('respects min', () => { - const type = t.arr.options({min: 2}); - validate(type, [1, 2]); - expect(() => validate(type, [1])).toThrowErrorMatchingInlineSnapshot(`"ARR_LEN"`); - }); - }); - }); - - describe('tuple', () => { - test('accepts only correct tuples', () => { - const type = t.Tuple(t.str, t.num); - validate(type, ['asdf', 123]); - expect(() => validate(type, ['asdf'])).toThrowErrorMatchingInlineSnapshot(`"TUP"`); - expect(() => validate(type, ['asdf', '123'])).toThrowErrorMatchingInlineSnapshot(`"NUM"`); - }); - }); - - describe('object', () => { - test('accepts object of "any"', () => { - const type = t.obj; - validate(type, {}); - validate(type, {foo: 'bar'}); - }); - - test('checks for required fields', () => { - const type = t.Object(t.prop('id', t.str), t.propOpt('foo', t.str)); - validate(type, {id: 'asdf'}); - validate(type, {id: 'asdf', foo: 'bar'}); - expect(() => validate(type, {foo: 'bar'})).toThrowErrorMatchingInlineSnapshot(`"STR"`); - }); - }); - - describe('map', () => { - test('accepts empty object as input', () => { - const type = t.map; - validate(type, {}); - }); - - test('does not accept empty array as input', () => { - const type = t.map; - expect(() => validate(type, [])).toThrow(); - }); - - test('validates "any" map', () => { - const type = t.map; - validate(type, { - a: 'str', - b: 123, - c: true, - }); - }); - - test('validates contained type', () => { - const type = t.Map(t.str); - validate(type, {}); - validate(type, {a: ''}); - validate(type, {b: 'asdf'}); - expect(() => validate(type, {c: 123})).toThrowErrorMatchingInlineSnapshot(`"STR"`); - expect(() => validate(type, {c: false})).toThrowErrorMatchingInlineSnapshot(`"STR"`); - expect(() => validate(type, [])).toThrowErrorMatchingInlineSnapshot(`"MAP"`); - }); - }); - - describe('ref', () => { - test('validates after recursively resolving', () => { - const t = system.t; - system.alias('MyNum1', t.num); - system.alias('MyNum2', t.Ref('MyNum1')); - const ref = t.Ref('MyNum2'); - validate(ref, 1); - expect(() => validate(ref, '1')).toThrowErrorMatchingInlineSnapshot(`"REF"`); - }); - }); - - describe('or', () => { - test('validates "one-of"', () => { - const or = t.Or(t.str, t.num).options({ - discriminator: [ - 'if', - ['==', 'string', ['type', ['get', '']]], - 0, - ['if', ['==', 'number', ['type', ['get', '']]], 1, -1], - ], - }); - validate(or, 1); - validate(or, 'a'); - expect(() => validate(or, null)).toThrowErrorMatchingInlineSnapshot(`"OR"`); - }); - }); - - describe('custom validators', () => { - const system = new TypeSystem(); - const t = system.t; - system.addCustomValidator({ - name: 'any-only-1', - fn: (value) => value !== 1, - }); - system.addCustomValidator({ - name: 'const-only-1', - fn: (value) => { - if (value !== 1) throw new Error('not 1'); - }, - }); - system.addCustomValidator({ - name: 'only-false', - fn: (value) => { - if (value !== false) throw new Error('not 1'); - }, - }); - system.addCustomValidator({ - name: 'only-2', - fn: (value) => { - if (value !== 2) throw new Error('not 1'); - }, - }); - system.addCustomValidator({ - name: 'only-abc', - fn: (value) => { - if (value !== 'abc') throw new Error('not 1'); - }, - }); - system.addCustomValidator({ - name: 'len-3', - fn: (value) => { - if ((value as any).length !== 3) throw new Error('not 1'); - }, - }); - system.addCustomValidator({ - name: 'noop', - fn: (value) => {}, - }); - system.addCustomValidator({ - name: 'first-element-1', - fn: (value) => { - if ((value as any)[0] !== 1) throw new Error('not 1'); - }, - }); - system.addCustomValidator({ - name: 'foo.is.bar', - fn: (value) => { - if ((value as any).foo !== 'bar') throw new Error('not 1'); - }, - }); - - test('any', () => { - const type = t.any.options({validator: ['any-only-1']}); - type.validate(1); - expect(() => type.validate(2)).toThrow(); - }); - - test('const', () => { - const type = t.Const<2>(2).options({validator: 'const-only-1'}); - expect(() => type.validate(1)).toThrowErrorMatchingInlineSnapshot(`"CONST"`); - expect(() => type.validate(2)).toThrowErrorMatchingInlineSnapshot(`"VALIDATION"`); - }); - - test('bool', () => { - const type = t.bool.options({validator: 'only-false'}); - type.validate(false); - expect(() => type.validate(1)).toThrowErrorMatchingInlineSnapshot(`"BOOL"`); - expect(() => type.validate(true)).toThrowErrorMatchingInlineSnapshot(`"VALIDATION"`); - }); - - test('num', () => { - const type = t.num.options({validator: ['only-2']}); - type.validate(2); - expect(() => type.validate(false)).toThrowErrorMatchingInlineSnapshot(`"NUM"`); - expect(() => type.validate(1)).toThrowErrorMatchingInlineSnapshot(`"VALIDATION"`); - }); - - test('str', () => { - const type = t.str.options({validator: ['only-abc']}); - type.validate('abc'); - expect(() => type.validate(123)).toThrowErrorMatchingInlineSnapshot(`"STR"`); - expect(() => type.validate('xyz')).toThrowErrorMatchingInlineSnapshot(`"VALIDATION"`); - }); - - test('arr', () => { - const type = t.arr.options({validator: ['len-3']}); - type.validate([1, 2, 3]); - expect(() => type.validate(123)).toThrowErrorMatchingInlineSnapshot(`"ARR"`); - expect(() => type.validate([1, 2, 3, 4])).toThrowErrorMatchingInlineSnapshot(`"VALIDATION"`); - }); - - test('tup', () => { - const type = t.Tuple(t.num).options({validator: ['noop', 'first-element-1']}); - type.validate([1]); - expect(() => type.validate(123)).toThrowErrorMatchingInlineSnapshot(`"TUP"`); - expect(() => type.validate([2])).toThrowErrorMatchingInlineSnapshot(`"VALIDATION"`); - }); - - test('obj', () => { - const type = t.Object(t.prop('foo', t.str)).options({validator: ['noop', 'foo.is.bar', 'noop']}); - type.validate({foo: 'bar'}); - expect(() => type.validate([])).toThrowErrorMatchingInlineSnapshot(`"OBJ"`); - expect(() => type.validate({foo: 'baz'})).toThrowErrorMatchingInlineSnapshot(`"VALIDATION"`); - }); - }); -}; diff --git a/src/json-type/type/__tests__/with-system.spec.ts b/src/json-type/type/__tests__/with-system.spec.ts deleted file mode 100644 index cf35ba4b38..0000000000 --- a/src/json-type/type/__tests__/with-system.spec.ts +++ /dev/null @@ -1,10 +0,0 @@ -import {TypeSystem} from '../../system/TypeSystem'; - -test('can use builder with type system wired in', () => { - const system = new TypeSystem(); - const t = system.t; - system.alias('User', t.Object(t.prop('id', t.str))); - const ref = t.Ref('User'); - const json = ref.toJson({id: '123'}); - expect(json).toBe('{"id":"123"}'); -}); diff --git a/src/json-type/type/classes.ts b/src/json-type/type/classes.ts deleted file mode 100644 index a933b6586e..0000000000 --- a/src/json-type/type/classes.ts +++ /dev/null @@ -1,34 +0,0 @@ -import {AbstractType} from './classes/AbstractType'; -import {AnyType} from './classes/AnyType'; -import {ConstType} from './classes/ConstType'; -import {BooleanType} from './classes/BooleanType'; -import {NumberType} from './classes/NumberType'; -import {StringType} from './classes/StringType'; -import {BinaryType} from './classes/BinaryType'; -import {ArrayType} from './classes/ArrayType'; -import {TupleType} from './classes/TupleType'; -import {ObjectType, ObjectFieldType, ObjectOptionalFieldType} from './classes/ObjectType'; -import {MapType} from './classes/MapType'; -import {RefType} from './classes/RefType'; -import {OrType} from './classes/OrType'; -import {FunctionType, FunctionStreamingType} from './classes/FunctionType'; - -export { - AbstractType, - AnyType, - ConstType, - BooleanType, - NumberType, - StringType, - BinaryType, - ArrayType, - TupleType, - ObjectFieldType, - ObjectOptionalFieldType, - ObjectType, - MapType, - RefType, - OrType, - FunctionType, - FunctionStreamingType, -}; diff --git a/src/json-type/type/classes/AbstractType.ts b/src/json-type/type/classes/AbstractType.ts deleted file mode 100644 index 38dd4ce866..0000000000 --- a/src/json-type/type/classes/AbstractType.ts +++ /dev/null @@ -1,318 +0,0 @@ -import * as schema from '../../schema'; -import {RandomJson} from '../../../json-random'; -import {Printable} from 'tree-dump/lib/types'; -import {ValidatorCodegenContext, ValidatorCodegenContextOptions} from '../../codegen/validator/ValidatorCodegenContext'; -import {JsonTypeValidator, ValidationPath} from '../../codegen/validator/types'; -import { - JsonTextEncoderCodegenContext, - JsonTextEncoderCodegenContextOptions, - JsonEncoderFn, -} from '../../codegen/json/JsonTextEncoderCodegenContext'; -import {CompiledBinaryEncoder} from '../../codegen/types'; -import { - CborEncoderCodegenContext, - CborEncoderCodegenContextOptions, -} from '../../codegen/binary/CborEncoderCodegenContext'; -import { - JsonEncoderCodegenContext, - JsonEncoderCodegenContextOptions, -} from '../../codegen/binary/JsonEncoderCodegenContext'; -import {CborEncoder} from '@jsonjoy.com/json-pack/lib/cbor/CborEncoder'; -import {JsExpression} from '@jsonjoy.com/util/lib/codegen/util/JsExpression'; -import { - MessagePackEncoderCodegenContext, - MessagePackEncoderCodegenContextOptions, -} from '../../codegen/binary/MessagePackEncoderCodegenContext'; -import {MsgPackEncoder} from '@jsonjoy.com/json-pack/lib/msgpack'; -import {lazy} from '@jsonjoy.com/util/lib/lazyFunction'; -import {EncodingFormat} from '@jsonjoy.com/json-pack/lib/constants'; -import {JsonEncoder} from '@jsonjoy.com/json-pack/lib/json/JsonEncoder'; -import {Writer} from '@jsonjoy.com/util/lib/buffers/Writer'; -import { - CapacityEstimatorCodegenContext, - CapacityEstimatorCodegenContextOptions, - CompiledCapacityEstimator, -} from '../../codegen/capacity/CapacityEstimatorCodegenContext'; -import {JsonValueCodec} from '@jsonjoy.com/json-pack/lib/codecs/types'; -import type * as jsonSchema from '../../../json-schema'; -import type {BaseType} from '../types'; -import type {TypeSystem} from '../../system/TypeSystem'; -import type {json_string} from '@jsonjoy.com/util/lib/json-brand'; -import type * as ts from '../../typescript/types'; -import type {TypeExportContext} from '../../system/TypeExportContext'; -import type {Validators} from './types'; -import type * as jtd from '../../jtd/types'; - -export abstract class AbstractType implements BaseType, Printable { - /** Default type system to use, if any. */ - public system?: TypeSystem; - - protected validators: Validators = {}; - protected encoders = new Map(); - - /** @todo Retype this to `Schema`. */ - protected abstract schema: S; - - public getSystem(): TypeSystem { - const system = this.system; - if (!system) throw new Error('NO_SYSTEM'); - return system; - } - - public getTypeName(): S['kind'] { - return this.schema.kind; - } - - /** - * @todo Add ability to export the whole schema, including aliases. - */ - public getSchema(): S { - return this.schema; - } - - public getValidatorNames(): string[] { - const {validator} = this.schema as schema.WithValidator; - if (!validator) return []; - return Array.isArray(validator) ? validator : [validator]; - } - - public toJsonSchema(ctx?: TypeExportContext): jsonSchema.JsonSchemaNode { - const schema = this.getSchema(); - const jsonSchema = {}; - if (schema.title) jsonSchema.title = schema.title; - if (schema.description) jsonSchema.description = schema.description; - if (schema.examples) jsonSchema.examples = schema.examples.map((example: schema.TExample) => example.value); - return jsonSchema; - } - - public options(options: schema.Optional): this { - Object.assign(this.schema, options); - return this; - } - - public getOptions(): schema.Optional { - const {kind, ...options} = this.schema; - return options as any; - } - - /** Validates own schema, throws on errors. */ - public abstract validateSchema(): void; - - public validate(value: unknown): void { - const validator = this.validator('string'); - const err = validator(value); - if (err) throw new Error(JSON.parse(err as string)[0]); - } - - public compileValidator(options: Partial>): JsonTypeValidator { - const ctx = new ValidatorCodegenContext({ - system: this.system, - errors: 'object', - ...options, - type: this as any, - }); - this.codegenValidator(ctx, [], ctx.codegen.options.args[0]); - return ctx.compile(); - } - - private __compileValidator(kind: keyof Validators): JsonTypeValidator { - return (this.validators[kind] = this.compileValidator({ - errors: kind, - system: this.system, - skipObjectExtraFieldsCheck: kind === 'boolean', - unsafeMode: kind === 'boolean', - })); - } - - public validator(kind: keyof Validators): JsonTypeValidator { - return this.validators[kind] || lazy(() => this.__compileValidator(kind)); - } - - protected compileJsonTextEncoder(options: Omit): JsonEncoderFn { - const ctx = new JsonTextEncoderCodegenContext({ - ...options, - system: this.system, - type: this as any, - }); - const r = ctx.codegen.options.args[0]; - const value = new JsExpression(() => r); - this.codegenJsonTextEncoder(ctx, value); - return ctx.compile(); - } - - public codegenJsonTextEncoder(ctx: JsonTextEncoderCodegenContext, value: JsExpression): void { - throw new Error(`${this.toStringName()}.codegenJsonTextEncoder() not implemented`); - } - - private __jsonEncoder: JsonEncoderFn | undefined; - public jsonTextEncoder(): JsonEncoderFn { - return ( - this.__jsonEncoder || (this.__jsonEncoder = lazy(() => (this.__jsonEncoder = this.compileJsonTextEncoder({})))) - ); - } - - public compileEncoder(format: EncodingFormat, name?: string): CompiledBinaryEncoder { - switch (format) { - case EncodingFormat.Cbor: { - const encoder = this.compileCborEncoder({name}); - this.encoders.set(EncodingFormat.Cbor, encoder); - return encoder; - } - case EncodingFormat.MsgPack: { - const encoder = this.compileMessagePackEncoder({name}); - this.encoders.set(EncodingFormat.MsgPack, encoder); - return encoder; - } - case EncodingFormat.Json: { - const encoder = this.compileJsonEncoder({name}); - this.encoders.set(EncodingFormat.Json, encoder); - return encoder; - } - default: - throw new Error(`Unsupported encoding format: ${format}`); - } - } - - public encoder(kind: EncodingFormat): CompiledBinaryEncoder { - const encoders = this.encoders; - const cachedEncoder = encoders.get(kind); - if (cachedEncoder) return cachedEncoder; - const temporaryWrappedEncoder = lazy(() => this.compileEncoder(kind)); - encoders.set(kind, temporaryWrappedEncoder); - return temporaryWrappedEncoder; - } - - public encode(codec: JsonValueCodec, value: unknown): Uint8Array { - const encoder = this.encoder(codec.format); - const writer = codec.encoder.writer; - writer.reset(); - encoder(value, codec.encoder); - return writer.flush(); - } - - public codegenValidator(ctx: ValidatorCodegenContext, path: ValidationPath, r: string): void { - throw new Error(`${this.toStringName()}.codegenValidator() not implemented`); - } - - public compileCborEncoder( - options: Omit, - ): CompiledBinaryEncoder { - const ctx = new CborEncoderCodegenContext({ - system: this.system, - encoder: new CborEncoder(), - ...options, - type: this as any, - }); - const r = ctx.codegen.options.args[0]; - const value = new JsExpression(() => r); - this.codegenCborEncoder(ctx, value); - return ctx.compile(); - } - - public codegenCborEncoder(ctx: CborEncoderCodegenContext, value: JsExpression): void { - throw new Error(`${this.toStringName()}.codegenCborEncoder() not implemented`); - } - - public compileMessagePackEncoder( - options: Omit, - ): CompiledBinaryEncoder { - const ctx = new MessagePackEncoderCodegenContext({ - system: this.system, - encoder: new MsgPackEncoder(), - ...options, - type: this as any, - }); - const r = ctx.codegen.options.args[0]; - const value = new JsExpression(() => r); - this.codegenMessagePackEncoder(ctx, value); - return ctx.compile(); - } - - public codegenMessagePackEncoder(ctx: MessagePackEncoderCodegenContext, value: JsExpression): void { - throw new Error(`${this.toStringName()}.codegenMessagePackEncoder() not implemented`); - } - - public compileJsonEncoder( - options: Omit, - ): CompiledBinaryEncoder { - const writer = new Writer(); - const ctx = new JsonEncoderCodegenContext({ - system: this.system, - encoder: new JsonEncoder(writer), - ...options, - type: this as any, - }); - const r = ctx.codegen.options.args[0]; - const value = new JsExpression(() => r); - this.codegenJsonEncoder(ctx, value); - return ctx.compile(); - } - - public codegenJsonEncoder(ctx: JsonEncoderCodegenContext, value: JsExpression): void { - throw new Error(`${this.toStringName()}.codegenJsonEncoder() not implemented`); - } - - public compileCapacityEstimator( - options: Omit, - ): CompiledCapacityEstimator { - const ctx = new CapacityEstimatorCodegenContext({ - system: this.system, - ...options, - type: this as any, - }); - const r = ctx.codegen.options.args[0]; - const value = new JsExpression(() => r); - this.codegenCapacityEstimator(ctx, value); - return ctx.compile(); - } - - public codegenCapacityEstimator(ctx: CapacityEstimatorCodegenContext, value: JsExpression): void { - throw new Error(`${this.toStringName()}.codegenCapacityEstimator() not implemented`); - } - - private __capacityEstimator: CompiledCapacityEstimator | undefined; - public capacityEstimator(): CompiledCapacityEstimator { - return ( - this.__capacityEstimator || - (this.__capacityEstimator = lazy(() => (this.__capacityEstimator = this.compileCapacityEstimator({})))) - ); - } - - public random(): unknown { - return RandomJson.generate({nodeCount: 5}); - } - - public toTypeScriptAst(): ts.TsNode { - const node: ts.TsUnknownKeyword = {node: 'UnknownKeyword'}; - return node; - } - - public toJson(value: unknown, system: TypeSystem | undefined = this.system): json_string { - return JSON.stringify(value) as json_string>; - } - - protected toStringTitle(): string { - return this.getTypeName(); - } - - protected toStringOptions(): string { - const options = this.getOptions() as schema.Display; - const title = options.title || options.intro || options.description; - if (!title) return ''; - return JSON.stringify(title); - } - - protected toStringName(): string { - return 'AbstractType'; - } - - public toString(tab: string = ''): string { - const options = this.toStringOptions(); - return this.toStringTitle() + (options ? ` ${options}` : ''); - } - - public toJtdForm(): jtd.JtdForm { - const form: jtd.JtdEmptyForm = {nullable: false}; - return form; - } -} diff --git a/src/json-type/type/classes/AnyType.ts b/src/json-type/type/classes/AnyType.ts deleted file mode 100644 index 77a4e69133..0000000000 --- a/src/json-type/type/classes/AnyType.ts +++ /dev/null @@ -1,120 +0,0 @@ -import * as schema from '../../schema'; -import {RandomJson} from '../../../json-random'; -import {validateTType} from '../../schema/validate'; -import {ValidatorCodegenContext} from '../../codegen/validator/ValidatorCodegenContext'; -import {ValidationPath} from '../../codegen/validator/types'; -import {JsonTextEncoderCodegenContext} from '../../codegen/json/JsonTextEncoderCodegenContext'; -import {CborEncoderCodegenContext} from '../../codegen/binary/CborEncoderCodegenContext'; -import {JsonEncoderCodegenContext} from '../../codegen/binary/JsonEncoderCodegenContext'; -import {BinaryEncoderCodegenContext} from '../../codegen/binary/BinaryEncoderCodegenContext'; -import {JsExpression} from '@jsonjoy.com/util/lib/codegen/util/JsExpression'; -import {MessagePackEncoderCodegenContext} from '../../codegen/binary/MessagePackEncoderCodegenContext'; -import {EncodingFormat} from '@jsonjoy.com/json-pack/lib/constants'; -import {BinaryJsonEncoder} from '@jsonjoy.com/json-pack/lib/types'; -import {CapacityEstimatorCodegenContext} from '../../codegen/capacity/CapacityEstimatorCodegenContext'; -import {AbstractType} from './AbstractType'; -import type * as jsonSchema from '../../../json-schema'; -import type * as ts from '../../typescript/types'; -import type {TypeExportContext} from '../../system/TypeExportContext'; -import type * as jtd from '../../jtd/types'; - -export class AnyType extends AbstractType { - constructor(protected schema: schema.AnySchema) { - super(); - } - - public toJsonSchema(ctx?: TypeExportContext): jsonSchema.JsonSchemaAny { - return { - type: ['string', 'number', 'boolean', 'null', 'array', 'object'], - ...super.toJsonSchema(ctx), - }; - } - - public validateSchema(): void { - validateTType(this.getSchema(), 'any'); - } - - public codegenValidator(ctx: ValidatorCodegenContext, path: ValidationPath, r: string): void { - ctx.emitCustomValidators(this, path, r); - } - - public codegenJsonTextEncoder(ctx: JsonTextEncoderCodegenContext, value: JsExpression): void { - ctx.js(/* js */ `s += stringify(${value.use()});`); - } - - private codegenBinaryEncoder(ctx: BinaryEncoderCodegenContext, value: JsExpression): void { - ctx.codegen.link('Value'); - const r = ctx.codegen.var(value.use()); - ctx.codegen.if( - `${r} instanceof Value`, - () => { - ctx.codegen.if( - `${r}.type`, - () => { - const type = - ctx instanceof CborEncoderCodegenContext - ? EncodingFormat.Cbor - : ctx instanceof MessagePackEncoderCodegenContext - ? EncodingFormat.MsgPack - : EncodingFormat.Json; - ctx.js(`${r}.type.encoder(${type})(${r}.data, encoder);`); - }, - () => { - ctx.js(/* js */ `encoder.writeAny(${r}.data);`); - }, - ); - }, - () => { - ctx.js(/* js */ `encoder.writeAny(${r});`); - }, - ); - } - - public codegenCborEncoder(ctx: CborEncoderCodegenContext, value: JsExpression): void { - this.codegenBinaryEncoder(ctx, value); - } - - public codegenMessagePackEncoder(ctx: MessagePackEncoderCodegenContext, value: JsExpression): void { - this.codegenBinaryEncoder(ctx, value); - } - - public codegenJsonEncoder(ctx: JsonEncoderCodegenContext, value: JsExpression): void { - this.codegenBinaryEncoder(ctx, value); - } - - public codegenCapacityEstimator(ctx: CapacityEstimatorCodegenContext, value: JsExpression): void { - const codegen = ctx.codegen; - codegen.link('Value'); - const r = codegen.var(value.use()); - codegen.if( - `${r} instanceof Value`, - () => { - codegen.if( - `${r}.type`, - () => { - ctx.codegen.js(`size += ${r}.type.capacityEstimator()(${r}.data);`); - }, - () => { - ctx.codegen.js(`size += maxEncodingCapacity(${r}.data);`); - }, - ); - }, - () => { - ctx.codegen.js(`size += maxEncodingCapacity(${r});`); - }, - ); - } - - public random(): unknown { - return RandomJson.generate({nodeCount: 5}); - } - - public toTypeScriptAst(): ts.TsType { - return {node: 'AnyKeyword'}; - } - - public toJtdForm(): jtd.JtdEmptyForm { - const form: jtd.JtdEmptyForm = {nullable: true}; - return form; - } -} diff --git a/src/json-type/type/classes/ArrayType.ts b/src/json-type/type/classes/ArrayType.ts deleted file mode 100644 index 76b25ae6f4..0000000000 --- a/src/json-type/type/classes/ArrayType.ts +++ /dev/null @@ -1,218 +0,0 @@ -import {JsExpression} from '@jsonjoy.com/util/lib/codegen/util/JsExpression'; -import {BinaryJsonEncoder} from '@jsonjoy.com/json-pack/lib/types'; -import {printTree} from 'tree-dump/lib/printTree'; -import * as schema from '../../schema'; -import {validateMinMax, validateTType} from '../../schema/validate'; -import {ValidatorCodegenContext} from '../../codegen/validator/ValidatorCodegenContext'; -import {ValidationPath} from '../../codegen/validator/types'; -import {ValidationError} from '../../constants'; -import {JsonTextEncoderCodegenContext} from '../../codegen/json/JsonTextEncoderCodegenContext'; -import {CborEncoderCodegenContext} from '../../codegen/binary/CborEncoderCodegenContext'; -import {JsonEncoderCodegenContext} from '../../codegen/binary/JsonEncoderCodegenContext'; -import {BinaryEncoderCodegenContext} from '../../codegen/binary/BinaryEncoderCodegenContext'; -import {MessagePackEncoderCodegenContext} from '../../codegen/binary/MessagePackEncoderCodegenContext'; -import {CapacityEstimatorCodegenContext} from '../../codegen/capacity/CapacityEstimatorCodegenContext'; -import {MaxEncodingOverhead} from '../../../json-size'; -import {AbstractType} from './AbstractType'; -import {ConstType} from './ConstType'; -import {BooleanType} from './BooleanType'; -import {NumberType} from './NumberType'; -import type * as jsonSchema from '../../../json-schema'; -import type {SchemaOf, Type} from '../types'; -import type {TypeSystem} from '../../system/TypeSystem'; -import type {json_string} from '@jsonjoy.com/util/lib/json-brand'; -import type * as ts from '../../typescript/types'; -import type {TypeExportContext} from '../../system/TypeExportContext'; - -export class ArrayType extends AbstractType>> { - protected schema: schema.ArraySchema; - - constructor( - protected type: T, - options?: schema.Optional, - ) { - super(); - this.schema = schema.s.Array(schema.s.any, options); - } - - public getSchema(ctx?: TypeExportContext): schema.ArraySchema> { - return { - ...this.schema, - type: this.type.getSchema(ctx) as any, - }; - } - - public toJsonSchema(): jsonSchema.JsonSchemaArray { - const schema = this.getSchema(); - const jsonSchema = { - type: 'array', - items: this.type.toJsonSchema(), - ...super.toJsonSchema(), - }; - if (schema.min !== undefined) jsonSchema.minItems = schema.min; - if (schema.max !== undefined) jsonSchema.maxItems = schema.max; - return jsonSchema; - } - - public getOptions(): schema.Optional>> { - const {kind, type, ...options} = this.schema; - return options as any; - } - - public validateSchema(): void { - const schema = this.getSchema(); - validateTType(schema, 'arr'); - const {min, max} = schema; - validateMinMax(min, max); - this.type.validateSchema(); - } - - public codegenValidator(ctx: ValidatorCodegenContext, path: ValidationPath, r: string): void { - const rl = ctx.codegen.getRegister(); - const ri = ctx.codegen.getRegister(); - const rv = ctx.codegen.getRegister(); - const err = ctx.err(ValidationError.ARR, path); - const errLen = ctx.err(ValidationError.ARR_LEN, path); - const {min, max} = this.schema; - ctx.js(/* js */ `if (!Array.isArray(${r})) return ${err};`); - ctx.js(`var ${rl} = ${r}.length;`); - if (min !== undefined) ctx.js(`if (${rl} < ${min}) return ${errLen};`); - if (max !== undefined) ctx.js(`if (${rl} > ${max}) return ${errLen};`); - ctx.js(`for (var ${rv}, ${ri} = ${r}.length; ${ri}-- !== 0;) {`); - ctx.js(`${rv} = ${r}[${ri}];`); - this.type.codegenValidator(ctx, [...path, {r: ri}], rv); - ctx.js(`}`); - ctx.emitCustomValidators(this, path, r); - } - - public codegenJsonTextEncoder(ctx: JsonTextEncoderCodegenContext, value: JsExpression): void { - ctx.writeText('['); - const codegen = ctx.codegen; - const r = codegen.getRegister(); // array - const rl = codegen.getRegister(); // array.length - const rll = codegen.getRegister(); // last - const ri = codegen.getRegister(); // index - ctx.js(/* js */ `var ${r} = ${value.use()}, ${rl} = ${r}.length, ${rll} = ${rl} - 1, ${ri} = 0;`); - ctx.js(/* js */ `for(; ${ri} < ${rll}; ${ri}++) ` + '{'); - this.type.codegenJsonTextEncoder(ctx, new JsExpression(() => `${r}[${ri}]`)); - ctx.js(/* js */ `s += ',';`); - ctx.js(`}`); - ctx.js(`if (${rl}) {`); - this.type.codegenJsonTextEncoder(ctx, new JsExpression(() => `${r}[${rll}]`)); - ctx.js(`}`); - ctx.writeText(']'); - } - - private codegenBinaryEncoder(ctx: BinaryEncoderCodegenContext, value: JsExpression): void { - const type = this.type; - const codegen = ctx.codegen; - const r = codegen.getRegister(); // array - const rl = codegen.getRegister(); // array.length - const ri = codegen.getRegister(); // index - const rItem = codegen.getRegister(); // item - const expr = new JsExpression(() => `${rItem}`); - ctx.js(/* js */ `var ${r} = ${value.use()}, ${rl} = ${r}.length, ${ri} = 0, ${rItem};`); - ctx.js(/* js */ `encoder.writeArrHdr(${rl});`); - ctx.js(/* js */ `for(; ${ri} < ${rl}; ${ri}++) ` + '{'); - ctx.js(/* js */ `${rItem} = ${r}[${ri}];`); - if (ctx instanceof CborEncoderCodegenContext) type.codegenCborEncoder(ctx, expr); - else if (ctx instanceof MessagePackEncoderCodegenContext) type.codegenMessagePackEncoder(ctx, expr); - else throw new Error('Unknown encoder'); - ctx.js(`}`); - } - - public codegenCborEncoder(ctx: CborEncoderCodegenContext, value: JsExpression): void { - this.codegenBinaryEncoder(ctx, value); - } - - public codegenMessagePackEncoder(ctx: MessagePackEncoderCodegenContext, value: JsExpression): void { - this.codegenBinaryEncoder(ctx, value); - } - - public codegenJsonEncoder(ctx: JsonEncoderCodegenContext, value: JsExpression): void { - const type = this.type; - const codegen = ctx.codegen; - const expr = new JsExpression(() => `${rItem}`); - const r = codegen.var(value.use()); - const rLen = codegen.var(`${r}.length`); - const rLast = codegen.var(`${rLen} - 1`); - const ri = codegen.var('0'); - const rItem = codegen.var(); - ctx.blob( - ctx.gen((encoder) => { - encoder.writeStartArr(); - }), - ); - codegen.js(`for(; ${ri} < ${rLast}; ${ri}++) {`); - codegen.js(`${rItem} = ${r}[${ri}];`); - type.codegenJsonEncoder(ctx, expr); - ctx.blob( - ctx.gen((encoder) => { - encoder.writeArrSeparator(); - }), - ); - ctx.js(`}`); - ctx.js(`if (${rLen}) {`); - codegen.js(`${rItem} = ${r}[${rLast}];`); - type.codegenJsonEncoder(ctx, expr); - ctx.js(`}`); - ctx.blob( - ctx.gen((encoder) => { - encoder.writeEndArr(); - }), - ); - } - - public codegenCapacityEstimator(ctx: CapacityEstimatorCodegenContext, value: JsExpression): void { - const codegen = ctx.codegen; - ctx.inc(MaxEncodingOverhead.Array); - const rLen = codegen.var(`${value.use()}.length`); - const type = this.type; - codegen.js(`size += ${MaxEncodingOverhead.ArrayElement} * ${rLen}`); - const fn = type.compileCapacityEstimator({ - system: ctx.options.system, - name: ctx.options.name, - }); - const isConstantSizeType = type instanceof ConstType || type instanceof BooleanType || type instanceof NumberType; - if (isConstantSizeType) { - codegen.js(`size += ${rLen} * ${fn(null)};`); - } else { - const r = codegen.var(value.use()); - const rFn = codegen.linkDependency(fn); - const ri = codegen.getRegister(); - codegen.js(`for(var ${ri} = ${rLen}; ${ri}-- !== 0;) size += ${rFn}(${r}[${ri}]);`); - } - } - - public random(): unknown[] { - let length = Math.round(Math.random() * 10); - const {min, max} = this.schema; - if (min !== undefined && length < min) length = min + length; - if (max !== undefined && length > max) length = max; - const arr = []; - for (let i = 0; i < length; i++) arr.push(this.type.random()); - return arr; - } - - public toTypeScriptAst(): ts.TsArrayType { - return { - node: 'ArrayType', - elementType: this.type.toTypeScriptAst() as ts.TsType, - }; - } - - public toJson(value: unknown, system: TypeSystem | undefined = this.system): json_string { - const length = (value as unknown[]).length; - if (!length) return '[]' as json_string; - const last = length - 1; - const type = this.type; - let str = '['; - for (let i = 0; i < last; i++) str += (type as any).toJson((value as unknown[])[i] as any, system) + ','; - str += (type as any).toJson((value as unknown[])[last] as any, system); - return (str + ']') as json_string; - } - - public toString(tab: string = ''): string { - return super.toString(tab) + printTree(tab, [(tab) => this.type.toString(tab)]); - } -} diff --git a/src/json-type/type/classes/BinaryType.ts b/src/json-type/type/classes/BinaryType.ts deleted file mode 100644 index 06b0a1ae49..0000000000 --- a/src/json-type/type/classes/BinaryType.ts +++ /dev/null @@ -1,123 +0,0 @@ -import {JsExpression} from '@jsonjoy.com/util/lib/codegen/util/JsExpression'; -import {BinaryJsonEncoder} from '@jsonjoy.com/json-pack/lib/types'; -import {printTree} from 'tree-dump/lib/printTree'; -import * as schema from '../../schema'; -import {RandomJson} from '../../../json-random'; -import {stringifyBinary} from '../../../json-binary'; -import {validateTType} from '../../schema/validate'; -import {ValidatorCodegenContext} from '../../codegen/validator/ValidatorCodegenContext'; -import {ValidationPath} from '../../codegen/validator/types'; -import {ValidationError} from '../../constants'; -import {JsonTextEncoderCodegenContext} from '../../codegen/json/JsonTextEncoderCodegenContext'; -import {CborEncoderCodegenContext} from '../../codegen/binary/CborEncoderCodegenContext'; -import {JsonEncoderCodegenContext} from '../../codegen/binary/JsonEncoderCodegenContext'; -import {BinaryEncoderCodegenContext} from '../../codegen/binary/BinaryEncoderCodegenContext'; -import {MessagePackEncoderCodegenContext} from '../../codegen/binary/MessagePackEncoderCodegenContext'; -import {CapacityEstimatorCodegenContext} from '../../codegen/capacity/CapacityEstimatorCodegenContext'; -import {MaxEncodingOverhead} from '../../../json-size'; -import {AbstractType} from './AbstractType'; -import type * as jsonSchema from '../../../json-schema'; -import type {SchemaOf, Type} from '../types'; -import type {TypeSystem} from '../../system/TypeSystem'; -import type {json_string} from '@jsonjoy.com/util/lib/json-brand'; -import type * as ts from '../../typescript/types'; -import type {TypeExportContext} from '../../system/TypeExportContext'; - -export class BinaryType extends AbstractType { - protected schema: schema.BinarySchema; - - constructor( - protected type: T, - options?: schema.Optional, - ) { - super(); - this.schema = schema.s.Binary(schema.s.any, options); - } - - public getSchema(): schema.BinarySchema> { - return { - ...this.schema, - type: this.type.getSchema() as any, - }; - } - - public toJsonSchema(ctx?: TypeExportContext): jsonSchema.JsonSchemaBinary { - return { - type: 'binary', - ...super.toJsonSchema(ctx), - }; - } - - public getOptions(): schema.Optional>> { - const {kind, type, ...options} = this.schema; - return options as any; - } - - public validateSchema(): void { - validateTType(this.getSchema(), 'bin'); - this.type.validateSchema(); - } - - public codegenValidator(ctx: ValidatorCodegenContext, path: ValidationPath, r: string): void { - const hasBuffer = typeof Buffer === 'function'; - const err = ctx.err(ValidationError.BIN, path); - ctx.js( - // prettier-ignore - /* js */ `if(!(${r} instanceof Uint8Array)${hasBuffer ? /* js */ ` && !Buffer.isBuffer(${r})` : ''}) return ${err};`, - ); - ctx.emitCustomValidators(this, path, r); - } - - public codegenJsonTextEncoder(ctx: JsonTextEncoderCodegenContext, value: JsExpression): void { - ctx.linkBase64(); - ctx.writeText('"data:application/octet-stream;base64,'); - ctx.js(/* js */ `s += toBase64(${value.use()});`); - ctx.writeText('"'); - } - - private codegenBinaryEncoder(ctx: BinaryEncoderCodegenContext, value: JsExpression): void { - ctx.js(/* js */ `encoder.writeBin(${value.use()});`); - } - - public codegenCborEncoder(ctx: CborEncoderCodegenContext, value: JsExpression): void { - this.codegenBinaryEncoder(ctx, value); - } - - public codegenMessagePackEncoder(ctx: MessagePackEncoderCodegenContext, value: JsExpression): void { - this.codegenBinaryEncoder(ctx, value); - } - - public codegenJsonEncoder(ctx: JsonEncoderCodegenContext, value: JsExpression): void { - this.codegenBinaryEncoder(ctx, value); - } - - public codegenCapacityEstimator(ctx: CapacityEstimatorCodegenContext, value: JsExpression): void { - ctx.inc(MaxEncodingOverhead.Binary); - ctx.codegen.js(`size += ${MaxEncodingOverhead.BinaryLengthMultiplier} * ${value.use()}.length;`); - } - - public random(): Uint8Array { - const octets = RandomJson.genString() - .split('') - .map((c) => c.charCodeAt(0)); - return new Uint8Array(octets); - } - - public toTypeScriptAst(): ts.TsGenericTypeAnnotation { - return { - node: 'GenericTypeAnnotation', - id: { - node: 'Identifier', - name: 'Uint8Array', - }, - }; - } - - public toJson(value: unknown, system: TypeSystem | undefined = this.system): json_string { - return ('"' + stringifyBinary(value as Uint8Array) + '"') as json_string; - } - - public toString(tab: string = ''): string { - return super.toString(tab) + printTree(tab, [(tab) => this.type.toString(tab)]); - } -} diff --git a/src/json-type/type/classes/BooleanType.ts b/src/json-type/type/classes/BooleanType.ts deleted file mode 100644 index 281fdc8d75..0000000000 --- a/src/json-type/type/classes/BooleanType.ts +++ /dev/null @@ -1,86 +0,0 @@ -import * as schema from '../../schema'; -import {RandomJson} from '../../../json-random'; -import {validateTType} from '../../schema/validate'; -import {ValidatorCodegenContext} from '../../codegen/validator/ValidatorCodegenContext'; -import {ValidationPath} from '../../codegen/validator/types'; -import {ValidationError} from '../../constants'; -import {JsonTextEncoderCodegenContext} from '../../codegen/json/JsonTextEncoderCodegenContext'; -import {CborEncoderCodegenContext} from '../../codegen/binary/CborEncoderCodegenContext'; -import {JsonEncoderCodegenContext} from '../../codegen/binary/JsonEncoderCodegenContext'; -import {BinaryEncoderCodegenContext} from '../../codegen/binary/BinaryEncoderCodegenContext'; -import {JsExpression} from '@jsonjoy.com/util/lib/codegen/util/JsExpression'; -import {MessagePackEncoderCodegenContext} from '../../codegen/binary/MessagePackEncoderCodegenContext'; -import {BinaryJsonEncoder} from '@jsonjoy.com/json-pack/lib/types'; -import {CapacityEstimatorCodegenContext} from '../../codegen/capacity/CapacityEstimatorCodegenContext'; -import {MaxEncodingOverhead} from '../../../json-size'; -import {AbstractType} from './AbstractType'; -import type * as jsonSchema from '../../../json-schema'; -import type {TypeSystem} from '../../system/TypeSystem'; -import type {json_string} from '@jsonjoy.com/util/lib/json-brand'; -import type * as ts from '../../typescript/types'; -import type {TypeExportContext} from '../../system/TypeExportContext'; -import type * as jtd from '../../jtd/types'; - -export class BooleanType extends AbstractType { - constructor(protected schema: schema.BooleanSchema) { - super(); - } - - public toJsonSchema(ctx?: TypeExportContext): jsonSchema.JsonSchemaBoolean { - return { - type: 'boolean', - ...super.toJsonSchema(ctx), - }; - } - - public validateSchema(): void { - validateTType(this.getSchema(), 'bool'); - } - - public codegenValidator(ctx: ValidatorCodegenContext, path: ValidationPath, r: string): void { - const err = ctx.err(ValidationError.BOOL, path); - ctx.js(/* js */ `if(typeof ${r} !== "boolean") return ${err};`); - ctx.emitCustomValidators(this, path, r); - } - - public codegenJsonTextEncoder(ctx: JsonTextEncoderCodegenContext, value: JsExpression): void { - ctx.js(/* js */ `s += ${value.use()} ? 'true' : 'false';`); - } - - protected codegenBinaryEncoder(ctx: BinaryEncoderCodegenContext, value: JsExpression): void { - ctx.js(/* js */ `encoder.writeBoolean(${value.use()});`); - } - - public codegenCborEncoder(ctx: CborEncoderCodegenContext, value: JsExpression): void { - this.codegenBinaryEncoder(ctx, value); - } - - public codegenMessagePackEncoder(ctx: MessagePackEncoderCodegenContext, value: JsExpression): void { - this.codegenBinaryEncoder(ctx, value); - } - - public codegenJsonEncoder(ctx: JsonEncoderCodegenContext, value: JsExpression): void { - this.codegenBinaryEncoder(ctx, value); - } - - public codegenCapacityEstimator(ctx: CapacityEstimatorCodegenContext, value: JsExpression): void { - ctx.inc(MaxEncodingOverhead.Boolean); - } - - public random(): boolean { - return RandomJson.genBoolean(); - } - - public toTypeScriptAst(): ts.TsBooleanKeyword { - return {node: 'BooleanKeyword'}; - } - - public toJson(value: unknown, system: TypeSystem | undefined = this.system) { - return (value ? 'true' : 'false') as json_string; - } - - public toJtdForm(): jtd.JtdTypeForm { - const form: jtd.JtdTypeForm = {type: 'boolean'}; - return form; - } -} diff --git a/src/json-type/type/classes/ConstType.ts b/src/json-type/type/classes/ConstType.ts deleted file mode 100644 index cecd80affc..0000000000 --- a/src/json-type/type/classes/ConstType.ts +++ /dev/null @@ -1,156 +0,0 @@ -import * as schema from '../../schema'; -import {cloneBinary} from '../../../json-clone'; -import {validateTType} from '../../schema/validate'; -import {ValidatorCodegenContext} from '../../codegen/validator/ValidatorCodegenContext'; -import {ValidationPath} from '../../codegen/validator/types'; -import {ValidationError} from '../../constants'; -import {$$deepEqual} from '../../../json-equal/$$deepEqual'; -import {JsonTextEncoderCodegenContext} from '../../codegen/json/JsonTextEncoderCodegenContext'; -import {CborEncoderCodegenContext} from '../../codegen/binary/CborEncoderCodegenContext'; -import {JsonEncoderCodegenContext} from '../../codegen/binary/JsonEncoderCodegenContext'; -import {BinaryEncoderCodegenContext} from '../../codegen/binary/BinaryEncoderCodegenContext'; -import {JsExpression} from '@jsonjoy.com/util/lib/codegen/util/JsExpression'; -import {MessagePackEncoderCodegenContext} from '../../codegen/binary/MessagePackEncoderCodegenContext'; -import {BinaryJsonEncoder} from '@jsonjoy.com/json-pack/lib/types'; -import {CapacityEstimatorCodegenContext} from '../../codegen/capacity/CapacityEstimatorCodegenContext'; -import {maxEncodingCapacity} from '../../../json-size'; -import {AbstractType} from './AbstractType'; -import type * as jsonSchema from '../../../json-schema'; -import type {TypeSystem} from '../../system/TypeSystem'; -import type {json_string} from '@jsonjoy.com/util/lib/json-brand'; -import type * as ts from '../../typescript/types'; -import type {TypeExportContext} from '../../system/TypeExportContext'; -import type * as jtd from '../../jtd/types'; - -export class ConstType extends AbstractType> { - private __json: json_string; - - constructor(protected schema: schema.ConstSchema) { - super(); - this.__json = JSON.stringify(schema.value) as any; - } - - public value() { - return this.schema.value; - } - - public toJsonSchema(ctx?: TypeExportContext): jsonSchema.JsonSchemaValueNode { - const schema = this.schema; - return { - type: typeof this.schema.value as any, - const: schema.value, - ...super.toJsonSchema(ctx), - }; - } - - public getOptions(): schema.Optional> { - const {kind, value, ...options} = this.schema; - return options as any; - } - - public validateSchema(): void { - validateTType(this.getSchema(), 'const'); - } - - public codegenValidator(ctx: ValidatorCodegenContext, path: ValidationPath, r: string): void { - const value = this.schema.value; - const equals = $$deepEqual(value); - const fn = ctx.codegen.addConstant(equals); - ctx.js(`if (!${fn}(${r})) return ${ctx.err(ValidationError.CONST, path)}`); - ctx.emitCustomValidators(this, path, r); - } - - public codegenJsonTextEncoder(ctx: JsonTextEncoderCodegenContext, value: JsExpression): void { - ctx.writeText(JSON.stringify(this.schema.value)); - } - - private codegenBinaryEncoder(ctx: BinaryEncoderCodegenContext, value: JsExpression): void { - ctx.blob( - ctx.gen((encoder) => { - encoder.writeAny(this.schema.value); - }), - ); - } - - public codegenCborEncoder(ctx: CborEncoderCodegenContext, value: JsExpression): void { - this.codegenBinaryEncoder(ctx, value); - } - - public codegenMessagePackEncoder(ctx: MessagePackEncoderCodegenContext, value: JsExpression): void { - this.codegenBinaryEncoder(ctx, value); - } - - public codegenJsonEncoder(ctx: JsonEncoderCodegenContext, value: JsExpression): void { - this.codegenBinaryEncoder(ctx, value); - } - - public codegenCapacityEstimator(ctx: CapacityEstimatorCodegenContext, value: JsExpression): void { - ctx.inc(maxEncodingCapacity(this.value())); - } - - public random(): unknown { - return cloneBinary(this.schema.value); - } - - public toTypeScriptAst() { - const value = this.schema.value; - if (value === null) { - const node: ts.TsNullKeyword = {node: 'NullKeyword'}; - return node; - } - switch (typeof value) { - case 'string': { - const node: ts.TsStringLiteral = {node: 'StringLiteral', text: value}; - return node; - } - case 'number': { - const node: ts.TsNumericLiteral = {node: 'NumericLiteral', text: value.toString()}; - return node; - } - case 'boolean': { - const node: ts.TsTrueKeyword | ts.TsFalseKeyword = {node: value ? 'TrueKeyword' : 'FalseKeyword'}; - return node; - } - case 'object': { - const node: ts.TsObjectKeyword = {node: 'ObjectKeyword'}; - return node; - } - default: { - const node: ts.TsUnknownKeyword = {node: 'UnknownKeyword'}; - return node; - } - } - } - - public toJson(value: unknown, system: TypeSystem | undefined = this.system) { - return this.__json; - } - - public toString(tab: string = ''): string { - return `${super.toString(tab)} → ${JSON.stringify(this.schema.value)}`; - } - - public toJtdForm(): jtd.JtdForm { - const value = this.value(); - const type = typeof value; - switch (type) { - case 'boolean': - case 'string': - return {type}; - case 'number': { - if (value !== Math.round(value)) return {type: 'float64'}; - if (value >= 0) { - if (value <= 255) return {type: 'uint8'}; - if (value <= 65535) return {type: 'uint16'}; - if (value <= 4294967295) return {type: 'uint32'}; - } else { - if (value >= -128) return {type: 'int8'}; - if (value >= -32768) return {type: 'int16'}; - if (value >= -2147483648) return {type: 'int32'}; - } - return {type: 'float64'}; - } - } - return super.toJtdForm(); - } -} diff --git a/src/json-type/type/classes/FunctionType.ts b/src/json-type/type/classes/FunctionType.ts deleted file mode 100644 index 98a3a8a9f8..0000000000 --- a/src/json-type/type/classes/FunctionType.ts +++ /dev/null @@ -1,185 +0,0 @@ -import {printTree} from 'tree-dump/lib/printTree'; -import * as schema from '../../schema'; -import {validateTType} from '../../schema/validate'; -import {AbstractType} from './AbstractType'; -import type {SchemaOf, Type} from '../types'; -import type * as ts from '../../typescript/types'; -import type {ResolveType} from '../../system'; -import type {Observable} from 'rxjs'; - -const fnNotImplemented: schema.FunctionValue = async () => { - throw new Error('NOT_IMPLEMENTED'); -}; - -const toStringTree = (tab: string = '', type: FunctionType | FunctionStreamingType) => { - return printTree(tab, [ - (tab) => 'req: ' + type.req.toString(tab + ' '), - (tab) => 'res: ' + type.res.toString(tab + ' '), - ]); -}; - -type FunctionImpl = ( - req: ResolveType, - ctx: Ctx, -) => Promise>; - -export class FunctionType extends AbstractType< - schema.FunctionSchema, SchemaOf> -> { - protected schema: schema.FunctionSchema, SchemaOf>; - - public fn: schema.FunctionValue>, schema.TypeOf>> = fnNotImplemented; - - constructor( - public readonly req: Req, - public readonly res: Res, - options?: schema.Optional, SchemaOf>>, - ) { - super(); - this.schema = { - ...options, - ...schema.s.Function(schema.s.any, schema.s.any), - } as any; - } - - public getSchema(): schema.FunctionSchema, SchemaOf> { - return { - ...this.schema, - req: this.req.getSchema() as SchemaOf, - res: this.res.getSchema() as SchemaOf, - }; - } - - public validateSchema(): void { - const schema = this.getSchema(); - validateTType(schema, 'fn'); - this.req.validateSchema(); - this.res.validateSchema(); - } - - public random(): unknown { - return async () => this.res.random(); - } - - public singleton?: FunctionImpl = undefined; - - public implement(singleton: FunctionImpl): this { - this.singleton = singleton; - return this; - } - - public toTypeScriptAst(): ts.TsFunctionType { - const node: ts.TsFunctionType = { - node: 'FunctionType', - parameters: [ - { - node: 'Parameter', - name: { - node: 'Identifier', - name: 'request', - }, - type: this.req.toTypeScriptAst(), - }, - ], - type: { - node: 'TypeReference', - typeName: { - node: 'Identifier', - name: 'Promise', - }, - typeArguments: [this.res.toTypeScriptAst()], - }, - }; - return node; - } - - public toString(tab: string = ''): string { - return super.toString(tab) + toStringTree(tab, this); - } -} - -type FunctionStreamingImpl = ( - req: Observable>, - ctx: Ctx, -) => Observable>; - -export class FunctionStreamingType extends AbstractType< - schema.FunctionStreamingSchema, SchemaOf> -> { - public readonly isStreaming = true; - protected schema: schema.FunctionStreamingSchema, SchemaOf>; - - constructor( - public readonly req: Req, - public readonly res: Res, - options?: schema.Optional, SchemaOf>>, - ) { - super(); - this.schema = { - ...options, - ...schema.s.Function$(schema.s.any, schema.s.any), - } as any; - } - - public getSchema(): schema.FunctionStreamingSchema, SchemaOf> { - return { - ...this.schema, - req: this.req.getSchema() as SchemaOf, - res: this.res.getSchema() as SchemaOf, - }; - } - - public validateSchema(): void { - const schema = this.getSchema(); - validateTType(schema, 'fn$'); - this.req.validateSchema(); - this.res.validateSchema(); - } - - public random(): unknown { - return async () => this.res.random(); - } - - public singleton?: FunctionStreamingImpl = undefined; - - public implement(singleton: FunctionStreamingImpl): this { - this.singleton = singleton; - return this; - } - - public toTypeScriptAst(): ts.TsFunctionType { - const node: ts.TsFunctionType = { - node: 'FunctionType', - parameters: [ - { - node: 'Parameter', - name: { - node: 'Identifier', - name: 'request$', - }, - type: { - node: 'TypeReference', - typeName: { - node: 'Identifier', - name: 'Observable', - }, - typeArguments: [this.req.toTypeScriptAst()], - }, - }, - ], - type: { - node: 'TypeReference', - typeName: { - node: 'Identifier', - name: 'Observable', - }, - typeArguments: [this.res.toTypeScriptAst()], - }, - }; - return node; - } - - public toString(tab: string = ''): string { - return super.toString(tab) + toStringTree(tab, this); - } -} diff --git a/src/json-type/type/classes/MapType.ts b/src/json-type/type/classes/MapType.ts deleted file mode 100644 index 47a19b2c11..0000000000 --- a/src/json-type/type/classes/MapType.ts +++ /dev/null @@ -1,220 +0,0 @@ -import {JsExpression} from '@jsonjoy.com/util/lib/codegen/util/JsExpression'; -import {BinaryJsonEncoder} from '@jsonjoy.com/json-pack/lib/types'; -import {asString} from '@jsonjoy.com/util/lib/strings/asString'; -import {printTree} from 'tree-dump/lib/printTree'; -import * as schema from '../../schema'; -import {RandomJson} from '../../../json-random'; -import {validateTType} from '../../schema/validate'; -import {ValidatorCodegenContext} from '../../codegen/validator/ValidatorCodegenContext'; -import {ValidationPath} from '../../codegen/validator/types'; -import {ValidationError} from '../../constants'; -import {JsonTextEncoderCodegenContext} from '../../codegen/json/JsonTextEncoderCodegenContext'; -import {CborEncoderCodegenContext} from '../../codegen/binary/CborEncoderCodegenContext'; -import {JsonEncoderCodegenContext} from '../../codegen/binary/JsonEncoderCodegenContext'; -import {BinaryEncoderCodegenContext} from '../../codegen/binary/BinaryEncoderCodegenContext'; -import {MessagePackEncoderCodegenContext} from '../../codegen/binary/MessagePackEncoderCodegenContext'; -import {CapacityEstimatorCodegenContext} from '../../codegen/capacity/CapacityEstimatorCodegenContext'; -import {MaxEncodingOverhead} from '../../../json-size'; -import {AbstractType} from './AbstractType'; -import type * as jsonSchema from '../../../json-schema'; -import type {SchemaOf, Type} from '../types'; -import type {TypeSystem} from '../../system/TypeSystem'; -import type {json_string} from '@jsonjoy.com/util/lib/json-brand'; -import type * as ts from '../../typescript/types'; -import type {TypeExportContext} from '../../system/TypeExportContext'; - -export class MapType extends AbstractType>> { - protected schema: schema.MapSchema; - - constructor( - protected type: T, - options?: schema.Optional, - ) { - super(); - this.schema = schema.s.Map(schema.s.any, options); - } - - public getSchema(ctx?: TypeExportContext): schema.MapSchema> { - return { - ...this.schema, - type: this.type.getSchema(ctx) as any, - }; - } - - public toJsonSchema(): jsonSchema.JsonSchemaObject { - const jsonSchema = { - type: 'object', - patternProperties: { - '.*': this.type.toJsonSchema(), - }, - }; - return jsonSchema; - } - - public getOptions(): schema.Optional>> { - const {kind, type, ...options} = this.schema; - return options as any; - } - - public validateSchema(): void { - const schema = this.getSchema(); - validateTType(schema, 'map'); - this.type.validateSchema(); - } - - public codegenValidator(ctx: ValidatorCodegenContext, path: ValidationPath, r: string): void { - const err = ctx.err(ValidationError.MAP, path); - ctx.js(`if (!${r} || (typeof ${r} !== 'object') || (${r}.constructor !== Object)) return ${err};`); - const rKeys = ctx.codegen.var(`Object.keys(${r});`); - const rLength = ctx.codegen.var(`${rKeys}.length`); - const rKey = ctx.codegen.r(); - const rValue = ctx.codegen.r(); - ctx.js(`for (var ${rKey}, ${rValue}, i = 0; i < ${rLength}; i++) {`); - ctx.js(`${rKey} = ${rKeys}[i];`); - ctx.js(`${rValue} = ${r}[${rKey}];`); - this.type.codegenValidator(ctx, [...path, {r: rKey}], rValue); - ctx.js(`}`); - ctx.emitCustomValidators(this, path, r); - } - - public codegenJsonTextEncoder(ctx: JsonTextEncoderCodegenContext, value: JsExpression): void { - ctx.writeText('{'); - const r = ctx.codegen.var(value.use()); - const rKeys = ctx.codegen.var(`Object.keys(${r})`); - const rLength = ctx.codegen.var(`${rKeys}.length`); - const rKey = ctx.codegen.var(); - ctx.codegen.if(`${rLength}`, () => { - ctx.js(`${rKey} = ${rKeys}[0];`); - ctx.js(`s += asString(${rKey}) + ':';`); - this.type.codegenJsonTextEncoder(ctx, new JsExpression(() => `${r}[${rKey}]`)); - }); - ctx.js(`for (var i = 1; i < ${rLength}; i++) {`); - ctx.js(`${rKey} = ${rKeys}[i];`); - ctx.js(`s += ',' + asString(${rKey}) + ':';`); - this.type.codegenJsonTextEncoder(ctx, new JsExpression(() => `${r}[${rKey}]`)); - ctx.js(`}`); - ctx.writeText('}'); - } - - private codegenBinaryEncoder(ctx: BinaryEncoderCodegenContext, value: JsExpression): void { - const type = this.type; - const codegen = ctx.codegen; - const r = codegen.var(value.use()); - const rKeys = codegen.var(`Object.keys(${r})`); - const rKey = codegen.var(); - const rLength = codegen.var(`${rKeys}.length`); - const ri = codegen.var('0'); - ctx.js(`encoder.writeObjHdr(${rLength});`); - ctx.js(`for(; ${ri} < ${rLength}; ${ri}++){`); - ctx.js(`${rKey} = ${rKeys}[${ri}];`); - ctx.js(`encoder.writeStr(${rKey});`); - const expr = new JsExpression(() => `${r}[${rKey}]`); - if (ctx instanceof CborEncoderCodegenContext) type.codegenCborEncoder(ctx, expr); - else if (ctx instanceof MessagePackEncoderCodegenContext) type.codegenMessagePackEncoder(ctx, expr); - else throw new Error('Unknown encoder'); - ctx.js(`}`); - } - - public codegenCborEncoder(ctx: CborEncoderCodegenContext, value: JsExpression): void { - this.codegenBinaryEncoder(ctx, value); - } - - public codegenMessagePackEncoder(ctx: MessagePackEncoderCodegenContext, value: JsExpression): void { - this.codegenBinaryEncoder(ctx, value); - } - - public codegenJsonEncoder(ctx: JsonEncoderCodegenContext, value: JsExpression): void { - const type = this.type; - const objStartBlob = ctx.gen((encoder) => encoder.writeStartObj()); - const objEndBlob = ctx.gen((encoder) => encoder.writeEndObj()); - const separatorBlob = ctx.gen((encoder) => encoder.writeObjSeparator()); - const keySeparatorBlob = ctx.gen((encoder) => encoder.writeObjKeySeparator()); - const codegen = ctx.codegen; - const r = codegen.var(value.use()); - const rKeys = codegen.var(`Object.keys(${r})`); - const rKey = codegen.var(); - const rLength = codegen.var(`${rKeys}.length`); - ctx.blob(objStartBlob); - ctx.codegen.if(`${rLength}`, () => { - ctx.js(`${rKey} = ${rKeys}[0];`); - codegen.js(`encoder.writeStr(${rKey});`); - ctx.blob(keySeparatorBlob); - type.codegenJsonEncoder(ctx, new JsExpression(() => `${r}[${rKey}]`)); - }); - ctx.js(`for (var i = 1; i < ${rLength}; i++) {`); - ctx.js(`${rKey} = ${rKeys}[i];`); - ctx.blob(separatorBlob); - codegen.js(`encoder.writeStr(${rKey});`); - ctx.blob(keySeparatorBlob); - type.codegenJsonEncoder(ctx, new JsExpression(() => `${r}[${rKey}]`)); - ctx.js(`}`); - ctx.blob(objEndBlob); - } - - public codegenCapacityEstimator(ctx: CapacityEstimatorCodegenContext, value: JsExpression): void { - const codegen = ctx.codegen; - ctx.inc(MaxEncodingOverhead.Object); - const r = codegen.var(value.use()); - const rKeys = codegen.var(`Object.keys(${r})`); - const rKey = codegen.var(); - const rLen = codegen.var(`${rKeys}.length`); - codegen.js(`size += ${MaxEncodingOverhead.ObjectElement} * ${rLen}`); - const type = this.type; - const fn = type.compileCapacityEstimator({ - system: ctx.options.system, - name: ctx.options.name, - }); - const rFn = codegen.linkDependency(fn); - const ri = codegen.var('0'); - codegen.js(`for (; ${ri} < ${rLen}; ${ri}++) {`); - codegen.js(`${rKey} = ${rKeys}[${ri}];`); - codegen.js( - `size += ${MaxEncodingOverhead.String} + ${MaxEncodingOverhead.StringLengthMultiplier} * ${rKey}.length;`, - ); - codegen.js(`size += ${rFn}(${r}[${rKey}]);`); - codegen.js(`}`); - } - - public random(): Record { - const length = Math.round(Math.random() * 10); - const res: Record = {}; - for (let i = 0; i < length; i++) res[RandomJson.genString(length)] = this.type.random(); - return res; - } - - public toTypeScriptAst(): ts.TsTypeReference { - const node: ts.TsTypeReference = { - node: 'TypeReference', - typeName: 'Record', - typeArguments: [{node: 'StringKeyword'}, this.type.toTypeScriptAst()], - }; - // augmentWithComment(this.schema, node); - return node; - } - - public toJson(value: unknown, system: TypeSystem | undefined = this.system): json_string { - const map = value as Record; - const keys = Object.keys(map); - const length = keys.length; - if (!length) return '{}' as json_string; - const last = length - 1; - const type = this.type; - let str = '{'; - for (let i = 0; i < last; i++) { - const key = keys[i]; - const val = (value as any)[key]; - if (val === undefined) continue; - str += asString(key) + ':' + type.toJson(val as any, system) + ','; - } - const key = keys[last]; - const val = (value as any)[key]; - if (val !== undefined) { - str += asString(key) + ':' + type.toJson(val as any, system); - } else if (str.length > 1) str = str.slice(0, -1); - return (str + '}') as json_string; - } - - public toString(tab: string = ''): string { - return super.toString(tab) + printTree(tab, [(tab) => this.type.toString(tab)]); - } -} diff --git a/src/json-type/type/classes/NumberType.ts b/src/json-type/type/classes/NumberType.ts deleted file mode 100644 index 5d6d441713..0000000000 --- a/src/json-type/type/classes/NumberType.ts +++ /dev/null @@ -1,254 +0,0 @@ -import * as schema from '../../schema'; -import {floats, ints, uints} from '../../util'; -import {validateTType, validateWithValidator} from '../../schema/validate'; -import {ValidatorCodegenContext} from '../../codegen/validator/ValidatorCodegenContext'; -import {ValidationPath} from '../../codegen/validator/types'; -import {ValidationError} from '../../constants'; -import {JsonTextEncoderCodegenContext} from '../../codegen/json/JsonTextEncoderCodegenContext'; -import {CborEncoderCodegenContext} from '../../codegen/binary/CborEncoderCodegenContext'; -import {JsonEncoderCodegenContext} from '../../codegen/binary/JsonEncoderCodegenContext'; -import {BinaryEncoderCodegenContext} from '../../codegen/binary/BinaryEncoderCodegenContext'; -import {JsExpression} from '@jsonjoy.com/util/lib/codegen/util/JsExpression'; -import {MessagePackEncoderCodegenContext} from '../../codegen/binary/MessagePackEncoderCodegenContext'; -import {BinaryJsonEncoder} from '@jsonjoy.com/json-pack/lib/types'; -import {CapacityEstimatorCodegenContext} from '../../codegen/capacity/CapacityEstimatorCodegenContext'; -import {MaxEncodingOverhead} from '../../../json-size'; -import {AbstractType} from './AbstractType'; -import type * as jsonSchema from '../../../json-schema'; -import type {TypeSystem} from '../../system/TypeSystem'; -import type {json_string} from '@jsonjoy.com/util/lib/json-brand'; -import type * as ts from '../../typescript/types'; -import type {TypeExportContext} from '../../system/TypeExportContext'; -import type * as jtd from '../../jtd/types'; - -export class NumberType extends AbstractType { - constructor(protected schema: schema.NumberSchema) { - super(); - } - - public toJsonSchema(ctx?: TypeExportContext): jsonSchema.JsonSchemaNumber { - const schema = this.getSchema(); - const jsonSchema = { - type: 'number', - ...super.toJsonSchema(ctx), - }; - if (schema.format && ints.has(schema.format)) jsonSchema.type = 'integer'; - if (schema.gt !== undefined) jsonSchema.exclusiveMinimum = schema.gt; - if (schema.gte !== undefined) jsonSchema.minimum = schema.gte; - if (schema.lt !== undefined) jsonSchema.exclusiveMaximum = schema.lt; - if (schema.lte !== undefined) jsonSchema.maximum = schema.lte; - return jsonSchema; - } - - public validateSchema(): void { - const schema = this.getSchema(); - validateTType(schema, 'num'); - validateWithValidator(schema); - const {format, gt, gte, lt, lte} = schema; - if (gt !== undefined && typeof gt !== 'number') throw new Error('GT_TYPE'); - if (gte !== undefined && typeof gte !== 'number') throw new Error('GTE_TYPE'); - if (lt !== undefined && typeof lt !== 'number') throw new Error('LT_TYPE'); - if (lte !== undefined && typeof lte !== 'number') throw new Error('LTE_TYPE'); - if (gt !== undefined && gte !== undefined) throw new Error('GT_GTE'); - if (lt !== undefined && lte !== undefined) throw new Error('LT_LTE'); - if ((gt !== undefined || gte !== undefined) && (lt !== undefined || lte !== undefined)) - if ((gt ?? gte)! > (lt ?? lte)!) throw new Error('GT_LT'); - if (format !== undefined) { - if (typeof format !== 'string') throw new Error('FORMAT_TYPE'); - if (!format) throw new Error('FORMAT_EMPTY'); - switch (format) { - case 'i': - case 'u': - case 'f': - case 'i8': - case 'i16': - case 'i32': - case 'i64': - case 'u8': - case 'u16': - case 'u32': - case 'u64': - case 'f32': - case 'f64': - break; - default: - throw new Error('FORMAT_INVALID'); - } - } - } - - public codegenValidator(ctx: ValidatorCodegenContext, path: ValidationPath, r: string): void { - const {format, gt, gte, lt, lte} = this.schema; - if (format && ints.has(format)) { - const errInt = ctx.err(ValidationError.INT, path); - ctx.js(/* js */ `if(!Number.isInteger(${r})) return ${errInt};`); - if (uints.has(format)) { - const err = ctx.err(ValidationError.UINT, path); - ctx.js(/* js */ `if(${r} < 0) return ${err};`); - switch (format) { - case 'u8': { - ctx.js(/* js */ `if(${r} > 0xFF) return ${err};`); - break; - } - case 'u16': { - ctx.js(/* js */ `if(${r} > 0xFFFF) return ${err};`); - break; - } - case 'u32': { - ctx.js(/* js */ `if(${r} > 0xFFFFFFFF) return ${err};`); - break; - } - } - } else { - switch (format) { - case 'i8': { - ctx.js(/* js */ `if(${r} > 0x7F || ${r} < -0x80) return ${errInt};`); - break; - } - case 'i16': { - ctx.js(/* js */ `if(${r} > 0x7FFF || ${r} < -0x8000) return ${errInt};`); - break; - } - case 'i32': { - ctx.js(/* js */ `if(${r} > 0x7FFFFFFF || ${r} < -0x80000000) return ${errInt};`); - break; - } - } - } - } else if (floats.has(format)) { - const err = ctx.err(ValidationError.NUM, path); - ctx.codegen.js(/* js */ `if(!Number.isFinite(${r})) return ${err};`); - } else { - const err = ctx.err(ValidationError.NUM, path); - ctx.codegen.js(/* js */ `if(typeof ${r} !== "number") return ${err};`); - } - if (gt !== undefined) { - const err = ctx.err(ValidationError.GT, path); - ctx.codegen.js(/* js */ `if(${r} <= ${gt}) return ${err};`); - } - if (gte !== undefined) { - const err = ctx.err(ValidationError.GTE, path); - ctx.codegen.js(/* js */ `if(${r} < ${gte}) return ${err};`); - } - if (lt !== undefined) { - const err = ctx.err(ValidationError.LT, path); - ctx.codegen.js(/* js */ `if(${r} >= ${lt}) return ${err};`); - } - if (lte !== undefined) { - const err = ctx.err(ValidationError.LTE, path); - ctx.codegen.js(/* js */ `if(${r} > ${lte}) return ${err};`); - } - ctx.emitCustomValidators(this, path, r); - } - - public codegenJsonTextEncoder(ctx: JsonTextEncoderCodegenContext, value: JsExpression): void { - ctx.js(/* js */ `s += ${value.use()};`); - } - - private codegenBinaryEncoder(ctx: BinaryEncoderCodegenContext, value: JsExpression): void { - const {format} = this.schema; - const v = value.use(); - if (uints.has(format)) ctx.js(/* js */ `encoder.writeUInteger(${v});`); - else if (ints.has(format)) ctx.js(/* js */ `encoder.writeInteger(${v});`); - else if (floats.has(format)) ctx.js(/* js */ `encoder.writeFloat(${v});`); - else ctx.js(/* js */ `encoder.writeNumber(${v});`); - } - - public codegenCborEncoder(ctx: CborEncoderCodegenContext, value: JsExpression): void { - this.codegenBinaryEncoder(ctx, value); - } - - public codegenMessagePackEncoder(ctx: MessagePackEncoderCodegenContext, value: JsExpression): void { - this.codegenBinaryEncoder(ctx, value); - } - - public codegenJsonEncoder(ctx: JsonEncoderCodegenContext, value: JsExpression): void { - this.codegenBinaryEncoder(ctx, value); - } - - public codegenCapacityEstimator(ctx: CapacityEstimatorCodegenContext, value: JsExpression): void { - ctx.inc(MaxEncodingOverhead.Number); - } - - public random(): number { - let num = Math.random(); - let min = Number.MIN_SAFE_INTEGER; - let max = Number.MAX_SAFE_INTEGER; - if (this.schema.gt !== undefined) min = this.schema.gt; - if (this.schema.gte !== undefined) min = this.schema.gte + 0.000000000000001; - if (this.schema.lt !== undefined) max = this.schema.lt; - if (this.schema.lte !== undefined) max = this.schema.lte - 0.000000000000001; - if (this.schema.format) { - switch (this.schema.format) { - case 'i8': - min = Math.max(min, -0x80); - max = Math.min(max, 0x7f); - break; - case 'i16': - min = Math.max(min, -0x8000); - max = Math.min(max, 0x7fff); - break; - case 'i32': - min = Math.max(min, -0x80000000); - max = Math.min(max, 0x7fffffff); - break; - case 'i64': - case 'i': - min = Math.max(min, -0x8000000000); - max = Math.min(max, 0x7fffffffff); - break; - case 'u8': - min = Math.max(min, 0); - max = Math.min(max, 0xff); - break; - case 'u16': - min = Math.max(min, 0); - max = Math.min(max, 0xffff); - break; - case 'u32': - min = Math.max(min, 0); - max = Math.min(max, 0xffffffff); - break; - case 'u64': - case 'u': - min = Math.max(min, 0); - max = Math.min(max, 0xffffffffffff); - break; - } - return Math.round(num * (max - min)) + min; - } - num = num * (max - min) + min; - if (Math.random() > 0.7) num = Math.round(num); - if (num === -0) return 0; - return num; - } - - public toTypeScriptAst(): ts.TsNumberKeyword { - return {node: 'NumberKeyword'}; - } - - public toJson(value: unknown, system: TypeSystem | undefined = this.system) { - return ('' + value) as json_string; - } - - public toJtdForm(): jtd.JtdTypeForm { - switch (this.schema.format) { - case 'u8': - return {type: 'uint8'}; - case 'u16': - return {type: 'uint16'}; - case 'u32': - return {type: 'uint32'}; - case 'i8': - return {type: 'int8'}; - case 'i16': - return {type: 'int16'}; - case 'i32': - return {type: 'int32'}; - case 'f32': - return {type: 'float32'}; - default: - return {type: 'float64'}; - } - } -} diff --git a/src/json-type/type/classes/ObjectType.ts b/src/json-type/type/classes/ObjectType.ts deleted file mode 100644 index 75496a3d6b..0000000000 --- a/src/json-type/type/classes/ObjectType.ts +++ /dev/null @@ -1,588 +0,0 @@ -import {normalizeAccessor} from '@jsonjoy.com/util/lib/codegen/util/normalizeAccessor'; -import {JsExpression} from '@jsonjoy.com/util/lib/codegen/util/JsExpression'; -import {asString} from '@jsonjoy.com/util/lib/strings/asString'; -import {printTree} from 'tree-dump/lib/printTree'; -import * as schema from '../../schema'; -import {RandomJson} from '../../../json-random'; -import {validateTType, validateWithValidator} from '../../schema/validate'; -import {ValidatorCodegenContext} from '../../codegen/validator/ValidatorCodegenContext'; -import {ValidationPath} from '../../codegen/validator/types'; -import {ValidationError} from '../../constants'; -import {canSkipObjectKeyUndefinedCheck} from '../../codegen/validator/util'; -import {JsonTextEncoderCodegenContext} from '../../codegen/json/JsonTextEncoderCodegenContext'; -import {CborEncoderCodegenContext} from '../../codegen/binary/CborEncoderCodegenContext'; -import {JsonEncoderCodegenContext} from '../../codegen/binary/JsonEncoderCodegenContext'; -import {MessagePackEncoderCodegenContext} from '../../codegen/binary/MessagePackEncoderCodegenContext'; -import {CapacityEstimatorCodegenContext} from '../../codegen/capacity/CapacityEstimatorCodegenContext'; -import {MaxEncodingOverhead, maxEncodingCapacity} from '../../../json-size'; -import {AbstractType} from './AbstractType'; -import type * as jsonSchema from '../../../json-schema'; -import type {SchemaOf, SchemaOfObjectFields, Type} from '../types'; -import type {TypeSystem} from '../../system/TypeSystem'; -import type {json_string} from '@jsonjoy.com/util/lib/json-brand'; -import type * as ts from '../../typescript/types'; -import type {TypeExportContext} from '../../system/TypeExportContext'; -import type {ExcludeFromTuple, PickFromTuple} from '../../../util/types'; - -const augmentWithComment = ( - type: schema.Schema | schema.ObjectFieldSchema, - node: ts.TsDeclaration | ts.TsPropertySignature | ts.TsTypeLiteral, -) => { - if (type.title || type.description) { - let comment = ''; - if (type.title) comment += '# ' + type.title; - if (type.title && type.description) comment += '\n\n'; - if (type.description) comment += type.description; - node.comment = comment; - } -}; - -export class ObjectFieldType extends AbstractType< - schema.ObjectFieldSchema> -> { - protected schema: schema.ObjectFieldSchema; - - constructor( - public readonly key: K, - public readonly value: V, - ) { - super(); - this.schema = schema.s.prop(key, schema.s.any); - } - - public getSchema(): schema.ObjectFieldSchema> { - return { - ...this.schema, - type: this.value.getSchema() as any, - }; - } - - public getOptions(): schema.Optional>> { - const {kind, key, type, optional, ...options} = this.schema; - return options as any; - } - - public validateSchema(): void { - const schema = this.getSchema(); - validateTType(schema, 'field'); - const {key, optional} = schema; - if (typeof key !== 'string') throw new Error('KEY_TYPE'); - if (optional !== undefined && typeof optional !== 'boolean') throw new Error('OPTIONAL_TYPE'); - this.value.validateSchema(); - } - - protected toStringTitle(): string { - return `"${this.key}":`; - } - - public toString(tab: string = ''): string { - return super.toString(tab) + printTree(tab + ' ', [(tab) => this.value.toString(tab)]); - } -} - -export class ObjectOptionalFieldType extends ObjectFieldType { - public optional = true; - - constructor( - public readonly key: K, - public readonly value: V, - ) { - super(key, value); - this.schema = schema.s.propOpt(key, schema.s.any); - } - - protected toStringTitle(): string { - return `"${this.key}"?:`; - } -} - -export class ObjectType[] = ObjectFieldType[]> extends AbstractType< - schema.ObjectSchema> -> { - protected schema: schema.ObjectSchema = schema.s.obj; - - constructor(public readonly fields: F) { - super(); - } - - public getSchema(): schema.ObjectSchema> { - return { - ...this.schema, - fields: this.fields.map((f) => f.getSchema()) as any, - }; - } - - public toJsonSchema(ctx?: TypeExportContext): jsonSchema.JsonSchemaObject { - const jsonSchema = { - type: 'object', - properties: {}, - ...super.toJsonSchema(ctx), - }; - const required = []; - for (const field of this.fields) { - jsonSchema.properties![field.key] = field.value.toJsonSchema(ctx); - if (!(field instanceof ObjectOptionalFieldType)) required.push(field.key); - } - if (required.length) jsonSchema.required = required; - if (this.schema.unknownFields === false) jsonSchema.additionalProperties = false; - return jsonSchema; - } - - public getOptions(): schema.Optional>> { - const {kind, fields, ...options} = this.schema; - return options as any; - } - - public getField>>>( - key: K, - ): ObjectFieldType | undefined { - return this.fields.find((f) => f.key === key); - } - - public extend[]>(o: ObjectType): ObjectType<[...F, ...F2]> { - const type = new ObjectType([...this.fields, ...o.fields]) as ObjectType<[...F, ...F2]>; - type.system = this.system; - return type; - } - - public omit>>>( - key: K, - ): ObjectType>> { - const type = new ObjectType(this.fields.filter((f) => f.key !== key) as any); - type.system = this.system; - return type; - } - - public pick>>>( - key: K, - ): ObjectType>> { - const field = this.fields.find((f) => f.key === key); - if (!field) throw new Error('FIELD_NOT_FOUND'); - const type = new ObjectType([field] as any); - type.system = this.system; - return type; - } - - public validateSchema(): void { - const schema = this.getSchema(); - validateTType(schema, 'obj'); - validateWithValidator(schema); - const {fields, unknownFields} = schema; - if (!Array.isArray(fields)) throw new Error('FIELDS_TYPE'); - if (unknownFields !== undefined && typeof unknownFields !== 'boolean') throw new Error('UNKNOWN_FIELDS_TYPE'); - for (const field of this.fields) field.validateSchema(); - } - - public codegenValidator(ctx: ValidatorCodegenContext, path: ValidationPath, r: string): void { - const fields = this.fields; - const length = fields.length; - const canSkipObjectTypeCheck = ctx.options.unsafeMode && length > 0; - if (!canSkipObjectTypeCheck) { - const err = ctx.err(ValidationError.OBJ, path); - ctx.js(/* js */ `if (typeof ${r} !== 'object' || !${r} || (${r} instanceof Array)) return ${err};`); - } - const checkExtraKeys = length && !this.schema.unknownFields && !ctx.options.skipObjectExtraFieldsCheck; - if (checkExtraKeys) { - const rk = ctx.codegen.getRegister(); - ctx.js(`for (var ${rk} in ${r}) {`); - ctx.js( - `switch (${rk}) { case ${fields - .map((field) => JSON.stringify(field.key)) - .join(': case ')}: break; default: return ${ctx.err(ValidationError.KEYS, [...path, {r: rk}])};}`, - ); - ctx.js(`}`); - } - for (let i = 0; i < length; i++) { - const field = fields[i]; - const rv = ctx.codegen.getRegister(); - const accessor = normalizeAccessor(field.key); - const keyPath = [...path, field.key]; - if (field instanceof ObjectOptionalFieldType) { - ctx.js(/* js */ `var ${rv} = ${r}${accessor};`); - ctx.js(`if (${rv} !== undefined) {`); - field.value.codegenValidator(ctx, keyPath, rv); - ctx.js(`}`); - } else { - if (!canSkipObjectKeyUndefinedCheck((field.value as AbstractType).getSchema().kind)) { - const err = ctx.err(ValidationError.KEY, [...path, field.key]); - ctx.js(/* js */ `var ${rv} = ${r}${accessor};`); - ctx.js(/* js */ `if (${rv} === undefined) return ${err};`); - } - field.value.codegenValidator(ctx, keyPath, `${r}${accessor}`); - } - } - ctx.emitCustomValidators(this, path, r); - } - - public codegenJsonTextEncoder(ctx: JsonTextEncoderCodegenContext, value: JsExpression): void { - const {schema, fields} = this; - const codegen = ctx.codegen; - const r = codegen.getRegister(); - ctx.js(/* js */ `var ${r} = ${value.use()};`); - const rKeys = ctx.codegen.getRegister(); - if (schema.encodeUnknownFields) { - ctx.js(/* js */ `var ${rKeys} = new Set(Object.keys(${r}));`); - } - const requiredFields = fields.filter((field) => !(field instanceof ObjectOptionalFieldType)); - const optionalFields = fields.filter((field) => field instanceof ObjectOptionalFieldType); - ctx.writeText('{'); - for (let i = 0; i < requiredFields.length; i++) { - const field = requiredFields[i]; - if (i) ctx.writeText(','); - ctx.writeText(JSON.stringify(field.key) + ':'); - const accessor = normalizeAccessor(field.key); - const valueExpression = new JsExpression(() => `${r}${accessor}`); - if (schema.encodeUnknownFields) ctx.js(/* js */ `${rKeys}.delete(${JSON.stringify(field.key)});`); - field.value.codegenJsonTextEncoder(ctx, valueExpression); - } - const rHasFields = codegen.getRegister(); - if (!requiredFields.length) ctx.js(/* js */ `var ${rHasFields} = false;`); - for (let i = 0; i < optionalFields.length; i++) { - const field = optionalFields[i]; - const accessor = normalizeAccessor(field.key); - const rValue = codegen.getRegister(); - if (schema.encodeUnknownFields) ctx.js(/* js */ `${rKeys}.delete(${JSON.stringify(field.key)});`); - ctx.js(/* js */ `var ${rValue} = ${r}${accessor};`); - ctx.js(`if (${rValue} !== undefined) {`); - if (requiredFields.length) { - ctx.writeText(','); - } else { - ctx.js(`if (${rHasFields}) s += ',';`); - ctx.js(/* js */ `${rHasFields} = true;`); - } - ctx.writeText(JSON.stringify(field.key) + ':'); - const valueExpression = new JsExpression(() => `${rValue}`); - field.value.codegenJsonTextEncoder(ctx, valueExpression); - ctx.js(`}`); - } - if (schema.encodeUnknownFields) { - const [rList, ri, rLength, rk] = [codegen.r(), codegen.r(), codegen.r(), codegen.r()]; - ctx.js(`var ${rLength} = ${rKeys}.size; -if (${rLength}) { - var ${rk}, ${rList} = Array.from(${rKeys}.values()); - for (var ${ri} = 0; ${ri} < ${rLength}; ${ri}++) { - ${rk} = ${rList}[${ri}]; - s += ',' + asString(${rk}) + ':' + stringify(${r}[${rk}]); - } -}`); - } - ctx.writeText('}'); - } - - public codegenCborEncoder(ctx: CborEncoderCodegenContext, value: JsExpression): void { - const codegen = ctx.codegen; - const r = codegen.r(); - const fields = this.fields; - const length = fields.length; - const requiredFields = fields.filter((field) => !(field instanceof ObjectOptionalFieldType)); - const optionalFields = fields.filter((field) => field instanceof ObjectOptionalFieldType); - const requiredLength = requiredFields.length; - const optionalLength = optionalFields.length; - const encodeUnknownFields = !!this.schema.encodeUnknownFields; - const emitRequiredFields = () => { - for (let i = 0; i < requiredLength; i++) { - const field = requiredFields[i]; - ctx.blob(ctx.gen((encoder) => encoder.writeStr(field.key))); - const accessor = normalizeAccessor(field.key); - field.value.codegenCborEncoder(ctx, new JsExpression(() => `${r}${accessor}`)); - } - }; - const emitOptionalFields = () => { - for (let i = 0; i < optionalLength; i++) { - const field = optionalFields[i]; - const accessor = normalizeAccessor(field.key); - codegen.js(`if (${r}${accessor} !== undefined) {`); - ctx.blob(ctx.gen((encoder) => encoder.writeStr(field.key))); - field.value.codegenCborEncoder(ctx, new JsExpression(() => `${r}${accessor}`)); - codegen.js(`}`); - } - }; - const emitUnknownFields = () => { - const rKeys = codegen.r(); - const rKey = codegen.r(); - const ri = codegen.r(); - const rLength = codegen.r(); - const keys = fields.map((field) => JSON.stringify(field.key)); - const rKnownFields = codegen.addConstant(`new Set([${keys.join(',')}])`); - codegen.js(`var ${rKeys} = Object.keys(${r}), ${rLength} = ${rKeys}.length, ${rKey};`); - codegen.js(`for (var ${ri} = 0; ${ri} < ${rLength}; ${ri}++) {`); - codegen.js(`${rKey} = ${rKeys}[${ri}];`); - codegen.js(`if (${rKnownFields}.has(${rKey})) continue;`); - codegen.js(`encoder.writeStr(${rKey});`); - codegen.js(`encoder.writeAny(${r}[${rKey}]);`); - codegen.js(`}`); - }; - ctx.js(/* js */ `var ${r} = ${value.use()};`); - if (!encodeUnknownFields && !optionalLength) { - ctx.blob(ctx.gen((encoder) => encoder.writeObjHdr(length))); - emitRequiredFields(); - } else if (!encodeUnknownFields) { - ctx.blob(ctx.gen((encoder) => encoder.writeStartObj())); - emitRequiredFields(); - emitOptionalFields(); - ctx.blob(ctx.gen((encoder) => encoder.writeEndObj())); - } else { - ctx.blob(ctx.gen((encoder) => encoder.writeStartObj())); - emitRequiredFields(); - emitOptionalFields(); - emitUnknownFields(); - ctx.blob(ctx.gen((encoder) => encoder.writeEndObj())); - } - } - - public codegenMessagePackEncoder(ctx: MessagePackEncoderCodegenContext, value: JsExpression): void { - const codegen = ctx.codegen; - const r = codegen.r(); - const fields = this.fields; - const length = fields.length; - const requiredFields = fields.filter((field) => !(field instanceof ObjectOptionalFieldType)); - const optionalFields = fields.filter((field) => field instanceof ObjectOptionalFieldType); - const requiredLength = requiredFields.length; - const optionalLength = optionalFields.length; - const totalMaxKnownFields = requiredLength + optionalLength; - if (totalMaxKnownFields > 0xffff) throw new Error('Too many fields'); - const encodeUnknownFields = !!this.schema.encodeUnknownFields; - const rFieldCount = codegen.r(); - const emitRequiredFields = () => { - for (let i = 0; i < requiredLength; i++) { - const field = requiredFields[i]; - ctx.blob(ctx.gen((encoder) => encoder.writeStr(field.key))); - const accessor = normalizeAccessor(field.key); - field.value.codegenMessagePackEncoder(ctx, new JsExpression(() => `${r}${accessor}`)); - } - }; - const emitOptionalFields = () => { - for (let i = 0; i < optionalLength; i++) { - const field = optionalFields[i]; - const accessor = normalizeAccessor(field.key); - codegen.if(`${r}${accessor} !== undefined`, () => { - codegen.js(`${rFieldCount}++;`); - ctx.blob(ctx.gen((encoder) => encoder.writeStr(field.key))); - field.value.codegenMessagePackEncoder(ctx, new JsExpression(() => `${r}${accessor}`)); - }); - } - }; - const emitUnknownFields = () => { - const ri = codegen.r(); - const rKeys = codegen.r(); - const rKey = codegen.r(); - const rLength = codegen.r(); - const keys = fields.map((field) => JSON.stringify(field.key)); - const rKnownFields = codegen.addConstant(`new Set([${keys.join(',')}])`); - codegen.js(`var ${rKeys} = Object.keys(${r}), ${rLength} = ${rKeys}.length, ${rKey};`); - codegen.js(`for (var ${ri} = 0; ${ri} < ${rLength}; ${ri}++) {`); - codegen.js(`${rKey} = ${rKeys}[${ri}];`); - codegen.js(`if (${rKnownFields}.has(${rKey})) continue;`); - codegen.js(`${rFieldCount}++;`); - codegen.js(`encoder.writeStr(${rKey});`); - codegen.js(`encoder.writeAny(${r}[${rKey}]);`); - codegen.js(`}`); - }; - ctx.js(/* js */ `var ${r} = ${value.use()};`); - if (!encodeUnknownFields && !optionalLength) { - ctx.blob(ctx.gen((encoder) => encoder.writeObjHdr(length))); - emitRequiredFields(); - } else if (!encodeUnknownFields) { - codegen.js(`var ${rFieldCount} = ${requiredLength};`); - const rHeaderPosition = codegen.var('writer.x'); - ctx.blob(ctx.gen((encoder) => encoder.writeObjHdr(0xffff))); - emitRequiredFields(); - emitOptionalFields(); - codegen.js(`view.setUint16(${rHeaderPosition} + 1, ${rFieldCount});`); - } else { - codegen.js(`var ${rFieldCount} = ${requiredLength};`); - const rHeaderPosition = codegen.var('writer.x'); - ctx.blob(ctx.gen((encoder) => encoder.writeObjHdr(0xffffffff))); - emitRequiredFields(); - emitOptionalFields(); - emitUnknownFields(); - codegen.js(`view.setUint32(${rHeaderPosition} + 1, ${rFieldCount});`); - } - } - - public codegenJsonEncoder(ctx: JsonEncoderCodegenContext, value: JsExpression): void { - const codegen = ctx.codegen; - const r = codegen.var(value.use()); - const fields = this.fields; - const requiredFields = fields.filter((field) => !(field instanceof ObjectOptionalFieldType)); - const optionalFields = fields.filter((field) => field instanceof ObjectOptionalFieldType); - const requiredLength = requiredFields.length; - const optionalLength = optionalFields.length; - const encodeUnknownFields = !!this.schema.encodeUnknownFields; - const separatorBlob = ctx.gen((encoder) => encoder.writeObjSeparator()); - const keySeparatorBlob = ctx.gen((encoder) => encoder.writeObjKeySeparator()); - const endBlob = ctx.gen((encoder) => encoder.writeEndObj()); - const emitRequiredFields = () => { - for (let i = 0; i < requiredLength; i++) { - const field = requiredFields[i]; - ctx.blob( - ctx.gen((encoder) => { - encoder.writeStr(field.key); - encoder.writeObjKeySeparator(); - }), - ); - const accessor = normalizeAccessor(field.key); - field.value.codegenJsonEncoder(ctx, new JsExpression(() => `${r}${accessor}`)); - ctx.blob(separatorBlob); - } - }; - const emitOptionalFields = () => { - for (let i = 0; i < optionalLength; i++) { - const field = optionalFields[i]; - const accessor = normalizeAccessor(field.key); - codegen.if(`${r}${accessor} !== undefined`, () => { - ctx.blob( - ctx.gen((encoder) => { - encoder.writeStr(field.key); - }), - ); - ctx.blob(keySeparatorBlob); - field.value.codegenJsonEncoder(ctx, new JsExpression(() => `${r}${accessor}`)); - ctx.blob(separatorBlob); - }); - } - }; - const emitUnknownFields = () => { - const rKeys = codegen.r(); - const rKey = codegen.r(); - const ri = codegen.r(); - const rLength = codegen.r(); - const keys = fields.map((field) => JSON.stringify(field.key)); - const rKnownFields = codegen.addConstant(`new Set([${keys.join(',')}])`); - codegen.js(`var ${rKeys} = Object.keys(${r}), ${rLength} = ${rKeys}.length, ${rKey};`); - codegen.js(`for (var ${ri} = 0; ${ri} < ${rLength}; ${ri}++) {`); - codegen.js(`${rKey} = ${rKeys}[${ri}];`); - codegen.js(`if (${rKnownFields}.has(${rKey})) continue;`); - codegen.js(`encoder.writeStr(${rKey});`); - ctx.blob(keySeparatorBlob); - codegen.js(`encoder.writeAny(${r}[${rKey}]);`); - ctx.blob(separatorBlob); - codegen.js(`}`); - }; - const emitEnding = () => { - const rewriteLastSeparator = () => { - for (let i = 0; i < endBlob.length; i++) ctx.js(`uint8[writer.x - ${endBlob.length - i}] = ${endBlob[i]};`); - }; - if (requiredFields.length) { - rewriteLastSeparator(); - } else { - codegen.if( - `uint8[writer.x - 1] === ${separatorBlob[separatorBlob.length - 1]}`, - () => { - rewriteLastSeparator(); - }, - () => { - ctx.blob(endBlob); - }, - ); - } - }; - ctx.blob( - ctx.gen((encoder) => { - encoder.writeStartObj(); - }), - ); - if (!encodeUnknownFields && !optionalLength) { - emitRequiredFields(); - emitEnding(); - } else if (!encodeUnknownFields) { - emitRequiredFields(); - emitOptionalFields(); - emitEnding(); - } else { - emitRequiredFields(); - emitOptionalFields(); - emitUnknownFields(); - emitEnding(); - } - } - - public codegenCapacityEstimator(ctx: CapacityEstimatorCodegenContext, value: JsExpression): void { - const codegen = ctx.codegen; - const r = codegen.var(value.use()); - const encodeUnknownFields = !!this.schema.encodeUnknownFields; - if (encodeUnknownFields) { - codegen.js(`size += maxEncodingCapacity(${r});`); - return; - } - const fields = this.fields; - const overhead = MaxEncodingOverhead.Object + fields.length * MaxEncodingOverhead.ObjectElement; - ctx.inc(overhead); - for (const field of fields) { - ctx.inc(maxEncodingCapacity(field.key)); - const accessor = normalizeAccessor(field.key); - const isOptional = field instanceof ObjectOptionalFieldType; - const block = () => field.value.codegenCapacityEstimator(ctx, new JsExpression(() => `${r}${accessor}`)); - if (isOptional) { - codegen.if(`${r}${accessor} !== undefined`, block); - } else block(); - } - } - - public random(): Record { - const schema = this.schema; - const obj: Record = schema.unknownFields ? >RandomJson.genObject() : {}; - for (const field of this.fields) { - if (field instanceof ObjectOptionalFieldType) if (Math.random() > 0.5) continue; - obj[field.key] = field.value.random(); - } - return obj; - } - - public toTypeScriptAst(): ts.TsTypeLiteral { - const node: ts.TsTypeLiteral = { - node: 'TypeLiteral', - members: [], - }; - const fields = this.fields; - for (const field of fields) { - const member: ts.TsPropertySignature = { - node: 'PropertySignature', - name: field.key, - type: field.value.toTypeScriptAst(), - }; - if (field instanceof ObjectOptionalFieldType) member.optional = true; - augmentWithComment(field.getSchema(), member); - node.members.push(member); - } - if (this.schema.unknownFields || this.schema.encodeUnknownFields) - node.members.push({ - node: 'IndexSignature', - type: {node: 'UnknownKeyword'}, - }); - augmentWithComment(this.schema, node); - return node; - } - - public toJson(value: unknown, system: TypeSystem | undefined = this.system): json_string { - const fields = this.fields; - const length = fields.length; - if (!length) return '{}' as json_string; - const last = length - 1; - let str = '{'; - for (let i = 0; i < last; i++) { - const field = fields[i]; - const key = field.key; - const fieldType = field.value; - const val = (value as any)[key]; - if (val === undefined) continue; - str += asString(key) + ':' + fieldType.toJson(val as any, system) + ','; - } - const key = fields[last].key; - const val = (value as any)[key]; - if (val !== undefined) { - str += asString(key) + ':' + fields[last].value.toJson(val as any, system); - } else if (str.length > 1) str = str.slice(0, -1); - return (str + '}') as json_string; - } - - public toString(tab: string = ''): string { - return ( - super.toString(tab) + - printTree( - tab, - this.fields.map((field) => (tab) => field.toString(tab)), - ) - ); - } -} diff --git a/src/json-type/type/classes/OrType.ts b/src/json-type/type/classes/OrType.ts deleted file mode 100644 index 574c75fd57..0000000000 --- a/src/json-type/type/classes/OrType.ts +++ /dev/null @@ -1,184 +0,0 @@ -import * as schema from '../../schema'; -import {printTree} from 'tree-dump/lib/printTree'; -import {validateTType} from '../../schema/validate'; -import {ValidatorCodegenContext} from '../../codegen/validator/ValidatorCodegenContext'; -import {ValidationPath} from '../../codegen/validator/types'; -import {ValidationError} from '../../constants'; -import {JsonTextEncoderCodegenContext} from '../../codegen/json/JsonTextEncoderCodegenContext'; -import {CborEncoderCodegenContext} from '../../codegen/binary/CborEncoderCodegenContext'; -import {JsonEncoderCodegenContext} from '../../codegen/binary/JsonEncoderCodegenContext'; -import {BinaryEncoderCodegenContext} from '../../codegen/binary/BinaryEncoderCodegenContext'; -import {JsExpression} from '@jsonjoy.com/util/lib/codegen/util/JsExpression'; -import {MessagePackEncoderCodegenContext} from '../../codegen/binary/MessagePackEncoderCodegenContext'; -import {BinaryJsonEncoder} from '@jsonjoy.com/json-pack/lib/types'; -import {CapacityEstimatorCodegenContext} from '../../codegen/capacity/CapacityEstimatorCodegenContext'; -import {JsonExpressionCodegen} from '@jsonjoy.com/json-expression'; -import {operatorsMap} from '@jsonjoy.com/json-expression/lib/operators'; -import {Vars} from '@jsonjoy.com/json-expression/lib/Vars'; -import {Discriminator} from '../discriminator'; -import {AbstractType} from './AbstractType'; -import type * as jsonSchema from '../../../json-schema'; -import type {SchemaOf, Type} from '../types'; -import type {TypeSystem} from '../../system/TypeSystem'; -import type {json_string} from '@jsonjoy.com/util/lib/json-brand'; -import type * as ts from '../../typescript/types'; -import type {TypeExportContext} from '../../system/TypeExportContext'; - -export class OrType extends AbstractType}>> { - protected schema: schema.OrSchema; - - constructor( - protected types: T, - options?: Omit, - ) { - super(); - this.schema = { - ...schema.s.Or(), - ...options, - discriminator: options?.discriminator ?? Discriminator.createExpression(types), - }; - } - - public getSchema(): schema.OrSchema<{[K in keyof T]: SchemaOf}> { - return { - ...this.schema, - types: this.types.map((type) => type.getSchema()) as any, - }; - } - - public toJsonSchema(ctx?: TypeExportContext): jsonSchema.JsonSchemaOr { - return { - anyOf: this.types.map((type) => type.toJsonSchema(ctx)), - }; - } - - public getOptions(): schema.Optional}>> { - const {kind, types, ...options} = this.schema; - return options as any; - } - - public options(options: schema.Optional & Partial>): this { - Object.assign(this.schema, options); - return this; - } - - private __discriminator: undefined | ((val: unknown) => number) = undefined; - public discriminator(): (val: unknown) => number { - if (this.__discriminator) return this.__discriminator; - const expr = this.schema.discriminator; - if (!expr || (expr[0] === 'num' && expr[1] === 0)) throw new Error('NO_DISCRIMINATOR'); - const codegen = new JsonExpressionCodegen({ - expression: expr, - operators: operatorsMap, - }); - const fn = codegen.run().compile(); - return (this.__discriminator = (data: unknown) => +(fn(new Vars(data)) as any)); - } - - public validateSchema(): void { - const schema = this.getSchema(); - validateTType(schema, 'or'); - const {types, discriminator} = schema; - if (!discriminator || (discriminator[0] === 'num' && discriminator[1] === -1)) throw new Error('DISCRIMINATOR'); - if (!Array.isArray(types)) throw new Error('TYPES_TYPE'); - if (!types.length) throw new Error('TYPES_LENGTH'); - for (const type of this.types) type.validateSchema(); - } - - public codegenValidator(ctx: ValidatorCodegenContext, path: ValidationPath, r: string): void { - const types = this.types; - const codegen = ctx.codegen; - const length = types.length; - if (length === 1) { - types[0].codegenValidator(ctx, path, r); - return; - } - const discriminator = this.discriminator(); - const d = codegen.linkDependency(discriminator); - codegen.switch( - `${d}(${r})`, - types.map((type, index) => [ - index, - () => { - type.codegenValidator(ctx, path, r); - }, - ]), - () => { - const err = ctx.err(ValidationError.OR, path); - ctx.js(`return ${err}`); - }, - ); - } - - public codegenJsonTextEncoder(ctx: JsonTextEncoderCodegenContext, value: JsExpression): void { - ctx.js(/* js */ `s += stringify(${value.use()});`); - } - - private codegenBinaryEncoder(ctx: BinaryEncoderCodegenContext, value: JsExpression): void { - const codegen = ctx.codegen; - const discriminator = this.discriminator(); - const d = codegen.linkDependency(discriminator); - const types = this.types; - codegen.switch( - `${d}(${value.use()})`, - types.map((type, index) => [ - index, - () => { - if (ctx instanceof CborEncoderCodegenContext) type.codegenCborEncoder(ctx, value); - else if (ctx instanceof MessagePackEncoderCodegenContext) type.codegenMessagePackEncoder(ctx, value); - else if (ctx instanceof JsonEncoderCodegenContext) type.codegenJsonEncoder(ctx, value); - }, - ]), - ); - } - - public codegenCborEncoder(ctx: CborEncoderCodegenContext, value: JsExpression): void { - this.codegenBinaryEncoder(ctx, value); - } - - public codegenMessagePackEncoder(ctx: MessagePackEncoderCodegenContext, value: JsExpression): void { - this.codegenBinaryEncoder(ctx, value); - } - - public codegenJsonEncoder(ctx: JsonEncoderCodegenContext, value: JsExpression): void { - this.codegenBinaryEncoder(ctx, value); - } - - public codegenCapacityEstimator(ctx: CapacityEstimatorCodegenContext, value: JsExpression): void { - const codegen = ctx.codegen; - const discriminator = this.discriminator(); - const d = codegen.linkDependency(discriminator); - const types = this.types; - codegen.switch( - `${d}(${value.use()})`, - types.map((type, index) => [ - index, - () => { - type.codegenCapacityEstimator(ctx, value); - }, - ]), - ); - } - - public random(): unknown { - const types = this.types; - const index = Math.floor(Math.random() * types.length); - return types[index].random(); - } - - public toTypeScriptAst(): ts.TsUnionType { - const node: ts.TsUnionType = { - node: 'UnionType', - types: this.types.map((t) => t.toTypeScriptAst()), - }; - return node; - } - - public toJson(value: unknown, system: TypeSystem | undefined = this.system): json_string { - return JSON.stringify(value) as json_string; - } - - public toString(tab: string = ''): string { - return super.toString(tab) + printTree(tab, [...this.types.map((type) => (tab: string) => type.toString(tab))]); - } -} diff --git a/src/json-type/type/classes/RefType.ts b/src/json-type/type/classes/RefType.ts deleted file mode 100644 index 956f18d834..0000000000 --- a/src/json-type/type/classes/RefType.ts +++ /dev/null @@ -1,165 +0,0 @@ -import * as schema from '../../schema'; -import {validateTType} from '../../schema/validate'; -import {ValidatorCodegenContext} from '../../codegen/validator/ValidatorCodegenContext'; -import {ValidationPath} from '../../codegen/validator/types'; -import {ValidationError} from '../../constants'; -import {JsonTextEncoderCodegenContext} from '../../codegen/json/JsonTextEncoderCodegenContext'; -import {CompiledBinaryEncoder} from '../../codegen/types'; -import {CborEncoderCodegenContext} from '../../codegen/binary/CborEncoderCodegenContext'; -import {JsonEncoderCodegenContext} from '../../codegen/binary/JsonEncoderCodegenContext'; -import {BinaryEncoderCodegenContext} from '../../codegen/binary/BinaryEncoderCodegenContext'; -import {JsExpression} from '@jsonjoy.com/util/lib/codegen/util/JsExpression'; -import {MessagePackEncoderCodegenContext} from '../../codegen/binary/MessagePackEncoderCodegenContext'; -import {EncodingFormat} from '@jsonjoy.com/json-pack/lib/constants'; -import {BinaryJsonEncoder} from '@jsonjoy.com/json-pack/lib/types'; -import {CapacityEstimatorCodegenContext} from '../../codegen/capacity/CapacityEstimatorCodegenContext'; -import {AbstractType} from './AbstractType'; -import type * as jsonSchema from '../../../json-schema'; -import type {SchemaOf, Type} from '../types'; -import type {TypeSystem} from '../../system/TypeSystem'; -import type {json_string} from '@jsonjoy.com/util/lib/json-brand'; -import type * as ts from '../../typescript/types'; -import type {TypeExportContext} from '../../system/TypeExportContext'; - -export class RefType extends AbstractType>> { - protected schema: schema.RefSchema>; - - constructor(ref: string) { - super(); - this.schema = schema.s.Ref>(ref); - } - - public getRef(): string { - return this.schema.ref; - } - - public toJsonSchema(ctx?: TypeExportContext): jsonSchema.JsonSchemaRef { - const ref = this.schema.ref; - if (ctx) ctx.mentionRef(ref); - const jsonSchema = { - $ref: `#/$defs/${ref}`, - ...super.toJsonSchema(ctx), - }; - return jsonSchema; - } - - public getOptions(): schema.Optional>> { - const {kind, ref, ...options} = this.schema; - return options as any; - } - - public validateSchema(): void { - const schema = this.getSchema(); - validateTType(schema, 'ref'); - const {ref} = schema; - if (typeof ref !== 'string') throw new Error('REF_TYPE'); - if (!ref) throw new Error('REF_EMPTY'); - } - - public codegenValidator(ctx: ValidatorCodegenContext, path: ValidationPath, r: string): void { - const refErr = (errorRegister: string): string => { - switch (ctx.options.errors) { - case 'boolean': - return errorRegister; - case 'string': { - return ctx.err(ValidationError.REF, [...path, {r: errorRegister}]); - } - case 'object': - default: { - return ctx.err(ValidationError.REF, [...path], {refId: this.schema.ref, refError: errorRegister}); - } - } - }; - const system = ctx.options.system || this.system; - if (!system) throw new Error('NO_SYSTEM'); - const validator = system.resolve(this.schema.ref).type.validator(ctx.options.errors!); - const d = ctx.codegen.linkDependency(validator); - const rerr = ctx.codegen.getRegister(); - ctx.js(/* js */ `var ${rerr} = ${d}(${r});`); - ctx.js(/* js */ `if (${rerr}) return ${refErr(rerr)};`); - } - - public codegenJsonTextEncoder(ctx: JsonTextEncoderCodegenContext, value: JsExpression): void { - const system = ctx.options.system || this.system; - if (!system) throw new Error('NO_SYSTEM'); - const encoder = system.resolve(this.schema.ref).type.jsonTextEncoder(); - const d = ctx.codegen.linkDependency(encoder); - ctx.js(/* js */ `s += ${d}(${value.use()});`); - } - - private codegenBinaryEncoder(ctx: BinaryEncoderCodegenContext, value: JsExpression): void { - const system = ctx.options.system || this.system; - if (!system) throw new Error('NO_SYSTEM'); - const kind = - ctx instanceof CborEncoderCodegenContext - ? EncodingFormat.Cbor - : ctx instanceof MessagePackEncoderCodegenContext - ? EncodingFormat.MsgPack - : EncodingFormat.Json; - const targetType = system.resolve(this.schema.ref).type; - switch (targetType.getTypeName()) { - case 'str': - case 'bool': - case 'num': - case 'any': - case 'tup': { - if (ctx instanceof CborEncoderCodegenContext) targetType.codegenCborEncoder(ctx, value); - else if (ctx instanceof MessagePackEncoderCodegenContext) targetType.codegenMessagePackEncoder(ctx, value); - else if (ctx instanceof JsonEncoderCodegenContext) targetType.codegenJsonEncoder(ctx, value); - break; - } - default: { - const encoder = targetType.encoder(kind) as CompiledBinaryEncoder; - const d = ctx.codegen.linkDependency(encoder); - ctx.js(/* js */ `${d}(${value.use()}, encoder);`); - } - } - } - - public codegenCborEncoder(ctx: CborEncoderCodegenContext, value: JsExpression): void { - this.codegenBinaryEncoder(ctx, value); - } - - public codegenMessagePackEncoder(ctx: MessagePackEncoderCodegenContext, value: JsExpression): void { - this.codegenBinaryEncoder(ctx, value); - } - - public codegenJsonEncoder(ctx: JsonEncoderCodegenContext, value: JsExpression): void { - this.codegenBinaryEncoder(ctx, value); - } - - public codegenCapacityEstimator(ctx: CapacityEstimatorCodegenContext, value: JsExpression): void { - const system = ctx.options.system || this.system; - if (!system) throw new Error('NO_SYSTEM'); - const estimator = system.resolve(this.schema.ref).type.capacityEstimator(); - const d = ctx.codegen.linkDependency(estimator); - ctx.codegen.js(`size += ${d}(${value.use()});`); - } - - public random(): unknown { - if (!this.system) throw new Error('NO_SYSTEM'); - const alias = this.system.resolve(this.schema.ref); - return alias.type.random(); - } - - public toTypeScriptAst(): ts.TsGenericTypeAnnotation { - return { - node: 'GenericTypeAnnotation', - id: { - node: 'Identifier', - name: this.schema.ref, - }, - }; - } - - public toJson(value: unknown, system: TypeSystem | undefined = this.system): json_string { - if (!system) return 'null' as json_string; - const alias = system.resolve(this.schema.ref); - return alias.type.toJson(value, system) as json_string; - } - - public toStringTitle(tab: string = ''): string { - const options = this.toStringOptions(); - return `${super.toStringTitle()} → [${this.schema.ref}]` + (options ? ` ${options}` : ''); - } -} diff --git a/src/json-type/type/classes/StringType.ts b/src/json-type/type/classes/StringType.ts deleted file mode 100644 index 41e6396ed7..0000000000 --- a/src/json-type/type/classes/StringType.ts +++ /dev/null @@ -1,126 +0,0 @@ -import * as schema from '../../schema'; -import {RandomJson} from '../../../json-random'; -import {asString} from '@jsonjoy.com/util/lib/strings/asString'; -import {validateMinMax, validateTType, validateWithValidator} from '../../schema/validate'; -import {ValidatorCodegenContext} from '../../codegen/validator/ValidatorCodegenContext'; -import {ValidationPath} from '../../codegen/validator/types'; -import {ValidationError} from '../../constants'; -import {JsonTextEncoderCodegenContext} from '../../codegen/json/JsonTextEncoderCodegenContext'; -import {CborEncoderCodegenContext} from '../../codegen/binary/CborEncoderCodegenContext'; -import {JsonEncoderCodegenContext} from '../../codegen/binary/JsonEncoderCodegenContext'; -import {BinaryEncoderCodegenContext} from '../../codegen/binary/BinaryEncoderCodegenContext'; -import {JsExpression} from '@jsonjoy.com/util/lib/codegen/util/JsExpression'; -import {MessagePackEncoderCodegenContext} from '../../codegen/binary/MessagePackEncoderCodegenContext'; -import {BinaryJsonEncoder} from '@jsonjoy.com/json-pack/lib/types'; -import {CapacityEstimatorCodegenContext} from '../../codegen/capacity/CapacityEstimatorCodegenContext'; -import {MaxEncodingOverhead} from '../../../json-size'; -import {AbstractType} from './AbstractType'; -import type * as jsonSchema from '../../../json-schema'; -import type {TypeSystem} from '../../system/TypeSystem'; -import type {json_string} from '@jsonjoy.com/util/lib/json-brand'; -import type * as ts from '../../typescript/types'; -import type {TypeExportContext} from '../../system/TypeExportContext'; -import type * as jtd from '../../jtd/types'; - -export class StringType extends AbstractType { - constructor(protected schema: schema.StringSchema) { - super(); - } - - public toJsonSchema(ctx?: TypeExportContext): jsonSchema.JsonSchemaString { - const schema = this.getSchema(); - const jsonSchema = { - type: 'string', - ...super.toJsonSchema(ctx), - }; - if (schema.min !== undefined) jsonSchema.minLength = schema.min; - if (schema.max !== undefined) jsonSchema.maxLength = schema.max; - return jsonSchema; - } - - public validateSchema(): void { - const schema = this.getSchema(); - validateTType(schema, 'str'); - validateWithValidator(schema); - const {min, max, ascii, noJsonEscape} = schema; - validateMinMax(min, max); - if (ascii !== undefined) { - if (typeof ascii !== 'boolean') throw new Error('ASCII'); - } - if (noJsonEscape !== undefined) { - if (typeof noJsonEscape !== 'boolean') throw new Error('NO_JSON_ESCAPE_TYPE'); - } - } - - public codegenValidator(ctx: ValidatorCodegenContext, path: ValidationPath, r: string): void { - const error = ctx.err(ValidationError.STR, path); - ctx.js(/* js */ `if(typeof ${r} !== "string") return ${error};`); - const {min, max} = this.schema; - if (typeof min === 'number' && min === max) { - const err = ctx.err(ValidationError.STR_LEN, path); - ctx.js(/* js */ `if(${r}.length !== ${min}) return ${err};`); - } else { - if (typeof min === 'number') { - const err = ctx.err(ValidationError.STR_LEN, path); - ctx.js(/* js */ `if(${r}.length < ${min}) return ${err};`); - } - if (typeof max === 'number') { - const err = ctx.err(ValidationError.STR_LEN, path); - ctx.js(/* js */ `if(${r}.length > ${max}) return ${err};`); - } - } - ctx.emitCustomValidators(this, path, r); - } - - public codegenJsonTextEncoder(ctx: JsonTextEncoderCodegenContext, value: JsExpression): void { - if (this.schema.noJsonEscape) { - ctx.writeText('"'); - ctx.js(/* js */ `s += ${value.use()};`); - ctx.writeText('"'); - } else ctx.js(/* js */ `s += asString(${value.use()});`); - } - - private codegenBinaryEncoder(ctx: BinaryEncoderCodegenContext, value: JsExpression): void { - const ascii = this.schema.ascii; - const v = value.use(); - if (ascii) ctx.js(/* js */ `encoder.writeAsciiStr(${v});`); - else ctx.js(/* js */ `encoder.writeStr(${v});`); - } - - public codegenCborEncoder(ctx: CborEncoderCodegenContext, value: JsExpression): void { - this.codegenBinaryEncoder(ctx, value); - } - - public codegenMessagePackEncoder(ctx: MessagePackEncoderCodegenContext, value: JsExpression): void { - this.codegenBinaryEncoder(ctx, value); - } - - public codegenJsonEncoder(ctx: JsonEncoderCodegenContext, value: JsExpression): void { - this.codegenBinaryEncoder(ctx, value); - } - - public codegenCapacityEstimator(ctx: CapacityEstimatorCodegenContext, value: JsExpression): void { - ctx.inc(MaxEncodingOverhead.String); - ctx.codegen.js(`size += ${MaxEncodingOverhead.StringLengthMultiplier} * ${value.use()}.length;`); - } - - public random(): string { - let length = Math.round(Math.random() * 10); - const {min, max} = this.schema; - if (min !== undefined && length < min) length = min + length; - if (max !== undefined && length > max) length = max; - return RandomJson.genString(length); - } - - public toTypeScriptAst(): ts.TsStringKeyword { - return {node: 'StringKeyword'}; - } - - public toJson(value: unknown, system: TypeSystem | undefined = this.system): json_string { - return >(this.schema.noJsonEscape ? '"' + value + '"' : asString(value as string)); - } - - public toJtdForm(): jtd.JtdTypeForm { - return {type: 'string'}; - } -} diff --git a/src/json-type/type/classes/TupleType.ts b/src/json-type/type/classes/TupleType.ts deleted file mode 100644 index 00ac27582d..0000000000 --- a/src/json-type/type/classes/TupleType.ts +++ /dev/null @@ -1,187 +0,0 @@ -import * as schema from '../../schema'; -import {printTree} from 'tree-dump/lib/printTree'; -import {validateTType} from '../../schema/validate'; -import {ValidatorCodegenContext} from '../../codegen/validator/ValidatorCodegenContext'; -import {ValidationPath} from '../../codegen/validator/types'; -import {ValidationError} from '../../constants'; -import {JsonTextEncoderCodegenContext} from '../../codegen/json/JsonTextEncoderCodegenContext'; -import {CborEncoderCodegenContext} from '../../codegen/binary/CborEncoderCodegenContext'; -import {JsonEncoderCodegenContext} from '../../codegen/binary/JsonEncoderCodegenContext'; -import {JsExpression} from '@jsonjoy.com/util/lib/codegen/util/JsExpression'; -import {MessagePackEncoderCodegenContext} from '../../codegen/binary/MessagePackEncoderCodegenContext'; -import {CapacityEstimatorCodegenContext} from '../../codegen/capacity/CapacityEstimatorCodegenContext'; -import {MaxEncodingOverhead} from '../../../json-size'; -import {AbstractType} from './AbstractType'; -import type * as jsonSchema from '../../../json-schema'; -import type {SchemaOf, Type} from '../types'; -import type {TypeSystem} from '../../system/TypeSystem'; -import type {json_string} from '@jsonjoy.com/util/lib/json-brand'; -import type * as ts from '../../typescript/types'; -import type {TypeExportContext} from '../../system/TypeExportContext'; - -export class TupleType extends AbstractType}>> { - protected schema: schema.TupleSchema; - - constructor( - public readonly types: T, - options?: Omit, - ) { - super(); - this.schema = {...schema.s.Tuple(), ...options}; - } - - public getSchema(): schema.TupleSchema<{[K in keyof T]: SchemaOf}> { - return { - ...this.schema, - types: this.types.map((type) => type.getSchema()) as any, - }; - } - - public toJsonSchema(ctx?: TypeExportContext): jsonSchema.JsonSchemaArray { - const jsonSchema = { - type: 'array', - prefixItems: this.types.map((type) => type.toJsonSchema(ctx)), - items: false, - ...super.toJsonSchema(ctx), - }; - return jsonSchema; - } - - public getOptions(): schema.Optional}>> { - const {kind, types, ...options} = this.schema; - return options as any; - } - - public validateSchema(): void { - const schema = this.getSchema(); - validateTType(schema, 'tup'); - const {types} = schema; - if (!Array.isArray(types)) throw new Error('TYPES_TYPE'); - if (!types.length) throw new Error('TYPES_LENGTH'); - for (const type of this.types) type.validateSchema(); - } - - public codegenValidator(ctx: ValidatorCodegenContext, path: ValidationPath, r: string): void { - const err = ctx.err(ValidationError.TUP, path); - const types = this.types; - ctx.js(/* js */ `if (!Array.isArray(${r}) || ${r}.length !== ${types.length}) return ${err};`); - for (let i = 0; i < this.types.length; i++) { - const rv = ctx.codegen.getRegister(); - ctx.js(/* js */ `var ${rv} = ${r}[${i}];`); - types[i].codegenValidator(ctx, [...path, i], rv); - } - ctx.emitCustomValidators(this, path, r); - } - - public codegenJsonTextEncoder(ctx: JsonTextEncoderCodegenContext, value: JsExpression): void { - ctx.writeText('['); - const types = this.types; - const length = types.length; - const last = length - 1; - for (let i = 0; i < last; i++) { - types[i].codegenJsonTextEncoder(ctx, new JsExpression(() => `${value.use()}[${i}]`)); - ctx.writeText(','); - } - types[last].codegenJsonTextEncoder(ctx, new JsExpression(() => `${value.use()}[${last}]`)); - ctx.writeText(']'); - } - - private codegenBinaryEncoder( - ctx: CborEncoderCodegenContext | MessagePackEncoderCodegenContext, - value: JsExpression, - ): void { - const types = this.types; - const length = types.length; - ctx.blob( - ctx.gen((encoder) => { - encoder.writeArrHdr(length); - }), - ); - const r = ctx.codegen.r(); - ctx.js(/* js */ `var ${r} = ${value.use()};`); - for (let i = 0; i < length; i++) - if (ctx instanceof CborEncoderCodegenContext) - types[i].codegenCborEncoder(ctx, new JsExpression(() => `${r}[${i}]`)); - else types[i].codegenMessagePackEncoder(ctx, new JsExpression(() => `${r}[${i}]`)); - } - - public codegenCborEncoder(ctx: CborEncoderCodegenContext, value: JsExpression): void { - this.codegenBinaryEncoder(ctx, value); - } - - public codegenMessagePackEncoder(ctx: MessagePackEncoderCodegenContext, value: JsExpression): void { - this.codegenBinaryEncoder(ctx, value); - } - - public codegenJsonEncoder(ctx: JsonEncoderCodegenContext, value: JsExpression): void { - const codegen = ctx.codegen; - const expr = new JsExpression(() => `${rItem}`); - const r = codegen.var(value.use()); - const rItem = codegen.var(); - ctx.blob( - ctx.gen((encoder) => { - encoder.writeStartArr(); - }), - ); - const types = this.types; - const length = types.length; - const arrSepBlob = ctx.gen((encoder) => { - encoder.writeArrSeparator(); - }); - for (let i = 0; i < length; i++) { - const type = types[i]; - const isLast = i === length - 1; - codegen.js(`${rItem} = ${r}[${i}];`); - type.codegenJsonEncoder(ctx, expr); - if (!isLast) ctx.blob(arrSepBlob); - } - ctx.blob( - ctx.gen((encoder) => { - encoder.writeEndArr(); - }), - ); - } - - public codegenCapacityEstimator(ctx: CapacityEstimatorCodegenContext, value: JsExpression): void { - const codegen = ctx.codegen; - const r = codegen.var(value.use()); - const types = this.types; - const overhead = MaxEncodingOverhead.Array + MaxEncodingOverhead.ArrayElement * types.length; - ctx.inc(overhead); - for (let i = 0; i < types.length; i++) { - const type = types[i]; - const fn = type.compileCapacityEstimator({ - system: ctx.options.system, - name: ctx.options.name, - }); - const rFn = codegen.linkDependency(fn); - codegen.js(`size += ${rFn}(${r}[${i}]);`); - } - } - - public random(): unknown[] { - return this.types.map((type) => type.random()); - } - - public toTypeScriptAst(): ts.TsTupleType { - return { - node: 'TupleType', - elements: this.types.map((type) => type.toTypeScriptAst() as ts.TsType), - }; - } - - public toJson(value: unknown, system: TypeSystem | undefined = this.system): json_string { - const types = this.types; - const length = types.length; - if (!length) return '[]' as json_string; - const last = length - 1; - let str = '['; - for (let i = 0; i < last; i++) str += (types[i] as any).toJson((value as unknown[])[i] as any, system) + ','; - str += (types[last] as any).toJson((value as unknown[])[last] as any, system); - return (str + ']') as json_string; - } - - public toString(tab: string = ''): string { - return super.toString(tab) + printTree(tab, [...this.types.map((type) => (tab: string) => type.toString(tab))]); - } -} diff --git a/src/json-type/type/classes/__tests__/ObjectType.spec.ts b/src/json-type/type/classes/__tests__/ObjectType.spec.ts deleted file mode 100644 index e686e931e9..0000000000 --- a/src/json-type/type/classes/__tests__/ObjectType.spec.ts +++ /dev/null @@ -1,84 +0,0 @@ -import {t} from '../..'; -import {ResolveType} from '../../../system'; - -describe('.extend()', () => { - test('can extend an object', () => { - const obj1 = t.Object(t.prop('a', t.str)); - const obj2 = t.Object(t.prop('b', t.num)); - const obj3 = obj1.extend(obj2); - expect(typeof obj1.getField('a')).toBe('object'); - expect(typeof obj1.getField('b' as any)).toBe('undefined'); - expect(typeof obj2.getField('a' as any)).toBe('undefined'); - expect(typeof obj2.getField('b')).toBe('object'); - expect(typeof obj3.getField('a')).toBe('object'); - expect(typeof obj3.getField('b')).toBe('object'); - const val1: ResolveType = { - a: 'hello', - }; - const val2: ResolveType = { - b: 123, - }; - const val3: ResolveType = { - a: 'hello', - b: 123, - }; - }); - - test('can extend an empty object', () => { - const obj1 = t.Object(); - const obj2 = t.Object(t.prop('b', t.num)); - const obj3 = obj1.extend(obj2); - expect(typeof obj1.getField('b')).toBe('undefined'); - expect(typeof obj2.getField('b')).toBe('object'); - expect(typeof obj3.getField('b')).toBe('object'); - const val1: ResolveType = {}; - const val2: ResolveType = { - b: 123, - }; - const val3: ResolveType = { - b: 123, - }; - }); -}); - -describe('.omit()', () => { - test('can remove a field from an object', () => { - const obj1 = t.Object(t.prop('a', t.str), t.prop('b', t.num)); - const obj2 = obj1.omit('b'); - expect(typeof obj1.getField('a')).toBe('object'); - expect(typeof obj1.getField('b')).toBe('object'); - expect(typeof obj2.getField('a')).toBe('object'); - expect(typeof obj2.getField('b' as any)).toBe('undefined'); - const val1: ResolveType = { - a: 'hello', - b: 123, - }; - const val2: ResolveType = { - a: 'hello', - }; - }); -}); - -describe('.pick()', () => { - test('can pick a field from object', () => { - const obj1 = t.Object(t.prop('a', t.str), t.prop('b', t.num)); - const obj2 = obj1.pick('a'); - const obj3 = obj1.pick('b'); - expect(typeof obj1.getField('a')).toBe('object'); - expect(typeof obj1.getField('b')).toBe('object'); - expect(typeof obj2.getField('a')).toBe('object'); - expect(typeof obj2.getField('b' as any)).toBe('undefined'); - expect(typeof obj3.getField('a' as any)).toBe('undefined'); - expect(typeof obj3.getField('b')).toBe('object'); - const val1: ResolveType = { - a: 'hello', - b: 123, - }; - const val2: ResolveType = { - a: 'hello', - }; - const val3: ResolveType = { - b: 123, - }; - }); -}); diff --git a/src/json-type/type/classes/types.ts b/src/json-type/type/classes/types.ts deleted file mode 100644 index 2077f6efe5..0000000000 --- a/src/json-type/type/classes/types.ts +++ /dev/null @@ -1,7 +0,0 @@ -import type {JsonTypeValidator} from '../../codegen/validator/types'; - -export interface Validators { - object?: JsonTypeValidator; - string?: JsonTypeValidator; - boolean?: JsonTypeValidator; -} diff --git a/src/json-type/type/discriminator.ts b/src/json-type/type/discriminator.ts deleted file mode 100644 index a489105236..0000000000 --- a/src/json-type/type/discriminator.ts +++ /dev/null @@ -1,98 +0,0 @@ -import {Expr} from '@jsonjoy.com/json-expression'; -import {BooleanType, ConstType, NumberType, ObjectFieldType, ObjectType, StringType, TupleType} from './classes'; -import {Type} from './types'; - -export class Discriminator { - public static findConst(type: Type): Discriminator | undefined { - if (type instanceof ConstType) return new Discriminator('', type); - else if (type instanceof TupleType) { - const types = type.types; - for (let i = 0; i < types.length; i++) { - const t = types[i]; - const d = Discriminator.findConst(t); - if (d) return new Discriminator('/' + i + d.path, d.type); - } - } else if (type instanceof ObjectType) { - const fields = type.fields as ObjectFieldType[]; - for (let i = 0; i < fields.length; i++) { - const f = fields[i]; - const d = Discriminator.findConst(f.value); - if (d) return new Discriminator('/' + f.key + d.path, d.type); - } - } - return undefined; - } - - public static find(type: Type): Discriminator { - const constDiscriminator = Discriminator.findConst(type); - return constDiscriminator ?? new Discriminator('', type); - } - - public static createExpression(types: Type[]): Expr { - const length = types.length; - const specifiers = new Set(); - const discriminators: Discriminator[] = []; - for (let i = 1; i < length; i++) { - const type = types[i]; - const d = Discriminator.find(type); - const specifier = d.toSpecifier(); - if (specifiers.has(specifier)) throw new Error('Duplicate discriminator: ' + specifier); - specifiers.add(specifier); - discriminators.push(d); - } - let expr: Expr = 0; - for (let i = 0; i < discriminators.length; i++) { - const d = discriminators[i]; - expr = ['?', d.condition(), i + 1, expr]; - } - return expr; - } - - constructor( - public readonly path: string, - public readonly type: Type, - ) {} - - condition(): Expr { - if (this.type instanceof ConstType) return ['==', this.type.value(), ['$', this.path]]; - if (this.type instanceof BooleanType) return ['==', ['type', ['$', this.path]], 'boolean']; - if (this.type instanceof NumberType) return ['==', ['type', ['$', this.path]], 'number']; - if (this.type instanceof StringType) return ['==', ['type', ['$', this.path]], 'string']; - switch (this.typeSpecifier()) { - case 'obj': - return ['==', ['type', ['$', this.path]], 'object']; - case 'arr': - return ['==', ['type', ['$', this.path]], 'array']; - } - throw new Error('Cannot create condition for discriminator: ' + this.toSpecifier()); - } - - typeSpecifier(): string { - const mnemonic = this.type.getTypeName(); - switch (mnemonic) { - case 'bool': - case 'str': - case 'num': - case 'const': - return mnemonic; - case 'obj': - case 'map': - return 'obj'; - case 'arr': - case 'tup': - return 'arr'; - case 'fn': - case 'fn$': - return 'fn'; - } - return ''; - } - - toSpecifier(): string { - const type = this.type; - const path = this.path; - const typeSpecifier = this.typeSpecifier(); - const value = type instanceof ConstType ? type.value() : 0; - return JSON.stringify([path, typeSpecifier, value]); - } -} diff --git a/src/json-type/type/index.ts b/src/json-type/type/index.ts deleted file mode 100644 index f51a0d84b1..0000000000 --- a/src/json-type/type/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -export * from './types'; -export * from './classes'; - -import {TypeBuilder} from './TypeBuilder'; - -export const t = new TypeBuilder(); diff --git a/src/json-type/type/types.ts b/src/json-type/type/types.ts deleted file mode 100644 index d92c32a70f..0000000000 --- a/src/json-type/type/types.ts +++ /dev/null @@ -1,50 +0,0 @@ -import type * as schema from '../schema'; -import type * as classes from './classes'; - -export type * from './classes'; - -export interface BaseType { - getSchema(): S; -} - -export type Type = - | classes.AnyType - | classes.ConstType - | classes.BooleanType - | classes.NumberType - | classes.StringType - | classes.BinaryType - | classes.ArrayType - | classes.TupleType - | classes.ObjectType - | classes.MapType - | classes.RefType - | classes.OrType - | classes.FunctionType - | classes.FunctionStreamingType; - -export type SchemaOf = T extends BaseType ? U : never; -export type SchemaOfMap> = {[K in keyof M]: SchemaOf}; - -export type SchemaOfObjectFieldType = - F extends classes.ObjectOptionalFieldType - ? schema.ObjectOptionalFieldSchema> - : F extends classes.ObjectFieldType - ? schema.ObjectFieldSchema> - : never; - -export type SchemaOfObjectFields = {[K in keyof F]: SchemaOfObjectFieldType}; - -export type TypeMap = {[name: string]: schema.Schema}; - -export type FilterFunctions = { - [K in keyof T as T[K] extends classes.FunctionType - ? K - : T[K] extends classes.FunctionStreamingType - ? K - : never]: T[K] extends classes.FunctionType - ? T[K] - : T[K] extends classes.FunctionStreamingType - ? T[K] - : never; -}; diff --git a/src/json-type/typescript/toText.ts b/src/json-type/typescript/toText.ts deleted file mode 100644 index 6604f083ad..0000000000 --- a/src/json-type/typescript/toText.ts +++ /dev/null @@ -1,124 +0,0 @@ -import {wordWrap} from '@jsonjoy.com/util/lib/strings/wordWrap'; -import {TsIdentifier, TsNode, TsParameter} from './types'; -import {TAB, isSimpleType, normalizeKey} from './util'; - -const formatComment = (comment: string | undefined, __: string): string => { - if (!comment) return ''; - const lines = wordWrap(comment, {width: 80 - 3 - __.length}); - return __ + '/**\n' + __ + ' * ' + lines.join('\n' + __ + ' * ') + '\n' + __ + ' */\n'; -}; - -export const toText = (node: TsNode | TsNode[] | TsIdentifier | TsParameter, __: string = ''): string => { - if (Array.isArray(node)) return node.map((s) => toText(s, __)).join('\n'); - - const ____ = __ + TAB; - - switch (node.node) { - case 'ModuleDeclaration': { - let out: string = ''; - out += `${__}${node.export ? 'export ' : ''}namespace ${node.name} {\n`; - out += toText(node.statements, ____); - out += `${__}}\n`; - return out; - } - case 'InterfaceDeclaration': { - const {name, members, comment} = node; - let out: string = ''; - out += formatComment(comment, __); - out += `${__}${node.export ? 'export ' : ''}interface ${name} {\n`; - out += toText(members, ____); - out += `\n${__}}\n`; - return out; - } - case 'TypeAliasDeclaration': { - let out: string = ''; - out += formatComment(node.comment, __); - out += `${__}${node.export ? 'export ' : ''}type ${node.name} = ${toText(node.type, __)};\n`; - return out; - } - case 'PropertySignature': { - const name = normalizeKey(node.name); - let out: string = ''; - out += formatComment(node.comment, __); - return out + `${__}${name}${node.optional ? '?' : ''}: ${toText(node.type, __)};`; - } - case 'IndexSignature': { - return `${__}[key: string]: ${toText(node.type, __)};`; - } - case 'ArrayType': { - const simple = isSimpleType(node.elementType); - const inner = toText(node.elementType, __); - return simple ? `${inner}[]` : `Array<${inner}>`; - } - case 'TupleType': { - const hasObject = node.elements.some((e) => e.node === 'TypeLiteral'); - if (hasObject) { - return `[\n${____}${node.elements.map((e) => toText(e, ____)).join(',\n' + ____)}\n${__}]`; - } else return `[${node.elements.map((e) => toText(e, __)).join(', ')}]`; - } - case 'GenericTypeAnnotation': { - return node.id.name; - } - case 'StringKeyword': { - return 'string'; - } - case 'NumberKeyword': { - return 'number'; - } - case 'BooleanKeyword': { - return 'boolean'; - } - case 'NullKeyword': { - return 'null'; - } - case 'AnyKeyword': { - return 'any'; - } - case 'UnknownKeyword': { - return 'unknown'; - } - case 'TypeLiteral': { - return !node.members.length ? '{}' : `{\n${toText(node.members, ____)}\n${__}}`; - } - case 'StringLiteral': { - return JSON.stringify(node.text); - } - case 'NumericLiteral': { - return node.text; - } - case 'TrueKeyword': { - return 'true'; - } - case 'FalseKeyword': { - return 'false'; - } - case 'UnionType': { - return node.types.map((t) => toText(t, ____)).join(' | '); - } - case 'TypeReference': { - return ( - (typeof node.typeName === 'string' ? node.typeName : toText(node.typeName, __)) + - (node.typeArguments && node.typeArguments.length > 0 - ? `<${node.typeArguments.map((t) => toText(t, __)).join(', ')}>` - : '') - ); - } - case 'Identifier': { - return node.name; - } - case 'FunctionType': { - const {parameters, type} = node; - const params = parameters.map((p) => toText(p, __)).join(', '); - return `(${params}) => ${toText(type, __)}`; - } - case 'ObjectKeyword': { - return 'object'; - } - case 'Parameter': { - const {name, type} = node; - return `${toText(name, __)}: ${toText(type, __)}`; - } - } - - return ''; -}; diff --git a/src/json-type/typescript/types.ts b/src/json-type/typescript/types.ts deleted file mode 100644 index 83398a51be..0000000000 --- a/src/json-type/typescript/types.ts +++ /dev/null @@ -1,178 +0,0 @@ -/** A module declaration, e.g. "namespace Foo {". */ -export interface TsModuleDeclaration { - node: 'ModuleDeclaration'; - name: string; - statements: TsDeclaration[]; - comment?: string; - export?: boolean; -} - -/** An interface declaration, e.g. "interface Bar {". */ -export interface TsInterfaceDeclaration { - node: 'InterfaceDeclaration'; - name: string; - members: Array; - comment?: string; - export?: boolean; -} - -/** A property of an interface type. */ -export interface TsPropertySignature { - node: 'PropertySignature'; - name: string; - type: TsType; - optional?: boolean; - comment?: string; -} - -/** An index interface signature, e.g. "[key: string]: unknown". */ -export interface TsIndexSignature { - node: 'IndexSignature'; - type: TsType; -} - -/** A type alias declaration, e.g. "type Baz = ...". */ -export interface TsTypeAliasDeclaration { - node: 'TypeAliasDeclaration'; - name: string; - type: TsType; - comment?: string; - export?: boolean; -} - -/** All possible declarations that can be statements of a module. */ -export type TsDeclaration = TsModuleDeclaration | TsInterfaceDeclaration | TsTypeAliasDeclaration; - -/** An "Array<*>" type. */ -export interface TsArrayType { - node: 'ArrayType'; - elementType: TsType; -} - -export interface TsTupleType { - node: 'TupleType'; - elements: TsType[]; -} - -/** "string" */ -export interface TsStringKeyword { - node: 'StringKeyword'; -} - -/** "number" */ -export interface TsNumberKeyword { - node: 'NumberKeyword'; -} - -/** "boolean" */ -export interface TsBooleanKeyword { - node: 'BooleanKeyword'; -} - -/** "null" */ -export interface TsNullKeyword { - node: 'NullKeyword'; -} - -/** "any" */ -export interface TsAnyKeyword { - node: 'AnyKeyword'; -} - -/** "object" */ -export interface TsObjectKeyword { - node: 'ObjectKeyword'; -} - -/** "unknown" */ -export interface TsUnknownKeyword { - node: 'UnknownKeyword'; -} - -/** Inline interface type. */ -export interface TsTypeLiteral { - node: 'TypeLiteral'; - members: Array; - comment?: string; -} - -/** Exact string as type. */ -export interface TsStringLiteral { - node: 'StringLiteral'; - text: string; -} - -/** Exact number as type. */ -export interface TsNumericLiteral { - node: 'NumericLiteral'; - text: string; -} - -/** "true" */ -export interface TsTrueKeyword { - node: 'TrueKeyword'; -} - -/** "false" */ -export interface TsFalseKeyword { - node: 'FalseKeyword'; -} - -/** List of types separated by "|" pipe. */ -export interface TsUnionType { - node: 'UnionType'; - types: TsType[]; -} - -export interface TsIdentifier { - node: 'Identifier'; - name: string; -} - -export interface TsGenericTypeAnnotation { - node: 'GenericTypeAnnotation'; - id: TsIdentifier; -} - -/** A reference to a type alias, e.g. "foo: Reference". */ -export interface TsTypeReference { - node: 'TypeReference'; - typeName: string | TsIdentifier; - typeArguments?: TsType[]; -} - -export interface TsFunctionType { - node: 'FunctionType'; - parameters: TsParameter[]; - type: TsType; -} - -export interface TsParameter { - node: 'Parameter'; - name: TsIdentifier; - type: TsType; -} - -/** All type annotations. */ -export type TsType = - | TsAnyKeyword - | TsUnknownKeyword - | TsNullKeyword - | TsBooleanKeyword - | TsTrueKeyword - | TsFalseKeyword - | TsNumberKeyword - | TsStringKeyword - | TsStringLiteral - | TsArrayType - | TsTupleType - | TsObjectKeyword - | TsTypeLiteral - | TsNumericLiteral - | TsUnionType - | TsTypeReference - | TsGenericTypeAnnotation - | TsFunctionType; - -/** Any possible TypeScript AST node. */ -export type TsNode = TsDeclaration | TsType | TsPropertySignature | TsIndexSignature; diff --git a/src/json-type/typescript/util.ts b/src/json-type/typescript/util.ts deleted file mode 100644 index 063d291bfa..0000000000 --- a/src/json-type/typescript/util.ts +++ /dev/null @@ -1,73 +0,0 @@ -import type {TsNode} from './types'; - -export const TAB = ' '; - -export const keywords = new Set([ - 'break', - 'case', - 'catch', - 'class', - 'const', - 'continue', - 'debugger', - 'default', - 'delete', - 'do', - 'else', - 'enum', - 'export', - 'extends', - 'false', - 'finally', - 'for', - 'function', - 'if', - 'import', - 'in', - 'instanceof', - 'new', - 'null', - 'return', - 'super', - 'switch', - 'this', - 'throw', - 'true', - 'try', - 'typeof', - 'var', - 'void', - 'while', - 'with', - 'as', - 'implements', - 'interface', - 'let', - 'package', - 'private', - 'protected', - 'public', - 'static', - 'yield', - 'any', - 'boolean', - 'constructor', - 'declare', - 'get', - 'module', - 'require', - 'number', - 'set', - 'string', - 'symbol', - 'type', - 'from', - 'of', - 'unknown', -]); - -export const normalizeKey = (prop: string): string => - /^[a-z_][a-z_0-9]*$/i.test(prop) && !keywords.has(prop) ? prop : JSON.stringify(prop); - -export const isSimpleType = ({node}: TsNode): boolean => - node === 'NumberKeyword' || node === 'StringKeyword' || node === 'BooleanKeyword'; diff --git a/src/json-type/util.ts b/src/json-type/util.ts deleted file mode 100644 index cb7c378d35..0000000000 --- a/src/json-type/util.ts +++ /dev/null @@ -1,11 +0,0 @@ -import {NumberSchema} from './schema'; - -export const UINTS: NumberSchema['format'][] = ['u', 'u8', 'u16', 'u32', 'u64']; -export const INTS: NumberSchema['format'][] = ['i', 'i8', 'i16', 'i32', 'i64', ...UINTS]; -export const FLOATS: NumberSchema['format'][] = ['f', 'f32', 'f64']; - -export const uints = new Set(UINTS); -export const ints = new Set(INTS); -export const floats = new Set(FLOATS); - -export const primitives = new Set(['nil', 'undef', 'bool', 'num', 'str', 'bin']); diff --git a/yarn.lock b/yarn.lock index 678236ea2b..9ebdbfccbc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -569,10 +569,10 @@ "@jsonjoy.com/json-pointer" "^1.0.0" "@jsonjoy.com/util" "^1.3.0" -"@jsonjoy.com/json-pack@^1.0.4": - version "1.0.4" - resolved "https://registry.yarnpkg.com/@jsonjoy.com/json-pack/-/json-pack-1.0.4.tgz#ab59c642a2e5368e8bcfd815d817143d4f3035d0" - integrity sha512-aOcSN4MeAtFROysrbqG137b7gaDDSmVrl5mpo6sT/w+kcXpWnzhMjmY/Fh/sDx26NBxyIE7MB1seqLeCAzy9Sg== +"@jsonjoy.com/json-pack@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@jsonjoy.com/json-pack/-/json-pack-1.1.0.tgz#33ca57ee29d12feef540f2139225597469dec894" + integrity sha512-zlQONA+msXPPwHWZMKFVS78ewFczIll5lXiVPwFPCZUsrOKdxc2AvxU1HoNBmMRhqDZUR9HkC3UOm+6pME6Xsg== dependencies: "@jsonjoy.com/base64" "^1.1.1" "@jsonjoy.com/util" "^1.1.2" @@ -586,6 +586,17 @@ dependencies: "@jsonjoy.com/util" "^1.3.0" +"@jsonjoy.com/json-type@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@jsonjoy.com/json-type/-/json-type-1.0.0.tgz#e7b36a5f87386b5153c374ab3f7601071f46b687" + integrity sha512-tuIjos9v1aEfrWUjD97pGyMe/DVC1Kc2iktDrSD4U3FzPjRThiG+LR1QkSKVQajNqpSQIo0u+r+bpflRmT1enw== + dependencies: + "@jsonjoy.com/json-expression" "^1.0.0" + "@jsonjoy.com/json-pack" "^1.1.0" + "@jsonjoy.com/util" "^1.4.0" + sonic-forest "^1.0.3" + tree-dump "^1.0.2" + "@jsonjoy.com/util@^1.1.2": version "1.1.3" resolved "https://registry.yarnpkg.com/@jsonjoy.com/util/-/util-1.1.3.tgz#75b1c3cf21b70e665789d1ad3eabeff8b7fd1429" @@ -596,6 +607,11 @@ resolved "https://registry.yarnpkg.com/@jsonjoy.com/util/-/util-1.3.0.tgz#e5623885bb5e0c48c1151e4dae422fb03a5887a1" integrity sha512-Cebt4Vk7k1xHy87kHY7KSPLT77A7Ev7IfOblyLZhtYEhrdQ6fX4EoLq3xOQ3O/DRMEh2ok5nyC180E+ABS8Wmw== +"@jsonjoy.com/util@^1.4.0": + version "1.4.0" + resolved "https://registry.yarnpkg.com/@jsonjoy.com/util/-/util-1.4.0.tgz#42d2d7a0b1e961446a5749ef6bc1d6e5dd093ea8" + integrity sha512-LLGQO+U1CZyBqPTLkvP3vhWDMmF5BJHSUeiroA3cc6LfyJL5skfcgDhA+EB94lyHIE/z+eZmGBwXYaDO103Qfw== + "@sinclair/typebox@^0.27.8": version "0.27.8" resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.27.8.tgz#6667fac16c436b5434a387a34dedb013198f6e6e"