From 7b6909c6e128975801f13ab2c4e6d94be5e31cdc Mon Sep 17 00:00:00 2001 From: streamich Date: Sun, 10 Aug 2025 00:36:46 +0200 Subject: [PATCH 01/18] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20allow=20"key"=20ty?= =?UTF-8?q?pe=20in=20tuples?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/__tests__/fixtures.ts | 20 +++++------ src/codegen/json/__tests__/json.spec.ts | 32 ++++++++--------- .../validator/__tests__/codegen.spec.ts | 34 +++++++++--------- src/schema/SchemaBuilder.ts | 4 +-- src/schema/__tests__/SchemaBuilder.spec.ts | 2 +- src/schema/__tests__/TypeOf.spec.ts | 35 +++++++++++-------- src/schema/__tests__/type.spec.ts | 16 ++++----- src/schema/schema.ts | 24 +++++++------ src/type/classes/ObjType.ts | 4 +-- 9 files changed, 90 insertions(+), 81 deletions(-) diff --git a/src/__tests__/fixtures.ts b/src/__tests__/fixtures.ts index 3709e62b..aa398661 100644 --- a/src/__tests__/fixtures.ts +++ b/src/__tests__/fixtures.ts @@ -33,29 +33,29 @@ export const primitiveSchemas = { export const compositeSchemas = { simpleArray: s.Array(s.String()), arrayWithBounds: s.Array(s.Number(), {min: 2, max: 5}), - simpleObject: s.Object([s.prop('id', s.String()), s.prop('name', s.String()), s.prop('active', s.Boolean())]), + simpleObject: s.Object([s.Key('id', s.String()), s.Key('name', s.String()), s.Key('active', s.Boolean())]), objectWithOptionalFields: s.Object([ - s.prop('id', s.String()), - s.propOpt('name', s.String()), - s.propOpt('count', s.Number()), + s.Key('id', s.String()), + s.KeyOpt('name', s.String()), + s.KeyOpt('count', s.Number()), ]), nestedObject: s.Object([ - s.prop( + s.Key( 'user', s.Object([ - s.prop('id', s.Number()), - s.prop('profile', s.Object([s.prop('name', s.String()), s.prop('email', s.String())])), + s.Key('id', s.Number()), + s.Key('profile', s.Object([s.Key('name', s.String()), s.Key('email', s.String())])), ]), ), - s.prop('tags', s.Array(s.String())), + s.Key('tags', s.Array(s.String())), ]), tuple: s.Tuple([s.String(), s.Number(), s.Boolean()]), map: s.Map(s.String()), - mapWithComplexValue: s.Map(s.Object([s.prop('value', s.Number()), s.prop('label', s.String())])), + mapWithComplexValue: s.Map(s.Object([s.Key('value', s.Number()), s.Key('label', s.String())])), union: s.Or(s.String(), s.Number(), s.Boolean()), complexUnion: s.Or( s.String(), - s.Object([s.prop('type', s.Const('object' as const)), s.prop('data', s.Any())]), + s.Object([s.Key('type', s.Const('object' as const)), s.Key('data', s.Any())]), s.Array(s.Number()), ), binary: s.bin, diff --git a/src/codegen/json/__tests__/json.spec.ts b/src/codegen/json/__tests__/json.spec.ts index 1964f289..1488a073 100644 --- a/src/codegen/json/__tests__/json.spec.ts +++ b/src/codegen/json/__tests__/json.spec.ts @@ -89,21 +89,21 @@ describe('"arr" type', () => { describe('"obj" type', () => { test('serializes object with required fields', () => { - const type = s.Object([s.prop('a', s.num), s.prop('b', s.str)]); + const type = s.Object([s.Key('a', s.num), s.Key('b', s.str)]); exec(type, {a: 123, b: 'asdf'}); }); test('serializes object with constant string with required fields', () => { - const type = s.Object([s.prop('a', s.num), s.prop('b', s.Const<'asdf'>('asdf'))]); + const type = s.Object([s.Key('a', s.num), s.Key('b', s.Const<'asdf'>('asdf'))]); exec(type, {a: 123, b: 'asdf'}); }); test('can serialize optional 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), + s.Key('a', s.num), + s.Key('b', s.Const<'asdf'>('asdf')), + s.KeyOpt('c', s.str), + s.KeyOpt('d', s.num), ]); exec(type, {a: 123, b: 'asdf'}); exec(type, {a: 123, b: 'asdf', c: 'qwerty'}); @@ -112,7 +112,7 @@ describe('"obj" type', () => { 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)], + [s.Key('a', s.num), s.Key('b', s.Const<'asdf'>('asdf')), s.KeyOpt('c', s.str), s.KeyOpt('d', s.num)], {encodeUnknownKeys: true}, ); exec(type, {a: 123, b: 'asdf'}); @@ -155,19 +155,19 @@ describe('general', () => { test('serializes according to schema a POJO object', () => { const type = s.Object({ keys: [ - s.prop('a', s.num), - s.prop('b', s.str), - s.prop('c', s.nil), - s.prop('d', s.bool), - s.prop( + s.Key('a', s.num), + s.Key('b', s.str), + s.Key('c', s.nil), + s.Key('d', s.bool), + s.Key( 'arr', s.Array( s.Object({ - keys: [s.prop('foo', s.Array(s.num)), s.prop('.!@#', s.str)], + keys: [s.Key('foo', s.Array(s.num)), s.Key('.!@#', s.str)], }), ), ), - s.prop('bin', s.bin), + s.Key('bin', s.bin), ], }); const json = { @@ -196,7 +196,7 @@ describe('general', () => { }); test('can encode binary', () => { - const type = s.Object([s.prop('bin', s.bin)]); + const type = s.Object([s.Key('bin', s.bin)]); const json = { bin: new Uint8Array([1, 2, 3]), }; @@ -211,7 +211,7 @@ describe('"ref" type', () => { test('can serialize reference by resolving to type', () => { const system = new ModuleType(); 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 schema = s.Object([s.Key('name', s.str), s.Key('id', s.Ref('ID')), s.Key('createdAt', s.num)]); const type = system.t.import(schema); const fn = JsonTextCodegen.get(type); const json = { diff --git a/src/codegen/validator/__tests__/codegen.spec.ts b/src/codegen/validator/__tests__/codegen.spec.ts index bc054a0c..18e61ed4 100644 --- a/src/codegen/validator/__tests__/codegen.spec.ts +++ b/src/codegen/validator/__tests__/codegen.spec.ts @@ -33,24 +33,24 @@ test('validates according to schema a POJO object', () => { const type = s.Object({ decodeUnknownKeys: false, keys: [ - s.prop( + s.Key( 'collection', s.Object({ decodeUnknownKeys: false, keys: [ - 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.Key('id', s.str), + s.Key('ts', s.num), + s.Key('cid', s.str), + s.Key('prid', s.str), + s.KeyOpt('slug', s.str), + s.KeyOpt('name', s.str), + s.KeyOpt('src', s.str), + s.KeyOpt('authz', s.str), + s.Key('tags', s.Array(s.str)), ], }), ), - s.prop('bin.', s.bin), + s.Key('bin.', s.bin), ], }); const json = { @@ -922,7 +922,7 @@ describe('"obj" type', () => { test('object can have a field of any type', () => { const type = s.Object({ - keys: [s.prop('foo', s.any)], + keys: [s.Key('foo', s.any)], }); exec(type, {foo: 123}, null); exec(type, {foo: null}, null); @@ -941,7 +941,7 @@ describe('"obj" type', () => { test('can detect extra properties in object', () => { const type = s.Object({ - keys: [s.prop('foo', s.any), s.propOpt('zup', s.any)], + keys: [s.Key('foo', s.any), s.KeyOpt('zup', s.any)], }); exec(type, {foo: 123}, null); exec(type, {foo: 123, zup: 'asdf'}, null); @@ -960,7 +960,7 @@ describe('"obj" type', () => { test('can disable extra property check', () => { const type = s.Object({ - keys: [s.prop('foo', s.any), s.propOpt('zup', s.any)], + keys: [s.Key('foo', s.any), s.KeyOpt('zup', s.any)], }); exec(type, {foo: 123}, null, {skipObjectExtraFieldsCheck: true}); exec(type, {foo: 123, zup: 'asdf'}, null, { @@ -1035,7 +1035,7 @@ describe('"or" type', () => { }); test('checks inner type', () => { - const type = s.Or(s.Object(s.prop('type', s.Const<'num'>('num')), s.prop('foo', s.num)), s.num); + const type = s.Or(s.Object(s.Key('type', s.Const<'num'>('num')), s.Key('foo', s.num)), s.num); exec(type, {type: 'num', foo: 123}, null); exec( type, @@ -1052,7 +1052,7 @@ describe('"or" type', () => { test('object key can be of multiple types', () => { const type = s.Object({ keys: [ - s.prop('foo', { + s.Key('foo', { ...s.Or(s.num, s.str), discriminator: [ 'if', @@ -1080,7 +1080,7 @@ describe('"or" type', () => { test('array can be of multiple types', () => { const type = s.Object({ keys: [ - s.prop( + s.Key( 'gg', s.Array({ ...s.Or(s.num, s.str), diff --git a/src/schema/SchemaBuilder.ts b/src/schema/SchemaBuilder.ts index c10e9b6a..0266fd1f 100644 --- a/src/schema/SchemaBuilder.ts +++ b/src/schema/SchemaBuilder.ts @@ -137,7 +137,7 @@ export class SchemaBuilder { } /** Declares an object property. */ - public prop( + public Key( key: K, value: V, options: Omit<_.NoT<_.KeySchema>, 'key' | 'value' | 'optional'> = {}, @@ -151,7 +151,7 @@ export class SchemaBuilder { } /** Declares an optional object property. */ - public propOpt( + public KeyOpt( key: K, value: V, options: Omit<_.NoT<_.KeySchema>, 'key' | 'value' | 'optional'> = {}, diff --git a/src/schema/__tests__/SchemaBuilder.spec.ts b/src/schema/__tests__/SchemaBuilder.spec.ts index de62b856..58235949 100644 --- a/src/schema/__tests__/SchemaBuilder.spec.ts +++ b/src/schema/__tests__/SchemaBuilder.spec.ts @@ -34,7 +34,7 @@ describe('object', () => { }); test('can specify types', () => { - const type = s.Object([s.prop('id', s.String()), s.prop('name', s.str)]); + const type = s.Object([s.Key('id', s.String()), s.Key('name', s.str)]); expect(type).toEqual({ kind: 'obj', keys: [ diff --git a/src/schema/__tests__/TypeOf.spec.ts b/src/schema/__tests__/TypeOf.spec.ts index 6005e844..25670e13 100644 --- a/src/schema/__tests__/TypeOf.spec.ts +++ b/src/schema/__tests__/TypeOf.spec.ts @@ -100,6 +100,13 @@ describe('"arr" kind', () => { const arr1: T1 = ['foo', 1] satisfies [string, number]; const arr2: [string, number] = ['foo', 1] satisfies T1; }); + + test('named tuple members', () => { + const schema1 = s.Tuple([s.Key('foo', s.str), s.num]); + type T1 = TypeOf; + const arr1: T1 = ['foo', 1] satisfies [string, number]; + const arr2: [string, number] = ['foo', 1] satisfies T1; + }); }); test('can infer a simple "const" type', () => { @@ -127,11 +134,11 @@ test('can infer a simple "tuple" type', () => { test('can infer a simple "obj" type', () => { const schema1 = s.obj; - const schema2 = s.Object(s.prop('foo', s.str), s.propOpt('bar', s.num)); + const schema2 = s.Object(s.Key('foo', s.str), s.KeyOpt('bar', s.num)); const schema3 = s.Object({ - keys: [s.prop('bar', s.bool)], + keys: [s.Key('bar', s.bool)], }); - const schema4 = s.Object([s.prop('baz', s.num), s.propOpt('bazOptional', s.bool), s.propOpt('z', s.str)], {}); + const schema4 = s.Object([s.Key('baz', s.num), s.KeyOpt('bazOptional', s.bool), s.KeyOpt('z', s.str)], {}); type T1 = TypeOf; type T2 = TypeOf; type T3 = TypeOf; @@ -174,7 +181,7 @@ test('can infer a simple "or" type', () => { test('can infer a simple "ref" type', () => { const schema1 = s.str; - const schema2 = s.Object(s.prop('foo', s.Ref('another-str'))); + const schema2 = s.Object(s.Key('foo', s.Ref('another-str'))); type T1 = TypeOf; type T2 = TypeOf; const val1: T1 = 'foo'; @@ -204,9 +211,9 @@ test('can infer a simple "fn$" type', () => { }); test('can infer a complex "fn" type', () => { - const arr = s.Array(s.Object(s.prop('op', s.str), s.prop('path', s.str))); - const req = s.Object(s.prop('id', s.str), s.prop('age', s.num), s.prop('patch', s.Object(s.prop('ops', arr)))); - const res = s.Object(s.prop('id', s.String())); + const arr = s.Array(s.Object(s.Key('op', s.str), s.Key('path', s.str))); + const req = s.Object(s.Key('id', s.str), s.Key('age', s.num), s.Key('patch', s.Object(s.Key('ops', arr)))); + const res = s.Object(s.Key('id', s.String())); const schema1 = s.Function(req, res); type T1 = TypeOf; const val1: T1 = async ({patch, id}) => { @@ -217,12 +224,12 @@ test('can infer a complex "fn" type', () => { 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), + s.Key('id', s.str), + s.Key('age', s.num), + s.Key('tags', s.Array(s.Or(s.str, s.num))), + s.Key('data', s.Object(s.Key('foo', s.str), s.Key('bar', s.num))), + s.Key('approved', s.bool), + s.Key('meta', s.any), ); type T = TypeOf; const val: T = { @@ -239,7 +246,7 @@ test('can infer a realistic schema', () => { }); 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)))); + const schema = s.Object(s.KeyOpt('meta', s.Object(s.Key('foo', s.str), s.KeyOpt('bar', s.num)))); type T = TypeOf; const val0: T = {}; const val1: T = { diff --git a/src/schema/__tests__/type.spec.ts b/src/schema/__tests__/type.spec.ts index 1fe425af..c05123b4 100644 --- a/src/schema/__tests__/type.spec.ts +++ b/src/schema/__tests__/type.spec.ts @@ -5,16 +5,16 @@ test('can generate any type', () => { kind: 'obj', title: 'User address', description: 'Various address fields for user', - keys: [...s.Object(s.prop('street', s.String()), s.prop('zip', s.String())).keys], + keys: [...s.Object(s.Key('street', s.String()), s.Key('zip', s.String())).keys], }; 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)), + s.Key('id', s.Number({format: 'i'})), + s.Key('alwaysOne', s.Const<1>(1)), + s.Key('name', s.String()), + s.Key('address', address), + s.Key('timeCreated', s.Number()), + s.Key('tags', s.Array(s.Or(s.Number(), s.String()))), + s.Key('elements', s.Map(s.str)), ); expect(userType).toMatchObject({ diff --git a/src/schema/schema.ts b/src/schema/schema.ts index e874d9b1..a5aed8fc 100644 --- a/src/schema/schema.ts +++ b/src/schema/schema.ts @@ -479,17 +479,19 @@ export type TypeOfValue = T extends BoolSchema ] : T extends ConSchema ? U - : T extends ObjSchema - ? NoEmptyInterface>> - : T extends MapSchema - ? Record> - : T extends BinSchema - ? Uint8Array - : T extends FnSchema - ? (req: TypeOf, ctx: Ctx) => UndefToVoid> | Promise>> - : T extends FnRxSchema - ? (req$: Observable>, ctx: Ctx) => Observable>> - : never; + : T extends KeySchema + ? TypeOf + : T extends ObjSchema + ? NoEmptyInterface>> + : T extends MapSchema + ? Record> + : T extends BinSchema + ? Uint8Array + : T extends FnSchema + ? (req: TypeOf, ctx: Ctx) => UndefToVoid> | Promise>> + : T extends FnRxSchema + ? (req$: Observable>, ctx: Ctx) => Observable>> + : never; export type TypeOfMap> = { [K in keyof M]: TypeOf; diff --git a/src/type/classes/ObjType.ts b/src/type/classes/ObjType.ts index 4f549145..d4e810e9 100644 --- a/src/type/classes/ObjType.ts +++ b/src/type/classes/ObjType.ts @@ -11,7 +11,7 @@ export class ObjKeyType extends AbsType> { @@ -43,7 +43,7 @@ export class ObjKeyOptType extends ObjKeyType< public readonly val: V, ) { super(key, val); - (this as any).schema = schema.s.propOpt(key, schema.s.any) as any; + (this as any).schema = schema.s.KeyOpt(key, schema.s.any) as any; } protected toStringTitle(): string { From 0baaa1f3041690c6694c51596e883c51d9e9ba6d Mon Sep 17 00:00:00 2001 From: streamich Date: Sun, 10 Aug 2025 10:11:14 +0200 Subject: [PATCH 02/18] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20correct=20generic?= =?UTF-8?q?=20types?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/type/classes/ObjType.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/type/classes/ObjType.ts b/src/type/classes/ObjType.ts index d4e810e9..63f57c1a 100644 --- a/src/type/classes/ObjType.ts +++ b/src/type/classes/ObjType.ts @@ -51,14 +51,14 @@ export class ObjKeyOptType extends ObjKeyType< } } -export class ObjType[] = ObjKeyType[]> extends AbsType< +export class ObjType | ObjKeyOptType)[] = (ObjKeyType | ObjKeyOptType)[]> extends AbsType< schema.ObjSchema> > { constructor(public readonly keys: F) { super(schema.s.obj as any); } - private _key(field: ObjKeyType, options?: schema.Optional>): void { + private _key(field: ObjKeyType | ObjKeyOptType, options?: schema.Optional>): void { if (options) field.options(options); field.system = this.system; this.keys.push(field as any); @@ -94,7 +94,7 @@ export class ObjType[] = ObjKeyType[]> ): ObjType<[...F, ObjKeyOptType]> { this._key(new ObjKeyOptType(key, value), options); return this; - } + }d public getSchema(): schema.ObjSchema> { return { From 2dd8bbdf0f89f22abe28d8c51e8c0681281ddb26 Mon Sep 17 00:00:00 2001 From: streamich Date: Sun, 10 Aug 2025 10:16:50 +0200 Subject: [PATCH 03/18] =?UTF-8?q?test:=20=F0=9F=92=8D=20pass=20all=20tests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/copilot-instructions.md | 2 +- biome.json | 94 +++++++++---------- package.json | 25 +---- src/__bench__/encode.ts | 6 +- src/__tests__/fixtures.ts | 4 +- src/codegen/AbstractCodege.ts | 2 +- src/codegen/binary/AbstractBinaryCodegen.ts | 14 +-- .../binary/__tests__/testBinaryCodegen.ts | 2 +- src/codegen/binary/cbor copy/CborCodegen.ts | 10 +- .../cbor copy/__tests__/CborCodegen.spec.ts | 6 +- src/codegen/binary/cbor/CborCodegen.ts | 8 +- .../binary/cbor/__tests__/CborCodegen.spec.ts | 8 +- .../binary/cbor/__tests__/fuzzing.spec.ts | 6 +- src/codegen/binary/json/JsonCodegen.ts | 10 +- .../binary/json/__tests__/JsonCodegen.spec.ts | 8 +- .../binary/json/__tests__/fuzzing.spec.ts | 4 +- src/codegen/binary/msgpack/MsgPackCodegen.ts | 8 +- .../msgpack/__tests__/MsgPackCodegen.spec.ts | 6 +- .../binary/msgpack/__tests__/fuzzing.spec.ts | 6 +- .../capacity/CapacityEstimatorCodegen.ts | 2 +- .../CapacityEstimatorCodegenContext.spec.ts | 2 +- src/codegen/discriminator/index.ts | 4 +- src/codegen/json/JsonTextCodegen.ts | 12 +-- .../json/__tests__/JsonTextCodegen.spec.ts | 2 +- src/codegen/validator/ValidatorCodegen.ts | 18 ++-- .../validator/__tests__/codegen.spec.ts | 4 +- src/json-schema/converter.ts | 14 +-- src/jtd/converter.ts | 2 +- src/random/Random.ts | 14 +-- src/random/__tests__/random.spec.ts | 4 +- src/schema/__tests__/TypeOf.spec.ts | 2 +- src/schema/__tests__/validate.spec.ts | 2 +- src/schema/schema.ts | 4 +- src/schema/validate.ts | 2 +- src/type/TypeBuilder.ts | 2 +- src/type/__tests__/TypeBuilder.spec.ts | 4 +- src/type/__tests__/discriminator.spec.ts | 2 +- src/type/__tests__/fixtures.ts | 2 +- src/type/__tests__/validate.spec.ts | 2 +- src/type/__tests__/validateTestSuite.ts | 2 +- src/type/classes.ts | 20 ++-- src/type/classes/AbsType.ts | 4 +- src/type/classes/AliasType.ts | 4 +- src/type/classes/AnyType.ts | 2 +- src/type/classes/ArrType.ts | 4 +- src/type/classes/BinType.ts | 2 +- src/type/classes/BoolType.ts | 2 +- src/type/classes/ConType.ts | 2 +- src/type/classes/FnType.ts | 2 +- src/type/classes/MapType.ts | 2 +- src/type/classes/ModuleType/index.ts | 8 +- src/type/classes/NumType.ts | 2 +- src/type/classes/ObjType.ts | 17 ++-- src/type/classes/OrType.ts | 4 +- src/type/classes/RefType.ts | 2 +- src/type/classes/__tests__/AliasType.spec.ts | 2 +- src/type/classes/__tests__/StrType.spec.ts | 2 +- src/type/discriminator.ts | 2 +- src/type/index.ts | 2 +- src/typescript/converter.ts | 4 +- src/value/ObjValue.ts | 6 +- src/value/Value.ts | 2 +- src/value/util.ts | 4 +- tsconfig.json | 4 +- 64 files changed, 210 insertions(+), 226 deletions(-) diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 685c3c66..0215a648 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -1,4 +1,4 @@ - Do not add trivial comments, usually do not add blank lines inside functions. - Use Angular style commits, e.g `feat: implemented xyz`. - Make sure tests (`yarn test`) pass. -- In the end, make sure linter and formatter pass. +- When you are doing a PR, make sure linter and formatter pass. diff --git a/biome.json b/biome.json index 5440d500..a75d95dd 100644 --- a/biome.json +++ b/biome.json @@ -1,49 +1,49 @@ { - "$schema": "https://biomejs.dev/schemas/1.9.3/schema.json", - "organizeImports": { - "enabled": true - }, - "formatter": { - "indentStyle": "space", - "lineWidth": 120 - }, - "javascript": { - "formatter": { - "quoteStyle": "single", - "trailingCommas": "all", - "bracketSpacing": false - } - }, - "linter": { - "enabled": true, - "rules": { - "recommended": true, - "style": { - "noNonNullAssertion": "off", - "useNodejsImportProtocol": "off", - "useTemplate": "off", - "noInferrableTypes": "off", - "noUselessElse": "off", - "noParameterAssign": "off", - "noCommaOperator": "off", - "useSingleVarDeclarator": "off", - "noUnusedTemplateLiteral": "off", - "useDefaultParameterLast": "off", - "useEnumInitializers": "off" - }, - "suspicious": { - "noExplicitAny": "off", - "useIsArray": "off", - "noAssignInExpressions": "off", - "noConfusingVoidType": "off" - }, - "complexity": { - "noStaticOnlyClass": "off", - "useOptionalChain": "off" - }, - "security": { - "noGlobalEval": "off" - } - } - } + "$schema": "https://biomejs.dev/schemas/1.9.3/schema.json", + "organizeImports": { + "enabled": true + }, + "formatter": { + "indentStyle": "space", + "lineWidth": 120 + }, + "javascript": { + "formatter": { + "quoteStyle": "single", + "trailingCommas": "all", + "bracketSpacing": false + } + }, + "linter": { + "enabled": true, + "rules": { + "recommended": true, + "style": { + "noNonNullAssertion": "off", + "useNodejsImportProtocol": "off", + "useTemplate": "off", + "noInferrableTypes": "off", + "noUselessElse": "off", + "noParameterAssign": "off", + "noCommaOperator": "off", + "useSingleVarDeclarator": "off", + "noUnusedTemplateLiteral": "off", + "useDefaultParameterLast": "off", + "useEnumInitializers": "off" + }, + "suspicious": { + "noExplicitAny": "off", + "useIsArray": "off", + "noAssignInExpressions": "off", + "noConfusingVoidType": "off" + }, + "complexity": { + "noStaticOnlyClass": "off", + "useOptionalChain": "off" + }, + "security": { + "noGlobalEval": "off" + } + } + } } diff --git a/package.json b/package.json index a390ca10..fd23e245 100644 --- a/package.json +++ b/package.json @@ -16,26 +16,14 @@ "type": "github", "url": "https://github.com/sponsors/streamich" }, - "keywords": [ - "json-type", - "type", - "schema", - "json-schema", - "jtd", - "json", - "pointer", - "jit" - ], + "keywords": ["json-type", "type", "schema", "json-schema", "jtd", "json", "pointer", "jit"], "engines": { "node": ">=10.0" }, "main": "lib/index.js", "types": "lib/index.d.ts", "typings": "lib/index.d.ts", - "files": [ - "LICENSE", - "lib/" - ], + "files": ["LICENSE", "lib/"], "license": "Apache-2.0", "scripts": { "format": "biome format ./src", @@ -79,16 +67,11 @@ "typescript": "^5.6.2" }, "jest": { - "moduleFileExtensions": [ - "ts", - "js" - ], + "moduleFileExtensions": ["ts", "js"], "transform": { "^.+\\.ts$": "ts-jest" }, - "transformIgnorePatterns": [ - ".*/node_modules/.*" - ], + "transformIgnorePatterns": [".*/node_modules/.*"], "testRegex": ".*/(__tests__|__jest__|demo)/.*\\.(test|spec)\\.ts$" } } diff --git a/src/__bench__/encode.ts b/src/__bench__/encode.ts index 7f90d44f..f10d4c76 100644 --- a/src/__bench__/encode.ts +++ b/src/__bench__/encode.ts @@ -1,11 +1,11 @@ /* tslint:disable no-console */ -import {ModuleType} from '..'; +import {Writer} from '@jsonjoy.com/buffers/lib/Writer'; import {CborEncoder} from '@jsonjoy.com/json-pack/lib/cbor/CborEncoder'; +import {EncodingFormat} from '@jsonjoy.com/json-pack/lib/constants'; import {JsonEncoder} from '@jsonjoy.com/json-pack/lib/json/JsonEncoder'; +import {ModuleType} from '..'; import type {CompiledBinaryEncoder} from '../codegen/types'; -import {EncodingFormat} from '@jsonjoy.com/json-pack/lib/constants'; -import {Writer} from '@jsonjoy.com/buffers/lib/Writer'; const system = new ModuleType(); const {t} = system; diff --git a/src/__tests__/fixtures.ts b/src/__tests__/fixtures.ts index aa398661..73a2d9a9 100644 --- a/src/__tests__/fixtures.ts +++ b/src/__tests__/fixtures.ts @@ -4,10 +4,10 @@ * across multiple test modules. */ +import {RandomJson} from '@jsonjoy.com/json-random'; +import {genRandomExample} from '@jsonjoy.com/json-random/lib/examples'; import {s} from '../schema'; import {t} from '../type'; -import {genRandomExample} from '@jsonjoy.com/json-random/lib/examples'; -import {RandomJson} from '@jsonjoy.com/json-random'; export const randomJson = () => { return Math.random() < 0.5 ? genRandomExample() : RandomJson.generate(); diff --git a/src/codegen/AbstractCodege.ts b/src/codegen/AbstractCodege.ts index fab07703..f0982758 100644 --- a/src/codegen/AbstractCodege.ts +++ b/src/codegen/AbstractCodege.ts @@ -1,5 +1,5 @@ -import type {JsExpression} from '@jsonjoy.com/codegen/lib/util/JsExpression'; import type {Codegen} from '@jsonjoy.com/codegen'; +import type {JsExpression} from '@jsonjoy.com/codegen/lib/util/JsExpression'; import type { AnyType, ArrType, diff --git a/src/codegen/binary/AbstractBinaryCodegen.ts b/src/codegen/binary/AbstractBinaryCodegen.ts index eb182608..ca9794a4 100644 --- a/src/codegen/binary/AbstractBinaryCodegen.ts +++ b/src/codegen/binary/AbstractBinaryCodegen.ts @@ -1,12 +1,6 @@ -import {Codegen, CodegenStepExecJs} from '@jsonjoy.com/codegen'; import {concat} from '@jsonjoy.com/buffers/lib/concat'; -import {WriteBlobStep} from './WriteBlobStep'; -import {Value} from '../../value/Value'; -import {CapacityEstimatorCodegen} from '../capacity'; -import {AbstractCodegen} from '../AbstractCodege'; -import {floats, ints, uints} from '../../util'; +import {Codegen, CodegenStepExecJs} from '@jsonjoy.com/codegen'; import {JsExpression} from '@jsonjoy.com/codegen/lib/util/JsExpression'; -import {DiscriminatorCodegen} from '../discriminator'; import type {BinaryJsonEncoder} from '@jsonjoy.com/json-pack/lib/types'; import type { AnyType, @@ -21,7 +15,13 @@ import type { StrType, Type, } from '../../type'; +import {floats, ints, uints} from '../../util'; +import {Value} from '../../value/Value'; +import {AbstractCodegen} from '../AbstractCodege'; +import {CapacityEstimatorCodegen} from '../capacity'; +import {DiscriminatorCodegen} from '../discriminator'; import type {CompiledBinaryEncoder, SchemaPath} from '../types'; +import {WriteBlobStep} from './WriteBlobStep'; type Step = WriteBlobStep | CodegenStepExecJs; diff --git a/src/codegen/binary/__tests__/testBinaryCodegen.ts b/src/codegen/binary/__tests__/testBinaryCodegen.ts index eae09443..92e34b15 100644 --- a/src/codegen/binary/__tests__/testBinaryCodegen.ts +++ b/src/codegen/binary/__tests__/testBinaryCodegen.ts @@ -1,5 +1,5 @@ -import {ModuleType} from '../../../type/classes/ModuleType'; import type {Type} from '../../../type'; +import {ModuleType} from '../../../type/classes/ModuleType'; export const testBinaryCodegen = (transcode: (system: ModuleType, type: Type, value: unknown) => void) => { describe('"any" type', () => { diff --git a/src/codegen/binary/cbor copy/CborCodegen.ts b/src/codegen/binary/cbor copy/CborCodegen.ts index c7fd5eba..82968385 100644 --- a/src/codegen/binary/cbor copy/CborCodegen.ts +++ b/src/codegen/binary/cbor copy/CborCodegen.ts @@ -1,11 +1,11 @@ -import {AbstractBinaryCodegen} from '../AbstractBinaryCodegen'; -import {writer} from '../writer'; import {JsExpression} from '@jsonjoy.com/codegen/lib/util/JsExpression'; +import {normalizeAccessor} from '@jsonjoy.com/codegen/lib/util/normalizeAccessor'; import {CborEncoder} from '@jsonjoy.com/json-pack/lib/cbor/CborEncoder'; -import {lazyKeyedFactory} from '../../util'; -import {ObjKeyOptType, type MapType, type ObjType, type Type} from '../../../type'; +import {type MapType, ObjKeyOptType, type ObjType, type Type} from '../../../type'; import type {CompiledBinaryEncoder, SchemaPath} from '../../types'; -import {normalizeAccessor} from '@jsonjoy.com/codegen/lib/util/normalizeAccessor'; +import {lazyKeyedFactory} from '../../util'; +import {AbstractBinaryCodegen} from '../AbstractBinaryCodegen'; +import {writer} from '../writer'; export class CborCodegen extends AbstractBinaryCodegen { public static readonly get = lazyKeyedFactory((type: Type, name?: string) => { diff --git a/src/codegen/binary/cbor copy/__tests__/CborCodegen.spec.ts b/src/codegen/binary/cbor copy/__tests__/CborCodegen.spec.ts index e795e2f9..ab768063 100644 --- a/src/codegen/binary/cbor copy/__tests__/CborCodegen.spec.ts +++ b/src/codegen/binary/cbor copy/__tests__/CborCodegen.spec.ts @@ -1,9 +1,9 @@ -import {testBinaryCodegen} from '../../__tests__/testBinaryCodegen'; import {Writer} from '@jsonjoy.com/buffers/lib/Writer'; -import {CborEncoder} from '@jsonjoy.com/json-pack/lib/cbor/CborEncoder'; import {CborDecoder} from '@jsonjoy.com/json-pack/lib/cbor/CborDecoder'; -import {CborCodegen} from '../CborCodegen'; +import {CborEncoder} from '@jsonjoy.com/json-pack/lib/cbor/CborEncoder'; import type {ModuleType, Type} from '../../../../type'; +import {testBinaryCodegen} from '../../__tests__/testBinaryCodegen'; +import {CborCodegen} from '../CborCodegen'; const encoder = new CborEncoder(new Writer(16)); const decoder = new CborDecoder(); diff --git a/src/codegen/binary/cbor/CborCodegen.ts b/src/codegen/binary/cbor/CborCodegen.ts index dd95934c..44f51b1e 100644 --- a/src/codegen/binary/cbor/CborCodegen.ts +++ b/src/codegen/binary/cbor/CborCodegen.ts @@ -1,11 +1,11 @@ -import {AbstractBinaryCodegen} from '../AbstractBinaryCodegen'; -import {writer} from '../writer'; import {JsExpression} from '@jsonjoy.com/codegen/lib/util/JsExpression'; +import {normalizeAccessor} from '@jsonjoy.com/codegen/lib/util/normalizeAccessor'; import {CborEncoder} from '@jsonjoy.com/json-pack/lib/cbor/CborEncoder'; -import {lazyKeyedFactory} from '../../util'; import {ObjKeyOptType, type ObjType, type Type} from '../../../type'; import type {CompiledBinaryEncoder, SchemaPath} from '../../types'; -import {normalizeAccessor} from '@jsonjoy.com/codegen/lib/util/normalizeAccessor'; +import {lazyKeyedFactory} from '../../util'; +import {AbstractBinaryCodegen} from '../AbstractBinaryCodegen'; +import {writer} from '../writer'; export class CborCodegen extends AbstractBinaryCodegen { public static readonly get = lazyKeyedFactory((type: Type, name?: string) => { diff --git a/src/codegen/binary/cbor/__tests__/CborCodegen.spec.ts b/src/codegen/binary/cbor/__tests__/CborCodegen.spec.ts index c8aaa168..baff32ce 100644 --- a/src/codegen/binary/cbor/__tests__/CborCodegen.spec.ts +++ b/src/codegen/binary/cbor/__tests__/CborCodegen.spec.ts @@ -1,10 +1,10 @@ -import {testBinaryCodegen} from '../../__tests__/testBinaryCodegen'; import {Writer} from '@jsonjoy.com/buffers/lib/Writer'; -import {CborEncoder} from '@jsonjoy.com/json-pack/lib/cbor/CborEncoder'; import {CborDecoder} from '@jsonjoy.com/json-pack/lib/cbor/CborDecoder'; -import {CborCodegen} from '../CborCodegen'; -import type {ModuleType} from '../../../../type/classes/ModuleType'; +import {CborEncoder} from '@jsonjoy.com/json-pack/lib/cbor/CborEncoder'; import type {Type} from '../../../../type'; +import type {ModuleType} from '../../../../type/classes/ModuleType'; +import {testBinaryCodegen} from '../../__tests__/testBinaryCodegen'; +import {CborCodegen} from '../CborCodegen'; const encoder = new CborEncoder(new Writer(16)); const decoder = new CborDecoder(); diff --git a/src/codegen/binary/cbor/__tests__/fuzzing.spec.ts b/src/codegen/binary/cbor/__tests__/fuzzing.spec.ts index bc9e3be2..e8427ab1 100644 --- a/src/codegen/binary/cbor/__tests__/fuzzing.spec.ts +++ b/src/codegen/binary/cbor/__tests__/fuzzing.spec.ts @@ -1,9 +1,9 @@ -import {TypeBuilder} from '../../../../type/TypeBuilder'; import {Writer} from '@jsonjoy.com/buffers/lib/Writer'; -import {CborEncoder} from '@jsonjoy.com/json-pack/lib/cbor/CborEncoder'; import {CborDecoder} from '@jsonjoy.com/json-pack/lib/cbor/CborDecoder'; -import {CborCodegen} from '../CborCodegen'; +import {CborEncoder} from '@jsonjoy.com/json-pack/lib/cbor/CborEncoder'; import {randomJson} from '../../../../__tests__/fixtures'; +import {TypeBuilder} from '../../../../type/TypeBuilder'; +import {CborCodegen} from '../CborCodegen'; const encoder = new CborEncoder(new Writer(16)); const decoder = new CborDecoder(); diff --git a/src/codegen/binary/json/JsonCodegen.ts b/src/codegen/binary/json/JsonCodegen.ts index 843aeae4..e55606ef 100644 --- a/src/codegen/binary/json/JsonCodegen.ts +++ b/src/codegen/binary/json/JsonCodegen.ts @@ -1,11 +1,11 @@ -import {AbstractBinaryCodegen} from '../AbstractBinaryCodegen'; -import {writer} from '../writer'; import {JsExpression} from '@jsonjoy.com/codegen/lib/util/JsExpression'; -import {JsonEncoder} from '@jsonjoy.com/json-pack/lib/json/JsonEncoder'; -import {ObjKeyOptType, type ArrType, type MapType, type ObjType, type Type} from '../../../type'; import {normalizeAccessor} from '@jsonjoy.com/codegen/lib/util/normalizeAccessor'; -import {lazyKeyedFactory} from '../../util'; +import {JsonEncoder} from '@jsonjoy.com/json-pack/lib/json/JsonEncoder'; +import {type ArrType, type MapType, ObjKeyOptType, type ObjType, type Type} from '../../../type'; import type {CompiledBinaryEncoder, SchemaPath} from '../../types'; +import {lazyKeyedFactory} from '../../util'; +import {AbstractBinaryCodegen} from '../AbstractBinaryCodegen'; +import {writer} from '../writer'; export class JsonCodegen extends AbstractBinaryCodegen { public static readonly get = lazyKeyedFactory((type: Type, name?: string) => { diff --git a/src/codegen/binary/json/__tests__/JsonCodegen.spec.ts b/src/codegen/binary/json/__tests__/JsonCodegen.spec.ts index b3936bea..d298aabf 100644 --- a/src/codegen/binary/json/__tests__/JsonCodegen.spec.ts +++ b/src/codegen/binary/json/__tests__/JsonCodegen.spec.ts @@ -1,10 +1,10 @@ -import {testBinaryCodegen} from '../../__tests__/testBinaryCodegen'; -import {JsonEncoder} from '@jsonjoy.com/json-pack/lib/json/JsonEncoder'; import {Writer} from '@jsonjoy.com/buffers/lib/Writer'; import {parse} from '@jsonjoy.com/json-pack/lib/json-binary'; -import {JsonCodegen} from '../JsonCodegen'; -import type {ModuleType} from '../../../../type/classes/ModuleType'; +import {JsonEncoder} from '@jsonjoy.com/json-pack/lib/json/JsonEncoder'; import type {Type} from '../../../../type'; +import type {ModuleType} from '../../../../type/classes/ModuleType'; +import {testBinaryCodegen} from '../../__tests__/testBinaryCodegen'; +import {JsonCodegen} from '../JsonCodegen'; const encoder = new JsonEncoder(new Writer(16)); diff --git a/src/codegen/binary/json/__tests__/fuzzing.spec.ts b/src/codegen/binary/json/__tests__/fuzzing.spec.ts index 5cff04e3..f7f1a9c1 100644 --- a/src/codegen/binary/json/__tests__/fuzzing.spec.ts +++ b/src/codegen/binary/json/__tests__/fuzzing.spec.ts @@ -1,10 +1,10 @@ import {Writer} from '@jsonjoy.com/buffers/lib/Writer'; import {parse} from '@jsonjoy.com/json-pack/lib/json-binary'; -import {JsonEncoder} from '@jsonjoy.com/json-pack/lib/json/JsonEncoder'; import {JsonDecoder} from '@jsonjoy.com/json-pack/lib/json/JsonDecoder'; +import {JsonEncoder} from '@jsonjoy.com/json-pack/lib/json/JsonEncoder'; +import {randomJson} from '../../../../__tests__/fixtures'; import {TypeBuilder} from '../../../../type/TypeBuilder'; import {JsonCodegen} from '../JsonCodegen'; -import {randomJson} from '../../../../__tests__/fixtures'; const encoder = new JsonEncoder(new Writer(16)); const decoder = new JsonDecoder(); diff --git a/src/codegen/binary/msgpack/MsgPackCodegen.ts b/src/codegen/binary/msgpack/MsgPackCodegen.ts index df0cc3d8..6dea0735 100644 --- a/src/codegen/binary/msgpack/MsgPackCodegen.ts +++ b/src/codegen/binary/msgpack/MsgPackCodegen.ts @@ -1,11 +1,11 @@ -import {AbstractBinaryCodegen} from '../AbstractBinaryCodegen'; -import {writer} from '../writer'; import {JsExpression} from '@jsonjoy.com/codegen/lib/util/JsExpression'; +import {normalizeAccessor} from '@jsonjoy.com/codegen/lib/util/normalizeAccessor'; import {MsgPackEncoder} from '@jsonjoy.com/json-pack/lib/msgpack/MsgPackEncoder'; -import {lazyKeyedFactory} from '../../util'; import {ObjKeyOptType, type ObjType, type Type} from '../../../type'; -import {normalizeAccessor} from '@jsonjoy.com/codegen/lib/util/normalizeAccessor'; import type {CompiledBinaryEncoder, SchemaPath} from '../../types'; +import {lazyKeyedFactory} from '../../util'; +import {AbstractBinaryCodegen} from '../AbstractBinaryCodegen'; +import {writer} from '../writer'; export class MsgPackCodegen extends AbstractBinaryCodegen { public static readonly get = lazyKeyedFactory((type: Type, name?: string) => { diff --git a/src/codegen/binary/msgpack/__tests__/MsgPackCodegen.spec.ts b/src/codegen/binary/msgpack/__tests__/MsgPackCodegen.spec.ts index da622c45..3675bbc7 100644 --- a/src/codegen/binary/msgpack/__tests__/MsgPackCodegen.spec.ts +++ b/src/codegen/binary/msgpack/__tests__/MsgPackCodegen.spec.ts @@ -1,9 +1,9 @@ -import {testBinaryCodegen} from '../../__tests__/testBinaryCodegen'; import {Writer} from '@jsonjoy.com/buffers/lib/Writer'; -import {MsgPackEncoder} from '@jsonjoy.com/json-pack/lib/msgpack/MsgPackEncoder'; import {MsgPackDecoder} from '@jsonjoy.com/json-pack/lib/msgpack/MsgPackDecoder'; -import {MsgPackCodegen} from '../MsgPackCodegen'; +import {MsgPackEncoder} from '@jsonjoy.com/json-pack/lib/msgpack/MsgPackEncoder'; import type {ModuleType, Type} from '../../../../type'; +import {testBinaryCodegen} from '../../__tests__/testBinaryCodegen'; +import {MsgPackCodegen} from '../MsgPackCodegen'; const encoder = new MsgPackEncoder(new Writer(16)); const decoder = new MsgPackDecoder(); diff --git a/src/codegen/binary/msgpack/__tests__/fuzzing.spec.ts b/src/codegen/binary/msgpack/__tests__/fuzzing.spec.ts index 40cb2e2b..c47c1b9a 100644 --- a/src/codegen/binary/msgpack/__tests__/fuzzing.spec.ts +++ b/src/codegen/binary/msgpack/__tests__/fuzzing.spec.ts @@ -1,8 +1,8 @@ -import {TypeBuilder} from '../../../../type/TypeBuilder'; -import {MsgPackEncoder} from '@jsonjoy.com/json-pack/lib/msgpack/MsgPackEncoder'; import {MsgPackDecoder} from '@jsonjoy.com/json-pack/lib/msgpack/MsgPackDecoder'; -import {MsgPackCodegen} from '../MsgPackCodegen'; +import {MsgPackEncoder} from '@jsonjoy.com/json-pack/lib/msgpack/MsgPackEncoder'; import {randomJson} from '../../../../__tests__/fixtures'; +import {TypeBuilder} from '../../../../type/TypeBuilder'; +import {MsgPackCodegen} from '../MsgPackCodegen'; test('can encode random values', () => { for (let i = 0; i < 10; i++) { diff --git a/src/codegen/capacity/CapacityEstimatorCodegen.ts b/src/codegen/capacity/CapacityEstimatorCodegen.ts index 1ce00eba..e7560ca5 100644 --- a/src/codegen/capacity/CapacityEstimatorCodegen.ts +++ b/src/codegen/capacity/CapacityEstimatorCodegen.ts @@ -3,9 +3,9 @@ import {JsExpression} from '@jsonjoy.com/codegen/lib/util/JsExpression'; import {normalizeAccessor} from '@jsonjoy.com/codegen/lib/util/normalizeAccessor'; import {MaxEncodingOverhead, maxEncodingCapacity} from '@jsonjoy.com/util/lib/json-size'; import {BoolType, ConType, NumType, ObjKeyOptType} from '../../type'; +import type {ArrType, MapType, ObjKeyType, ObjType, OrType, RefType, Type} from '../../type'; import {DiscriminatorCodegen} from '../discriminator'; import {lazyKeyedFactory} from '../util'; -import type {ObjKeyType, ArrType, MapType, RefType, Type, OrType, ObjType} from '../../type'; export type CompiledCapacityEstimator = (value: unknown) => number; diff --git a/src/codegen/capacity/__tests__/CapacityEstimatorCodegenContext.spec.ts b/src/codegen/capacity/__tests__/CapacityEstimatorCodegenContext.spec.ts index ff3ea486..fdef71bc 100644 --- a/src/codegen/capacity/__tests__/CapacityEstimatorCodegenContext.spec.ts +++ b/src/codegen/capacity/__tests__/CapacityEstimatorCodegenContext.spec.ts @@ -1,7 +1,7 @@ import {maxEncodingCapacity} from '@jsonjoy.com/util/lib/json-size'; -import {CapacityEstimatorCodegen} from '../CapacityEstimatorCodegen'; import {t} from '../../../type'; import {ModuleType} from '../../../type/classes/ModuleType'; +import {CapacityEstimatorCodegen} from '../CapacityEstimatorCodegen'; describe('"any" type', () => { test('returns the same result as maxEncodingCapacity()', () => { diff --git a/src/codegen/discriminator/index.ts b/src/codegen/discriminator/index.ts index 81651d76..3a3ee544 100644 --- a/src/codegen/discriminator/index.ts +++ b/src/codegen/discriminator/index.ts @@ -1,8 +1,8 @@ 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 {lazyKeyedFactory} from '../util'; +import {operatorsMap} from '@jsonjoy.com/json-expression/lib/operators'; import type {OrType} from '../../type'; +import {lazyKeyedFactory} from '../util'; export type DiscriminatorFn = (val: unknown) => number; diff --git a/src/codegen/json/JsonTextCodegen.ts b/src/codegen/json/JsonTextCodegen.ts index 4f45bd4a..523812d0 100644 --- a/src/codegen/json/JsonTextCodegen.ts +++ b/src/codegen/json/JsonTextCodegen.ts @@ -1,14 +1,14 @@ -import {Codegen, CodegenStepExecJs} from '@jsonjoy.com/codegen'; -import {asString} from '@jsonjoy.com/util/lib/strings/asString'; import {toBase64} from '@jsonjoy.com/base64/lib/toBase64'; +import {Codegen, CodegenStepExecJs} from '@jsonjoy.com/codegen'; import {JsExpression} from '@jsonjoy.com/codegen/lib/util/JsExpression'; -import {stringify} from '@jsonjoy.com/json-pack/lib/json-binary/codec'; import {normalizeAccessor} from '@jsonjoy.com/codegen/lib/util/normalizeAccessor'; +import {stringify} from '@jsonjoy.com/json-pack/lib/json-binary/codec'; +import type {json_string} from '@jsonjoy.com/util/lib/json-brand'; +import {asString} from '@jsonjoy.com/util/lib/strings/asString'; import {ObjKeyOptType} from '../../type'; -import {lazyKeyedFactory} from '../util'; +import type {ArrType, ConType, MapType, ObjType, OrType, RefType, StrType, Type} from '../../type'; import {DiscriminatorCodegen} from '../discriminator'; -import type {ArrType, MapType, OrType, RefType, ConType, ObjType, StrType, Type} from '../../type'; -import type {json_string} from '@jsonjoy.com/util/lib/json-brand'; +import {lazyKeyedFactory} from '../util'; export type JsonEncoderFn = (value: T) => json_string; diff --git a/src/codegen/json/__tests__/JsonTextCodegen.spec.ts b/src/codegen/json/__tests__/JsonTextCodegen.spec.ts index 15bb1407..2e00f8ff 100644 --- a/src/codegen/json/__tests__/JsonTextCodegen.spec.ts +++ b/src/codegen/json/__tests__/JsonTextCodegen.spec.ts @@ -1,7 +1,7 @@ +import {parse} from '@jsonjoy.com/json-pack/lib/json-binary/codec'; import {t} from '../../../type'; import {ModuleType} from '../../../type/classes/ModuleType'; import {JsonTextCodegen} from '../JsonTextCodegen'; -import {parse} from '@jsonjoy.com/json-pack/lib/json-binary/codec'; describe('"any" type', () => { test('stringify simple JSON', () => { diff --git a/src/codegen/validator/ValidatorCodegen.ts b/src/codegen/validator/ValidatorCodegen.ts index ee8cc8c3..d470251c 100644 --- a/src/codegen/validator/ValidatorCodegen.ts +++ b/src/codegen/validator/ValidatorCodegen.ts @@ -1,12 +1,9 @@ import {Codegen} from '@jsonjoy.com/codegen'; import {JsExpression} from '@jsonjoy.com/codegen/lib/util/JsExpression'; -import {ValidationError, ValidationErrorMessage} from '../../constants'; +import {normalizeAccessor} from '@jsonjoy.com/codegen/lib/util/normalizeAccessor'; import {deepEqualCodegen} from '@jsonjoy.com/util/lib/json-equal/deepEqualCodegen'; -import {AbstractCodegen} from '../AbstractCodege'; -import {floats, ints, uints} from '../../util'; -import {isAscii, isUtf8} from '../../util/stringFormats'; +import {ValidationError, ValidationErrorMessage} from '../../constants'; import { - ObjKeyOptType, type AnyType, type ArrType, type BinType, @@ -14,18 +11,21 @@ import { type ConType, type MapType, type NumType, + ObjKeyOptType, type ObjType, type OrType, type RefType, type StrType, type Type, } from '../../type'; -import {normalizeAccessor} from '@jsonjoy.com/codegen/lib/util/normalizeAccessor'; -import {lazyKeyedFactory} from '../util'; +import {floats, ints, uints} from '../../util'; +import {isAscii, isUtf8} from '../../util/stringFormats'; +import {AbstractCodegen} from '../AbstractCodege'; import {DiscriminatorCodegen} from '../discriminator'; -import {canSkipObjectKeyUndefinedCheck} from './util'; -import type {JsonTypeValidator} from './types'; import type {SchemaPath} from '../types'; +import {lazyKeyedFactory} from '../util'; +import type {JsonTypeValidator} from './types'; +import {canSkipObjectKeyUndefinedCheck} from './util'; export interface ValidatorCodegenOptions { /** Type for which to generate the validator. */ diff --git a/src/codegen/validator/__tests__/codegen.spec.ts b/src/codegen/validator/__tests__/codegen.spec.ts index 18e61ed4..9550b1a8 100644 --- a/src/codegen/validator/__tests__/codegen.spec.ts +++ b/src/codegen/validator/__tests__/codegen.spec.ts @@ -1,7 +1,7 @@ -import {ModuleType} from '../../../type/classes/ModuleType'; import {b} from '@jsonjoy.com/buffers/lib/b'; import {ValidationError} from '../../../constants'; -import {type OrSchema, s, type Schema} from '../../../schema'; +import {type OrSchema, type Schema, s} from '../../../schema'; +import {ModuleType} from '../../../type/classes/ModuleType'; import {ValidatorCodegen, type ValidatorCodegenOptions} from '../ValidatorCodegen'; const exec = (schema: Schema, json: unknown, error: any, options: Partial = {}) => { diff --git a/src/json-schema/converter.ts b/src/json-schema/converter.ts index a8c71062..39810d4f 100644 --- a/src/json-schema/converter.ts +++ b/src/json-schema/converter.ts @@ -1,6 +1,5 @@ -import {TypeExportContext} from '../type/classes/ModuleType/TypeExportContext'; +import type * as schema from '../schema'; import type {AliasType} from '../type'; -import type {ObjType} from '../type/classes/ObjType'; import type {AbsType} from '../type/classes/AbsType'; import type {AnyType} from '../type/classes/AnyType'; import type {ArrType} from '../type/classes/ArrType'; @@ -8,23 +7,24 @@ import type {BinType} from '../type/classes/BinType'; import type {BoolType} from '../type/classes/BoolType'; import type {ConType} from '../type/classes/ConType'; import type {MapType} from '../type/classes/MapType'; +import {TypeExportContext} from '../type/classes/ModuleType/TypeExportContext'; import type {NumType} from '../type/classes/NumType'; +import type {ObjType} from '../type/classes/ObjType'; import type {OrType} from '../type/classes/OrType'; import type {RefType} from '../type/classes/RefType'; import type {StrType} from '../type/classes/StrType'; -import type * as schema from '../schema'; import type { - JsonSchemaNode, - JsonSchemaGenericKeywords, JsonSchemaAny, JsonSchemaArray, JsonSchemaBinary, JsonSchemaBoolean, - JsonSchemaString, + JsonSchemaGenericKeywords, + JsonSchemaNode, JsonSchemaNumber, JsonSchemaObject, - JsonSchemaRef, JsonSchemaOr, + JsonSchemaRef, + JsonSchemaString, JsonSchemaValueNode, } from './types'; diff --git a/src/jtd/converter.ts b/src/jtd/converter.ts index 286f0801..757ac2a0 100644 --- a/src/jtd/converter.ts +++ b/src/jtd/converter.ts @@ -1,4 +1,4 @@ -import {ObjKeyOptType, type ArrType, type ObjType, type RefType, type Type} from '../type'; +import {type ArrType, ObjKeyOptType, type ObjType, type RefType, type Type} from '../type'; import type * as jtd from './types'; const NUMS_TYPE_MAPPING = new Map([ diff --git a/src/random/Random.ts b/src/random/Random.ts index 1c7a9e36..b325a291 100644 --- a/src/random/Random.ts +++ b/src/random/Random.ts @@ -1,24 +1,24 @@ -import {of} from 'rxjs'; import {RandomJson} from '@jsonjoy.com/json-random'; import {cloneBinary} from '@jsonjoy.com/util/lib/json-clone'; -import {ObjKeyOptType, type ObjKeyType, type ObjType} from '../type/classes/ObjType'; +import {of} from 'rxjs'; import type { + AbsType, AnyType, - t, - Type, - StrType, ArrType, BinType, BoolType, ConType, + FnRxType, FnType, MapType, NumType, OrType, RefType, - AbsType, - FnRxType, + StrType, + Type, + t, } from '../type'; +import {ObjKeyOptType, type ObjKeyType, type ObjType} from '../type/classes/ObjType'; export class Random { public static readonly gen = (type: T): t.infer => { diff --git a/src/random/__tests__/random.spec.ts b/src/random/__tests__/random.spec.ts index 57a36274..9e68e575 100644 --- a/src/random/__tests__/random.spec.ts +++ b/src/random/__tests__/random.spec.ts @@ -3,10 +3,10 @@ * Tests that generated random values conform to their JSON Type schemas. */ -import {t, type Type} from '../../type'; import {allSchemas, schemaCategories} from '../../__tests__/fixtures'; -import {Random} from '../Random'; import {ValidatorCodegen} from '../../codegen/validator/ValidatorCodegen'; +import {type Type, t} from '../../type'; +import {Random} from '../Random'; const validate = (type: Type, value: unknown) => { const validator = ValidatorCodegen.get({type, errors: 'object'}); diff --git a/src/schema/__tests__/TypeOf.spec.ts b/src/schema/__tests__/TypeOf.spec.ts index 25670e13..623c833b 100644 --- a/src/schema/__tests__/TypeOf.spec.ts +++ b/src/schema/__tests__/TypeOf.spec.ts @@ -1,5 +1,5 @@ import {EMPTY, map} from 'rxjs'; -import {s, type TypeOf} from '..'; +import {type TypeOf, s} from '..'; test('can infer a simple "any" type', () => { const schema1 = s.any; diff --git a/src/schema/__tests__/validate.spec.ts b/src/schema/__tests__/validate.spec.ts index 4c3842db..2978b3a7 100644 --- a/src/schema/__tests__/validate.spec.ts +++ b/src/schema/__tests__/validate.spec.ts @@ -1,5 +1,5 @@ +import type {Schema, SchemaBase, SchemaExample} from '../schema'; import {validateSchema, validateTType} from '../validate'; -import type {SchemaExample, SchemaBase, Schema} from '../schema'; describe('validate display', () => { test('validates valid display', () => { diff --git a/src/schema/schema.ts b/src/schema/schema.ts index a5aed8fc..de9faff1 100644 --- a/src/schema/schema.ts +++ b/src/schema/schema.ts @@ -1,7 +1,7 @@ -import type {Observable} from 'rxjs'; +import type {Expr} from '@jsonjoy.com/json-expression'; import type {Mutable} from '@jsonjoy.com/util/lib/types'; +import type {Observable} from 'rxjs'; import type {Display} from './common'; -import type {Expr} from '@jsonjoy.com/json-expression'; export interface SchemaBase extends Display { /** diff --git a/src/schema/validate.ts b/src/schema/validate.ts index b17f23f7..0ccd40af 100644 --- a/src/schema/validate.ts +++ b/src/schema/validate.ts @@ -1,5 +1,5 @@ import type {Display} from './common'; -import type {SchemaExample, SchemaBase, Schema, ObjSchema} from './schema'; +import type {ObjSchema, Schema, SchemaBase, SchemaExample} from './schema'; const validateDisplay = ({title, description, intro}: Display): void => { if (title !== undefined && typeof title !== 'string') throw new Error('INVALID_TITLE'); diff --git a/src/type/TypeBuilder.ts b/src/type/TypeBuilder.ts index a05c8afb..14021683 100644 --- a/src/type/TypeBuilder.ts +++ b/src/type/TypeBuilder.ts @@ -195,7 +195,7 @@ export class TypeBuilder { return new classes.ArrType(item, head, tail, options).sys(this.system); } - public Object[]>(...keys: F) { + public Object | classes.ObjKeyOptType)[]>(...keys: F) { return new classes.ObjType(keys).sys(this.system); } diff --git a/src/type/__tests__/TypeBuilder.spec.ts b/src/type/__tests__/TypeBuilder.spec.ts index 5bc0b11a..1edb44d2 100644 --- a/src/type/__tests__/TypeBuilder.spec.ts +++ b/src/type/__tests__/TypeBuilder.spec.ts @@ -1,8 +1,8 @@ -import {NumType, ObjType, StrType} from '../classes'; -import {ObjKeyType} from '../classes/ObjType'; import {type SchemaOf, t} from '..'; import type {NumSchema, TypeOf} from '../../schema'; import {validateSchema} from '../../schema/validate'; +import {NumType, ObjType, StrType} from '../classes'; +import {ObjKeyType} from '../classes/ObjType'; test('number', () => { const type = t.Number({ diff --git a/src/type/__tests__/discriminator.spec.ts b/src/type/__tests__/discriminator.spec.ts index 198d5adc..e348ed16 100644 --- a/src/type/__tests__/discriminator.spec.ts +++ b/src/type/__tests__/discriminator.spec.ts @@ -1,6 +1,6 @@ import {t} from '..'; -import {Discriminator} from '../discriminator'; import {ValidatorCodegen} from '../../codegen/validator/ValidatorCodegen'; +import {Discriminator} from '../discriminator'; describe('Discriminator', () => { test('can find const discriminator at root node', () => { diff --git a/src/type/__tests__/fixtures.ts b/src/type/__tests__/fixtures.ts index b768e274..cd31f266 100644 --- a/src/type/__tests__/fixtures.ts +++ b/src/type/__tests__/fixtures.ts @@ -1,5 +1,5 @@ -import type {TypeOf} from '../../schema'; import {type SchemaOf, t} from '..'; +import type {TypeOf} from '../../schema'; export const everyType = t.Object( // t.prop('id', t.str.options({noJsonEscape: true})), diff --git a/src/type/__tests__/validate.spec.ts b/src/type/__tests__/validate.spec.ts index 4605774c..3c6431e1 100644 --- a/src/type/__tests__/validate.spec.ts +++ b/src/type/__tests__/validate.spec.ts @@ -1,6 +1,6 @@ import {type Type, t} from '..'; -import {validateTestSuite} from './validateTestSuite'; import {ValidatorCodegen} from '../../codegen/validator/ValidatorCodegen'; +import {validateTestSuite} from './validateTestSuite'; const validate = (type: Type, value: unknown) => { const validator = ValidatorCodegen.get({type, errors: 'object'}); diff --git a/src/type/__tests__/validateTestSuite.ts b/src/type/__tests__/validateTestSuite.ts index 8ff2ce3c..7c282c46 100644 --- a/src/type/__tests__/validateTestSuite.ts +++ b/src/type/__tests__/validateTestSuite.ts @@ -1,4 +1,4 @@ -import {type Type, t, ModuleType} from '..'; +import {ModuleType, type Type, t} from '..'; export const validateTestSuite = (validate: (type: Type, value: unknown) => void) => { const system = new ModuleType(); diff --git a/src/type/classes.ts b/src/type/classes.ts index 74d9d7f7..0bb933eb 100644 --- a/src/type/classes.ts +++ b/src/type/classes.ts @@ -1,18 +1,18 @@ import {AbsType} from './classes/AbsType'; +import {AliasType} from './classes/AliasType'; import {AnyType} from './classes/AnyType'; -import {ConType} from './classes/ConType'; -import {BoolType} from './classes/BoolType'; -import {NumType} from './classes/NumType'; -import {StrType} from './classes/StrType'; -import {BinType} from './classes/BinType'; import {ArrType} from './classes/ArrType'; -import {ObjType, ObjKeyType, ObjKeyOptType} from './classes/ObjType'; +import {BinType} from './classes/BinType'; +import {BoolType} from './classes/BoolType'; +import {ConType} from './classes/ConType'; +import {FnRxType, FnType} from './classes/FnType'; import {MapType} from './classes/MapType'; -import {RefType} from './classes/RefType'; -import {OrType} from './classes/OrType'; -import {FnType, FnRxType} from './classes/FnType'; -import {AliasType} from './classes/AliasType'; import {ModuleType} from './classes/ModuleType'; +import {NumType} from './classes/NumType'; +import {ObjKeyOptType, ObjKeyType, ObjType} from './classes/ObjType'; +import {OrType} from './classes/OrType'; +import {RefType} from './classes/RefType'; +import {StrType} from './classes/StrType'; export { AbsType, diff --git a/src/type/classes/AbsType.ts b/src/type/classes/AbsType.ts index 5b81fc78..5b258b19 100644 --- a/src/type/classes/AbsType.ts +++ b/src/type/classes/AbsType.ts @@ -1,7 +1,7 @@ -import {Value} from '../../value'; -import type * as schema from '../../schema'; import type {Printable} from 'tree-dump/lib/types'; +import type * as schema from '../../schema'; import type {SchemaExample} from '../../schema'; +import {Value} from '../../value'; import type {BaseType, ModuleType} from '../types'; export abstract class AbsType implements BaseType, Printable { diff --git a/src/type/classes/AliasType.ts b/src/type/classes/AliasType.ts index 4170e0d5..17308537 100644 --- a/src/type/classes/AliasType.ts +++ b/src/type/classes/AliasType.ts @@ -1,7 +1,7 @@ import {printTree} from 'tree-dump/lib/printTree'; -import type {ModuleType} from './ModuleType'; -import type {Type} from '../../type'; import type {Printable} from 'tree-dump/lib/types'; +import type {Type} from '../../type'; +import type {ModuleType} from './ModuleType'; export class AliasType implements Printable { public constructor( diff --git a/src/type/classes/AnyType.ts b/src/type/classes/AnyType.ts index f54c9598..d5d794a6 100644 --- a/src/type/classes/AnyType.ts +++ b/src/type/classes/AnyType.ts @@ -1,4 +1,4 @@ -import {AbsType} from './AbsType'; import type * as schema from '../../schema'; +import {AbsType} from './AbsType'; export class AnyType extends AbsType {} diff --git a/src/type/classes/ArrType.ts b/src/type/classes/ArrType.ts index 41ddb283..f82b5e0b 100644 --- a/src/type/classes/ArrType.ts +++ b/src/type/classes/ArrType.ts @@ -1,8 +1,8 @@ +import {printTree} from 'tree-dump'; import * as schema from '../../schema'; +import type {SchemaOf, Type} from '../types'; import {AbsType} from './AbsType'; -import {printTree} from 'tree-dump'; import type {TypeExportContext} from './ModuleType/TypeExportContext'; -import type {SchemaOf, Type} from '../types'; export class ArrType< T extends Type | void = any, diff --git a/src/type/classes/BinType.ts b/src/type/classes/BinType.ts index 7a2abeaa..7a8cac0e 100644 --- a/src/type/classes/BinType.ts +++ b/src/type/classes/BinType.ts @@ -1,7 +1,7 @@ import {printTree} from 'tree-dump/lib/printTree'; import * as schema from '../../schema'; -import {AbsType} from './AbsType'; import type {SchemaOf, Type} from '../types'; +import {AbsType} from './AbsType'; export class BinType extends AbsType { constructor( diff --git a/src/type/classes/BoolType.ts b/src/type/classes/BoolType.ts index 91b546b2..16883464 100644 --- a/src/type/classes/BoolType.ts +++ b/src/type/classes/BoolType.ts @@ -1,4 +1,4 @@ -import {AbsType} from './AbsType'; import type * as schema from '../../schema'; +import {AbsType} from './AbsType'; export class BoolType extends AbsType {} diff --git a/src/type/classes/ConType.ts b/src/type/classes/ConType.ts index b9deb1e1..48e9cbde 100644 --- a/src/type/classes/ConType.ts +++ b/src/type/classes/ConType.ts @@ -1,5 +1,5 @@ -import {AbsType} from './AbsType'; import type * as schema from '../../schema'; +import {AbsType} from './AbsType'; export class ConType extends AbsType> { public literal() { diff --git a/src/type/classes/FnType.ts b/src/type/classes/FnType.ts index b78f8eaf..e03c945f 100644 --- a/src/type/classes/FnType.ts +++ b/src/type/classes/FnType.ts @@ -1,7 +1,7 @@ import {printTree} from 'tree-dump/lib/printTree'; import * as schema from '../../schema'; -import {AbsType} from './AbsType'; import type {SchemaOf, Type} from '../types'; +import {AbsType} from './AbsType'; const fnNotImplemented: schema.FunctionValue = async () => { throw new Error('NOT_IMPLEMENTED'); diff --git a/src/type/classes/MapType.ts b/src/type/classes/MapType.ts index af602076..b3311e7b 100644 --- a/src/type/classes/MapType.ts +++ b/src/type/classes/MapType.ts @@ -1,8 +1,8 @@ import {printTree} from 'tree-dump/lib/printTree'; import * as schema from '../../schema'; +import type {SchemaOf, Type} from '../types'; import {AbsType} from './AbsType'; import type {TypeExportContext} from './ModuleType/TypeExportContext'; -import type {SchemaOf, Type} from '../types'; export class MapType extends AbsType>> { constructor( diff --git a/src/type/classes/ModuleType/index.ts b/src/type/classes/ModuleType/index.ts index e80166f1..4c1cb1c2 100644 --- a/src/type/classes/ModuleType/index.ts +++ b/src/type/classes/ModuleType/index.ts @@ -1,11 +1,11 @@ -import {TypeBuilder} from '../../TypeBuilder'; -import {AliasType} from '../AliasType'; import {printTree} from 'tree-dump/lib/printTree'; -import type {RefType} from '../RefType'; import type {Printable} from 'tree-dump/lib/types'; import type {KeySchema, ModuleSchema, ObjSchema, Schema, TypeMap} from '../../../schema'; -import type {Type} from '../../../type'; import {Walker} from '../../../schema/Walker'; +import type {Type} from '../../../type'; +import {TypeBuilder} from '../../TypeBuilder'; +import {AliasType} from '../AliasType'; +import type {RefType} from '../RefType'; export class ModuleType implements Printable { public static readonly from = (module: ModuleSchema): ModuleType => { diff --git a/src/type/classes/NumType.ts b/src/type/classes/NumType.ts index ee7236ef..db88b468 100644 --- a/src/type/classes/NumType.ts +++ b/src/type/classes/NumType.ts @@ -1,5 +1,5 @@ -import {AbsType} from './AbsType'; import type * as schema from '../../schema'; +import {AbsType} from './AbsType'; export class NumType extends AbsType { public format(format: schema.NumSchema['format']): this { diff --git a/src/type/classes/ObjType.ts b/src/type/classes/ObjType.ts index 63f57c1a..72c32477 100644 --- a/src/type/classes/ObjType.ts +++ b/src/type/classes/ObjType.ts @@ -1,8 +1,8 @@ import {printTree} from 'tree-dump/lib/printTree'; import * as schema from '../../schema'; -import {AbsType} from './AbsType'; -import type {SchemaOf, SchemaOfObjectFields, Type} from '../types'; import type {ExcludeFromTuple, PickFromTuple} from '../../util/types'; +import type {SchemaOf, SchemaOfObjectFields, Type} from '../types'; +import {AbsType} from './AbsType'; export class ObjKeyType extends AbsType>> { public readonly optional: boolean = false; @@ -51,14 +51,17 @@ export class ObjKeyOptType extends ObjKeyType< } } -export class ObjType | ObjKeyOptType)[] = (ObjKeyType | ObjKeyOptType)[]> extends AbsType< - schema.ObjSchema> -> { +export class ObjType< + F extends (ObjKeyType | ObjKeyOptType)[] = (ObjKeyType | ObjKeyOptType)[], +> extends AbsType>> { constructor(public readonly keys: F) { super(schema.s.obj as any); } - private _key(field: ObjKeyType | ObjKeyOptType, options?: schema.Optional>): void { + private _key( + field: ObjKeyType | ObjKeyOptType, + options?: schema.Optional>, + ): void { if (options) field.options(options); field.system = this.system; this.keys.push(field as any); @@ -94,7 +97,7 @@ export class ObjType | ObjKeyOptType)[ ): ObjType<[...F, ObjKeyOptType]> { this._key(new ObjKeyOptType(key, value), options); return this; - }d + } public getSchema(): schema.ObjSchema> { return { diff --git a/src/type/classes/OrType.ts b/src/type/classes/OrType.ts index 0f2d1e27..859040ae 100644 --- a/src/type/classes/OrType.ts +++ b/src/type/classes/OrType.ts @@ -1,8 +1,8 @@ -import * as schema from '../../schema'; import {printTree} from 'tree-dump/lib/printTree'; +import * as schema from '../../schema'; import {Discriminator} from '../discriminator'; -import {AbsType} from './AbsType'; import type {SchemaOf, Type} from '../types'; +import {AbsType} from './AbsType'; export class OrType extends AbsType}>> { constructor( diff --git a/src/type/classes/RefType.ts b/src/type/classes/RefType.ts index 40f1a7c5..5e735586 100644 --- a/src/type/classes/RefType.ts +++ b/src/type/classes/RefType.ts @@ -1,6 +1,6 @@ -import {AbsType} from './AbsType'; import * as schema from '../../schema'; import type {SchemaOf, Type} from '../types'; +import {AbsType} from './AbsType'; export class RefType extends AbsType>> { constructor(ref: string) { diff --git a/src/type/classes/__tests__/AliasType.spec.ts b/src/type/classes/__tests__/AliasType.spec.ts index 4acab8b2..d3992768 100644 --- a/src/type/classes/__tests__/AliasType.spec.ts +++ b/src/type/classes/__tests__/AliasType.spec.ts @@ -1,6 +1,6 @@ -import {ModuleType} from '../ModuleType'; import type {TypeOf} from '../../../schema'; import type {SchemaOf, TypeOfAlias} from '../../types'; +import {ModuleType} from '../ModuleType'; test('can infer alias type', () => { const system = new ModuleType(); diff --git a/src/type/classes/__tests__/StrType.spec.ts b/src/type/classes/__tests__/StrType.spec.ts index 357c5dc9..ec1f70a0 100644 --- a/src/type/classes/__tests__/StrType.spec.ts +++ b/src/type/classes/__tests__/StrType.spec.ts @@ -1,7 +1,7 @@ import {t} from '../../..'; import {ValidatorCodegen} from '../../../codegen/validator/ValidatorCodegen'; -import {validateSchema} from '../../../schema/validate'; import {typeToJsonSchema} from '../../../json-schema/converter'; +import {validateSchema} from '../../../schema/validate'; test('can use helper functions to define type schema fields', () => { const string = t.String(); diff --git a/src/type/discriminator.ts b/src/type/discriminator.ts index 4c9ddc3d..d1170c5d 100644 --- a/src/type/discriminator.ts +++ b/src/type/discriminator.ts @@ -1,5 +1,5 @@ -import {ArrType, BoolType, ConType, NumType, type ObjKeyType, ObjType, StrType} from './classes'; import type {Expr} from '@jsonjoy.com/json-expression'; +import {ArrType, BoolType, ConType, NumType, type ObjKeyType, ObjType, StrType} from './classes'; import type {OrType, RefType, Type} from './types'; /** diff --git a/src/type/index.ts b/src/type/index.ts index 6d2fe4ac..475c2652 100644 --- a/src/type/index.ts +++ b/src/type/index.ts @@ -1,8 +1,8 @@ export * from './types'; export * from './classes'; -import {TypeBuilder} from './TypeBuilder'; import type {TypeOf} from '../schema'; +import {TypeBuilder} from './TypeBuilder'; import type {SchemaOf, Type} from './types'; export const t = new TypeBuilder(); diff --git a/src/typescript/converter.ts b/src/typescript/converter.ts index 2f551d06..ab657766 100644 --- a/src/typescript/converter.ts +++ b/src/typescript/converter.ts @@ -1,8 +1,8 @@ +import type * as schema from '../schema'; import {type ArrType, type FnRxType, type FnType, type MapType, ObjType, type OrType} from '../type/classes'; -import type {Type} from '../type/types'; import type {AliasType} from '../type/classes/AliasType'; +import type {Type} from '../type/types'; import type * as ts from './types'; -import type * as schema from '../schema'; const augmentWithComment = ( type: schema.Schema | schema.KeySchema, diff --git a/src/value/ObjValue.ts b/src/value/ObjValue.ts index 13a0e4fc..2c321b65 100644 --- a/src/value/ObjValue.ts +++ b/src/value/ObjValue.ts @@ -1,9 +1,9 @@ -import {Value} from './Value'; import {printTree} from 'tree-dump/lib/printTree'; -import {ModuleType} from '../type/classes/ModuleType'; +import type {Printable} from 'tree-dump/lib/types'; import type * as classes from '../type'; import type {TypeBuilder} from '../type/TypeBuilder'; -import type {Printable} from 'tree-dump/lib/types'; +import {ModuleType} from '../type/classes/ModuleType'; +import {Value} from './Value'; export type UnObjType = T extends classes.ObjType ? U : never; export type UnObjValue = T extends ObjValue ? U : never; diff --git a/src/value/Value.ts b/src/value/Value.ts index 2cf55ac6..0d60adee 100644 --- a/src/value/Value.ts +++ b/src/value/Value.ts @@ -1,5 +1,5 @@ -import {printTree} from 'tree-dump/lib/printTree'; import type {Printable} from 'tree-dump'; +import {printTree} from 'tree-dump/lib/printTree'; import type {ResolveType, Type} from '../type/types'; export class Value implements Printable { diff --git a/src/value/util.ts b/src/value/util.ts index f18e39eb..e681701c 100644 --- a/src/value/util.ts +++ b/src/value/util.ts @@ -1,6 +1,6 @@ -import {Value} from './Value'; -import {ObjValue} from './ObjValue'; import type * as classes from '../type'; +import {ObjValue} from './ObjValue'; +import {Value} from './Value'; export const value: { (type: T, data: unknown): ObjValue; diff --git a/tsconfig.json b/tsconfig.json index 3d3ff263..03c2ff49 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -3,9 +3,7 @@ "include": ["src"], "exclude": ["node_modules", "lib", "es6", "es2020", "esm", "docs", "README.md"], "typedocOptions": { - "entryPoints": [ - "src/index.ts" - ], + "entryPoints": ["src/index.ts"], "out": "typedocs" } } From cd1aff701f20d4cd002ee0296a252bfec2d900e3 Mon Sep 17 00:00:00 2001 From: streamich Date: Sun, 10 Aug 2025 10:20:11 +0200 Subject: [PATCH 04/18] =?UTF-8?q?refactor:=20=F0=9F=92=A1=20align=20naming?= =?UTF-8?q?=20use=20"key"=20in=20type=20builder?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/__bench__/encode.ts | 34 +++--- src/__tests__/fixtures.ts | 98 ++++++++--------- .../binary/__tests__/testBinaryCodegen.ts | 102 +++++++++--------- .../CapacityEstimatorCodegenContext.spec.ts | 10 +- .../json/__tests__/JsonTextCodegen.spec.ts | 10 +- .../validator/__tests__/codegen.spec.ts | 4 +- src/json-schema/__tests__/alias.spec.ts | 6 +- src/jtd/__tests__/converter.spec.ts | 2 +- src/random/Random.ts | 6 +- src/random/__tests__/random.spec.ts | 16 +-- src/type/TypeBuilder.ts | 12 +-- src/type/__tests__/SchemaOf.spec.ts | 13 ++- src/type/__tests__/TypeBuilder.spec.ts | 18 ++-- src/type/__tests__/discriminator.spec.ts | 16 +-- src/type/__tests__/fixtures.ts | 2 +- src/type/__tests__/getJsonSchema.spec.ts | 38 +++---- src/type/__tests__/random.spec.ts | 10 +- src/type/__tests__/toString.spec.ts | 42 ++++---- src/type/__tests__/toTypeScriptAst.spec.ts | 4 +- src/type/__tests__/validateTestSuite.ts | 2 +- src/type/classes.ts | 6 +- .../ModuleType/__tests__/TypeSystem.spec.ts | 4 +- src/type/classes/ModuleType/__tests__/demo.ts | 16 +-- .../ModuleType/__tests__/toTypeScript.spec.ts | 26 ++--- src/type/classes/ObjType.ts | 24 ++--- src/type/classes/__tests__/AliasType.spec.ts | 2 +- src/type/classes/__tests__/ObjType.spec.ts | 12 +-- src/value/ObjValue.ts | 2 +- src/value/__tests__/ObjValue.spec.ts | 4 +- 29 files changed, 275 insertions(+), 266 deletions(-) diff --git a/src/__bench__/encode.ts b/src/__bench__/encode.ts index f10d4c76..e958d38e 100644 --- a/src/__bench__/encode.ts +++ b/src/__bench__/encode.ts @@ -13,29 +13,29 @@ const {t} = system; const response = system.alias( 'Response', t.Object( - t.prop( + t.Key( '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.Key('id', t.String({ascii: true, noJsonEscape: true})), + t.Key('ts', t.num.options({format: 'u64'})), + t.Key('cid', t.String({ascii: true, noJsonEscape: true})), + t.Key('prid', t.String({ascii: true, noJsonEscape: true})), + t.Key('slug', t.String({ascii: true, noJsonEscape: true})), + t.KeyOpt('name', t.str), + t.KeyOpt('src', t.str), + t.KeyOpt('doc', t.str), + t.KeyOpt('longText', t.str), + t.Key('active', t.bool), + t.Key('views', t.Array(t.num)), ), ), - t.prop( + t.Key( '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})), + t.Key('id', t.String({ascii: true, noJsonEscape: true})), + t.Key('ts', t.num.options({format: 'u64'})), + t.Key('cid', t.String({ascii: true, noJsonEscape: true})), + t.Key('slug', t.String({ascii: true, noJsonEscape: true})), ), ), ), diff --git a/src/__tests__/fixtures.ts b/src/__tests__/fixtures.ts index 73a2d9a9..0c4193e5 100644 --- a/src/__tests__/fixtures.ts +++ b/src/__tests__/fixtures.ts @@ -98,28 +98,28 @@ export const User = t * Product catalog schema with arrays and formatted numbers */ export const Product = t.Object( - t.prop('id', t.String({format: 'ascii'})), - t.prop('name', t.String({min: 1, max: 100})), - t.prop('price', t.Number({format: 'f64', gte: 0})), - t.prop('inStock', t.bool), - t.prop('categories', t.Array(t.str, {min: 1})), - t.prop('tags', t.Array(t.str)), - t.propOpt('description', t.String({max: 1000})), - t.propOpt('discount', t.Number({gte: 0, lte: 1})), + t.Key('id', t.String({format: 'ascii'})), + t.Key('name', t.String({min: 1, max: 100})), + t.Key('price', t.Number({format: 'f64', gte: 0})), + t.Key('inStock', t.bool), + t.Key('categories', t.Array(t.str, {min: 1})), + t.Key('tags', t.Array(t.str)), + t.KeyOpt('description', t.String({max: 1000})), + t.KeyOpt('discount', t.Number({gte: 0, lte: 1})), ); /** * Blog post schema with timestamps and rich content */ export const BlogPost = t.Object( - t.prop('id', t.str), - t.prop('title', t.String({min: 1, max: 200})), - t.prop('content', t.str), - t.prop('author', t.Ref('User')), - t.prop('publishedAt', t.Number({format: 'u64'})), - t.prop('status', t.enum('draft', 'published', 'archived')), - t.propOpt('updatedAt', t.Number({format: 'u64'})), - t.propOpt('tags', t.Array(t.str)), + t.Key('id', t.str), + t.Key('title', t.String({min: 1, max: 200})), + t.Key('content', t.str), + t.Key('author', t.Ref('User')), + t.Key('publishedAt', t.Number({format: 'u64'})), + t.Key('status', t.enum('draft', 'published', 'archived')), + t.KeyOpt('updatedAt', t.Number({format: 'u64'})), + t.KeyOpt('tags', t.Array(t.str)), ); /** @@ -145,21 +145,21 @@ export const ApiResponse = t.Or( * File metadata schema with binary data */ export const FileMetadata = t.Object( - t.prop('name', t.str), - t.prop('size', t.Number({format: 'u64', gte: 0})), - t.prop('mimeType', t.str), - t.prop('data', t.Binary(t.any)), - t.prop('checksum', t.String({format: 'ascii', min: 64, max: 64})), - t.prop('uploadedAt', t.Number({format: 'u64'})), - t.propOpt('metadata', t.Map(t.str)), + t.Key('name', t.str), + t.Key('size', t.Number({format: 'u64', gte: 0})), + t.Key('mimeType', t.str), + t.Key('data', t.Binary(t.any)), + t.Key('checksum', t.String({format: 'ascii', min: 64, max: 64})), + t.Key('uploadedAt', t.Number({format: 'u64'})), + t.KeyOpt('metadata', t.Map(t.str)), ); /** * Configuration schema with maps and default values */ export const Configuration = t.Object( - t.prop('environment', t.enum('development', 'staging', 'production')), - t.prop( + t.Key('environment', t.enum('development', 'staging', 'production')), + t.Key( 'database', t.object({ host: t.str, @@ -167,9 +167,9 @@ export const Configuration = t.Object( name: t.str, }), ), - t.prop('features', t.Map(t.bool)), - t.prop('secrets', t.Map(t.str)), - t.propOpt( + t.Key('features', t.Map(t.bool)), + t.Key('secrets', t.Map(t.str)), + t.KeyOpt( 'logging', t.object({ level: t.enum('debug', 'info', 'warn', 'error'), @@ -182,29 +182,29 @@ export const Configuration = t.Object( * Event data schema with tuples and coordinates */ export const Event = t.Object( - t.prop('id', t.String({format: 'ascii'})), - t.prop('type', t.enum('click', 'view', 'purchase', 'signup')), - t.prop('timestamp', t.Number({format: 'u64'})), - t.prop('userId', t.maybe(t.str)), - t.prop('location', t.Tuple([t.Number({format: 'f64'}), t.Number({format: 'f64'})])), - t.prop('metadata', t.Map(t.Or(t.str, t.num, t.bool))), - t.propOpt('sessionId', t.str), + t.Key('id', t.String({format: 'ascii'})), + t.Key('type', t.enum('click', 'view', 'purchase', 'signup')), + t.Key('timestamp', t.Number({format: 'u64'})), + t.Key('userId', t.maybe(t.str)), + t.Key('location', t.Tuple([t.Number({format: 'f64'}), t.Number({format: 'f64'})])), + t.Key('metadata', t.Map(t.Or(t.str, t.num, t.bool))), + t.KeyOpt('sessionId', t.str), ); /** * Contact information schema with formatted strings */ export const ContactInfo = t.Object( - t.prop( + t.Key( 'name', t.object({ first: t.String({min: 1}), last: t.String({min: 1}), }), ), - t.prop('emails', t.Array(t.String({format: 'ascii'}), {min: 1})), - t.prop('phones', t.Array(t.tuple(t.enum('home', 'work', 'mobile'), t.str))), - t.propOpt( + t.Key('emails', t.Array(t.String({format: 'ascii'}), {min: 1})), + t.Key('phones', t.Array(t.tuple(t.enum('home', 'work', 'mobile'), t.str))), + t.KeyOpt( 'address', t.object({ street: t.str, @@ -213,20 +213,20 @@ export const ContactInfo = t.Object( postalCode: t.str, }), ), - t.propOpt('socialMedia', t.Map(t.String({format: 'ascii'}))), + t.KeyOpt('socialMedia', t.Map(t.String({format: 'ascii'}))), ); /** * Database record schema with references */ export const DatabaseRecord = t.Object( - t.prop('id', t.String({format: 'ascii'})), - t.prop('createdAt', t.Number({format: 'u64'})), - t.prop('updatedAt', t.Number({format: 'u64'})), - t.prop('version', t.Number({format: 'u32', gte: 1})), - t.prop('createdBy', t.Ref('User')), - t.propOpt('updatedBy', t.Ref('User')), - t.propOpt('deletedAt', t.Number({format: 'u64'})), + t.Key('id', t.String({format: 'ascii'})), + t.Key('createdAt', t.Number({format: 'u64'})), + t.Key('updatedAt', t.Number({format: 'u64'})), + t.Key('version', t.Number({format: 'u32', gte: 1})), + t.Key('createdBy', t.Ref('User')), + t.KeyOpt('updatedBy', t.Ref('User')), + t.KeyOpt('deletedAt', t.Number({format: 'u64'})), ); /** @@ -260,7 +260,7 @@ export const EventStream = t.Function$( * Complex nested schema */ export const ComplexNested = t.Object( - t.prop( + t.Key( 'data', t.Map( t.Or( @@ -278,7 +278,7 @@ export const ComplexNested = t.Object( ), ), ), - t.prop( + t.Key( 'metadata', t.object({ version: t.str, diff --git a/src/codegen/binary/__tests__/testBinaryCodegen.ts b/src/codegen/binary/__tests__/testBinaryCodegen.ts index 92e34b15..613f2384 100644 --- a/src/codegen/binary/__tests__/testBinaryCodegen.ts +++ b/src/codegen/binary/__tests__/testBinaryCodegen.ts @@ -236,7 +236,7 @@ export const testBinaryCodegen = (transcode: (system: ModuleType, type: Type, va test('can encode empty object, which has optional fields', () => { const system = new ModuleType(); const t = system.t; - const type = t.Object(t.propOpt('field1', t.str)); + const type = t.Object(t.KeyOpt('field1', t.str)); const value1: any = {}; expect(transcode(system, type, value1)).toStrictEqual(value1); const value2: any = {field1: 'abc'}; @@ -246,7 +246,7 @@ export const testBinaryCodegen = (transcode: (system: ModuleType, type: Type, va test('can encode fixed size object', () => { const system = new ModuleType(); const t = system.t; - const type = t.Object(t.prop('field1', t.str), t.prop('field2', t.num), t.prop('bool', t.bool)); + const type = t.Object(t.Key('field1', t.str), t.Key('field2', t.num), t.Key('bool', t.bool)); const value: any = { field1: 'abc', field2: 123, @@ -258,7 +258,7 @@ export const testBinaryCodegen = (transcode: (system: ModuleType, type: Type, va test('can encode object with an optional field', () => { const system = new ModuleType(); const t = system.t; - const type = t.Object(t.prop('id', t.str), t.propOpt('name', t.str)); + const type = t.Object(t.Key('id', t.str), t.KeyOpt('name', t.str)); const value: any = { id: 'xxxxx', name: 'Go Lang', @@ -270,10 +270,10 @@ export const testBinaryCodegen = (transcode: (system: ModuleType, type: Type, va const system = new ModuleType(); 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), + t.Key('id', t.str), + t.KeyOpt('name', t.str), + t.Key('age', t.num), + t.KeyOpt('address', t.str), ); const value: any = { id: 'xxxxx', @@ -288,7 +288,7 @@ export const testBinaryCodegen = (transcode: (system: ModuleType, type: Type, va const system = new ModuleType(); 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)) + .Object(t.Key('id', t.str), t.KeyOpt('name', t.str), t.Key('age', t.num), t.KeyOpt('address', t.str)) .options({encodeUnknownKeys: true}); const value: any = { id: 'xxxxx', @@ -305,12 +305,12 @@ export const testBinaryCodegen = (transcode: (system: ModuleType, type: Type, va 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( + t.Key('id', t.str), + t.KeyOpt('name', t.str), + t.Key('addr', t.Object(t.Key('street', t.str))), + t.Key( 'interests', - t.Object(t.propOpt('hobbies', t.Array(t.str)), t.propOpt('sports', t.Array(t.Tuple([t.num, t.str])))), + t.Object(t.KeyOpt('hobbies', t.Array(t.str)), t.KeyOpt('sports', t.Array(t.Tuple([t.num, t.str])))), ), ) .options({encodeUnknownKeys: true}); @@ -352,7 +352,7 @@ export const testBinaryCodegen = (transcode: (system: ModuleType, type: Type, va const system = new ModuleType(); const t = system.t; const type = t - .Object(t.propOpt('id', t.str), t.propOpt('name', t.str), t.propOpt('address', t.str)) + .Object(t.KeyOpt('id', t.str), t.KeyOpt('name', t.str), t.KeyOpt('address', t.str)) .options({encodeUnknownKeys: true}); let value: any = { id: 'xxxxx', @@ -379,7 +379,7 @@ export const testBinaryCodegen = (transcode: (system: ModuleType, type: Type, va const system = new ModuleType(); const t = system.t; const type = t - .Object(t.propOpt('id', t.str), t.propOpt('name', t.str), t.propOpt('address', t.str)) + .Object(t.KeyOpt('id', t.str), t.KeyOpt('name', t.str), t.KeyOpt('address', t.str)) .options({encodeUnknownKeys: false}); let value: any = { id: 'xxxxx', @@ -441,7 +441,7 @@ export const testBinaryCodegen = (transcode: (system: ModuleType, type: Type, va test('can encode a simple reference', () => { const system = new ModuleType(); const t = system.t; - system.alias('Obj', t.Object(t.prop('foo', t.str))); + system.alias('Obj', t.Object(t.Key('foo', t.str))); const type = t.Ref('Obj'); expect(transcode(system, type, {foo: 'bar'})).toStrictEqual({ foo: 'bar', @@ -468,29 +468,29 @@ export const testBinaryCodegen = (transcode: (system: ModuleType, type: Type, va const response = system.alias( 'Response', t.Object( - t.prop( + t.Key( '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.Key('id', t.String({ascii: true, noJsonEscape: true})), + t.Key('ts', t.num.options({format: 'u64'})), + t.Key('cid', t.String({ascii: true, noJsonEscape: true})), + t.Key('prid', t.String({ascii: true, noJsonEscape: true})), + t.Key('slug', t.String({ascii: true, noJsonEscape: true})), + t.KeyOpt('name', t.str), + t.KeyOpt('src', t.str), + t.KeyOpt('doc', t.str), + t.KeyOpt('longText', t.str), + t.Key('active', t.bool), + t.Key('views', t.Array(t.num)), ), ), - t.prop( + t.Key( '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})), + t.Key('id', t.String({ascii: true, noJsonEscape: true})), + t.Key('ts', t.num.options({format: 'u64'})), + t.Key('cid', t.String({ascii: true, noJsonEscape: true})), + t.Key('slug', t.String({ascii: true, noJsonEscape: true})), ), ), ), @@ -525,12 +525,12 @@ export const testBinaryCodegen = (transcode: (system: ModuleType, type: Type, va const system = new ModuleType(); 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), + t.Key('a', t.num), + t.Key('b', t.str), + t.Key('c', t.nil), + t.Key('d', t.bool), + t.Key('arr', t.Array(t.Object(t.Key('foo', t.Array(t.num)), t.Key('.!@#', t.str)))), + t.Key('bin', t.bin), ); const value = { a: 1.1, @@ -550,7 +550,7 @@ export const testBinaryCodegen = (transcode: (system: ModuleType, type: Type, va test('supports "encodeUnknownFields" property', () => { const system = new ModuleType(); const t = system.t; - const type = t.Object(t.prop('a', t.Object().options({encodeUnknownKeys: true}))); + const type = t.Object(t.Key('a', t.Object().options({encodeUnknownKeys: true}))); const value = { a: { foo: 123, @@ -563,7 +563,7 @@ export const testBinaryCodegen = (transcode: (system: ModuleType, type: Type, va test('supports "encodeUnknownFields" property', () => { const system = new ModuleType(); 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 type = t.Object(t.Key('a', t.num), t.KeyOpt('b', t.num), t.Key('c', t.bool), t.KeyOpt('d', t.nil)); const json1 = { a: 1.1, b: 3, @@ -591,18 +591,18 @@ export const testBinaryCodegen = (transcode: (system: ModuleType, type: Type, va const system = new ModuleType(); const t = system.t; const type = t.Object( - t.prop( + t.Key( '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), + t.Key('id', t.str), + t.Key('ts', t.num), + t.Key('cid', t.str), + t.Key('prid', t.str), + t.Key('slug', t.str), + t.KeyOpt('name', t.str), + t.KeyOpt('src', t.str), + t.KeyOpt('doc', t.str), + t.KeyOpt('authz', t.str), ), ), ); diff --git a/src/codegen/capacity/__tests__/CapacityEstimatorCodegenContext.spec.ts b/src/codegen/capacity/__tests__/CapacityEstimatorCodegenContext.spec.ts index fdef71bc..e884aa51 100644 --- a/src/codegen/capacity/__tests__/CapacityEstimatorCodegenContext.spec.ts +++ b/src/codegen/capacity/__tests__/CapacityEstimatorCodegenContext.spec.ts @@ -154,14 +154,14 @@ describe('"obj" type', () => { test('one required key', () => { const system = new ModuleType(); - const type = system.t.Object(system.t.prop('abc', system.t.str)); + const type = system.t.Object(system.t.Key('abc', system.t.str)); const estimator = CapacityEstimatorCodegen.get(type); expect(estimator({abc: 'foo'})).toBe(maxEncodingCapacity({abc: 'foo'})); }); test('one required and one optional keys', () => { const system = new ModuleType(); - const type = system.t.Object(system.t.prop('abc', system.t.str), system.t.propOpt('key', system.t.num)); + const type = system.t.Object(system.t.Key('abc', system.t.str), system.t.KeyOpt('key', system.t.num)); const estimator = CapacityEstimatorCodegen.get(type); expect(estimator({abc: 'foo', key: 111})).toBe(maxEncodingCapacity({abc: 'foo', key: 111})); }); @@ -203,7 +203,7 @@ describe('"ref" type', () => { test('two hops', () => { const system = new ModuleType(); 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))); + system.alias('User', system.t.Object(system.t.Key('id', system.t.Ref('Id')), system.t.Key('name', system.t.str))); const type = system.t.Ref('User'); const value = {id: 'asdf', name: 'foo'}; const estimator = CapacityEstimatorCodegen.get(type); @@ -231,8 +231,8 @@ describe('"or" type', () => { test('add circular reference test', () => { const system = new ModuleType(); 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 user = system.alias('User', t.Object(t.Key('id', t.str), t.KeyOpt('address', t.Ref('Address')))); + const address = system.alias('Address', t.Object(t.Key('id', t.str), t.KeyOpt('user', t.Ref('User')))); const value1 = { id: 'user-1', address: { diff --git a/src/codegen/json/__tests__/JsonTextCodegen.spec.ts b/src/codegen/json/__tests__/JsonTextCodegen.spec.ts index 2e00f8ff..c9cde7bc 100644 --- a/src/codegen/json/__tests__/JsonTextCodegen.spec.ts +++ b/src/codegen/json/__tests__/JsonTextCodegen.spec.ts @@ -100,7 +100,7 @@ describe('"or" type', () => { test('encodes extra fields with "encodeUnknownFields" when referenced by ref', () => { const system = new ModuleType(); const {t} = system; - const type = t.Object(t.prop('foo', t.str), t.propOpt('zzz', t.num)).options({encodeUnknownKeys: true}); + const type = t.Object(t.Key('foo', t.str), t.KeyOpt('zzz', t.num)).options({encodeUnknownKeys: true}); system.alias('foo', type); const type2 = system.t.Ref('foo'); const encoder = JsonTextCodegen.get(type2); @@ -110,8 +110,8 @@ test('encodes extra fields with "encodeUnknownFields" when referenced by ref', ( test('add circular reference test', () => { const system = new ModuleType(); 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 user = system.alias('User', t.Object(t.Key('id', t.str), t.KeyOpt('address', t.Ref('Address')))); + const address = system.alias('Address', t.Object(t.Key('id', t.str), t.KeyOpt('user', t.Ref('User')))); const value1 = { id: 'user-1', address: { @@ -154,10 +154,10 @@ test('add circular reference test', () => { test('add circular reference test with chain of refs', () => { const system = new ModuleType(); const {t} = system; - system.alias('User0', t.Object(t.prop('id', t.str), t.propOpt('address', t.Ref('Address')))); + system.alias('User0', t.Object(t.Key('id', t.str), t.KeyOpt('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('Address0', t.Object(t.Key('id', t.str), t.KeyOpt('user', t.Ref('User')))); system.alias('Address1', t.Ref('Address0')); const address = system.alias('Address', t.Ref('Address1')); const value1 = { diff --git a/src/codegen/validator/__tests__/codegen.spec.ts b/src/codegen/validator/__tests__/codegen.spec.ts index 9550b1a8..7e9be4de 100644 --- a/src/codegen/validator/__tests__/codegen.spec.ts +++ b/src/codegen/validator/__tests__/codegen.spec.ts @@ -1308,7 +1308,7 @@ describe('custom validators', () => { test('returns the error, which validator throws', () => { const system = new ModuleType(); const type = system.t.Object( - system.t.prop( + system.t.Key( 'id', system.t.str.validator((id: string): void => { if (!/^[a-z]+$/.test(id)) throw new Error('Asset ID must be a string.'); @@ -1336,7 +1336,7 @@ describe('custom validators', () => { throw new Error('Asset ID must be a string.'); }, 'assetId') .alias('ID'); - const type = system.t.Object(system.t.prop('id', system.t.Ref('ID'))); + const type = system.t.Object(system.t.Key('id', system.t.Ref('ID'))); const validator = ValidatorCodegen.get({type, errors: 'object'}); expect(validator({id: 'xxxxxxx'})).toBe(null); expect(validator({id: 'y'})).toBe(null); diff --git a/src/json-schema/__tests__/alias.spec.ts b/src/json-schema/__tests__/alias.spec.ts index ecb1eeb4..8a924f1a 100644 --- a/src/json-schema/__tests__/alias.spec.ts +++ b/src/json-schema/__tests__/alias.spec.ts @@ -4,9 +4,9 @@ import {aliasToJsonSchema} from '../converter'; test('can export recursive schema', () => { const system = new ModuleType(); const {t} = system; - const post = system.alias('Post', t.Object(t.prop('id', t.str), t.propOpt('author', t.Ref('User')))); - system.alias('Stream', t.Object(t.prop('id', t.str), t.prop('posts', t.Array(t.Ref('Post'))))); - system.alias('User', t.Object(t.prop('id', t.str), t.prop('name', t.str), t.prop('following', t.Ref('Stream')))); + const post = system.alias('Post', t.Object(t.Key('id', t.str), t.KeyOpt('author', t.Ref('User')))); + system.alias('Stream', t.Object(t.Key('id', t.str), t.Key('posts', t.Array(t.Ref('Post'))))); + system.alias('User', t.Object(t.Key('id', t.str), t.Key('name', t.str), t.Key('following', t.Ref('Stream')))); const schema = aliasToJsonSchema(post); expect(schema.$ref).toBe('#/$defs/Post'); expect(typeof schema.$defs?.Post).toBe('object'); diff --git a/src/jtd/__tests__/converter.spec.ts b/src/jtd/__tests__/converter.spec.ts index 1297cea1..7777c445 100644 --- a/src/jtd/__tests__/converter.spec.ts +++ b/src/jtd/__tests__/converter.spec.ts @@ -47,7 +47,7 @@ describe('JTD converter', () => { }); test('object type', () => { - const objectType = t.Object(t.prop('name', t.str), t.propOpt('age', t.num)); + const objectType = t.Object(t.Key('name', t.str), t.KeyOpt('age', t.num)); const jtdForm = toJtdForm(objectType); expect(jtdForm).toEqual({ properties: { diff --git a/src/random/Random.ts b/src/random/Random.ts index b325a291..47eb0c52 100644 --- a/src/random/Random.ts +++ b/src/random/Random.ts @@ -18,7 +18,7 @@ import type { Type, t, } from '../type'; -import {ObjKeyOptType, type ObjKeyType, type ObjType} from '../type/classes/ObjType'; +import {KeyOptType, type KeyType, type ObjType} from '../type/classes/ObjType'; export class Random { public static readonly gen = (type: T): t.infer => { @@ -172,8 +172,8 @@ export class Random { ? >RandomJson.genObject() : {}; for (const f of type.keys) { - const field = f as ObjKeyType; - const isOptional = field instanceof ObjKeyOptType; + const field = f as KeyType; + const isOptional = field instanceof KeyOptType; if (isOptional && Math.random() > 0.5) continue; obj[field.key] = this.gen(field.val); } diff --git a/src/random/__tests__/random.spec.ts b/src/random/__tests__/random.spec.ts index 9e68e575..0a778dd4 100644 --- a/src/random/__tests__/random.spec.ts +++ b/src/random/__tests__/random.spec.ts @@ -117,7 +117,7 @@ describe('Random', () => { }); test('obj generates valid objects', () => { - const type = t.Object(t.prop('id', t.String()), t.prop('count', t.Number())); + const type = t.Object(t.Key('id', t.String()), t.Key('count', t.Number())); for (let i = 0; i < 10; i++) { const value = Random.gen(type); expect(typeof value).toBe('object'); @@ -218,21 +218,21 @@ describe('Random', () => { test('handles nested complex structures', () => { const complexType = t.Object( - t.prop( + t.Key( 'users', t.Array( t.Object( - t.prop('id', t.Number()), - t.prop( + t.Key('id', t.Number()), + t.Key( 'profile', - t.Object(t.prop('name', t.String()), t.prop('preferences', t.Map(t.Or(t.String(), t.Boolean())))), + t.Object(t.Key('name', t.String()), t.Key('preferences', t.Map(t.Or(t.String(), t.Boolean())))), ), - t.propOpt('tags', t.Array(t.String())), + t.KeyOpt('tags', t.Array(t.String())), ), ), ), - t.prop('metadata', t.Map(t.Any())), - t.prop('config', t.tuple(t.String(), t.Number(), t.Object(t.prop('enabled', t.Boolean())))), + t.Key('metadata', t.Map(t.Any())), + t.Key('config', t.tuple(t.String(), t.Number(), t.Object(t.Key('enabled', t.Boolean())))), ); for (let i = 0; i < 5; i++) { diff --git a/src/type/TypeBuilder.ts b/src/type/TypeBuilder.ts index 14021683..21afe45b 100644 --- a/src/type/TypeBuilder.ts +++ b/src/type/TypeBuilder.ts @@ -119,7 +119,7 @@ export class TypeBuilder { */ public readonly object = >(record: R): classes.ObjType> => { const keys: classes.ObjKeyType[] = []; - for (const [key, value] of Object.entries(record)) keys.push(this.prop(key, value)); + for (const [key, value] of Object.entries(record)) keys.push(this.Key(key, value)); return new classes.ObjType>(keys as any).sys(this.system); }; @@ -199,11 +199,11 @@ export class TypeBuilder { return new classes.ObjType(keys).sys(this.system); } - public prop(key: K, value: V) { + public Key(key: K, value: V) { return new classes.ObjKeyType(key, value).sys(this.system); } - public propOpt(key: K, value: V) { + public KeyOpt(key: K, value: V) { return new classes.ObjKeyOptType(key, value).sys(this.system); } @@ -259,8 +259,8 @@ export class TypeBuilder { case 'obj': { const fields = node.keys.map((f: any) => f.optional - ? this.propOpt(f.key, this.import(f.value)).options(f) - : this.prop(f.key, this.import(f.value)).options(f), + ? this.KeyOpt(f.key, this.import(f.value)).options(f) + : this.Key(f.key, this.import(f.value)).options(f), ); return this.Object(...fields).options(node); } @@ -305,7 +305,7 @@ export class TypeBuilder { ? 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)))); + return this.Object(...Object.entries(value).map(([key, value]) => this.Key(key, this.from(value)))); default: return this.any; } diff --git a/src/type/__tests__/SchemaOf.spec.ts b/src/type/__tests__/SchemaOf.spec.ts index ff6cd1ee..5f129b17 100644 --- a/src/type/__tests__/SchemaOf.spec.ts +++ b/src/type/__tests__/SchemaOf.spec.ts @@ -61,6 +61,15 @@ describe('"arr" type', () => { const v2: T = [123, 'abc', 1]; }); + test('named 2-tuple', () => { + const type = t.Tuple([t.num, t.Key('id', t.str)]); + type S = SchemaOf; + type T = TypeOf; + const v1: T = [123, 'abc']; + // @ts-expect-error + const v2: T = [123, 'abc', 1]; + }); + test('2-tuple using shorthand', () => { const type = t.tuple(t.num, t.str); type S = SchemaOf; @@ -105,14 +114,14 @@ describe('"arr" type', () => { }); test('object', () => { - const type = t.Object(t.prop('a', t.num), t.prop('b', t.str)); + const type = t.Object(t.Key('a', t.num), t.Key('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)); + const type = t.Object(t.Key('a', t.num), t.KeyOpt('b', t.str)); type S = SchemaOf; type T = TypeOf; const v: T = {a: 123}; diff --git a/src/type/__tests__/TypeBuilder.spec.ts b/src/type/__tests__/TypeBuilder.spec.ts index 1edb44d2..7439b51a 100644 --- a/src/type/__tests__/TypeBuilder.spec.ts +++ b/src/type/__tests__/TypeBuilder.spec.ts @@ -2,7 +2,7 @@ import {type SchemaOf, t} from '..'; import type {NumSchema, TypeOf} from '../../schema'; import {validateSchema} from '../../schema/validate'; import {NumType, ObjType, StrType} from '../classes'; -import {ObjKeyType} from '../classes/ObjType'; +import {KeyType} from '../classes/ObjType'; test('number', () => { const type = t.Number({ @@ -56,10 +56,10 @@ test('array of any with options', () => { 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), + t.Key('id', t.str), + t.KeyOpt('name', t.str), + t.KeyOpt('age', t.num), + t.Key('verified', t.bool), ); expect(type.getSchema()).toStrictEqual({ kind: 'obj', @@ -162,7 +162,7 @@ describe('import()', () => { expect(type).toBeInstanceOf(ObjType); expect(type.kind()).toBe('obj'); const id = type.getField('id')!; - expect(id).toBeInstanceOf(ObjKeyType); + expect(id).toBeInstanceOf(KeyType); expect(id.kind()).toBe('key'); expect(id.val).toBeInstanceOf(StrType); expect(id.val.kind()).toBe('str'); @@ -219,9 +219,9 @@ describe('validateSchema()', () => { 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'})), + t.Key('id', t.String()), + t.Key('name', t.String({title: 'Name'})), + t.Key('age', t.Number({format: 'u16'})), ); validateSchema(type.getSchema()); }); diff --git a/src/type/__tests__/discriminator.spec.ts b/src/type/__tests__/discriminator.spec.ts index e348ed16..3d04b75a 100644 --- a/src/type/__tests__/discriminator.spec.ts +++ b/src/type/__tests__/discriminator.spec.ts @@ -28,7 +28,7 @@ describe('Discriminator', () => { }); 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 t1 = t.Object(t.Key('op', t.Const('replace')), t.Key('value', t.num), t.Key('path', t.str)); const d1 = Discriminator.find(t1); expect(d1!.toSpecifier()).toBe('["/op","con","replace"]'); }); @@ -47,7 +47,7 @@ describe('Discriminator', () => { 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 t2 = t.Object(t.Key('type', t.tuple(t.Const(25), t.str, t.any)), t.Key('value', t.num)); const d1 = Discriminator.find(t1); const d2 = Discriminator.find(t2); // const d3 = Discriminator.find(t3); @@ -71,12 +71,12 @@ describe('OrType', () => { 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)), + t.Object(t.Key('op', t.Const('replace')), t.Key('path', t.str), t.Key('value', t.any)), + t.Object(t.Key('op', t.Const('add')), t.Key('path', t.str), t.Key('value', t.any)), + t.Object(t.Key('op', t.Const('test')), t.Key('path', t.str), t.Key('value', t.any)), + t.Object(t.Key('op', t.Const('move')), t.Key('path', t.str), t.Key('from', t.str)), + t.Object(t.Key('op', t.Const('copy')), t.Key('path', t.str), t.Key('from', t.str)), + t.Object(t.Key('op', t.Const('remove')), t.Key('path', t.str)), ); const validator = ValidatorCodegen.get({type: or, errors: 'boolean'}); expect(validator({op: 'replace', path: '/foo', value: 123})).toBe(false); diff --git a/src/type/__tests__/fixtures.ts b/src/type/__tests__/fixtures.ts index cd31f266..88d2ae2a 100644 --- a/src/type/__tests__/fixtures.ts +++ b/src/type/__tests__/fixtures.ts @@ -11,7 +11,7 @@ export const everyType = t.Object( // t.prop('arr', t.arr), // t.prop('obj', t.obj), // t.prop('any', t.any), - t.prop('undef', t.undef), + t.Key('undef', t.undef), // t.prop('const', t.Const('const')), // t.prop('const2', t.Const(2)), // t.prop('emptyArray', t.arr.options({max: 0})), diff --git a/src/type/__tests__/getJsonSchema.spec.ts b/src/type/__tests__/getJsonSchema.spec.ts index e4ac134b..0ede63ec 100644 --- a/src/type/__tests__/getJsonSchema.spec.ts +++ b/src/type/__tests__/getJsonSchema.spec.ts @@ -4,29 +4,29 @@ import {typeToJsonSchema} from '../../json-schema'; test('can print a type', () => { const type = t .Object( - t.prop('id', t.str).options({ + t.Key('id', t.str).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( + t.Key('tags', t.Array(t.str).options({title: 'Tags'})).options({title: 'Always use tags'}), + t.KeyOpt('optional', t.any), + t.Key('booleanProperty', t.bool), + t.Key('numberProperty', t.num.options({format: 'f64', gt: 3.14})), + t.Key('binaryProperty', t.bin.options({format: 'cbor'})), + t.Key('arrayProperty', t.Array(t.any)), + t.Key('objectProperty', t.Object(t.Key('id', t.str.options({ascii: true, min: 3, max: 128})))), + t.Key('unionProperty', t.Or(t.str, t.num, t.nil.options({description: ''}))), + t.KeyOpt('enumAsConst', t.Or(t.Const('a' as const), t.Const('b' as const), t.Const('c' as const))), + t.KeyOpt('refField', t.Ref('refId')), + t.KeyOpt('und', t.undef), + t.Key( '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.Key('type', t.Const('replace' as const).options({title: 'Always use replace'})), + t.Key('path', t.str), + t.Key('value', t.any), ), ), - t.prop( + t.Key( 'binaryOperation', t .Binary( @@ -36,7 +36,7 @@ test('can print a type', () => { ) .options({format: 'cbor'}), ), - t.prop('map', t.Map(t.str)), + t.Key('map', t.Map(t.str)), ) .options({decodeUnknownKeys: true}); // console.log(JSON.stringify(type.toJsonSchema(), null, 2)); @@ -198,7 +198,7 @@ test('can print a type', () => { test('exports "ref" type to JSON Schema "$defs"', () => { const system = new ModuleType(); const t = system.t; - const type = t.Object(t.prop('id', t.str), t.prop('user', t.Ref('User'))); + const type = t.Object(t.Key('id', t.str), t.Key('user', t.Ref('User'))); const schema = typeToJsonSchema(type) as any; expect(schema.properties.user.$ref).toBe('#/$defs/User'); }); diff --git a/src/type/__tests__/random.spec.ts b/src/type/__tests__/random.spec.ts index 11a6071e..87773f1d 100644 --- a/src/type/__tests__/random.spec.ts +++ b/src/type/__tests__/random.spec.ts @@ -10,11 +10,11 @@ test('generates random JSON', () => { 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)), + t.Key('id', t.str), + t.Key('name', t.str), + t.Key('tags', t.Array(t.str)), + t.KeyOpt('scores', t.Array(t.num)), + t.Key('refs', t.Map(t.str)), ); const json = Random.gen(type); expect(typeof json).toBe('object'); diff --git a/src/type/__tests__/toString.spec.ts b/src/type/__tests__/toString.spec.ts index 79556d11..7d6b944e 100644 --- a/src/type/__tests__/toString.spec.ts +++ b/src/type/__tests__/toString.spec.ts @@ -3,29 +3,29 @@ import {t} from '..'; test('can print a type', () => { const type = t .Object( - t.prop('id', t.str).options({ + t.Key('id', t.str).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( + t.Key('tags', t.Array(t.str).options({title: 'Tags'})).options({title: 'Always use tags'}), + t.KeyOpt('optional', t.any), + t.Key('booleanProperty', t.bool), + t.Key('numberProperty', t.num.options({format: 'f64', gt: 3.14})), + t.Key('binaryProperty', t.bin.options({format: 'cbor'})), + t.Key('arrayProperty', t.Array(t.any)), + t.Key('objectProperty', t.Object(t.Key('id', t.str.options({ascii: true, min: 3, max: 128})))), + t.Key('unionProperty', t.Or(t.str, t.num, t.nil.options({description: ''}))), + t.KeyOpt('enumAsConst', t.Or(t.Const('a' as const), t.Const('b' as const), t.Const('c' as const))), + t.KeyOpt('refField', t.Ref('refId')), + t.KeyOpt('und', t.undef), + t.Key( '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.Key('type', t.Const('replace' as const).options({title: 'Always use replace'})), + t.Key('path', t.str), + t.Key('value', t.any), ), ), - t.prop( + t.Key( 'binaryOperation', t .Binary( @@ -35,10 +35,10 @@ test('can print a type', () => { ) .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)))), + t.Key('map', t.Map(t.num)), + t.Key('simpleFn1', t.fn), + t.Key('simpleFn2', t.fn$), + t.Key('function', t.Function(t.Object(t.Key('id', t.str)), t.Object(t.Key('name', t.str)))), ) .options({decodeUnknownKeys: true}); // console.log(type + ''); diff --git a/src/type/__tests__/toTypeScriptAst.spec.ts b/src/type/__tests__/toTypeScriptAst.spec.ts index 7ad5a1e1..71477a26 100644 --- a/src/type/__tests__/toTypeScriptAst.spec.ts +++ b/src/type/__tests__/toTypeScriptAst.spec.ts @@ -167,11 +167,11 @@ describe('obj', () => { const {t} = system; const type = system.t .Object( - t.prop('id', t.str).options({ + t.Key('id', t.str).options({ title: 'title-x', description: 'description-x', }), - t.propOpt('id', t.num), + t.KeyOpt('id', t.num), ) .options({ title: 'title', diff --git a/src/type/__tests__/validateTestSuite.ts b/src/type/__tests__/validateTestSuite.ts index 7c282c46..aa41c05a 100644 --- a/src/type/__tests__/validateTestSuite.ts +++ b/src/type/__tests__/validateTestSuite.ts @@ -418,7 +418,7 @@ export const validateTestSuite = (validate: (type: Type, value: unknown) => void }); test('checks for required fields', () => { - const type = t.Object(t.prop('id', t.str), t.propOpt('foo', t.str)); + const type = t.Object(t.Key('id', t.str), t.KeyOpt('foo', t.str)); validate(type, {id: 'asdf'}); validate(type, {id: 'asdf', foo: 'bar'}); expect(() => validate(type, {foo: 'bar'})).toThrowErrorMatchingInlineSnapshot(`"STR"`); diff --git a/src/type/classes.ts b/src/type/classes.ts index 0bb933eb..f9dd3701 100644 --- a/src/type/classes.ts +++ b/src/type/classes.ts @@ -9,7 +9,7 @@ import {FnRxType, FnType} from './classes/FnType'; import {MapType} from './classes/MapType'; import {ModuleType} from './classes/ModuleType'; import {NumType} from './classes/NumType'; -import {ObjKeyOptType, ObjKeyType, ObjType} from './classes/ObjType'; +import {KeyOptType, KeyType, ObjType} from './classes/ObjType'; import {OrType} from './classes/OrType'; import {RefType} from './classes/RefType'; import {StrType} from './classes/StrType'; @@ -23,8 +23,8 @@ export { StrType, BinType, ArrType, - ObjKeyType, - ObjKeyOptType, + KeyType as ObjKeyType, + KeyOptType as ObjKeyOptType, ObjType, MapType, RefType, diff --git a/src/type/classes/ModuleType/__tests__/TypeSystem.spec.ts b/src/type/classes/ModuleType/__tests__/TypeSystem.spec.ts index 7dea169f..8ac5be5b 100644 --- a/src/type/classes/ModuleType/__tests__/TypeSystem.spec.ts +++ b/src/type/classes/ModuleType/__tests__/TypeSystem.spec.ts @@ -4,10 +4,10 @@ describe('.toString()', () => { test('prints type system with nested refs', () => { const system = new ModuleType(); const {t} = system; - system.alias('User0', t.Object(t.prop('id', t.str), t.propOpt('address', t.Ref('Address')))); + system.alias('User0', t.Object(t.Key('id', t.str), t.KeyOpt('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('Address0', t.Object(t.Key('id', t.str), t.KeyOpt('user', t.Ref('User')))); system.alias('Address1', t.Ref('Address0')); const address = system.alias('Address', t.Ref('Address1')); expect(system.toString()).toMatchInlineSnapshot(` diff --git a/src/type/classes/ModuleType/__tests__/demo.ts b/src/type/classes/ModuleType/__tests__/demo.ts index 02af646c..ce6e04c2 100644 --- a/src/type/classes/ModuleType/__tests__/demo.ts +++ b/src/type/classes/ModuleType/__tests__/demo.ts @@ -4,18 +4,18 @@ const createTypes = (system: ModuleType) => { const t = system.t; // prettier-ignore - const MuCollection = t.Object(t.prop('id', t.str), t.propOpt('name', t.str)); + const MuCollection = t.Object(t.Key('id', t.str), t.KeyOpt('name', t.str)); // prettier-ignore - const MuBlock = t.Object(t.prop('id', t.str), t.prop('data', t.any)); + const MuBlock = t.Object(t.Key('id', t.str), t.Key('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 MuBlockCreateRequest = t.Object(t.KeyOpt('id', t.str)); + const MuBlockCreateResponse = t.Object(t.Key('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 MuBlockGetRequest = t.Object(t.Key('id', t.str)); + const MuBlockGetResponse = t.Object(t.Key('block', t.Ref('block.Block'))); const MuBlockGet = t.Function(MuBlockGetRequest, MuBlockGetResponse).options({ title: 'Get Block', description: 'Get a block by ID', @@ -26,8 +26,8 @@ const createTypes = (system: ModuleType) => { return { MuCollection, 'aa.collection.create': t.Function( - t.Object(t.prop('name', t.str)), - t.Object(t.prop('collection', t.Ref('collection.Collection'))), + t.Object(t.Key('name', t.str)), + t.Object(t.Key('collection', t.Ref('collection.Collection'))), ), MuBlock, diff --git a/src/type/classes/ModuleType/__tests__/toTypeScript.spec.ts b/src/type/classes/ModuleType/__tests__/toTypeScript.spec.ts index b2204ae4..5b011620 100644 --- a/src/type/classes/ModuleType/__tests__/toTypeScript.spec.ts +++ b/src/type/classes/ModuleType/__tests__/toTypeScript.spec.ts @@ -22,7 +22,7 @@ test('emit a simple type interface', () => { 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)), + t.Object(t.Key('id', t.str), t.Key('title', t.str), t.KeyOpt('body', t.str), t.KeyOpt('time', t.num)), ); // console.log(alias.toTypeScript()); expect(aliasToTsText(alias)).toMatchInlineSnapshot(` @@ -42,18 +42,18 @@ test('emit an interface with all type kinds', () => { 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('tupleWithRest', t.Tuple([t.str, t.num], t.bool)), - t.prop('tupleWithTail', t.Tuple([t.str, t.num], t.bool, [t.con('a')])), - t.prop('bin', t.bin), - t.prop('const', t.Const<'hello'>('hello')), + t.Key('id', t.str), + t.Key('title', t.bool), + t.KeyOpt('body', t.str), + t.KeyOpt('time', t.num), + t.Key('arr', t.Array(t.str)), + t.Key('arrOfObjects', t.Array(t.Object(t.Key('reg', t.str)))), + t.Key('obj', t.Object(t.Key('reg', t.str), t.Key('arr', t.Array(t.str)))), + t.Key('tuple', t.Tuple([t.str, t.num, t.bool])), + t.Key('tupleWithRest', t.Tuple([t.str, t.num], t.bool)), + t.Key('tupleWithTail', t.Tuple([t.str, t.num], t.bool, [t.con('a')])), + t.Key('bin', t.bin), + t.Key('const', t.Const<'hello'>('hello')), ), ); // console.log(alias.toTypeScript()); diff --git a/src/type/classes/ObjType.ts b/src/type/classes/ObjType.ts index 72c32477..a9a687af 100644 --- a/src/type/classes/ObjType.ts +++ b/src/type/classes/ObjType.ts @@ -4,7 +4,7 @@ import type {ExcludeFromTuple, PickFromTuple} from '../../util/types'; import type {SchemaOf, SchemaOfObjectFields, Type} from '../types'; import {AbsType} from './AbsType'; -export class ObjKeyType extends AbsType>> { +export class KeyType extends AbsType>> { public readonly optional: boolean = false; constructor( @@ -35,7 +35,7 @@ export class ObjKeyType extends AbsType extends ObjKeyType { +export class KeyOptType extends KeyType { public readonly optional: boolean = true; constructor( @@ -52,14 +52,14 @@ export class ObjKeyOptType extends ObjKeyType< } export class ObjType< - F extends (ObjKeyType | ObjKeyOptType)[] = (ObjKeyType | ObjKeyOptType)[], + F extends (KeyType | KeyOptType)[] = (KeyType | KeyOptType)[], > extends AbsType>> { constructor(public readonly keys: F) { super(schema.s.obj as any); } private _key( - field: ObjKeyType | ObjKeyOptType, + field: KeyType | KeyOptType, options?: schema.Optional>, ): void { if (options) field.options(options); @@ -78,8 +78,8 @@ export class ObjType< key: K, value: V, options?: schema.Optional>>, - ): ObjType<[...F, ObjKeyType]> { - this._key(new ObjKeyType(key, value), options); + ): ObjType<[...F, KeyType]> { + this._key(new KeyType(key, value), options); return this; } @@ -94,8 +94,8 @@ export class ObjType< key: K, value: V, options?: schema.Optional>>, - ): ObjType<[...F, ObjKeyOptType]> { - this._key(new ObjKeyOptType(key, value), options); + ): ObjType<[...F, KeyOptType]> { + this._key(new KeyOptType(key, value), options); return this; } @@ -113,11 +113,11 @@ export class ObjType< public getField>>>( key: K, - ): ObjKeyType | undefined { + ): KeyType | undefined { return this.keys.find((f) => f.key === key); } - public extend[]>(o: ObjType): ObjType<[...F, ...F2]> { + public extend[]>(o: ObjType): ObjType<[...F, ...F2]> { const type = new ObjType([...this.keys, ...o.keys]) as ObjType<[...F, ...F2]>; type.system = this.system; return type; @@ -125,7 +125,7 @@ export class ObjType< public omit>>>( key: K, - ): ObjType>> { + ): ObjType>> { const type = new ObjType(this.keys.filter((f) => f.key !== key) as any); type.system = this.system; return type; @@ -133,7 +133,7 @@ export class ObjType< public pick>>>( key: K, - ): ObjType>> { + ): ObjType>> { const field = this.keys.find((f) => f.key === key); if (!field) throw new Error('FIELD_NOT_FOUND'); const type = new ObjType([field] as any); diff --git a/src/type/classes/__tests__/AliasType.spec.ts b/src/type/classes/__tests__/AliasType.spec.ts index d3992768..4df36517 100644 --- a/src/type/classes/__tests__/AliasType.spec.ts +++ b/src/type/classes/__tests__/AliasType.spec.ts @@ -5,7 +5,7 @@ import {ModuleType} from '../ModuleType'; test('can infer alias type', () => { const system = new ModuleType(); const {t} = system; - const user = system.alias('User', t.Object(t.prop('id', t.str), t.propOpt('name', t.str))); + const user = system.alias('User', t.Object(t.Key('id', t.str), t.KeyOpt('name', t.str))); type T = TypeOf>>; const value: T = { id: 'string', diff --git a/src/type/classes/__tests__/ObjType.spec.ts b/src/type/classes/__tests__/ObjType.spec.ts index f8ee5fdd..70edf41a 100644 --- a/src/type/classes/__tests__/ObjType.spec.ts +++ b/src/type/classes/__tests__/ObjType.spec.ts @@ -3,7 +3,7 @@ import type {ResolveType} from '../../../type/types'; describe('.prop()', () => { test('can add a property to an object', () => { - const obj1 = t.Object(t.prop('a', t.str)); + const obj1 = t.Object(t.Key('a', t.str)); const obj2 = obj1.prop('b', t.num); const val1: ResolveType = { a: 'hello', @@ -48,8 +48,8 @@ describe('.opt()', () => { 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 obj1 = t.Object(t.Key('a', t.str)); + const obj2 = t.Object(t.Key('b', t.num)); const obj3 = obj1.extend(obj2); expect(typeof obj1.getField('a')).toBe('object'); expect(typeof obj1.getField('b' as any)).toBe('undefined'); @@ -71,7 +71,7 @@ describe('.extend()', () => { test('can extend an empty object', () => { const obj1 = t.Object(); - const obj2 = t.Object(t.prop('b', t.num)); + const obj2 = t.Object(t.Key('b', t.num)); const obj3 = obj1.extend(obj2); expect(typeof obj1.getField('b')).toBe('undefined'); expect(typeof obj2.getField('b')).toBe('object'); @@ -88,7 +88,7 @@ describe('.extend()', () => { 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 obj1 = t.Object(t.Key('a', t.str), t.Key('b', t.num)); const obj2 = obj1.omit('b'); expect(typeof obj1.getField('a')).toBe('object'); expect(typeof obj1.getField('b')).toBe('object'); @@ -106,7 +106,7 @@ describe('.omit()', () => { describe('.pick()', () => { test('can pick a field from object', () => { - const obj1 = t.Object(t.prop('a', t.str), t.prop('b', t.num)); + const obj1 = t.Object(t.Key('a', t.str), t.Key('b', t.num)); const obj2 = obj1.pick('a'); const obj3 = obj1.pick('b'); expect(typeof obj1.getField('a')).toBe('object'); diff --git a/src/value/ObjValue.ts b/src/value/ObjValue.ts index 2c321b65..09b142a9 100644 --- a/src/value/ObjValue.ts +++ b/src/value/ObjValue.ts @@ -62,7 +62,7 @@ export class ObjValue> extends Value implement const system = (this.type as classes.ObjType).getSystem(); const t = system.t; type = typeof type === 'function' ? type(t) : type; - return this.field(t.prop(key, type), data); + return this.field(t.Key(key, type), data); } public set(key: K, value: Value) { diff --git a/src/value/__tests__/ObjValue.spec.ts b/src/value/__tests__/ObjValue.spec.ts index edc117d3..4d0e7618 100644 --- a/src/value/__tests__/ObjValue.spec.ts +++ b/src/value/__tests__/ObjValue.spec.ts @@ -4,7 +4,7 @@ import {ObjValue} from '../ObjValue'; test('can retrieve field as Value', () => { const system = new ModuleType(); const {t} = system; - const obj = new ObjValue(t.Object(t.prop('foo', t.str)), {foo: 'bar'}); + const obj = new ObjValue(t.Object(t.Key('foo', t.str)), {foo: 'bar'}); const foo = obj.get('foo'); expect(foo.type.kind()).toBe('str'); expect(foo.data).toBe('bar'); @@ -13,7 +13,7 @@ test('can retrieve field as Value', () => { test('can print to string', () => { const system = new ModuleType(); const {t} = system; - const obj = new ObjValue(t.Object(t.prop('foo', t.str)), {foo: 'bar'}); + const obj = new ObjValue(t.Object(t.Key('foo', t.str)), {foo: 'bar'}); expect(obj + '').toMatchSnapshot(); }); From 61d77a11a88cd0ed1d8f48ae0fff75b75ff56656 Mon Sep 17 00:00:00 2001 From: streamich Date: Sun, 10 Aug 2025 10:28:32 +0200 Subject: [PATCH 05/18] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20support=20named=20?= =?UTF-8?q?keys=20in=20validation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/codegen/AbstractCodege.ts | 5 +++++ src/codegen/binary/cbor copy/CborCodegen.ts | 6 +++--- src/codegen/binary/cbor/CborCodegen.ts | 6 +++--- src/codegen/binary/json/JsonCodegen.ts | 6 +++--- src/codegen/binary/msgpack/MsgPackCodegen.ts | 6 +++--- .../capacity/CapacityEstimatorCodegen.ts | 8 ++++---- src/codegen/json/JsonTextCodegen.ts | 6 +++--- src/codegen/validator/ValidatorCodegen.ts | 9 +++++++-- .../validator/__tests__/codegen.spec.ts | 18 ++++++++++++++++++ src/jtd/converter.ts | 4 ++-- src/schema/schema.ts | 2 +- src/type/TypeBuilder.ts | 14 +++++++++----- src/type/classes.ts | 4 ++-- src/type/discriminator.ts | 4 ++-- src/type/types.ts | 4 ++-- src/value/ObjValue.ts | 8 ++++---- 16 files changed, 71 insertions(+), 39 deletions(-) diff --git a/src/codegen/AbstractCodege.ts b/src/codegen/AbstractCodege.ts index f0982758..fdb7a4f8 100644 --- a/src/codegen/AbstractCodege.ts +++ b/src/codegen/AbstractCodege.ts @@ -9,6 +9,7 @@ import type { MapType, NumType, ObjType, + KeyType, OrType, RefType, StrType, @@ -27,6 +28,7 @@ export abstract class AbstractCodegen any = (...d protected abstract onBin(path: SchemaPath, r: JsExpression, type: BinType): void; protected abstract onArr(path: SchemaPath, r: JsExpression, type: ArrType): void; protected abstract onObj(path: SchemaPath, r: JsExpression, type: ObjType): void; + protected abstract onKey(path: SchemaPath, r: JsExpression, type: KeyType): void; protected abstract onMap(path: SchemaPath, r: JsExpression, type: MapType): void; protected abstract onRef(path: SchemaPath, r: JsExpression, type: RefType): void; protected abstract onOr(path: SchemaPath, r: JsExpression, type: OrType): void; @@ -62,6 +64,9 @@ export abstract class AbstractCodegen any = (...d case 'obj': this.onObj(path, r, type as ObjType); break; + case 'key': + this.onKey(path, r, type as KeyType); + break; case 'map': this.onMap(path, r, type as MapType); break; diff --git a/src/codegen/binary/cbor copy/CborCodegen.ts b/src/codegen/binary/cbor copy/CborCodegen.ts index 82968385..0355be24 100644 --- a/src/codegen/binary/cbor copy/CborCodegen.ts +++ b/src/codegen/binary/cbor copy/CborCodegen.ts @@ -1,7 +1,7 @@ import {JsExpression} from '@jsonjoy.com/codegen/lib/util/JsExpression'; import {normalizeAccessor} from '@jsonjoy.com/codegen/lib/util/normalizeAccessor'; import {CborEncoder} from '@jsonjoy.com/json-pack/lib/cbor/CborEncoder'; -import {type MapType, ObjKeyOptType, type ObjType, type Type} from '../../../type'; +import {type MapType, KeyOptType, type ObjType, type Type} from '../../../type'; import type {CompiledBinaryEncoder, SchemaPath} from '../../types'; import {lazyKeyedFactory} from '../../util'; import {AbstractBinaryCodegen} from '../AbstractBinaryCodegen'; @@ -23,8 +23,8 @@ export class CborCodegen extends AbstractBinaryCodegen { const r = codegen.r(); const fields = type.keys; const length = fields.length; - const requiredFields = fields.filter((field) => !(field instanceof ObjKeyOptType)); - const optionalFields = fields.filter((field) => field instanceof ObjKeyOptType); + const requiredFields = fields.filter((field) => !(field instanceof KeyOptType)); + const optionalFields = fields.filter((field) => field instanceof KeyOptType); const requiredLength = requiredFields.length; const optionalLength = optionalFields.length; const encodeUnknownFields = !!type.schema.encodeUnknownKeys; diff --git a/src/codegen/binary/cbor/CborCodegen.ts b/src/codegen/binary/cbor/CborCodegen.ts index 44f51b1e..778d4efc 100644 --- a/src/codegen/binary/cbor/CborCodegen.ts +++ b/src/codegen/binary/cbor/CborCodegen.ts @@ -1,7 +1,7 @@ import {JsExpression} from '@jsonjoy.com/codegen/lib/util/JsExpression'; import {normalizeAccessor} from '@jsonjoy.com/codegen/lib/util/normalizeAccessor'; import {CborEncoder} from '@jsonjoy.com/json-pack/lib/cbor/CborEncoder'; -import {ObjKeyOptType, type ObjType, type Type} from '../../../type'; +import {KeyOptType, type ObjType, type Type} from '../../../type'; import type {CompiledBinaryEncoder, SchemaPath} from '../../types'; import {lazyKeyedFactory} from '../../util'; import {AbstractBinaryCodegen} from '../AbstractBinaryCodegen'; @@ -23,8 +23,8 @@ export class CborCodegen extends AbstractBinaryCodegen { const r = codegen.r(); const fields = type.keys; const length = fields.length; - const requiredFields = fields.filter((field) => !(field instanceof ObjKeyOptType)); - const optionalFields = fields.filter((field) => field instanceof ObjKeyOptType); + const requiredFields = fields.filter((field) => !(field instanceof KeyOptType)); + const optionalFields = fields.filter((field) => field instanceof KeyOptType); const requiredLength = requiredFields.length; const optionalLength = optionalFields.length; const encodeUnknownFields = !!type.schema.encodeUnknownKeys; diff --git a/src/codegen/binary/json/JsonCodegen.ts b/src/codegen/binary/json/JsonCodegen.ts index e55606ef..f8adc300 100644 --- a/src/codegen/binary/json/JsonCodegen.ts +++ b/src/codegen/binary/json/JsonCodegen.ts @@ -1,7 +1,7 @@ import {JsExpression} from '@jsonjoy.com/codegen/lib/util/JsExpression'; import {normalizeAccessor} from '@jsonjoy.com/codegen/lib/util/normalizeAccessor'; import {JsonEncoder} from '@jsonjoy.com/json-pack/lib/json/JsonEncoder'; -import {type ArrType, type MapType, ObjKeyOptType, type ObjType, type Type} from '../../../type'; +import {type ArrType, type MapType, KeyOptType, type ObjType, type Type} from '../../../type'; import type {CompiledBinaryEncoder, SchemaPath} from '../../types'; import {lazyKeyedFactory} from '../../util'; import {AbstractBinaryCodegen} from '../AbstractBinaryCodegen'; @@ -98,8 +98,8 @@ export class JsonCodegen extends AbstractBinaryCodegen { const codegen = this.codegen; const r = codegen.var(value.use()); const fields = type.keys; - const requiredFields = fields.filter((field) => !(field instanceof ObjKeyOptType)); - const optionalFields = fields.filter((field) => field instanceof ObjKeyOptType); + const requiredFields = fields.filter((field) => !(field instanceof KeyOptType)); + const optionalFields = fields.filter((field) => field instanceof KeyOptType); const requiredLength = requiredFields.length; const optionalLength = optionalFields.length; const encodeUnknownFields = !!type.schema.encodeUnknownKeys; diff --git a/src/codegen/binary/msgpack/MsgPackCodegen.ts b/src/codegen/binary/msgpack/MsgPackCodegen.ts index 6dea0735..b6ea4209 100644 --- a/src/codegen/binary/msgpack/MsgPackCodegen.ts +++ b/src/codegen/binary/msgpack/MsgPackCodegen.ts @@ -1,7 +1,7 @@ import {JsExpression} from '@jsonjoy.com/codegen/lib/util/JsExpression'; import {normalizeAccessor} from '@jsonjoy.com/codegen/lib/util/normalizeAccessor'; import {MsgPackEncoder} from '@jsonjoy.com/json-pack/lib/msgpack/MsgPackEncoder'; -import {ObjKeyOptType, type ObjType, type Type} from '../../../type'; +import {KeyOptType, type ObjType, type Type} from '../../../type'; import type {CompiledBinaryEncoder, SchemaPath} from '../../types'; import {lazyKeyedFactory} from '../../util'; import {AbstractBinaryCodegen} from '../AbstractBinaryCodegen'; @@ -23,8 +23,8 @@ export class MsgPackCodegen extends AbstractBinaryCodegen { const r = codegen.r(); const fields = type.keys; const length = fields.length; - const requiredFields = fields.filter((field) => !(field instanceof ObjKeyOptType)); - const optionalFields = fields.filter((field) => field instanceof ObjKeyOptType); + const requiredFields = fields.filter((field) => !(field instanceof KeyOptType)); + const optionalFields = fields.filter((field) => field instanceof KeyOptType); const requiredLength = requiredFields.length; const optionalLength = optionalFields.length; const totalMaxKnownFields = requiredLength + optionalLength; diff --git a/src/codegen/capacity/CapacityEstimatorCodegen.ts b/src/codegen/capacity/CapacityEstimatorCodegen.ts index e7560ca5..ec3fdb06 100644 --- a/src/codegen/capacity/CapacityEstimatorCodegen.ts +++ b/src/codegen/capacity/CapacityEstimatorCodegen.ts @@ -2,8 +2,8 @@ import {Codegen, CodegenStepExecJs} from '@jsonjoy.com/codegen'; import {JsExpression} from '@jsonjoy.com/codegen/lib/util/JsExpression'; import {normalizeAccessor} from '@jsonjoy.com/codegen/lib/util/normalizeAccessor'; import {MaxEncodingOverhead, maxEncodingCapacity} from '@jsonjoy.com/util/lib/json-size'; -import {BoolType, ConType, NumType, ObjKeyOptType} from '../../type'; -import type {ArrType, MapType, ObjKeyType, ObjType, OrType, RefType, Type} from '../../type'; +import {BoolType, ConType, NumType, KeyOptType} from '../../type'; +import type {ArrType, MapType, KeyType, ObjType, OrType, RefType, Type} from '../../type'; import {DiscriminatorCodegen} from '../discriminator'; import {lazyKeyedFactory} from '../util'; @@ -113,10 +113,10 @@ export class CapacityEstimatorCodegen { this.inc(MaxEncodingOverhead.Object); const fields = objectType.keys; for (const f of fields) { - const field = f as ObjKeyType; + const field = f as KeyType; const accessor = normalizeAccessor(field.key); const fieldExpression = new JsExpression(() => `${r}${accessor}`); - const isOptional = field instanceof ObjKeyOptType; + const isOptional = field instanceof KeyOptType; if (isOptional) { codegen.if(/* js */ `${JSON.stringify(field.key)} in ${r}`, () => { this.inc(MaxEncodingOverhead.ObjectElement); diff --git a/src/codegen/json/JsonTextCodegen.ts b/src/codegen/json/JsonTextCodegen.ts index 523812d0..0dbf8c2e 100644 --- a/src/codegen/json/JsonTextCodegen.ts +++ b/src/codegen/json/JsonTextCodegen.ts @@ -5,7 +5,7 @@ import {normalizeAccessor} from '@jsonjoy.com/codegen/lib/util/normalizeAccessor import {stringify} from '@jsonjoy.com/json-pack/lib/json-binary/codec'; import type {json_string} from '@jsonjoy.com/util/lib/json-brand'; import {asString} from '@jsonjoy.com/util/lib/strings/asString'; -import {ObjKeyOptType} from '../../type'; +import {KeyOptType} from '../../type'; import type {ArrType, ConType, MapType, ObjType, OrType, RefType, StrType, Type} from '../../type'; import {DiscriminatorCodegen} from '../discriminator'; import {lazyKeyedFactory} from '../util'; @@ -107,8 +107,8 @@ export class JsonTextCodegen { if (schema.encodeUnknownKeys) { this.js(/* js */ `var ${rKeys} = new Set(Object.keys(${r}));`); } - const requiredFields = fields.filter((field) => !(field instanceof ObjKeyOptType)); - const optionalFields = fields.filter((field) => field instanceof ObjKeyOptType) as ObjKeyOptType[]; + const requiredFields = fields.filter((field) => !(field instanceof KeyOptType)); + const optionalFields = fields.filter((field) => field instanceof KeyOptType) as KeyOptType[]; this.writeText('{'); for (let i = 0; i < requiredFields.length; i++) { const field = requiredFields[i]; diff --git a/src/codegen/validator/ValidatorCodegen.ts b/src/codegen/validator/ValidatorCodegen.ts index d470251c..298ffebc 100644 --- a/src/codegen/validator/ValidatorCodegen.ts +++ b/src/codegen/validator/ValidatorCodegen.ts @@ -11,12 +11,13 @@ import { type ConType, type MapType, type NumType, - ObjKeyOptType, + KeyOptType, type ObjType, type OrType, type RefType, type StrType, type Type, + KeyType, } from '../../type'; import {floats, ints, uints} from '../../util'; import {isAscii, isUtf8} from '../../util/stringFormats'; @@ -379,7 +380,7 @@ export class ValidatorCodegen extends AbstractCodegen { const accessor = normalizeAccessor(field.key); const keyPath = [...path, field.key]; codegen.js(/* js */ `var ${rv} = ${r.use()}${accessor};`); - if (field instanceof ObjKeyOptType) { + if (field instanceof KeyOptType) { codegen.js(/* js */ `if (${rv} !== undefined) {`); this.onNode(keyPath, new JsExpression(() => rv), field.val); codegen.js(/* js */ `}`); @@ -394,6 +395,10 @@ export class ValidatorCodegen extends AbstractCodegen { } } + protected onKey(path: SchemaPath, r: JsExpression, type: KeyType): void { + this.onNode([...path, type.key], r, type.val); + } + protected onMap(path: SchemaPath, r: JsExpression, type: MapType): void { const codegen = this.codegen; const err = this.err(ValidationError.MAP, path); diff --git a/src/codegen/validator/__tests__/codegen.spec.ts b/src/codegen/validator/__tests__/codegen.spec.ts index 7e9be4de..c956b196 100644 --- a/src/codegen/validator/__tests__/codegen.spec.ts +++ b/src/codegen/validator/__tests__/codegen.spec.ts @@ -841,6 +841,24 @@ describe('"arr" type', () => { }); }); + test('named head 2-tuple', () => { + const type = s.Tuple([s.Key('num', s.num), s.Key('str', s.str)]); + exec(type, [0, ''], null); + exec(type, [1, 'x'], null); + exec(type, ['', 'x'], { + code: 'NUM', + errno: ValidationError.NUM, + message: 'Not a number.', + path: [0, 'num'], + }); + exec(type, [-1, true], { + code: 'STR', + errno: ValidationError.STR, + message: 'Not a string.', + path: [1, 'str'], + }); + }); + test('head + elements', () => { const type = s.Tuple([s.Const(true)], s.num); exec(type, [true, 123], null); diff --git a/src/jtd/converter.ts b/src/jtd/converter.ts index 757ac2a0..afb88d8f 100644 --- a/src/jtd/converter.ts +++ b/src/jtd/converter.ts @@ -1,4 +1,4 @@ -import {type ArrType, ObjKeyOptType, type ObjType, type RefType, type Type} from '../type'; +import {type ArrType, KeyOptType, type ObjType, type RefType, type Type} from '../type'; import type * as jtd from './types'; const NUMS_TYPE_MAPPING = new Map([ @@ -86,7 +86,7 @@ export function toJtdForm(type: Type): jtd.JtdForm { if (fieldType) { const fieldJtd = toJtdForm(fieldType); // Check if field is optional - if (field instanceof ObjKeyOptType) { + if (field instanceof KeyOptType) { form.optionalProperties[fieldName] = fieldJtd; } else { form.properties[fieldName] = fieldJtd; diff --git a/src/schema/schema.ts b/src/schema/schema.ts index de9faff1..7ae42a7c 100644 --- a/src/schema/schema.ts +++ b/src/schema/schema.ts @@ -453,7 +453,7 @@ export type JsonSchema = | OptKeySchema | MapSchema; -export type Schema = JsonSchema | RefSchema | OrSchema | AnySchema | FnSchema | FnRxSchema | AliasSchema | ModuleSchema; +export type Schema = JsonSchema | RefSchema | OrSchema | AnySchema | FnSchema | FnRxSchema | AliasSchema | ModuleSchema | KeySchema | OptKeySchema; export type NoT = Omit; diff --git a/src/type/TypeBuilder.ts b/src/type/TypeBuilder.ts index 21afe45b..9034acb7 100644 --- a/src/type/TypeBuilder.ts +++ b/src/type/TypeBuilder.ts @@ -18,7 +18,7 @@ type ObjValueTuple, R extends any[] : R; type RecordToFields> = ObjValueTuple<{ - [K in keyof O]: classes.ObjKeyType; + [K in keyof O]: classes.KeyType; }>; export class TypeBuilder { @@ -118,7 +118,7 @@ export class TypeBuilder { * @returns An object type. */ public readonly object = >(record: R): classes.ObjType> => { - const keys: classes.ObjKeyType[] = []; + const keys: classes.KeyType[] = []; for (const [key, value] of Object.entries(record)) keys.push(this.Key(key, value)); return new classes.ObjType>(keys as any).sys(this.system); }; @@ -195,16 +195,16 @@ export class TypeBuilder { return new classes.ArrType(item, head, tail, options).sys(this.system); } - public Object | classes.ObjKeyOptType)[]>(...keys: F) { + public Object | classes.KeyOptType)[]>(...keys: F) { return new classes.ObjType(keys).sys(this.system); } public Key(key: K, value: V) { - return new classes.ObjKeyType(key, value).sys(this.system); + return new classes.KeyType(key, value).sys(this.system); } public KeyOpt(key: K, value: V) { - return new classes.ObjKeyOptType(key, value).sys(this.system); + return new classes.KeyOptType(key, value).sys(this.system); } public Map(val: T, key?: Type, options?: schema.Optional) { @@ -264,6 +264,10 @@ export class TypeBuilder { ); return this.Object(...fields).options(node); } + case 'key': + return node.optional + ? this.KeyOpt(node.key, this.import(node.value as schema.Schema)).options(node) + : this.Key(node.key, this.import(node.value as schema.Schema)).options(node); case 'map': return this.Map(this.import(node.value), node.key ? this.import(node.key) : undefined, node); case 'con': diff --git a/src/type/classes.ts b/src/type/classes.ts index f9dd3701..a270cd24 100644 --- a/src/type/classes.ts +++ b/src/type/classes.ts @@ -23,8 +23,8 @@ export { StrType, BinType, ArrType, - KeyType as ObjKeyType, - KeyOptType as ObjKeyOptType, + KeyType, + KeyOptType, ObjType, MapType, RefType, diff --git a/src/type/discriminator.ts b/src/type/discriminator.ts index d1170c5d..2551902d 100644 --- a/src/type/discriminator.ts +++ b/src/type/discriminator.ts @@ -1,5 +1,5 @@ import type {Expr} from '@jsonjoy.com/json-expression'; -import {ArrType, BoolType, ConType, NumType, type ObjKeyType, ObjType, StrType} from './classes'; +import {ArrType, BoolType, ConType, NumType, type KeyType, ObjType, StrType} from './classes'; import type {OrType, RefType, Type} from './types'; /** @@ -59,7 +59,7 @@ export class Discriminator { if (d) return new Discriminator('/' + i + d.path, d.type); } } else if (type instanceof ObjType) { - const fields = type.keys as ObjKeyType[]; + const fields = type.keys as KeyType[]; for (let i = 0; i < fields.length; i++) { const f = fields[i]; const d = Discriminator.findConst(f.val); diff --git a/src/type/types.ts b/src/type/types.ts index c7e9bc5a..219666b2 100644 --- a/src/type/types.ts +++ b/src/type/types.ts @@ -29,9 +29,9 @@ export type SchemaOfMap> = { [K in keyof M]: SchemaOf; }; -export type SchemaOfObjectFieldType = F extends classes.ObjKeyOptType +export type SchemaOfObjectFieldType = F extends classes.KeyOptType ? schema.OptKeySchema> - : F extends classes.ObjKeyType + : F extends classes.KeyType ? schema.KeySchema> : never; diff --git a/src/value/ObjValue.ts b/src/value/ObjValue.ts index 09b142a9..215ebd63 100644 --- a/src/value/ObjValue.ts +++ b/src/value/ObjValue.ts @@ -7,8 +7,8 @@ import {Value} from './Value'; export type UnObjType = T extends classes.ObjType ? U : never; export type UnObjValue = T extends ObjValue ? U : never; -export type UnObjFieldTypeVal = T extends classes.ObjKeyType ? U : never; -export type ObjFieldToTuple = F extends classes.ObjKeyType ? [K, V] : never; +export type UnObjFieldTypeVal = T extends classes.KeyType ? U : never; +export type ObjFieldToTuple = F extends classes.KeyType ? [K, V] : never; export type ToObject = T extends [string, unknown][] ? {[K in T[number] as K[0]]: K[1]} : never; export type ObjValueToTypeMap = ToObject<{ [K in keyof F]: ObjFieldToTuple; @@ -27,7 +27,7 @@ export class ObjValue> extends Value implement public keys(): string[] { const type = this.type as T; - return type.keys.map((field: classes.ObjKeyType) => field.key); + return type.keys.map((field: classes.KeyType) => field.key); } public get>>( @@ -41,7 +41,7 @@ export class ObjValue> extends Value implement return new Value(field.val, data) as any; } - public field>( + public field>( field: F | ((t: TypeBuilder) => F), data: classes.ResolveType>, ): ObjValue, F]>> { From 30dde7aa5ca48e7ee4be69df1d56a5979c15a521 Mon Sep 17 00:00:00 2001 From: streamich Date: Sun, 10 Aug 2025 10:33:03 +0200 Subject: [PATCH 06/18] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20support=20named=20?= =?UTF-8?q?tuple=20keys=20in=20capacity=20estimatro?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/codegen/capacity/CapacityEstimatorCodegen.ts | 7 +++++++ .../__tests__/CapacityEstimatorCodegenContext.spec.ts | 7 +++++++ 2 files changed, 14 insertions(+) diff --git a/src/codegen/capacity/CapacityEstimatorCodegen.ts b/src/codegen/capacity/CapacityEstimatorCodegen.ts index ec3fdb06..32fac8fd 100644 --- a/src/codegen/capacity/CapacityEstimatorCodegen.ts +++ b/src/codegen/capacity/CapacityEstimatorCodegen.ts @@ -174,6 +174,10 @@ export class CapacityEstimatorCodegen { ); } + protected genKey(r: JsExpression, type: KeyType): void { + this.onNode(r, type.val); + } + protected onNode(value: JsExpression, type: Type): void { const kind = type.kind(); switch (kind) { @@ -203,6 +207,9 @@ export class CapacityEstimatorCodegen { case 'obj': this.genObj(value, type); break; + case 'key': + this.genKey(value, type as KeyType); + break; case 'map': this.genMap(value, type as MapType); break; diff --git a/src/codegen/capacity/__tests__/CapacityEstimatorCodegenContext.spec.ts b/src/codegen/capacity/__tests__/CapacityEstimatorCodegenContext.spec.ts index e884aa51..670c9b54 100644 --- a/src/codegen/capacity/__tests__/CapacityEstimatorCodegenContext.spec.ts +++ b/src/codegen/capacity/__tests__/CapacityEstimatorCodegenContext.spec.ts @@ -135,6 +135,13 @@ describe('"arr" type', () => { const estimator = CapacityEstimatorCodegen.get(type); expect(estimator([1, 'abc', 'xxxxxxxxx'])).toBe(maxEncodingCapacity([1, 'abc', 'xxxxxxxxx'])); }); + + test('named tail 2-tuple', () => { + const system = new ModuleType(); + const type = system.t.Array(t.num).tail(t.Key('very_important', t.str), t.str); + const estimator = CapacityEstimatorCodegen.get(type); + expect(estimator([1, 'abc', 'xxxxxxxxx'])).toBe(maxEncodingCapacity([1, 'abc', 'xxxxxxxxx'])); + }); }); describe('"obj" type', () => { From 0af529dd1ac45f882dcc91f9e97ccc82e639176c Mon Sep 17 00:00:00 2001 From: streamich Date: Sun, 10 Aug 2025 10:57:44 +0200 Subject: [PATCH 07/18] =?UTF-8?q?refactor:=20=F0=9F=92=A1=20use=20abstract?= =?UTF-8?q?=20class=20in=20capacity=20estimator?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../capacity/CapacityEstimatorCodegen.ts | 183 ++++++++---------- .../CapacityEstimatorCodegenContext.spec.ts | 8 + 2 files changed, 88 insertions(+), 103 deletions(-) diff --git a/src/codegen/capacity/CapacityEstimatorCodegen.ts b/src/codegen/capacity/CapacityEstimatorCodegen.ts index 32fac8fd..bc0aeb87 100644 --- a/src/codegen/capacity/CapacityEstimatorCodegen.ts +++ b/src/codegen/capacity/CapacityEstimatorCodegen.ts @@ -3,9 +3,11 @@ import {JsExpression} from '@jsonjoy.com/codegen/lib/util/JsExpression'; import {normalizeAccessor} from '@jsonjoy.com/codegen/lib/util/normalizeAccessor'; import {MaxEncodingOverhead, maxEncodingCapacity} from '@jsonjoy.com/util/lib/json-size'; import {BoolType, ConType, NumType, KeyOptType} from '../../type'; -import type {ArrType, MapType, KeyType, ObjType, OrType, RefType, Type} from '../../type'; +import type {AnyType, ArrType, BinType, MapType, KeyType, ObjType, OrType, RefType, StrType, Type} from '../../type'; import {DiscriminatorCodegen} from '../discriminator'; import {lazyKeyedFactory} from '../util'; +import {AbstractCodegen} from '../AbstractCodege'; +import type {SchemaPath} from '../types'; export type CompiledCapacityEstimator = (value: unknown) => number; @@ -13,12 +15,12 @@ class IncrementSizeStep { constructor(public readonly inc: number) {} } -export class CapacityEstimatorCodegen { +export class CapacityEstimatorCodegen extends AbstractCodegen { public static readonly get = lazyKeyedFactory((type: Type, name?: string) => { const codegen = new CapacityEstimatorCodegen(type, name); const r = codegen.codegen.options.args[0]; const expression = new JsExpression(() => r); - codegen.onNode(expression, type); + codegen.onNode([], expression, type); return codegen.compile(); }); @@ -28,6 +30,7 @@ export class CapacityEstimatorCodegen { public readonly type: Type, name?: string, ) { + super(); this.codegen = new Codegen({ name: 'approxSize' + (name ? '_' + name : ''), args: ['r0'], @@ -48,94 +51,118 @@ export class CapacityEstimatorCodegen { this.codegen.linkDependency(maxEncodingCapacity, 'maxEncodingCapacity'); } - public inc(inc: number): void { - this.codegen.step(new IncrementSizeStep(inc)); + private inc(amount: number): void { + this.codegen.js(/* js */ `size += ${amount};`); } - public compile(): CompiledCapacityEstimator { - return this.codegen.compile(); + protected onAny(path: SchemaPath, r: JsExpression, type: AnyType): void { + const codegen = this.codegen; + const rv = codegen.var(r.use()); + codegen.js(/* js */ `size += maxEncodingCapacity(${rv});`); } - protected genAny(value: JsExpression): void { - const codegen = this.codegen; - const r = codegen.var(value.use()); - codegen.js(/* js */ `size += maxEncodingCapacity(${r});`); + protected onCon(path: SchemaPath, r: JsExpression, type: ConType): void { + this.inc(maxEncodingCapacity(type.literal())); } - protected genArr(value: JsExpression, type: ArrType): void { + protected onBool(path: SchemaPath, r: JsExpression, type: BoolType): void { + this.inc(MaxEncodingOverhead.Boolean); + } + + protected onNum(path: SchemaPath, r: JsExpression, type: NumType): void { + this.inc(MaxEncodingOverhead.Number); + } + + protected onStr(path: SchemaPath, r: JsExpression, type: StrType): void { + this.inc(MaxEncodingOverhead.String); + this.codegen.js(/* js */ `size += ${MaxEncodingOverhead.StringLengthMultiplier} * ${r.use()}.length;`); + } + + protected onBin(path: SchemaPath, r: JsExpression, type: BinType): void { + this.inc(MaxEncodingOverhead.Binary); + this.codegen.js(/* js */ `size += ${MaxEncodingOverhead.BinaryLengthMultiplier} * ${r.use()}.length;`); + } + + protected onArr(path: SchemaPath, r: JsExpression, type: ArrType): void { const codegen = this.codegen; this.inc(MaxEncodingOverhead.Array); - const rLen = codegen.var(/* js */ `${value.use()}.length`); + const rLen = codegen.var(/* js */ `${r.use()}.length`); codegen.js(/* js */ `size += ${MaxEncodingOverhead.ArrayElement} * ${rLen}`); - const itemType = type._type as Type | undefined; - const headType = type._head as Type[] | undefined; - const tailType = type._tail as Type[] | undefined; - const headLength = headType ? headType.length : 0; - const tailLength = tailType ? tailType.length : 0; - if (itemType) { - const isConstantSizeType = type instanceof ConType || type instanceof BoolType || type instanceof NumType; + const {_head = [], _type, _tail = []} = type; + const headLength = _head.length; + const tailLength = _tail.length; + // const tupleLength = headLength + tailLength; + // if (tupleLength) { + // codegen.if(/* js */ `${rLen} < ${tupleLength}`, () => { + // codegen.js(/* js */ `throw new Error('INV_LEN');`); + // }); + // } + if (_type) { + const isConstantSizeType = _type instanceof ConType || _type instanceof BoolType || _type instanceof NumType; if (isConstantSizeType) { const elementSize = - type instanceof ConType - ? maxEncodingCapacity(type.literal()) - : type instanceof BoolType + _type instanceof ConType + ? maxEncodingCapacity(_type.literal()) + : _type instanceof BoolType ? MaxEncodingOverhead.Boolean : MaxEncodingOverhead.Number; codegen.js(/* js */ `size += ${rLen} * ${elementSize};`); } else { - const r = codegen.var(value.use()); + const rv = codegen.var(r.use()); const ri = codegen.getRegister(); codegen.js(/* js */ `for(var ${ri} = ${headLength}; ${ri} < ${rLen} - ${tailLength}; ${ri}++) {`); - this.onNode(new JsExpression(() => /* js */ `${r}[${ri}]`), itemType); + this.onNode([...path, {r: ri}], new JsExpression(() => /* js */ `${rv}[${ri}]`), _type); codegen.js(/* js */ `}`); } } if (headLength > 0) { - const r = codegen.var(value.use()); - for (let i = 0; i < headLength; i++) this.onNode(new JsExpression(() => /* js */ `${r}[${i}]`), headType![i]); + const rr = codegen.var(r.use()); + for (let i = 0; i < headLength; i++) this.onNode([...path, i], new JsExpression(() => /* js */ `${rr}[${i}]`), _head![i]); } if (tailLength > 0) { - const r = codegen.var(value.use()); + const rr = codegen.var(r.use()); for (let i = 0; i < tailLength; i++) - this.onNode(new JsExpression(() => /* js */ `${r}[${rLen} - ${i + 1}]`), tailType![i]); + this.onNode([...path, {r: `${rLen} - ${tailLength - i}`}], new JsExpression(() => /* js */ `${rr}[${rLen} - ${i + 1}]`), _tail![i]); } } - protected genObj(value: JsExpression, type: Type): void { + protected onObj(path: SchemaPath, r: JsExpression, type: ObjType): void { const codegen = this.codegen; - const r = codegen.var(value.use()); - const objectType = type as ObjType; - const encodeUnknownFields = !!objectType.schema.encodeUnknownKeys; + const rv = codegen.var(r.use()); + const encodeUnknownFields = !!type.schema.encodeUnknownKeys; if (encodeUnknownFields) { - codegen.js(/* js */ `size += maxEncodingCapacity(${r});`); + codegen.js(/* js */ `size += maxEncodingCapacity(${rv});`); return; } this.inc(MaxEncodingOverhead.Object); - const fields = objectType.keys; - for (const f of fields) { - const field = f as KeyType; + const fields = type.keys; + for (const field of fields) { const accessor = normalizeAccessor(field.key); - const fieldExpression = new JsExpression(() => `${r}${accessor}`); + const fieldExpression = new JsExpression(() => `${rv}${accessor}`); const isOptional = field instanceof KeyOptType; if (isOptional) { - codegen.if(/* js */ `${JSON.stringify(field.key)} in ${r}`, () => { + codegen.if(/* js */ `${JSON.stringify(field.key)} in ${rv}`, () => { this.inc(MaxEncodingOverhead.ObjectElement); this.inc(maxEncodingCapacity(field.key)); - this.onNode(fieldExpression, field.val); + this.onNode([...path, field.key], fieldExpression, field.val); }); } else { this.inc(MaxEncodingOverhead.ObjectElement); this.inc(maxEncodingCapacity(field.key)); - this.onNode(fieldExpression, field.val); + this.onNode([...path, field.key], fieldExpression, field.val); } } } - protected genMap(value: JsExpression, type: MapType): void { + protected onKey(path: SchemaPath, r: JsExpression, type: KeyType): void { + this.onNode([...path, type.key], r, type.val); + } + + protected onMap(path: SchemaPath, r: JsExpression, type: MapType): void { const codegen = this.codegen; this.inc(MaxEncodingOverhead.Object); - const r = codegen.var(value.use()); - const rKeys = codegen.var(/* js */ `Object.keys(${r})`); + const rv = codegen.var(r.use()); + const rKeys = codegen.var(/* js */ `Object.keys(${rv})`); const rKey = codegen.var(); const rLen = codegen.var(/* js */ `${rKeys}.length`); codegen.js(/* js */ `size += ${MaxEncodingOverhead.ObjectElement} * ${rLen}`); @@ -146,81 +173,31 @@ export class CapacityEstimatorCodegen { codegen.js( /* js */ `size += ${MaxEncodingOverhead.String} + ${MaxEncodingOverhead.StringLengthMultiplier} * ${rKey}.length;`, ); - this.onNode(new JsExpression(() => /* js */ `${r}[${rKey}]`), valueType); + this.onNode([...path, {r: rKey}], new JsExpression(() => /* js */ `${rv}[${rKey}]`), valueType); codegen.js(/* js */ `}`); } - protected genRef(value: JsExpression, type: RefType): void { - const system = type.system; - if (!system) throw new Error('NO_SYSTEM'); - const estimator = CapacityEstimatorCodegen.get(system.resolve(type.ref()).type); + protected onRef(path: SchemaPath, r: JsExpression, type: RefType): void { + const system = type.getSystem(); + const alias = system.resolve(type.ref()); + const estimator = CapacityEstimatorCodegen.get(alias.type); const d = this.codegen.linkDependency(estimator); - this.codegen.js(/* js */ `size += ${d}(${value.use()});`); + this.codegen.js(/* js */ `size += ${d}(${r.use()});`); } - protected genOr(value: JsExpression, type: OrType): void { + protected onOr(path: SchemaPath, r: JsExpression, type: OrType): void { const codegen = this.codegen; const discriminator = DiscriminatorCodegen.get(type); const d = codegen.linkDependency(discriminator); const types = type.types; codegen.switch( - /* js */ `${d}(${value.use()})`, + /* js */ `${d}(${r.use()})`, types.map((childType: Type, index: number) => [ index, () => { - this.onNode(value, childType); + this.onNode(path, r, childType); }, ]), ); } - - protected genKey(r: JsExpression, type: KeyType): void { - this.onNode(r, type.val); - } - - protected onNode(value: JsExpression, type: Type): void { - const kind = type.kind(); - switch (kind) { - case 'any': - this.genAny(value); - break; - case 'bool': - this.inc(MaxEncodingOverhead.Boolean); - break; - case 'num': - this.inc(MaxEncodingOverhead.Number); - break; - case 'str': - this.inc(MaxEncodingOverhead.String); - this.codegen.js(/* js */ `size += ${MaxEncodingOverhead.StringLengthMultiplier} * ${value.use()}.length;`); - break; - case 'bin': - this.inc(MaxEncodingOverhead.Binary); - this.codegen.js(/* js */ `size += ${MaxEncodingOverhead.BinaryLengthMultiplier} * ${value.use()}.length;`); - break; - case 'con': - this.inc(maxEncodingCapacity((type as ConType).literal())); - break; - case 'arr': - this.genArr(value, type as ArrType); - break; - case 'obj': - this.genObj(value, type); - break; - case 'key': - this.genKey(value, type as KeyType); - break; - case 'map': - this.genMap(value, type as MapType); - break; - case 'ref': - this.genRef(value, type as RefType); - break; - case 'or': - this.genOr(value, type as OrType); - break; - default: - throw new Error(`"${kind}" type capacity estimation not implemented`); - } - } } diff --git a/src/codegen/capacity/__tests__/CapacityEstimatorCodegenContext.spec.ts b/src/codegen/capacity/__tests__/CapacityEstimatorCodegenContext.spec.ts index 670c9b54..f8a0384c 100644 --- a/src/codegen/capacity/__tests__/CapacityEstimatorCodegenContext.spec.ts +++ b/src/codegen/capacity/__tests__/CapacityEstimatorCodegenContext.spec.ts @@ -87,6 +87,14 @@ describe('"arr" type', () => { expect(estimator([])).toBe(maxEncodingCapacity([])); }); + test('"con" elements', () => { + const type = t.Array(t.con('abc')); + const estimator = CapacityEstimatorCodegen.get(type); + expect(estimator([])).toBe(maxEncodingCapacity([])); + expect(estimator(['abc'])).toBe(maxEncodingCapacity(['abc'])); + expect(estimator(['abc', 'abc'])).toBe(maxEncodingCapacity(['abc', 'abc'])); + }); + test('simple elements', () => { const system = new ModuleType(); const type = system.t.arr; From f0e1d4921da7dfa8af0bffc9b24a62b33cef6c91 Mon Sep 17 00:00:00 2001 From: streamich Date: Sun, 10 Aug 2025 11:02:24 +0200 Subject: [PATCH 08/18] =?UTF-8?q?fix:=20=F0=9F=90=9B=20correct=20capacity?= =?UTF-8?q?=20estimator=20tuple=20handling?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/codegen/capacity/CapacityEstimatorCodegen.ts | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/codegen/capacity/CapacityEstimatorCodegen.ts b/src/codegen/capacity/CapacityEstimatorCodegen.ts index bc0aeb87..7fec90dc 100644 --- a/src/codegen/capacity/CapacityEstimatorCodegen.ts +++ b/src/codegen/capacity/CapacityEstimatorCodegen.ts @@ -91,12 +91,6 @@ export class CapacityEstimatorCodegen extends AbstractCodegen { - // codegen.js(/* js */ `throw new Error('INV_LEN');`); - // }); - // } if (_type) { const isConstantSizeType = _type instanceof ConType || _type instanceof BoolType || _type instanceof NumType; if (isConstantSizeType) { @@ -106,7 +100,7 @@ export class CapacityEstimatorCodegen extends AbstractCodegen Date: Sun, 10 Aug 2025 11:12:34 +0200 Subject: [PATCH 09/18] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20harden=20tuple=20c?= =?UTF-8?q?apacity=20estimator=20codegen?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../capacity/CapacityEstimatorCodegen.ts | 2 +- .../CapacityEstimatorCodegenContext.spec.ts | 88 +++++++++++++++++++ 2 files changed, 89 insertions(+), 1 deletion(-) diff --git a/src/codegen/capacity/CapacityEstimatorCodegen.ts b/src/codegen/capacity/CapacityEstimatorCodegen.ts index 7fec90dc..5580d3c2 100644 --- a/src/codegen/capacity/CapacityEstimatorCodegen.ts +++ b/src/codegen/capacity/CapacityEstimatorCodegen.ts @@ -116,7 +116,7 @@ export class CapacityEstimatorCodegen extends AbstractCodegen 0) { const rr = codegen.var(r.use()); for (let i = 0; i < tailLength; i++) - this.onNode([...path, {r: `${rLen} - ${tailLength - i}`}], new JsExpression(() => /* js */ `${rr}[${rLen} - ${i + 1}]`), _tail![i]); + this.onNode([...path, {r: `${rLen} - ${(tailLength - i)}`}], new JsExpression(() => /* js */ `${rr}[${rLen} - ${(tailLength - i)}]`), _tail![i]); } } diff --git a/src/codegen/capacity/__tests__/CapacityEstimatorCodegenContext.spec.ts b/src/codegen/capacity/__tests__/CapacityEstimatorCodegenContext.spec.ts index f8a0384c..36db297c 100644 --- a/src/codegen/capacity/__tests__/CapacityEstimatorCodegenContext.spec.ts +++ b/src/codegen/capacity/__tests__/CapacityEstimatorCodegenContext.spec.ts @@ -150,6 +150,94 @@ describe('"arr" type', () => { const estimator = CapacityEstimatorCodegen.get(type); expect(estimator([1, 'abc', 'xxxxxxxxx'])).toBe(maxEncodingCapacity([1, 'abc', 'xxxxxxxxx'])); }); + + test('named head 2-tuple', () => { + const system = new ModuleType(); + const type = system.t.Tuple([t.Key('first', t.Const('abc')), t.Key('second', t.Const('xxxxxxxxx'))], t.num); + const estimator = CapacityEstimatorCodegen.get(type); + expect(estimator(['abc', 'xxxxxxxxx', 1])).toBe(maxEncodingCapacity(['abc', 'xxxxxxxxx', 1])); + }); + + test('mixed head and tail tuple', () => { + const system = new ModuleType(); + const type = system.t.Tuple([t.Const('start')], t.str).tail(t.Const('end')); + const estimator = CapacityEstimatorCodegen.get(type); + expect(estimator(['start', 'middle1', 'middle2', 'end'])).toBe(maxEncodingCapacity(['start', 'middle1', 'middle2', 'end'])); + }); + + test('complex named tail tuple', () => { + const system = new ModuleType(); + const type = system.t.Array(t.num).tail( + t.Key('status', t.str), + t.Key('timestamp', t.num), + t.Key('metadata', t.bool) + ); + const estimator = CapacityEstimatorCodegen.get(type); + expect(estimator([1, 2, 3, 'success', 1234567890, true])).toBe(maxEncodingCapacity([1, 2, 3, 'success', 1234567890, true])); + }); + + test('empty array with head/tail definition', () => { + const system = new ModuleType(); + const type = system.t.Tuple([t.Const('required')], t.str).tail(t.Const('end')); + const estimator = CapacityEstimatorCodegen.get(type); + expect(estimator(['required', 'end'])).toBe(maxEncodingCapacity(['required', 'end'])); + }); + + test('head tuple with different types', () => { + const system = new ModuleType(); + const type = system.t.Tuple([ + t.Key('id', t.num), + t.Key('name', t.str), + t.Key('active', t.bool) + ], t.str); + const estimator = CapacityEstimatorCodegen.get(type); + expect(estimator([42, 'test', true, 'extra1', 'extra2'])).toBe(maxEncodingCapacity([42, 'test', true, 'extra1', 'extra2'])); + }); + + test('tail tuple with different types', () => { + const system = new ModuleType(); + const type = system.t.Array(t.str).tail( + t.Key('count', t.num), + t.Key('valid', t.bool) + ); + const estimator = CapacityEstimatorCodegen.get(type); + expect(estimator(['item1', 'item2', 'item3', 5, true])).toBe(maxEncodingCapacity(['item1', 'item2', 'item3', 5, true])); + }); + + test('nested objects in named tuples', () => { + const system = new ModuleType(); + const type = system.t.Array(t.Object(t.Key('value', t.num))).tail( + t.Key('summary', t.Object(t.Key('total', t.num), t.Key('average', t.num))) + ); + const estimator = CapacityEstimatorCodegen.get(type); + const data = [ + {value: 10}, + {value: 20}, + {total: 30, average: 15} // summary + ]; + expect(estimator(data)).toBe(maxEncodingCapacity(data)); + }); + + test('single element named tail', () => { + const system = new ModuleType(); + const type = system.t.Array(t.num).tail(t.Key('final', t.str)); + const estimator = CapacityEstimatorCodegen.get(type); + expect(estimator([1, 2, 3, 'done'])).toBe(maxEncodingCapacity([1, 2, 3, 'done'])); + }); + + test('single element named head', () => { + const system = new ModuleType(); + const type = system.t.Tuple([t.Key('header', t.str)], t.num); + const estimator = CapacityEstimatorCodegen.get(type); + expect(estimator(['header', 1, 2, 3])).toBe(maxEncodingCapacity(['header', 1, 2, 3])); + }); + + test('both head and tail with same type', () => { + const system = new ModuleType(); + const type = system.t.Tuple([t.Key('start', t.str)], t.num).tail(t.Key('end', t.str)); + const estimator = CapacityEstimatorCodegen.get(type); + expect(estimator(['begin', 1, 2, 3, 'finish'])).toBe(maxEncodingCapacity(['begin', 1, 2, 3, 'finish'])); + }); }); describe('"obj" type', () => { From aeef27a375e664ec62ecbceb4e5eb57111cfaa14 Mon Sep 17 00:00:00 2001 From: streamich Date: Sun, 10 Aug 2025 11:17:40 +0200 Subject: [PATCH 10/18] =?UTF-8?q?test:=20=F0=9F=92=8D=20feat:=20?= =?UTF-8?q?=F0=9F=8E=B8=20implement=20`onKey`=20in=20capacity=20estimators?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/codegen/binary/AbstractBinaryCodegen.ts | 2 ++ src/codegen/binary/cbor copy/CborCodegen.ts | 6 +++++- src/codegen/binary/cbor/CborCodegen.ts | 6 +++++- src/codegen/binary/json/JsonCodegen.ts | 6 +++++- src/codegen/binary/msgpack/MsgPackCodegen.ts | 6 +++++- 5 files changed, 22 insertions(+), 4 deletions(-) diff --git a/src/codegen/binary/AbstractBinaryCodegen.ts b/src/codegen/binary/AbstractBinaryCodegen.ts index ca9794a4..153f8ffc 100644 --- a/src/codegen/binary/AbstractBinaryCodegen.ts +++ b/src/codegen/binary/AbstractBinaryCodegen.ts @@ -8,8 +8,10 @@ import type { BinType, BoolType, ConType, + KeyType, MapType, NumType, + ObjType, OrType, RefType, StrType, diff --git a/src/codegen/binary/cbor copy/CborCodegen.ts b/src/codegen/binary/cbor copy/CborCodegen.ts index 0355be24..4dbd1e01 100644 --- a/src/codegen/binary/cbor copy/CborCodegen.ts +++ b/src/codegen/binary/cbor copy/CborCodegen.ts @@ -1,7 +1,7 @@ import {JsExpression} from '@jsonjoy.com/codegen/lib/util/JsExpression'; import {normalizeAccessor} from '@jsonjoy.com/codegen/lib/util/normalizeAccessor'; import {CborEncoder} from '@jsonjoy.com/json-pack/lib/cbor/CborEncoder'; -import {type MapType, KeyOptType, type ObjType, type Type} from '../../../type'; +import {type MapType, KeyOptType, type KeyType, type ObjType, type Type} from '../../../type'; import type {CompiledBinaryEncoder, SchemaPath} from '../../types'; import {lazyKeyedFactory} from '../../util'; import {AbstractBinaryCodegen} from '../AbstractBinaryCodegen'; @@ -79,6 +79,10 @@ export class CborCodegen extends AbstractBinaryCodegen { } } + protected onKey(path: SchemaPath, r: JsExpression, type: KeyType): void { + this.onNode([...path, type.key], r, type.val); + } + protected genEncoder(type: Type): CompiledBinaryEncoder { return CborCodegen.get(type); } diff --git a/src/codegen/binary/cbor/CborCodegen.ts b/src/codegen/binary/cbor/CborCodegen.ts index 778d4efc..6a7a10d7 100644 --- a/src/codegen/binary/cbor/CborCodegen.ts +++ b/src/codegen/binary/cbor/CborCodegen.ts @@ -1,7 +1,7 @@ import {JsExpression} from '@jsonjoy.com/codegen/lib/util/JsExpression'; import {normalizeAccessor} from '@jsonjoy.com/codegen/lib/util/normalizeAccessor'; import {CborEncoder} from '@jsonjoy.com/json-pack/lib/cbor/CborEncoder'; -import {KeyOptType, type ObjType, type Type} from '../../../type'; +import {KeyOptType, type KeyType, type ObjType, type Type} from '../../../type'; import type {CompiledBinaryEncoder, SchemaPath} from '../../types'; import {lazyKeyedFactory} from '../../util'; import {AbstractBinaryCodegen} from '../AbstractBinaryCodegen'; @@ -79,6 +79,10 @@ export class CborCodegen extends AbstractBinaryCodegen { } } + protected onKey(path: SchemaPath, r: JsExpression, type: KeyType): void { + this.onNode([...path, type.key], r, type.val); + } + protected genEncoder(type: Type): CompiledBinaryEncoder { return CborCodegen.get(type); } diff --git a/src/codegen/binary/json/JsonCodegen.ts b/src/codegen/binary/json/JsonCodegen.ts index f8adc300..e036e1c2 100644 --- a/src/codegen/binary/json/JsonCodegen.ts +++ b/src/codegen/binary/json/JsonCodegen.ts @@ -1,7 +1,7 @@ import {JsExpression} from '@jsonjoy.com/codegen/lib/util/JsExpression'; import {normalizeAccessor} from '@jsonjoy.com/codegen/lib/util/normalizeAccessor'; import {JsonEncoder} from '@jsonjoy.com/json-pack/lib/json/JsonEncoder'; -import {type ArrType, type MapType, KeyOptType, type ObjType, type Type} from '../../../type'; +import {type ArrType, type MapType, KeyOptType, type KeyType, type ObjType, type Type} from '../../../type'; import type {CompiledBinaryEncoder, SchemaPath} from '../../types'; import {lazyKeyedFactory} from '../../util'; import {AbstractBinaryCodegen} from '../AbstractBinaryCodegen'; @@ -218,6 +218,10 @@ export class JsonCodegen extends AbstractBinaryCodegen { this.blob(objEndBlob); } + protected onKey(path: SchemaPath, r: JsExpression, type: KeyType): void { + this.onNode([...path, type.key], r, type.val); + } + protected genEncoder(type: Type): CompiledBinaryEncoder { return JsonCodegen.get(type); } diff --git a/src/codegen/binary/msgpack/MsgPackCodegen.ts b/src/codegen/binary/msgpack/MsgPackCodegen.ts index b6ea4209..ffd30a73 100644 --- a/src/codegen/binary/msgpack/MsgPackCodegen.ts +++ b/src/codegen/binary/msgpack/MsgPackCodegen.ts @@ -1,7 +1,7 @@ import {JsExpression} from '@jsonjoy.com/codegen/lib/util/JsExpression'; import {normalizeAccessor} from '@jsonjoy.com/codegen/lib/util/normalizeAccessor'; import {MsgPackEncoder} from '@jsonjoy.com/json-pack/lib/msgpack/MsgPackEncoder'; -import {KeyOptType, type ObjType, type Type} from '../../../type'; +import {KeyOptType, type KeyType, type ObjType, type Type} from '../../../type'; import type {CompiledBinaryEncoder, SchemaPath} from '../../types'; import {lazyKeyedFactory} from '../../util'; import {AbstractBinaryCodegen} from '../AbstractBinaryCodegen'; @@ -88,6 +88,10 @@ export class MsgPackCodegen extends AbstractBinaryCodegen { } } + protected onKey(path: SchemaPath, r: JsExpression, type: KeyType): void { + this.onNode([...path, type.key], r, type.val); + } + protected genEncoder(type: Type): CompiledBinaryEncoder { return MsgPackCodegen.get(type); } From 160ec0966cf44ce6132400758bede54b303dc81f Mon Sep 17 00:00:00 2001 From: streamich Date: Sun, 10 Aug 2025 11:21:36 +0200 Subject: [PATCH 11/18] =?UTF-8?q?test:=20=F0=9F=92=8D=20add=20named=20tupl?= =?UTF-8?q?e=20tests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../binary/__tests__/testBinaryCodegen.ts | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/src/codegen/binary/__tests__/testBinaryCodegen.ts b/src/codegen/binary/__tests__/testBinaryCodegen.ts index 613f2384..bc37a133 100644 --- a/src/codegen/binary/__tests__/testBinaryCodegen.ts +++ b/src/codegen/binary/__tests__/testBinaryCodegen.ts @@ -222,6 +222,52 @@ export const testBinaryCodegen = (transcode: (system: ModuleType, type: Type, va const value4: any[] = []; expect(() => transcode(system, type, value4)).toThrow(); }); + + test('can encode named tuple with head and tail', () => { + const system = new ModuleType(); + const t = system.t; + const type = system.t.Tuple([t.Key('name', t.str)], t.num, [t.Key('status', t.bool)]); + const value: any[] = ['John', 42, 100, true]; + expect(transcode(system, type, value)).toStrictEqual(value); + }); + + test('can encode named tuple head only', () => { + const system = new ModuleType(); + const t = system.t; + const type = system.t.Tuple([t.Key('id', t.num), t.Key('name', t.str)], t.bool); + const value: any[] = [123, 'Alice', true, false]; + expect(transcode(system, type, value)).toStrictEqual(value); + }); + + test('can encode named tuple tail only', () => { + const system = new ModuleType(); + const t = system.t; + const type = system.t.Array(t.str).tail(t.Key('count', t.num), t.Key('valid', t.bool)); + const value: any[] = ['item1', 'item2', 5, true]; + expect(transcode(system, type, value)).toStrictEqual(value); + }); + + test('can encode complex named tuple', () => { + const system = new ModuleType(); + const t = system.t; + const type = system.t.Tuple( + [t.Key('header', t.str), t.Key('version', t.num)], + t.Object(t.Key('data', t.str)), + [t.Key('checksum', t.num), t.Key('timestamp', t.num)], + ); + const value: any[] = ['v1', 2, {data: 'test1'}, {data: 'test2'}, 12345, 1234567890]; + expect(transcode(system, type, value)).toStrictEqual(value); + }); + + test('can encode nested named tuple', () => { + const system = new ModuleType(); + const t = system.t; + const type = system.t.Tuple([ + t.Key('metadata', t.Tuple([t.Key('type', t.str), t.Key('size', t.num)])), + ], t.str); + const value: any[] = [['document', 1024], 'content1', 'content2']; + expect(transcode(system, type, value)).toStrictEqual(value); + }); }); describe('"obj" type', () => { From 8514f3e84ef72dea093e0cbb665d170f8f30d859 Mon Sep 17 00:00:00 2001 From: streamich Date: Sun, 10 Aug 2025 12:06:47 +0200 Subject: [PATCH 12/18] =?UTF-8?q?fix:=20=F0=9F=90=9B=20correct=20random=20?= =?UTF-8?q?string=20generation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/random/Random.ts | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/random/Random.ts b/src/random/Random.ts index 47eb0c52..595bbca7 100644 --- a/src/random/Random.ts +++ b/src/random/Random.ts @@ -1,4 +1,4 @@ -import {RandomJson} from '@jsonjoy.com/json-random'; +import {RandomJson, randomString} from '@jsonjoy.com/json-random'; import {cloneBinary} from '@jsonjoy.com/util/lib/json-clone'; import {of} from 'rxjs'; import type { @@ -193,11 +193,16 @@ export class Random { } public str(type: StrType): string { - let length = Math.round(Math.random() * 10); const schema = type.getSchema(); + const isAscii = schema.format === 'ascii' || schema.ascii; const {min, max} = schema; - if (min !== undefined && length < min) length = min + length; - if (max !== undefined && length > max) length = max; - return RandomJson.genString(length); + let targetLength = Math.round(Math.random() * 10); + if (min !== undefined && targetLength < min) targetLength = min + targetLength; + if (max !== undefined && targetLength > max) targetLength = max; + let str = isAscii ? randomString(['char', 32, 126, targetLength]) : RandomJson.genString(targetLength); + const length = str.length; + if (min !== undefined && length < min) str = str.padEnd(min, '.'); + if (max !== undefined && length > max) str = str.slice(0, max); + return str; } } From 1e9656911c14f8dc8025d640f12a9f77d44fb43a Mon Sep 17 00:00:00 2001 From: streamich Date: Sun, 10 Aug 2025 12:57:10 +0200 Subject: [PATCH 13/18] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20improve=20discrimi?= =?UTF-8?q?nator=20expression=20builder?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/__tests__/fixtures.ts | 38 +++++++++++++++++-- .../binary/cbor/__tests__/automated.spec.ts | 28 ++++++++++++++ src/type/classes/ModuleType/index.ts | 6 +-- src/type/discriminator.ts | 9 +++-- 4 files changed, 72 insertions(+), 9 deletions(-) create mode 100644 src/codegen/binary/cbor/__tests__/automated.spec.ts diff --git a/src/__tests__/fixtures.ts b/src/__tests__/fixtures.ts index 0c4193e5..0fc2029f 100644 --- a/src/__tests__/fixtures.ts +++ b/src/__tests__/fixtures.ts @@ -7,7 +7,11 @@ import {RandomJson} from '@jsonjoy.com/json-random'; import {genRandomExample} from '@jsonjoy.com/json-random/lib/examples'; import {s} from '../schema'; -import {t} from '../type'; +import {ModuleType} from '../type/classes/ModuleType'; +import type {Type} from '../type'; + +const mod = new ModuleType(); +export const t = mod.t; export const randomJson = () => { return Math.random() < 0.5 ? genRandomExample() : RandomJson.generate(); @@ -78,6 +82,18 @@ export const schemaCategories = { all: allSchemas, } as const; +const primitivesModule = new ModuleType(); +export const primitiveTypes = Object.entries(primitiveSchemas).reduce((acc, [key, schema]) => { + acc[key] = primitivesModule.t.import(schema); + return acc; +}, {} as Record); + +const compositesModule = new ModuleType(); +export const compositeTypes = Object.entries(compositeSchemas).reduce((acc, [key, schema]) => { + acc[key] = compositesModule.t.import(schema); + return acc; +}, {} as Record); + /** * User profile schema with nested objects and optional fields */ @@ -92,7 +108,8 @@ export const User = t age: t.Number({gte: 0, lte: 150}), verified: t.bool, }) - .opt('avatar', t.String({format: 'ascii'})); + .opt('avatar', t.String({format: 'ascii'})) + .alias('User').type; /** * Product catalog schema with arrays and formatted numbers @@ -189,7 +206,7 @@ export const Event = t.Object( t.Key('location', t.Tuple([t.Number({format: 'f64'}), t.Number({format: 'f64'})])), t.Key('metadata', t.Map(t.Or(t.str, t.num, t.bool))), t.KeyOpt('sessionId', t.str), -); +).alias('Event').type; /** * Contact information schema with formatted strings @@ -287,3 +304,18 @@ export const ComplexNested = t.Object( }), ), ); + +export const allSerializableTypes = { + ...primitiveTypes, + ...compositeTypes, + User, + Product, + BlogPost, + ApiResponse, + FileMetadata, + Configuration, + Event, + ContactInfo, + DatabaseRecord, + ComplexNested, +} as const; diff --git a/src/codegen/binary/cbor/__tests__/automated.spec.ts b/src/codegen/binary/cbor/__tests__/automated.spec.ts new file mode 100644 index 00000000..763f64c0 --- /dev/null +++ b/src/codegen/binary/cbor/__tests__/automated.spec.ts @@ -0,0 +1,28 @@ +import {Writer} from '@jsonjoy.com/buffers/lib/Writer'; +import {CborDecoder} from '@jsonjoy.com/json-pack/lib/cbor/CborDecoder'; +import {CborEncoder} from '@jsonjoy.com/json-pack/lib/cbor/CborEncoder'; +import {CborCodegen} from '../CborCodegen'; +import {Random} from '../../../../random'; +import {allSerializableTypes} from '../../../../__tests__/fixtures'; + +const encoder = new CborEncoder(new Writer(16)); +const decoder = new CborDecoder(); + +for (const [name, type] of Object.entries(allSerializableTypes)) { + test(`can encode and decode ${name}`, () => { + for (let i = 0; i < 100; i++) { + const json = Random.gen(type); + try { + const fn = CborCodegen.get(type); + fn(json, encoder); + const encoded = encoder.writer.flush(); + const decoded = decoder.decode(encoded); + expect(decoded).toEqual(json); + } catch (error) { + console.log(JSON.stringify(json, null, 2)); + console.log(type + ''); + throw error; + } + } + }); +} diff --git a/src/type/classes/ModuleType/index.ts b/src/type/classes/ModuleType/index.ts index 4c1cb1c2..5536ad19 100644 --- a/src/type/classes/ModuleType/index.ts +++ b/src/type/classes/ModuleType/index.ts @@ -1,10 +1,10 @@ import {printTree} from 'tree-dump/lib/printTree'; -import type {Printable} from 'tree-dump/lib/types'; -import type {KeySchema, ModuleSchema, ObjSchema, Schema, TypeMap} from '../../../schema'; import {Walker} from '../../../schema/Walker'; -import type {Type} from '../../../type'; import {TypeBuilder} from '../../TypeBuilder'; import {AliasType} from '../AliasType'; +import type {Printable} from 'tree-dump/lib/types'; +import type {KeySchema, ModuleSchema, ObjSchema, Schema, TypeMap} from '../../../schema'; +import type {Type} from '../../../type'; import type {RefType} from '../RefType'; export class ModuleType implements Printable { diff --git a/src/type/discriminator.ts b/src/type/discriminator.ts index 2551902d..e561070f 100644 --- a/src/type/discriminator.ts +++ b/src/type/discriminator.ts @@ -1,5 +1,5 @@ -import type {Expr} from '@jsonjoy.com/json-expression'; import {ArrType, BoolType, ConType, NumType, type KeyType, ObjType, StrType} from './classes'; +import type {Expr} from '@jsonjoy.com/json-expression'; import type {OrType, RefType, Type} from './types'; /** @@ -79,7 +79,10 @@ export class Discriminator { const length = types.length; const expanded: Type[] = []; const expand = (type: Type): Type[] => { - if (type.kind() === 'ref') type = (type as RefType).resolve(); + while (type.kind() === 'ref' || type.kind() === 'key') { + if (type.kind() === 'ref') type = (type as RefType).resolve(); + if (type.kind() === 'key') type = (type as KeyType).val; + } if (type.kind() === 'or') return (type as OrType).types.flatMap((t: Type) => expand(t)); return [type]; }; @@ -108,7 +111,7 @@ export class Discriminator { ) {} condition(): Expr { - if (this.type instanceof ConType) return ['==', this.type.literal(), ['$', this.path]]; + if (this.type instanceof ConType) return ['==', this.type.literal(), ['$', this.path, this.type.literal() === null ? '' : null]]; if (this.type instanceof BoolType) return ['==', ['type', ['$', this.path]], 'boolean']; if (this.type instanceof NumType) return ['==', ['type', ['$', this.path]], 'number']; if (this.type instanceof StrType) return ['==', ['type', ['$', this.path]], 'string']; From 9cba29818f884b61cd19f41314e16136d956ff0e Mon Sep 17 00:00:00 2001 From: streamich Date: Sun, 10 Aug 2025 12:57:37 +0200 Subject: [PATCH 14/18] =?UTF-8?q?chore:=20=F0=9F=A4=96=20remove=20copied?= =?UTF-8?q?=20folder?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/codegen/binary/cbor copy/CborCodegen.ts | 89 ------------------- .../cbor copy/__tests__/CborCodegen.spec.ts | 20 ----- 2 files changed, 109 deletions(-) delete mode 100644 src/codegen/binary/cbor copy/CborCodegen.ts delete mode 100644 src/codegen/binary/cbor copy/__tests__/CborCodegen.spec.ts diff --git a/src/codegen/binary/cbor copy/CborCodegen.ts b/src/codegen/binary/cbor copy/CborCodegen.ts deleted file mode 100644 index 4dbd1e01..00000000 --- a/src/codegen/binary/cbor copy/CborCodegen.ts +++ /dev/null @@ -1,89 +0,0 @@ -import {JsExpression} from '@jsonjoy.com/codegen/lib/util/JsExpression'; -import {normalizeAccessor} from '@jsonjoy.com/codegen/lib/util/normalizeAccessor'; -import {CborEncoder} from '@jsonjoy.com/json-pack/lib/cbor/CborEncoder'; -import {type MapType, KeyOptType, type KeyType, type ObjType, type Type} from '../../../type'; -import type {CompiledBinaryEncoder, SchemaPath} from '../../types'; -import {lazyKeyedFactory} from '../../util'; -import {AbstractBinaryCodegen} from '../AbstractBinaryCodegen'; -import {writer} from '../writer'; - -export class CborCodegen extends AbstractBinaryCodegen { - public static readonly get = lazyKeyedFactory((type: Type, name?: string) => { - const codegen = new CborCodegen(type, name); - const r = codegen.codegen.options.args[0]; - const expression = new JsExpression(() => r); - codegen.onNode([], expression, type); - return codegen.compile(); - }); - - protected encoder = new CborEncoder(writer); - - protected onObj(path: SchemaPath, value: JsExpression, type: ObjType): void { - const codegen = this.codegen; - const r = codegen.r(); - const fields = type.keys; - const length = fields.length; - const requiredFields = fields.filter((field) => !(field instanceof KeyOptType)); - const optionalFields = fields.filter((field) => field instanceof KeyOptType); - const requiredLength = requiredFields.length; - const optionalLength = optionalFields.length; - const encodeUnknownFields = !!type.schema.encodeUnknownKeys; - const emitRequiredFields = () => { - for (let i = 0; i < requiredLength; i++) { - const field = requiredFields[i]; - this.blob(this.gen((encoder) => encoder.writeStr(field.key))); - const accessor = normalizeAccessor(field.key); - this.onNode([...path, field.key], new JsExpression(() => `${r}${accessor}`), field.val); - } - }; - const emitOptionalFields = () => { - for (let i = 0; i < optionalLength; i++) { - const field = optionalFields[i]; - const accessor = normalizeAccessor(field.key); - codegen.js(`if (${JSON.stringify(field.key)} in ${r}) {`); - this.blob(this.gen((encoder) => encoder.writeStr(field.key))); - this.onNode([...path, field.key], new JsExpression(() => `${r}${accessor}`), field.val); - 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(`}`); - }; - codegen.js(/* js */ `var ${r} = ${value.use()};`); - if (!encodeUnknownFields && !optionalLength) { - this.blob(this.gen((encoder) => encoder.writeObjHdr(length))); - emitRequiredFields(); - } else if (!encodeUnknownFields) { - this.blob(this.gen((encoder) => encoder.writeStartObj())); - emitRequiredFields(); - emitOptionalFields(); - this.blob(this.gen((encoder) => encoder.writeEndObj())); - } else { - this.blob(this.gen((encoder) => encoder.writeStartObj())); - emitRequiredFields(); - emitOptionalFields(); - emitUnknownFields(); - this.blob(this.gen((encoder) => encoder.writeEndObj())); - } - } - - protected onKey(path: SchemaPath, r: JsExpression, type: KeyType): void { - this.onNode([...path, type.key], r, type.val); - } - - protected genEncoder(type: Type): CompiledBinaryEncoder { - return CborCodegen.get(type); - } -} diff --git a/src/codegen/binary/cbor copy/__tests__/CborCodegen.spec.ts b/src/codegen/binary/cbor copy/__tests__/CborCodegen.spec.ts deleted file mode 100644 index ab768063..00000000 --- a/src/codegen/binary/cbor copy/__tests__/CborCodegen.spec.ts +++ /dev/null @@ -1,20 +0,0 @@ -import {Writer} from '@jsonjoy.com/buffers/lib/Writer'; -import {CborDecoder} from '@jsonjoy.com/json-pack/lib/cbor/CborDecoder'; -import {CborEncoder} from '@jsonjoy.com/json-pack/lib/cbor/CborEncoder'; -import type {ModuleType, Type} from '../../../../type'; -import {testBinaryCodegen} from '../../__tests__/testBinaryCodegen'; -import {CborCodegen} from '../CborCodegen'; - -const encoder = new CborEncoder(new Writer(16)); -const decoder = new CborDecoder(); - -const transcode = (system: ModuleType, type: Type, value: unknown) => { - const fn = CborCodegen.get(type); - encoder.writer.reset(); - fn(value, encoder); - const encoded = encoder.writer.flush(); - const decoded = decoder.decode(encoded); - return decoded; -}; - -testBinaryCodegen(transcode); From 3e0379cc05c3f9cea3668ff154891a9a6c1afc6c Mon Sep 17 00:00:00 2001 From: streamich Date: Sun, 10 Aug 2025 14:23:37 +0200 Subject: [PATCH 15/18] =?UTF-8?q?fix:=20=F0=9F=90=9B=20improve=20JSON=20co?= =?UTF-8?q?degen?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/__tests__/fixtures.ts | 3 + src/codegen/binary/AbstractBinaryCodegen.ts | 18 +++- src/codegen/binary/json/JsonCodegen.ts | 33 +++---- .../binary/json/__tests__/JsonCodegen.spec.ts | 86 ++++++++++++++++++- .../binary/json/__tests__/automated.spec.ts | 33 +++++++ .../msgpack/__tests__/automated.spec.ts | 27 ++++++ .../CapacityEstimatorCodegenContext.spec.ts | 12 +++ src/type/classes/OrType.ts | 2 +- 8 files changed, 192 insertions(+), 22 deletions(-) create mode 100644 src/codegen/binary/json/__tests__/automated.spec.ts create mode 100644 src/codegen/binary/msgpack/__tests__/automated.spec.ts diff --git a/src/__tests__/fixtures.ts b/src/__tests__/fixtures.ts index 0fc2029f..6ed5680e 100644 --- a/src/__tests__/fixtures.ts +++ b/src/__tests__/fixtures.ts @@ -63,6 +63,9 @@ export const compositeSchemas = { s.Array(s.Number()), ), binary: s.bin, + doubleMap: s.Map(s.Map(s.str)), + nestedMaps: s.Map(s.Map(s.Map(s.nil))), + nestedArrays: s.Array(s.Array(s.Array(s.str))), } as const; /** diff --git a/src/codegen/binary/AbstractBinaryCodegen.ts b/src/codegen/binary/AbstractBinaryCodegen.ts index 153f8ffc..6507b699 100644 --- a/src/codegen/binary/AbstractBinaryCodegen.ts +++ b/src/codegen/binary/AbstractBinaryCodegen.ts @@ -8,10 +8,8 @@ import type { BinType, BoolType, ConType, - KeyType, MapType, NumType, - ObjType, OrType, RefType, StrType, @@ -148,7 +146,21 @@ var uint8 = writer.uint8, view = writer.view;`, } protected onBool(path: SchemaPath, r: JsExpression, type: BoolType): void { - this.codegen.js(/* js */ `encoder.writeBoolean(${r.use()});`); + this.codegen.if(`${r.use()}`, + () => { + this.blob( + this.gen((encoder) => { + encoder.writeBoolean(true); + }), + ); + }, + () => { + this.blob( + this.gen((encoder) => { + encoder.writeBoolean(false); + }), + ); + }); } protected onNum(path: SchemaPath, r: JsExpression, type: NumType): void { diff --git a/src/codegen/binary/json/JsonCodegen.ts b/src/codegen/binary/json/JsonCodegen.ts index e036e1c2..37d228eb 100644 --- a/src/codegen/binary/json/JsonCodegen.ts +++ b/src/codegen/binary/json/JsonCodegen.ts @@ -13,6 +13,8 @@ export class JsonCodegen extends AbstractBinaryCodegen { const r = codegen.codegen.options.args[0]; const expression = new JsExpression(() => r); codegen.onNode([], expression, type); + + // console.log(codegen.codegen.generate().js); return codegen.compile(); }); @@ -142,26 +144,26 @@ export class JsonCodegen extends AbstractBinaryCodegen { 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});`); + const rKnownFields = codegen.addConstant(/* js */ `new Set([${keys.join(',')}])`); + codegen.js(/* js */ `var ${rKeys} = Object.keys(${r}), ${rLength} = ${rKeys}.length, ${rKey};`); + codegen.js(/* js */ `for (var ${ri} = 0; ${ri} < ${rLength}; ${ri}++) {`); + codegen.js(/* js */ `${rKey} = ${rKeys}[${ri}];`); + codegen.js(/* js */ `if (${rKnownFields}.has(${rKey})) continue;`); + codegen.js(/* js */ `encoder.writeStr(${rKey});`); this.blob(keySeparatorBlob); - codegen.js(`encoder.writeAny(${r}[${rKey}]);`); + codegen.js(/* js */ `encoder.writeAny(${r}[${rKey}]);`); this.blob(separatorBlob); - codegen.js(`}`); + codegen.js(/* js */ `}`); }; const emitEnding = () => { const rewriteLastSeparator = () => { - for (let i = 0; i < endBlob.length; i++) codegen.js(`uint8[writer.x - ${endBlob.length - i}] = ${endBlob[i]};`); + for (let i = 0; i < endBlob.length; i++) codegen.js(/* js */ `uint8[writer.x - ${endBlob.length - i}] = ${endBlob[i]};`); }; if (requiredFields.length) { rewriteLastSeparator(); } else { codegen.if( - `uint8[writer.x - 1] === ${separatorBlob[separatorBlob.length - 1]}`, + /* js */ `uint8[writer.x - 1] === ${separatorBlob[separatorBlob.length - 1]}`, () => { rewriteLastSeparator(); }, @@ -192,30 +194,29 @@ export class JsonCodegen extends AbstractBinaryCodegen { } protected onMap(path: SchemaPath, val: JsExpression, type: MapType): void { - const objStartBlob = this.gen((encoder) => encoder.writeStartObj()); - const objEndBlob = this.gen((encoder) => encoder.writeEndObj()); const separatorBlob = this.gen((encoder) => encoder.writeObjSeparator()); const keySeparatorBlob = this.gen((encoder) => encoder.writeObjKeySeparator()); const codegen = this.codegen; const r = codegen.var(val.use()); const rKeys = codegen.var(`Object.keys(${r})`); const rKey = codegen.var(); + const ri = codegen.var(); const rLength = codegen.var(`${rKeys}.length`); - this.blob(objStartBlob); + this.blob(this.gen((encoder) => encoder.writeStartObj())); codegen.if(`${rLength}`, () => { codegen.js(`${rKey} = ${rKeys}[0];`); codegen.js(`encoder.writeStr(${rKey});`); this.blob(keySeparatorBlob); this.onNode([...path, {r: rKey}], new JsExpression(() => `${r}[${rKey}]`), type._value); }); - codegen.js(`for (var i = 1; i < ${rLength}; i++) {`); - codegen.js(`${rKey} = ${rKeys}[i];`); + codegen.js(`for (var ${ri} = 1; ${ri} < ${rLength}; ${ri}++) {`); + codegen.js(`${rKey} = ${rKeys}[${ri}];`); this.blob(separatorBlob); codegen.js(`encoder.writeStr(${rKey});`); this.blob(keySeparatorBlob); this.onNode([...path, {r: rKey}], new JsExpression(() => `${r}[${rKey}]`), type._value); codegen.js(`}`); - this.blob(objEndBlob); + this.blob(this.gen((encoder) => encoder.writeEndObj())); } protected onKey(path: SchemaPath, r: JsExpression, type: KeyType): void { diff --git a/src/codegen/binary/json/__tests__/JsonCodegen.spec.ts b/src/codegen/binary/json/__tests__/JsonCodegen.spec.ts index d298aabf..69f74673 100644 --- a/src/codegen/binary/json/__tests__/JsonCodegen.spec.ts +++ b/src/codegen/binary/json/__tests__/JsonCodegen.spec.ts @@ -1,10 +1,10 @@ import {Writer} from '@jsonjoy.com/buffers/lib/Writer'; import {parse} from '@jsonjoy.com/json-pack/lib/json-binary'; import {JsonEncoder} from '@jsonjoy.com/json-pack/lib/json/JsonEncoder'; -import type {Type} from '../../../../type'; -import type {ModuleType} from '../../../../type/classes/ModuleType'; +import {ModuleType} from '../../../../type/classes/ModuleType'; import {testBinaryCodegen} from '../../__tests__/testBinaryCodegen'; import {JsonCodegen} from '../JsonCodegen'; +import type {Type} from '../../../../type'; const encoder = new JsonEncoder(new Writer(16)); @@ -14,9 +14,91 @@ const transcode = (system: ModuleType, type: Type, value: unknown) => { fn(value, encoder); const encoded = encoder.writer.flush(); const json = Buffer.from(encoded).toString('utf-8'); + // console.log(value); // console.log(json); const decoded = parse(json); return decoded; }; testBinaryCodegen(transcode); + +test('fuzzer 1: map in map', () => { + const system = new ModuleType(); + const {t} = system; + const type = t.Map(t.Map(t.nil)); + const value = { + '^': { + 'ww9DP[c': null, + '2LL*vp ': null, + 'OW;a(w)': null, + 'T`jb_LZ': null, + 'C)crlQL': null, + 'kw&p(^-': null, + 'oKkF,u8': null + } + }; + const value2 = { + 'YS9mc}Zb': { + 'V2*_9': null, + 'j9?_0': null, + '@:ODe': null, + 'sS{Sx': null, + '4EMz|': null + }, + "bF@64u'7": { + 'q<_b%}$Q': null, + 'RäXpXBLü': null, + '$uJx]{ft': null, + 'bX%jLhr{': null, + 'Lr1bY-fY': null, + 'D]ml,C)W': null, + 'eK=DszFO': null, + '!RqC^GUz': null + }, + '9SEDa*#|': { + ';COK{m%=': null, + 'i`tJj:xE': null, + 'ffIhp!Om': null, + 'kiN&BfB5': null, + 'k+$es!mO': null, + 'O1(&D_bt': null, + 'cidA#*BD': null, + '!ZP5JBFq': null + }, + ';6(7#5m:': {}, + 'zhGX^&Y3': { + '1Z>iC': null, + '%вqL=': null, + '5?5{)': null, + '*2"H4': null, + ')&_O4': null + }, + '?6a1a5Y\\': { + '5,bCV': null, + 'z[x2s': null, + 'Ad/g9': null, + 'at#84': null, + '{@?".': null + }, + 'uaaAwаHb': { VXy: null, 'I(<': null, 'W V': null }, + '&sH?Bk2E': { + 'M[^ex': null, + '-ZP$E': null, + 'c*@uR': null, + '`sy3N': null, + 'g?DB ': null + } + }; + const value3 = { + '/7': { '|;L': null, '@K<': null, '*x:': null }, + Zf: { N1: null, 't%': null } + }; + for (let i = 0; i < 100; i++) { + const decoded = transcode(system, type, value); + const decoded2 = transcode(system, type, value2); + const decoded3 = transcode(system, type, value3); + expect(decoded).toEqual(value); + expect(decoded2).toEqual(value2); + expect(decoded3).toEqual(value3); + } +}); diff --git a/src/codegen/binary/json/__tests__/automated.spec.ts b/src/codegen/binary/json/__tests__/automated.spec.ts new file mode 100644 index 00000000..82a7fd0d --- /dev/null +++ b/src/codegen/binary/json/__tests__/automated.spec.ts @@ -0,0 +1,33 @@ +import {Writer} from '@jsonjoy.com/buffers/lib/Writer'; +import {JsonDecoder} from '@jsonjoy.com/json-pack/lib/json/JsonDecoder'; +import {JsonEncoder} from '@jsonjoy.com/json-pack/lib/json/JsonEncoder'; +import {parse} from '@jsonjoy.com/json-pack/lib/json-binary'; +import {JsonCodegen} from '../JsonCodegen'; +import {Random} from '../../../../random'; +import {allSerializableTypes} from '../../../../__tests__/fixtures'; + +const encoder = new JsonEncoder(new Writer(16)); +const decoder = new JsonDecoder(); + +for (const [name, type] of Object.entries(allSerializableTypes)) { + test(`can encode and decode ${name}`, () => { + for (let i = 0; i < 100; i++) { + const json = Random.gen(type); + // console.log(json); + try { + const fn = JsonCodegen.get(type); + fn(json, encoder); + const encoded = encoder.writer.flush(); + const text = Buffer.from(encoded).toString('utf-8'); + // console.log(text); + const decoded = parse(text); + // const decoded = decoder.decode(encoded); + expect(decoded).toEqual(json); + } catch (error) { + console.log(JSON.stringify(json, null, 2)); + console.log(type + ''); + throw error; + } + } + }); +} diff --git a/src/codegen/binary/msgpack/__tests__/automated.spec.ts b/src/codegen/binary/msgpack/__tests__/automated.spec.ts new file mode 100644 index 00000000..72809140 --- /dev/null +++ b/src/codegen/binary/msgpack/__tests__/automated.spec.ts @@ -0,0 +1,27 @@ +import {MsgPackDecoder} from '@jsonjoy.com/json-pack/lib/msgpack/MsgPackDecoder'; +import {MsgPackEncoder} from '@jsonjoy.com/json-pack/lib/msgpack/MsgPackEncoder'; +import {MsgPackCodegen} from '../MsgPackCodegen'; +import {Random} from '../../../../random'; +import {allSerializableTypes} from '../../../../__tests__/fixtures'; + + const encoder = new MsgPackEncoder(); + const decoder = new MsgPackDecoder(); + +for (const [name, type] of Object.entries(allSerializableTypes)) { + test(`can encode and decode ${name}`, () => { + for (let i = 0; i < 100; i++) { + const json = Random.gen(type); + try { + const fn = MsgPackCodegen.get(type); + fn(json, encoder); + const encoded = encoder.writer.flush(); + const decoded = decoder.decode(encoded); + expect(decoded).toEqual(json); + } catch (error) { + console.log(JSON.stringify(json, null, 2)); + console.log(type + ''); + throw error; + } + } + }); +} diff --git a/src/codegen/capacity/__tests__/CapacityEstimatorCodegenContext.spec.ts b/src/codegen/capacity/__tests__/CapacityEstimatorCodegenContext.spec.ts index 36db297c..460911a0 100644 --- a/src/codegen/capacity/__tests__/CapacityEstimatorCodegenContext.spec.ts +++ b/src/codegen/capacity/__tests__/CapacityEstimatorCodegenContext.spec.ts @@ -2,6 +2,7 @@ import {maxEncodingCapacity} from '@jsonjoy.com/util/lib/json-size'; import {t} from '../../../type'; import {ModuleType} from '../../../type/classes/ModuleType'; import {CapacityEstimatorCodegen} from '../CapacityEstimatorCodegen'; +import {Random} from '../../../random'; describe('"any" type', () => { test('returns the same result as maxEncodingCapacity()', () => { @@ -354,3 +355,14 @@ test('add circular reference test', () => { const estimator = CapacityEstimatorCodegen.get(user.type); expect(estimator(value1)).toBe(maxEncodingCapacity(value1)); }); + +test('fuzzer: map in map', () => { + const system = new ModuleType(); + const {t} = system; + const type = t.Map(t.Map(t.nil)); + const estimator = CapacityEstimatorCodegen.get(type); + for (let i = 0; i < 100; i++) { + const value = Random.gen(type); + expect(estimator(value)).toBe(maxEncodingCapacity(value)); + } +}); diff --git a/src/type/classes/OrType.ts b/src/type/classes/OrType.ts index 859040ae..dd374f77 100644 --- a/src/type/classes/OrType.ts +++ b/src/type/classes/OrType.ts @@ -32,7 +32,7 @@ export class OrType extends AbsTypediscriminator === -1) || (discriminator.length === 2 && discriminator[0] === 'num' && discriminator[1] === -1)) { this.schema.discriminator = Discriminator.createExpression(this.types); } } From 96a8c72e06ced0915731de2708c9d2645676b463 Mon Sep 17 00:00:00 2001 From: streamich Date: Sun, 10 Aug 2025 14:27:59 +0200 Subject: [PATCH 16/18] =?UTF-8?q?test:=20=F0=9F=92=8D=20pass=20all=20binar?= =?UTF-8?q?y=20codegen=20tests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/codegen/binary/json/__tests__/automated.spec.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/codegen/binary/json/__tests__/automated.spec.ts b/src/codegen/binary/json/__tests__/automated.spec.ts index 82a7fd0d..00aded8a 100644 --- a/src/codegen/binary/json/__tests__/automated.spec.ts +++ b/src/codegen/binary/json/__tests__/automated.spec.ts @@ -20,8 +20,8 @@ for (const [name, type] of Object.entries(allSerializableTypes)) { const encoded = encoder.writer.flush(); const text = Buffer.from(encoded).toString('utf-8'); // console.log(text); - const decoded = parse(text); - // const decoded = decoder.decode(encoded); + // const decoded = parse(text); + const decoded = decoder.decode(encoded); expect(decoded).toEqual(json); } catch (error) { console.log(JSON.stringify(json, null, 2)); From 645f92a66f2674eb361fa746704ad46b26d31adc Mon Sep 17 00:00:00 2001 From: streamich Date: Sun, 10 Aug 2025 14:29:08 +0200 Subject: [PATCH 17/18] =?UTF-8?q?test:=20=F0=9F=92=8D=20update=20snapshots?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../__snapshots__/metaschema.spec.ts.snap | 123 ++++++++++++------ .../__snapshots__/toString.spec.ts.snap | 7 +- 2 files changed, 87 insertions(+), 43 deletions(-) diff --git a/src/metaschema/__tests__/__snapshots__/metaschema.spec.ts.snap b/src/metaschema/__tests__/__snapshots__/metaschema.spec.ts.snap index 941b0511..a1aa2d8d 100644 --- a/src/metaschema/__tests__/__snapshots__/metaschema.spec.ts.snap +++ b/src/metaschema/__tests__/__snapshots__/metaschema.spec.ts.snap @@ -157,7 +157,8 @@ exports[`can import metaschema 1`] = ` │ │ │ "f64", │ │ │ [ │ │ │ "$", - │ │ │ "" + │ │ │ "", + │ │ │ null │ │ │ ] │ │ │ ], │ │ │ 12, @@ -168,7 +169,8 @@ exports[`can import metaschema 1`] = ` │ │ │ "f32", │ │ │ [ │ │ │ "$", - │ │ │ "" + │ │ │ "", + │ │ │ null │ │ │ ] │ │ │ ], │ │ │ 11, @@ -179,7 +181,8 @@ exports[`can import metaschema 1`] = ` │ │ │ "u64", │ │ │ [ │ │ │ "$", - │ │ │ "" + │ │ │ "", + │ │ │ null │ │ │ ] │ │ │ ], │ │ │ 10, @@ -190,7 +193,8 @@ exports[`can import metaschema 1`] = ` │ │ │ "u32", │ │ │ [ │ │ │ "$", - │ │ │ "" + │ │ │ "", + │ │ │ null │ │ │ ] │ │ │ ], │ │ │ 9, @@ -201,7 +205,8 @@ exports[`can import metaschema 1`] = ` │ │ │ "u16", │ │ │ [ │ │ │ "$", - │ │ │ "" + │ │ │ "", + │ │ │ null │ │ │ ] │ │ │ ], │ │ │ 8, @@ -212,7 +217,8 @@ exports[`can import metaschema 1`] = ` │ │ │ "u8", │ │ │ [ │ │ │ "$", - │ │ │ "" + │ │ │ "", + │ │ │ null │ │ │ ] │ │ │ ], │ │ │ 7, @@ -223,7 +229,8 @@ exports[`can import metaschema 1`] = ` │ │ │ "i64", │ │ │ [ │ │ │ "$", - │ │ │ "" + │ │ │ "", + │ │ │ null │ │ │ ] │ │ │ ], │ │ │ 6, @@ -234,7 +241,8 @@ exports[`can import metaschema 1`] = ` │ │ │ "i32", │ │ │ [ │ │ │ "$", - │ │ │ "" + │ │ │ "", + │ │ │ null │ │ │ ] │ │ │ ], │ │ │ 5, @@ -245,7 +253,8 @@ exports[`can import metaschema 1`] = ` │ │ │ "i16", │ │ │ [ │ │ │ "$", - │ │ │ "" + │ │ │ "", + │ │ │ null │ │ │ ] │ │ │ ], │ │ │ 4, @@ -256,7 +265,8 @@ exports[`can import metaschema 1`] = ` │ │ │ "i8", │ │ │ [ │ │ │ "$", - │ │ │ "" + │ │ │ "", + │ │ │ null │ │ │ ] │ │ │ ], │ │ │ 3, @@ -267,7 +277,8 @@ exports[`can import metaschema 1`] = ` │ │ │ "f", │ │ │ [ │ │ │ "$", - │ │ │ "" + │ │ │ "", + │ │ │ null │ │ │ ] │ │ │ ], │ │ │ 2, @@ -278,7 +289,8 @@ exports[`can import metaschema 1`] = ` │ │ │ "u", │ │ │ [ │ │ │ "$", - │ │ │ "" + │ │ │ "", + │ │ │ null │ │ │ ] │ │ │ ], │ │ │ 1, @@ -350,7 +362,8 @@ exports[`can import metaschema 1`] = ` │ │ │ "utf8", │ │ │ [ │ │ │ "$", - │ │ │ "" + │ │ │ "", + │ │ │ null │ │ │ ] │ │ │ ], │ │ │ 1, @@ -402,7 +415,8 @@ exports[`can import metaschema 1`] = ` │ │ │ "bencode", │ │ │ [ │ │ │ "$", - │ │ │ "" + │ │ │ "", + │ │ │ null │ │ │ ] │ │ │ ], │ │ │ 7, @@ -413,7 +427,8 @@ exports[`can import metaschema 1`] = ` │ │ │ "ubjson", │ │ │ [ │ │ │ "$", - │ │ │ "" + │ │ │ "", + │ │ │ null │ │ │ ] │ │ │ ], │ │ │ 6, @@ -424,7 +439,8 @@ exports[`can import metaschema 1`] = ` │ │ │ "bson", │ │ │ [ │ │ │ "$", - │ │ │ "" + │ │ │ "", + │ │ │ null │ │ │ ] │ │ │ ], │ │ │ 5, @@ -435,7 +451,8 @@ exports[`can import metaschema 1`] = ` │ │ │ "ion", │ │ │ [ │ │ │ "$", - │ │ │ "" + │ │ │ "", + │ │ │ null │ │ │ ] │ │ │ ], │ │ │ 4, @@ -446,7 +463,8 @@ exports[`can import metaschema 1`] = ` │ │ │ "resp3", │ │ │ [ │ │ │ "$", - │ │ │ "" + │ │ │ "", + │ │ │ null │ │ │ ] │ │ │ ], │ │ │ 3, @@ -457,7 +475,8 @@ exports[`can import metaschema 1`] = ` │ │ │ "msgpack", │ │ │ [ │ │ │ "$", - │ │ │ "" + │ │ │ "", + │ │ │ null │ │ │ ] │ │ │ ], │ │ │ 2, @@ -468,7 +487,8 @@ exports[`can import metaschema 1`] = ` │ │ │ "cbor", │ │ │ [ │ │ │ "$", - │ │ │ "" + │ │ │ "", + │ │ │ null │ │ │ ] │ │ │ ], │ │ │ 1, @@ -788,7 +808,8 @@ exports[`can import metaschema 1`] = ` │ │ "map", │ │ [ │ │ "$", - │ │ "/kind" + │ │ "/kind", + │ │ null │ │ ] │ │ ], │ │ 8, @@ -799,7 +820,8 @@ exports[`can import metaschema 1`] = ` │ │ "key", │ │ [ │ │ "$", - │ │ "/kind" + │ │ "/kind", + │ │ null │ │ ] │ │ ], │ │ 7, @@ -810,7 +832,8 @@ exports[`can import metaschema 1`] = ` │ │ "obj", │ │ [ │ │ "$", - │ │ "/kind" + │ │ "/kind", + │ │ null │ │ ] │ │ ], │ │ 6, @@ -821,7 +844,8 @@ exports[`can import metaschema 1`] = ` │ │ "con", │ │ [ │ │ "$", - │ │ "/kind" + │ │ "/kind", + │ │ null │ │ ] │ │ ], │ │ 5, @@ -832,7 +856,8 @@ exports[`can import metaschema 1`] = ` │ │ "arr", │ │ [ │ │ "$", - │ │ "/kind" + │ │ "/kind", + │ │ null │ │ ] │ │ ], │ │ 4, @@ -843,7 +868,8 @@ exports[`can import metaschema 1`] = ` │ │ "bin", │ │ [ │ │ "$", - │ │ "/kind" + │ │ "/kind", + │ │ null │ │ ] │ │ ], │ │ 3, @@ -854,7 +880,8 @@ exports[`can import metaschema 1`] = ` │ │ "str", │ │ [ │ │ "$", - │ │ "/kind" + │ │ "/kind", + │ │ null │ │ ] │ │ ], │ │ 2, @@ -865,7 +892,8 @@ exports[`can import metaschema 1`] = ` │ │ "num", │ │ [ │ │ "$", - │ │ "/kind" + │ │ "/kind", + │ │ null │ │ ] │ │ ], │ │ 1, @@ -896,7 +924,8 @@ exports[`can import metaschema 1`] = ` │ "fn$", │ [ │ "$", - │ "/kind" + │ "/kind", + │ null │ ] │ ], │ 13, @@ -907,7 +936,8 @@ exports[`can import metaschema 1`] = ` │ "fn", │ [ │ "$", - │ "/kind" + │ "/kind", + │ null │ ] │ ], │ 12, @@ -918,7 +948,8 @@ exports[`can import metaschema 1`] = ` │ "any", │ [ │ "$", - │ "/kind" + │ "/kind", + │ null │ ] │ ], │ 11, @@ -929,7 +960,8 @@ exports[`can import metaschema 1`] = ` │ "or", │ [ │ "$", - │ "/kind" + │ "/kind", + │ null │ ] │ ], │ 10, @@ -940,7 +972,8 @@ exports[`can import metaschema 1`] = ` │ "ref", │ [ │ "$", - │ "/kind" + │ "/kind", + │ null │ ] │ ], │ 9, @@ -951,7 +984,8 @@ exports[`can import metaschema 1`] = ` │ "map", │ [ │ "$", - │ "/kind" + │ "/kind", + │ null │ ] │ ], │ 8, @@ -962,7 +996,8 @@ exports[`can import metaschema 1`] = ` │ "key", │ [ │ "$", - │ "/kind" + │ "/kind", + │ null │ ] │ ], │ 7, @@ -973,7 +1008,8 @@ exports[`can import metaschema 1`] = ` │ "obj", │ [ │ "$", - │ "/kind" + │ "/kind", + │ null │ ] │ ], │ 6, @@ -984,7 +1020,8 @@ exports[`can import metaschema 1`] = ` │ "con", │ [ │ "$", - │ "/kind" + │ "/kind", + │ null │ ] │ ], │ 5, @@ -995,7 +1032,8 @@ exports[`can import metaschema 1`] = ` │ "arr", │ [ │ "$", - │ "/kind" + │ "/kind", + │ null │ ] │ ], │ 4, @@ -1006,7 +1044,8 @@ exports[`can import metaschema 1`] = ` │ "bin", │ [ │ "$", - │ "/kind" + │ "/kind", + │ null │ ] │ ], │ 3, @@ -1017,7 +1056,8 @@ exports[`can import metaschema 1`] = ` │ "str", │ [ │ "$", - │ "/kind" + │ "/kind", + │ null │ ] │ ], │ 2, @@ -1028,7 +1068,8 @@ exports[`can import metaschema 1`] = ` │ "num", │ [ │ "$", - │ "/kind" + │ "/kind", + │ null │ ] │ ], │ 1, diff --git a/src/type/__tests__/__snapshots__/toString.spec.ts.snap b/src/type/__tests__/__snapshots__/toString.spec.ts.snap index 75655bba..c37f7cf0 100644 --- a/src/type/__tests__/__snapshots__/toString.spec.ts.snap +++ b/src/type/__tests__/__snapshots__/toString.spec.ts.snap @@ -32,6 +32,7 @@ exports[`can print a type 1`] = ` │ │ null, │ │ [ │ │ "$", +│ │ "", │ │ "" │ │ ] │ │ ], @@ -65,7 +66,8 @@ exports[`can print a type 1`] = ` │ │ "c", │ │ [ │ │ "$", -│ │ "" +│ │ "", +│ │ null │ │ ] │ │ ], │ │ 2, @@ -76,7 +78,8 @@ exports[`can print a type 1`] = ` │ │ "b", │ │ [ │ │ "$", -│ │ "" +│ │ "", +│ │ null │ │ ] │ │ ], │ │ 1, From b679a9497715c374c01657819c7c93c9051bb49b Mon Sep 17 00:00:00 2001 From: streamich Date: Sun, 10 Aug 2025 14:29:57 +0200 Subject: [PATCH 18/18] =?UTF-8?q?style:=20=F0=9F=92=84=20fix=20linter=20is?= =?UTF-8?q?sues?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/__tests__/fixtures.ts | 42 ++++++++++-------- src/codegen/binary/AbstractBinaryCodegen.ts | 6 ++- .../binary/__tests__/testBinaryCodegen.ts | 13 +++--- src/codegen/binary/json/JsonCodegen.ts | 3 +- .../binary/json/__tests__/JsonCodegen.spec.ts | 26 +++++------ .../msgpack/__tests__/automated.spec.ts | 4 +- .../capacity/CapacityEstimatorCodegen.ts | 9 +++- .../CapacityEstimatorCodegenContext.spec.ts | 43 +++++++++---------- src/codegen/validator/ValidatorCodegen.ts | 2 +- src/schema/schema.ts | 12 +++++- src/type/__tests__/TypeBuilder.spec.ts | 7 +-- src/type/classes/OrType.ts | 5 ++- src/type/discriminator.ts | 3 +- 13 files changed, 98 insertions(+), 77 deletions(-) diff --git a/src/__tests__/fixtures.ts b/src/__tests__/fixtures.ts index 6ed5680e..6f4355ea 100644 --- a/src/__tests__/fixtures.ts +++ b/src/__tests__/fixtures.ts @@ -86,16 +86,22 @@ export const schemaCategories = { } as const; const primitivesModule = new ModuleType(); -export const primitiveTypes = Object.entries(primitiveSchemas).reduce((acc, [key, schema]) => { - acc[key] = primitivesModule.t.import(schema); - return acc; -}, {} as Record); +export const primitiveTypes = Object.entries(primitiveSchemas).reduce( + (acc, [key, schema]) => { + acc[key] = primitivesModule.t.import(schema); + return acc; + }, + {} as Record, +); const compositesModule = new ModuleType(); -export const compositeTypes = Object.entries(compositeSchemas).reduce((acc, [key, schema]) => { - acc[key] = compositesModule.t.import(schema); - return acc; -}, {} as Record); +export const compositeTypes = Object.entries(compositeSchemas).reduce( + (acc, [key, schema]) => { + acc[key] = compositesModule.t.import(schema); + return acc; + }, + {} as Record, +); /** * User profile schema with nested objects and optional fields @@ -201,15 +207,17 @@ export const Configuration = t.Object( /** * Event data schema with tuples and coordinates */ -export const Event = t.Object( - t.Key('id', t.String({format: 'ascii'})), - t.Key('type', t.enum('click', 'view', 'purchase', 'signup')), - t.Key('timestamp', t.Number({format: 'u64'})), - t.Key('userId', t.maybe(t.str)), - t.Key('location', t.Tuple([t.Number({format: 'f64'}), t.Number({format: 'f64'})])), - t.Key('metadata', t.Map(t.Or(t.str, t.num, t.bool))), - t.KeyOpt('sessionId', t.str), -).alias('Event').type; +export const Event = t + .Object( + t.Key('id', t.String({format: 'ascii'})), + t.Key('type', t.enum('click', 'view', 'purchase', 'signup')), + t.Key('timestamp', t.Number({format: 'u64'})), + t.Key('userId', t.maybe(t.str)), + t.Key('location', t.Tuple([t.Number({format: 'f64'}), t.Number({format: 'f64'})])), + t.Key('metadata', t.Map(t.Or(t.str, t.num, t.bool))), + t.KeyOpt('sessionId', t.str), + ) + .alias('Event').type; /** * Contact information schema with formatted strings diff --git a/src/codegen/binary/AbstractBinaryCodegen.ts b/src/codegen/binary/AbstractBinaryCodegen.ts index 6507b699..80d0777a 100644 --- a/src/codegen/binary/AbstractBinaryCodegen.ts +++ b/src/codegen/binary/AbstractBinaryCodegen.ts @@ -146,7 +146,8 @@ var uint8 = writer.uint8, view = writer.view;`, } protected onBool(path: SchemaPath, r: JsExpression, type: BoolType): void { - this.codegen.if(`${r.use()}`, + this.codegen.if( + `${r.use()}`, () => { this.blob( this.gen((encoder) => { @@ -160,7 +161,8 @@ var uint8 = writer.uint8, view = writer.view;`, encoder.writeBoolean(false); }), ); - }); + }, + ); } protected onNum(path: SchemaPath, r: JsExpression, type: NumType): void { diff --git a/src/codegen/binary/__tests__/testBinaryCodegen.ts b/src/codegen/binary/__tests__/testBinaryCodegen.ts index bc37a133..9b944c55 100644 --- a/src/codegen/binary/__tests__/testBinaryCodegen.ts +++ b/src/codegen/binary/__tests__/testBinaryCodegen.ts @@ -250,11 +250,10 @@ export const testBinaryCodegen = (transcode: (system: ModuleType, type: Type, va test('can encode complex named tuple', () => { const system = new ModuleType(); const t = system.t; - const type = system.t.Tuple( - [t.Key('header', t.str), t.Key('version', t.num)], - t.Object(t.Key('data', t.str)), - [t.Key('checksum', t.num), t.Key('timestamp', t.num)], - ); + const type = system.t.Tuple([t.Key('header', t.str), t.Key('version', t.num)], t.Object(t.Key('data', t.str)), [ + t.Key('checksum', t.num), + t.Key('timestamp', t.num), + ]); const value: any[] = ['v1', 2, {data: 'test1'}, {data: 'test2'}, 12345, 1234567890]; expect(transcode(system, type, value)).toStrictEqual(value); }); @@ -262,9 +261,7 @@ export const testBinaryCodegen = (transcode: (system: ModuleType, type: Type, va test('can encode nested named tuple', () => { const system = new ModuleType(); const t = system.t; - const type = system.t.Tuple([ - t.Key('metadata', t.Tuple([t.Key('type', t.str), t.Key('size', t.num)])), - ], t.str); + const type = system.t.Tuple([t.Key('metadata', t.Tuple([t.Key('type', t.str), t.Key('size', t.num)]))], t.str); const value: any[] = [['document', 1024], 'content1', 'content2']; expect(transcode(system, type, value)).toStrictEqual(value); }); diff --git a/src/codegen/binary/json/JsonCodegen.ts b/src/codegen/binary/json/JsonCodegen.ts index 37d228eb..5cbc782c 100644 --- a/src/codegen/binary/json/JsonCodegen.ts +++ b/src/codegen/binary/json/JsonCodegen.ts @@ -157,7 +157,8 @@ export class JsonCodegen extends AbstractBinaryCodegen { }; const emitEnding = () => { const rewriteLastSeparator = () => { - for (let i = 0; i < endBlob.length; i++) codegen.js(/* js */ `uint8[writer.x - ${endBlob.length - i}] = ${endBlob[i]};`); + for (let i = 0; i < endBlob.length; i++) + codegen.js(/* js */ `uint8[writer.x - ${endBlob.length - i}] = ${endBlob[i]};`); }; if (requiredFields.length) { rewriteLastSeparator(); diff --git a/src/codegen/binary/json/__tests__/JsonCodegen.spec.ts b/src/codegen/binary/json/__tests__/JsonCodegen.spec.ts index 69f74673..b6d5878e 100644 --- a/src/codegen/binary/json/__tests__/JsonCodegen.spec.ts +++ b/src/codegen/binary/json/__tests__/JsonCodegen.spec.ts @@ -34,8 +34,8 @@ test('fuzzer 1: map in map', () => { 'T`jb_LZ': null, 'C)crlQL': null, 'kw&p(^-': null, - 'oKkF,u8': null - } + 'oKkF,u8': null, + }, }; const value2 = { 'YS9mc}Zb': { @@ -43,17 +43,17 @@ test('fuzzer 1: map in map', () => { 'j9?_0': null, '@:ODe': null, 'sS{Sx': null, - '4EMz|': null + '4EMz|': null, }, "bF@64u'7": { 'q<_b%}$Q': null, - 'RäXpXBLü': null, + RäXpXBLü: null, '$uJx]{ft': null, 'bX%jLhr{': null, 'Lr1bY-fY': null, 'D]ml,C)W': null, 'eK=DszFO': null, - '!RqC^GUz': null + '!RqC^GUz': null, }, '9SEDa*#|': { ';COK{m%=': null, @@ -63,7 +63,7 @@ test('fuzzer 1: map in map', () => { 'k+$es!mO': null, 'O1(&D_bt': null, 'cidA#*BD': null, - '!ZP5JBFq': null + '!ZP5JBFq': null, }, ';6(7#5m:': {}, 'zhGX^&Y3': { @@ -71,27 +71,27 @@ test('fuzzer 1: map in map', () => { '%вqL=': null, '5?5{)': null, '*2"H4': null, - ')&_O4': null + ')&_O4': null, }, '?6a1a5Y\\': { '5,bCV': null, 'z[x2s': null, 'Ad/g9': null, 'at#84': null, - '{@?".': null + '{@?".': null, }, - 'uaaAwаHb': { VXy: null, 'I(<': null, 'W V': null }, + uaaAwаHb: {VXy: null, 'I(<': null, 'W V': null}, '&sH?Bk2E': { 'M[^ex': null, '-ZP$E': null, 'c*@uR': null, '`sy3N': null, - 'g?DB ': null - } + 'g?DB ': null, + }, }; const value3 = { - '/7': { '|;L': null, '@K<': null, '*x:': null }, - Zf: { N1: null, 't%': null } + '/7': {'|;L': null, '@K<': null, '*x:': null}, + Zf: {N1: null, 't%': null}, }; for (let i = 0; i < 100; i++) { const decoded = transcode(system, type, value); diff --git a/src/codegen/binary/msgpack/__tests__/automated.spec.ts b/src/codegen/binary/msgpack/__tests__/automated.spec.ts index 72809140..2facff92 100644 --- a/src/codegen/binary/msgpack/__tests__/automated.spec.ts +++ b/src/codegen/binary/msgpack/__tests__/automated.spec.ts @@ -4,8 +4,8 @@ import {MsgPackCodegen} from '../MsgPackCodegen'; import {Random} from '../../../../random'; import {allSerializableTypes} from '../../../../__tests__/fixtures'; - const encoder = new MsgPackEncoder(); - const decoder = new MsgPackDecoder(); +const encoder = new MsgPackEncoder(); +const decoder = new MsgPackDecoder(); for (const [name, type] of Object.entries(allSerializableTypes)) { test(`can encode and decode ${name}`, () => { diff --git a/src/codegen/capacity/CapacityEstimatorCodegen.ts b/src/codegen/capacity/CapacityEstimatorCodegen.ts index 5580d3c2..7ba653be 100644 --- a/src/codegen/capacity/CapacityEstimatorCodegen.ts +++ b/src/codegen/capacity/CapacityEstimatorCodegen.ts @@ -111,12 +111,17 @@ export class CapacityEstimatorCodegen extends AbstractCodegen 0) { const rr = codegen.var(r.use()); - for (let i = 0; i < headLength; i++) this.onNode([...path, i], new JsExpression(() => /* js */ `${rr}[${i}]`), _head![i]); + for (let i = 0; i < headLength; i++) + this.onNode([...path, i], new JsExpression(() => /* js */ `${rr}[${i}]`), _head![i]); } if (tailLength > 0) { const rr = codegen.var(r.use()); for (let i = 0; i < tailLength; i++) - this.onNode([...path, {r: `${rLen} - ${(tailLength - i)}`}], new JsExpression(() => /* js */ `${rr}[${rLen} - ${(tailLength - i)}]`), _tail![i]); + this.onNode( + [...path, {r: `${rLen} - ${tailLength - i}`}], + new JsExpression(() => /* js */ `${rr}[${rLen} - ${tailLength - i}]`), + _tail![i], + ); } } diff --git a/src/codegen/capacity/__tests__/CapacityEstimatorCodegenContext.spec.ts b/src/codegen/capacity/__tests__/CapacityEstimatorCodegenContext.spec.ts index 460911a0..6c623376 100644 --- a/src/codegen/capacity/__tests__/CapacityEstimatorCodegenContext.spec.ts +++ b/src/codegen/capacity/__tests__/CapacityEstimatorCodegenContext.spec.ts @@ -163,18 +163,20 @@ describe('"arr" type', () => { const system = new ModuleType(); const type = system.t.Tuple([t.Const('start')], t.str).tail(t.Const('end')); const estimator = CapacityEstimatorCodegen.get(type); - expect(estimator(['start', 'middle1', 'middle2', 'end'])).toBe(maxEncodingCapacity(['start', 'middle1', 'middle2', 'end'])); + expect(estimator(['start', 'middle1', 'middle2', 'end'])).toBe( + maxEncodingCapacity(['start', 'middle1', 'middle2', 'end']), + ); }); test('complex named tail tuple', () => { const system = new ModuleType(); - const type = system.t.Array(t.num).tail( - t.Key('status', t.str), - t.Key('timestamp', t.num), - t.Key('metadata', t.bool) - ); + const type = system.t + .Array(t.num) + .tail(t.Key('status', t.str), t.Key('timestamp', t.num), t.Key('metadata', t.bool)); const estimator = CapacityEstimatorCodegen.get(type); - expect(estimator([1, 2, 3, 'success', 1234567890, true])).toBe(maxEncodingCapacity([1, 2, 3, 'success', 1234567890, true])); + expect(estimator([1, 2, 3, 'success', 1234567890, true])).toBe( + maxEncodingCapacity([1, 2, 3, 'success', 1234567890, true]), + ); }); test('empty array with head/tail definition', () => { @@ -186,35 +188,32 @@ describe('"arr" type', () => { test('head tuple with different types', () => { const system = new ModuleType(); - const type = system.t.Tuple([ - t.Key('id', t.num), - t.Key('name', t.str), - t.Key('active', t.bool) - ], t.str); + const type = system.t.Tuple([t.Key('id', t.num), t.Key('name', t.str), t.Key('active', t.bool)], t.str); const estimator = CapacityEstimatorCodegen.get(type); - expect(estimator([42, 'test', true, 'extra1', 'extra2'])).toBe(maxEncodingCapacity([42, 'test', true, 'extra1', 'extra2'])); + expect(estimator([42, 'test', true, 'extra1', 'extra2'])).toBe( + maxEncodingCapacity([42, 'test', true, 'extra1', 'extra2']), + ); }); test('tail tuple with different types', () => { const system = new ModuleType(); - const type = system.t.Array(t.str).tail( - t.Key('count', t.num), - t.Key('valid', t.bool) - ); + const type = system.t.Array(t.str).tail(t.Key('count', t.num), t.Key('valid', t.bool)); const estimator = CapacityEstimatorCodegen.get(type); - expect(estimator(['item1', 'item2', 'item3', 5, true])).toBe(maxEncodingCapacity(['item1', 'item2', 'item3', 5, true])); + expect(estimator(['item1', 'item2', 'item3', 5, true])).toBe( + maxEncodingCapacity(['item1', 'item2', 'item3', 5, true]), + ); }); test('nested objects in named tuples', () => { const system = new ModuleType(); - const type = system.t.Array(t.Object(t.Key('value', t.num))).tail( - t.Key('summary', t.Object(t.Key('total', t.num), t.Key('average', t.num))) - ); + const type = system.t + .Array(t.Object(t.Key('value', t.num))) + .tail(t.Key('summary', t.Object(t.Key('total', t.num), t.Key('average', t.num)))); const estimator = CapacityEstimatorCodegen.get(type); const data = [ {value: 10}, {value: 20}, - {total: 30, average: 15} // summary + {total: 30, average: 15}, // summary ]; expect(estimator(data)).toBe(maxEncodingCapacity(data)); }); diff --git a/src/codegen/validator/ValidatorCodegen.ts b/src/codegen/validator/ValidatorCodegen.ts index 298ffebc..40d576f5 100644 --- a/src/codegen/validator/ValidatorCodegen.ts +++ b/src/codegen/validator/ValidatorCodegen.ts @@ -17,7 +17,7 @@ import { type RefType, type StrType, type Type, - KeyType, + type KeyType, } from '../../type'; import {floats, ints, uints} from '../../util'; import {isAscii, isUtf8} from '../../util/stringFormats'; diff --git a/src/schema/schema.ts b/src/schema/schema.ts index 7ae42a7c..950d6330 100644 --- a/src/schema/schema.ts +++ b/src/schema/schema.ts @@ -453,7 +453,17 @@ export type JsonSchema = | OptKeySchema | MapSchema; -export type Schema = JsonSchema | RefSchema | OrSchema | AnySchema | FnSchema | FnRxSchema | AliasSchema | ModuleSchema | KeySchema | OptKeySchema; +export type Schema = + | JsonSchema + | RefSchema + | OrSchema + | AnySchema + | FnSchema + | FnRxSchema + | AliasSchema + | ModuleSchema + | KeySchema + | OptKeySchema; export type NoT = Omit; diff --git a/src/type/__tests__/TypeBuilder.spec.ts b/src/type/__tests__/TypeBuilder.spec.ts index 7439b51a..3ad2a7ed 100644 --- a/src/type/__tests__/TypeBuilder.spec.ts +++ b/src/type/__tests__/TypeBuilder.spec.ts @@ -55,12 +55,7 @@ test('array of any with options', () => { }); test('can construct a realistic object', () => { - const type = t.Object( - t.Key('id', t.str), - t.KeyOpt('name', t.str), - t.KeyOpt('age', t.num), - t.Key('verified', t.bool), - ); + const type = t.Object(t.Key('id', t.str), t.KeyOpt('name', t.str), t.KeyOpt('age', t.num), t.Key('verified', t.bool)); expect(type.getSchema()).toStrictEqual({ kind: 'obj', keys: [ diff --git a/src/type/classes/OrType.ts b/src/type/classes/OrType.ts index dd374f77..c1f22b03 100644 --- a/src/type/classes/OrType.ts +++ b/src/type/classes/OrType.ts @@ -32,7 +32,10 @@ export class OrType extends AbsTypediscriminator === -1) || (discriminator.length === 2 && discriminator[0] === 'num' && discriminator[1] === -1)) { + if ( + discriminator === -1 || + (discriminator.length === 2 && discriminator[0] === 'num' && discriminator[1] === -1) + ) { this.schema.discriminator = Discriminator.createExpression(this.types); } } diff --git a/src/type/discriminator.ts b/src/type/discriminator.ts index e561070f..7e0aa55f 100644 --- a/src/type/discriminator.ts +++ b/src/type/discriminator.ts @@ -111,7 +111,8 @@ export class Discriminator { ) {} condition(): Expr { - if (this.type instanceof ConType) return ['==', this.type.literal(), ['$', this.path, this.type.literal() === null ? '' : null]]; + if (this.type instanceof ConType) + return ['==', this.type.literal(), ['$', this.path, this.type.literal() === null ? '' : null]]; if (this.type instanceof BoolType) return ['==', ['type', ['$', this.path]], 'boolean']; if (this.type instanceof NumType) return ['==', ['type', ['$', this.path]], 'number']; if (this.type instanceof StrType) return ['==', ['type', ['$', this.path]], 'string'];