From 52fdd9f20e013d449150fb52e2d18e706bfc19d8 Mon Sep 17 00:00:00 2001 From: streamich Date: Mon, 28 Jul 2025 09:28:23 +0200 Subject: [PATCH 1/3] =?UTF-8?q?chore:=20=F0=9F=A4=96=20remove=20type=20rou?= =?UTF-8?q?ter=20class?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/random/generators.ts | 1 - src/system/Method.ts | 21 ---- src/system/TypeRouter.ts | 111 ---------------------- src/system/__tests__/TypeSystem.spec.ts | 17 ++++ src/system/__tests__/toTypeScript.spec.ts | 47 --------- src/type/classes/ArrayType.ts | 1 - src/value/ObjectValue.ts | 14 --- 7 files changed, 17 insertions(+), 195 deletions(-) delete mode 100644 src/system/Method.ts delete mode 100644 src/system/TypeRouter.ts diff --git a/src/random/generators.ts b/src/random/generators.ts index f6b7f824..b3962b0d 100644 --- a/src/random/generators.ts +++ b/src/random/generators.ts @@ -1,6 +1,5 @@ import {RandomJson} from '@jsonjoy.com/util/lib/json-random'; import {cloneBinary} from '@jsonjoy.com/util/lib/json-clone'; -import type {AbstractType} from '../type/classes/AbstractType'; import type {AnyType} from '../type/classes/AnyType'; import type {ArrayType} from '../type/classes/ArrayType'; import type {BinaryType} from '../type/classes/BinaryType'; diff --git a/src/system/Method.ts b/src/system/Method.ts deleted file mode 100644 index 7cf538a5..00000000 --- a/src/system/Method.ts +++ /dev/null @@ -1,21 +0,0 @@ -import type {TypeOf} from '../schema'; -import type {SchemaOf} from '../type'; -import type {TypeAlias, TypeOfAlias, TypeSystem} from '.'; - -export class Method< - ID extends string, - Req extends TypeAlias, - Res extends TypeAlias, - Ctx = unknown, -> { - constructor( - public readonly system: TypeSystem, - public readonly id: ID, - public readonly req: Req, - public readonly res: Res, - public readonly fn: ( - req: TypeOf>>, - ctx: Ctx, - ) => Promise>>>, - ) {} -} diff --git a/src/system/TypeRouter.ts b/src/system/TypeRouter.ts deleted file mode 100644 index a15bdc69..00000000 --- a/src/system/TypeRouter.ts +++ /dev/null @@ -1,111 +0,0 @@ -import type * as classes from '../type/classes'; -import {TypeSystem} from './TypeSystem'; -import {toText} from '../typescript/toText'; -import type * as ts from '../typescript/types'; -import type {TypeBuilder} from '../type/TypeBuilder'; - -export interface TypeRouterOptions { - system: TypeSystem; - routes: R; -} - -export class TypeRouter { - public static create = ( - routes?: (t: TypeRouter) => NewRoutes, - ): TypeRouter => { - const system = new TypeSystem(); - const router = new TypeRouter({ - system, - routes: {}, - }); - return routes ? router.extend(routes) : (router as any); - }; - - public system: TypeSystem; - public t: TypeBuilder; - public routes: Routes; - - constructor(options: TypeRouterOptions) { - this.system = options.system; - this.t = this.system.t; - this.routes = options.routes; - // this.system.importTypes(this.routes); - } - - protected merge>(router: Router): TypeRouter> { - return new TypeRouter({ - system: this.system, - routes: { - ...this.routes, - ...router.routes, - }, - }); - } - - public extend(routes: (t: this) => NewRoutes): TypeRouter { - const router = new TypeRouter({ - system: this.system, - routes: routes(this), - }); - return this.merge(router); - } - - public fn>( - name: K, - type: R, - ): TypeRouter { - this.routes[name] = type; - return this; - } - - public fn$>( - name: K, - type: R, - ): TypeRouter { - this.routes[name] = type; - return this; - } - - public toTypeScriptAst(): ts.TsTypeLiteral { - const node: ts.TsTypeLiteral = { - node: 'TypeLiteral', - members: [], - }; - for (const [name, type] of Object.entries(this.routes)) { - const schema = type.getSchema(); - const property: ts.TsPropertySignature = { - node: 'PropertySignature', - name, - type: type.toTypeScriptAst(), - }; - if (schema.title) property.comment = schema.title; - node.members.push(property); - } - return node; - } - - public toTypeScriptModuleAst(): ts.TsModuleDeclaration { - const node: ts.TsModuleDeclaration = { - node: 'ModuleDeclaration', - name: 'Router', - export: true, - statements: [ - { - node: 'TypeAliasDeclaration', - name: 'Routes', - type: this.toTypeScriptAst(), - export: true, - }, - ], - }; - for (const alias of this.system.aliases.values()) node.statements.push({...alias.toTypeScriptAst(), export: true}); - return node; - } - - public toTypeScript(): string { - return toText(this.toTypeScriptModuleAst()); - } -} - -export type RoutesBase = Record | classes.FunctionStreamingType>; -type TypeRouterRoutes> = R extends TypeRouter ? R2 : never; diff --git a/src/system/__tests__/TypeSystem.spec.ts b/src/system/__tests__/TypeSystem.spec.ts index 032ed57d..9ccb6038 100644 --- a/src/system/__tests__/TypeSystem.spec.ts +++ b/src/system/__tests__/TypeSystem.spec.ts @@ -42,6 +42,23 @@ describe('.toString()', () => { │ └─ validators └─ "empty-string"" +`); + }); + + test('prints type with nested self reference', () => { + const system = new TypeSystem(); + const {t} = system; + system.alias('User', t.obj.prop('id', t.str).opt('friend', t.Ref('User'))); + expect(system.toString()).toMatchInlineSnapshot(` +"TypeSystem +├─ aliases +│ └─ User +│ └─ obj +│ ├─ "id": +│ │ └─ str +│ └─ "friend"?: +│ └─ ref → [User] +└─ validators" `); }); }); diff --git a/src/system/__tests__/toTypeScript.spec.ts b/src/system/__tests__/toTypeScript.spec.ts index 1b443730..7b8cd8dd 100644 --- a/src/system/__tests__/toTypeScript.spec.ts +++ b/src/system/__tests__/toTypeScript.spec.ts @@ -1,5 +1,4 @@ import {TypeSystem} from '..'; -import {TypeRouter} from '../TypeRouter'; test('generates TypeScript source for simple string type', () => { const system = new TypeSystem(); @@ -92,49 +91,3 @@ test('type interface inside a tuple', () => { " `); }); - -test('can export whole router', () => { - const system = new TypeSystem(); - const {t} = system; - const router = new TypeRouter({system, routes: {}}).extend(() => ({ - callMe: t.Function(t.str, t.num), - 'block.subscribe': t.Function$(t.Object(t.prop('id', t.str)), t.obj), - })); - expect(router.toTypeScript()).toMatchInlineSnapshot(` - "export namespace Router { - export type Routes = { - callMe: (request: string) => Promise; - "block.subscribe": (request$: Observable<{ - id: string; - }>) => Observable<{}>; - }; - } - " - `); -}); - -test('can export whole router and aliases', () => { - const system = new TypeSystem(); - const {t} = system; - system.alias('Document', t.Object(t.prop('id', t.str), t.prop('title', t.str)).options({title: 'The document'})); - const router = new TypeRouter({system, routes: {}}).extend(() => ({ - callMe: t.Function(t.str, t.num), - 'block.subscribe': t.Function$(t.Object(t.prop('id', t.str)), t.Ref('Document')), - })); - expect(router.toTypeScript()).toMatchInlineSnapshot(` - "export namespace Router { - export type Routes = { - callMe: (request: string) => Promise; - "block.subscribe": (request$: Observable<{ - id: string; - }>) => Observable; - }; - - export interface Document { - id: string; - title: string; - } - } - " - `); -}); diff --git a/src/type/classes/ArrayType.ts b/src/type/classes/ArrayType.ts index 75eddc75..475a00df 100644 --- a/src/type/classes/ArrayType.ts +++ b/src/type/classes/ArrayType.ts @@ -14,7 +14,6 @@ import type {BinaryEncoderCodegenContext} from '../../codegen/binary/BinaryEncod import type {SchemaOf, Type} from '../types'; import type {TypeSystem} from '../../system/TypeSystem'; import type {json_string} from '@jsonjoy.com/util/lib/json-brand'; -import type * as ts from '../../typescript/types'; import type {TypeExportContext} from '../../system/TypeExportContext'; export class ArrayType extends AbstractType>> { diff --git a/src/value/ObjectValue.ts b/src/value/ObjectValue.ts index ebc3b1ee..ec61e158 100644 --- a/src/value/ObjectValue.ts +++ b/src/value/ObjectValue.ts @@ -18,20 +18,6 @@ export type ObjectValueToTypeMap = ToObject<{ }>; export type TuplesToFields = T extends PropDefinition[] ? classes.ObjectFieldType[] : never; -// export type MergeObjectsTypes = -// A extends classes.ObjectType -// ? B extends classes.ObjectType -// ? classes.ObjectType<[...A2, ...B2]> : -// never : -// never; - -// export type MergeObjectValues = -// A extends ObjectValue -// ? B extends ObjectValue -// ? ObjectValue> : -// never : -// never; - type PropDefinition = [key: K, val: V, data: ResolveType]; type PropDef = (key: K, val: V, data: ResolveType) => PropDefinition; From 214c38448cccb2dc5e3b6edc56f40e05c4b59f22 Mon Sep 17 00:00:00 2001 From: streamich Date: Mon, 28 Jul 2025 10:15:09 +0200 Subject: [PATCH 2/3] =?UTF-8?q?test:=20=F0=9F=92=8D=20add=20validation=20t?= =?UTF-8?q?est?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/constants.ts | 5 +++++ src/type/__tests__/validateTestSuite.ts | 17 +++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/src/constants.ts b/src/constants.ts index ada402a0..084e98e0 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -1,3 +1,8 @@ +/** + * @module + * @todo Move to `src/validator/`. + */ + /** * Validation error codes. * diff --git a/src/type/__tests__/validateTestSuite.ts b/src/type/__tests__/validateTestSuite.ts index c739c36c..29b66c9a 100644 --- a/src/type/__tests__/validateTestSuite.ts +++ b/src/type/__tests__/validateTestSuite.ts @@ -1,4 +1,5 @@ import {type Type, t} from '..'; +import {ValidationError, ValidationErrorMessage} from '../../constants'; import {TypeSystem} from '../../system/TypeSystem'; export const validateTestSuite = (validate: (type: Type, value: unknown) => void) => { @@ -384,6 +385,22 @@ export const validateTestSuite = (validate: (type: Type, value: unknown) => void expect(() => validate(type, [1])).toThrowErrorMatchingInlineSnapshot(`"STR"`); }); + test('object nested in array', () => { + const type = t.obj + .prop('foo', t.array(t.obj.prop('bar', t.str))) + validate(type, {foo: [{bar: 'baz'}]}); + const validator = type.validator('object'); + const res = validator({foo: [{bar: 'baz'}]}); + expect(res).toBe(null); + const res2 = validator({foo: {bar: 'baz'}}); + expect(res2).toEqual({ + code: ValidationError[ValidationError.ARR], + errno: ValidationError.ARR, + message: ValidationErrorMessage[ValidationError.ARR], + path: [ 'foo' ] + }); + }); + describe('size bounds', () => { test('respects min and max', () => { const type = t.arr.options({min: 2, max: 4}); From dc93c9263847ae972f3454e928a7e8fee9b38311 Mon Sep 17 00:00:00 2001 From: streamich Date: Mon, 28 Jul 2025 10:15:27 +0200 Subject: [PATCH 3/3] =?UTF-8?q?style:=20=F0=9F=92=84=20run=20formatter?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/type/__tests__/validateTestSuite.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/type/__tests__/validateTestSuite.ts b/src/type/__tests__/validateTestSuite.ts index 29b66c9a..d10f59d1 100644 --- a/src/type/__tests__/validateTestSuite.ts +++ b/src/type/__tests__/validateTestSuite.ts @@ -386,8 +386,7 @@ export const validateTestSuite = (validate: (type: Type, value: unknown) => void }); test('object nested in array', () => { - const type = t.obj - .prop('foo', t.array(t.obj.prop('bar', t.str))) + const type = t.obj.prop('foo', t.array(t.obj.prop('bar', t.str))); validate(type, {foo: [{bar: 'baz'}]}); const validator = type.validator('object'); const res = validator({foo: [{bar: 'baz'}]}); @@ -397,7 +396,7 @@ export const validateTestSuite = (validate: (type: Type, value: unknown) => void code: ValidationError[ValidationError.ARR], errno: ValidationError.ARR, message: ValidationErrorMessage[ValidationError.ARR], - path: [ 'foo' ] + path: ['foo'], }); });