From 0b6c77fff0aa43eb7e54bdf1fc71b5ab196b5d63 Mon Sep 17 00:00:00 2001 From: streamich Date: Thu, 4 Sep 2025 14:03:32 +0200 Subject: [PATCH 1/5] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20improve=20ObjValue?= =?UTF-8?q?=20printing?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/type/classes/FnType.ts | 11 ++++++----- src/value/Value.ts | 20 +++++++++++++++++++- 2 files changed, 25 insertions(+), 6 deletions(-) diff --git a/src/type/classes/FnType.ts b/src/type/classes/FnType.ts index 35e314e..8998783 100644 --- a/src/type/classes/FnType.ts +++ b/src/type/classes/FnType.ts @@ -1,11 +1,7 @@ import {printTree} from 'tree-dump/lib/printTree'; import * as schema from '../../schema'; -import type {SchemaOf, Type} from '../types'; import {AbsType} from './AbsType'; - -const fnNotImplemented: schema.FunctionValue = async () => { - throw new Error('NOT_IMPLEMENTED'); -}; +import type {SchemaOf, Type} from '../types'; const toStringTree = (tab: string = '', type: FnType | FnRxType) => { return printTree(tab, [ @@ -71,6 +67,11 @@ export class FnType extends A return this; } + public exec(input: schema.TypeOf>) { + const func = this.schema.default as schema.FunctionValue>, schema.TypeOf>>; + return func(input); + } + public toString(tab: string = ''): string { return super.toString(tab) + toStringTree(tab, this); } diff --git a/src/value/Value.ts b/src/value/Value.ts index 378740c..0efc38c 100644 --- a/src/value/Value.ts +++ b/src/value/Value.ts @@ -2,15 +2,33 @@ import {printTree} from 'tree-dump/lib/printTree'; import type {Printable} from 'tree-dump'; import type {ResolveType, Type} from '../type/types'; +const copyForPrint = (data: unknown): unknown => { + if (typeof data === 'function') return '__fN---'; + if (Array.isArray(data)) return data.map(copyForPrint); + if (data && typeof data === 'object') { + const res: Record = {}; + for (const k in data) res[k] = copyForPrint((data as any)[k]); + return res; + } + return data; +}; + export class Value implements Printable { constructor( public data: ResolveType, public type?: T, ) {} + public name(): string { + return 'Value'; + } + public toString(tab: string = ''): string { const type = this.type; - return 'Value' + (type ? printTree(tab, [(tab) => type.toString(tab)]) : ''); + return this.name() + (type ? printTree(tab, [ + (tab) => type.toString(tab), + (tab) => (JSON.stringify(copyForPrint(this.data), null, 2) || 'und').replace(/"__fN---"/g, 'fn()').split('\n').join('\n' + tab), + ]) : ''); } } From d6a0e0e190f449825ebb1c356cad2003e392d8b4 Mon Sep 17 00:00:00 2001 From: streamich Date: Thu, 4 Sep 2025 14:03:49 +0200 Subject: [PATCH 2/5] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20introduce=20FnValue?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/value/FnValue.ts | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 src/value/FnValue.ts diff --git a/src/value/FnValue.ts b/src/value/FnValue.ts new file mode 100644 index 0000000..75b7163 --- /dev/null +++ b/src/value/FnValue.ts @@ -0,0 +1,15 @@ +import {Value} from './Value'; +import type {Printable} from 'tree-dump/lib/types'; +import type * as classes from '../type'; + +export class FnValue> extends Value implements Printable { + public async exec(input: classes.ResolveType, ctx?: unknown): Promise> { + const fn = this.data as any; + const output = await fn(input, ctx); + return new Value(output, this.type!.res); + } + + public name(): string { + return 'FnValue'; + } +} From 18fa826c8589e7e5200a2e22e1e17c2ce2d1c9e9 Mon Sep 17 00:00:00 2001 From: streamich Date: Thu, 4 Sep 2025 14:22:51 +0200 Subject: [PATCH 3/5] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20improve=20ObjValue?= =?UTF-8?q?=20as=20a=20router=20API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 2 +- src/value/ObjValue.ts | 21 ++++++++--- src/value/Value.ts | 3 +- src/value/__tests__/ObjValue-router.spec.ts | 12 +++++++ src/value/__tests__/ObjValue.fixtures.ts | 39 +++++++++++++++++++++ yarn.lock | 13 +++++-- 6 files changed, 81 insertions(+), 9 deletions(-) create mode 100644 src/value/__tests__/ObjValue-router.spec.ts create mode 100644 src/value/__tests__/ObjValue.fixtures.ts diff --git a/package.json b/package.json index 6d0c2cf..ae5190e 100644 --- a/package.json +++ b/package.json @@ -64,7 +64,7 @@ "@jsonjoy.com/util": "^1.9.0", "sonic-forest": "^1.2.1", "thingies": "^2.5.0", - "tree-dump": "^1.0.3" + "tree-dump": "^1.1.0" }, "devDependencies": { "@biomejs/biome": "^1.9.3", diff --git a/src/value/ObjValue.ts b/src/value/ObjValue.ts index 4780773..d1397c0 100644 --- a/src/value/ObjValue.ts +++ b/src/value/ObjValue.ts @@ -1,9 +1,9 @@ -import {printTree} from 'tree-dump/lib/printTree'; +import {ModuleType} from '../type/classes/ModuleType'; +import {Value} from './Value'; +import {FnValue} from './FnValue'; import type {Printable} from 'tree-dump/lib/types'; import type * as classes from '../type'; import type {TypeBuilder} from '../type/TypeBuilder'; -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; @@ -14,6 +14,8 @@ export type ObjValueToTypeMap = ToObject<{ [K in keyof F]: ObjFieldToTuple; }>; +export type Ensure = T extends X ? T : X; + export class ObjValue> extends Value implements Printable { public static new = (system: ModuleType = new ModuleType()) => new ObjValue({}, system.t.obj); @@ -41,6 +43,15 @@ export class ObjValue> extends Value implement return new Value(data, field.val) as any; } + public fn>>( + key: K, + ): FnValue< + Ensure>[K] extends classes.Type ? ObjValueToTypeMap>[K] : classes.Type, classes.FnType> + > { + const val = this.get(key); + return new FnValue(val.data, val.type as any); + } + public field>( field: F | ((t: TypeBuilder) => F), data: classes.ResolveType>, @@ -78,7 +89,7 @@ export class ObjValue> extends Value implement return this as any; } - public toString(tab: string = ''): string { - return 'ObjValue' + printTree(tab, [(tab) => this.type!.toString(tab)]); + public name(): string { + return 'ObjValue'; } } diff --git a/src/value/Value.ts b/src/value/Value.ts index 0efc38c..b8757c6 100644 --- a/src/value/Value.ts +++ b/src/value/Value.ts @@ -1,4 +1,5 @@ import {printTree} from 'tree-dump/lib/printTree'; +import {printJson} from 'tree-dump/lib/printJson'; import type {Printable} from 'tree-dump'; import type {ResolveType, Type} from '../type/types'; @@ -27,7 +28,7 @@ export class Value implements Printable { const type = this.type; return this.name() + (type ? printTree(tab, [ (tab) => type.toString(tab), - (tab) => (JSON.stringify(copyForPrint(this.data), null, 2) || 'und').replace(/"__fN---"/g, 'fn()').split('\n').join('\n' + tab), + (tab) => printJson(tab, copyForPrint(this.data)).replace(/"__fN---"/g, 'fn()'), ]) : ''); } } diff --git a/src/value/__tests__/ObjValue-router.spec.ts b/src/value/__tests__/ObjValue-router.spec.ts new file mode 100644 index 0000000..41cbe9a --- /dev/null +++ b/src/value/__tests__/ObjValue-router.spec.ts @@ -0,0 +1,12 @@ +import {createRouter} from './ObjValue.fixtures'; + +test('can retrieve field as Value', async () => { + const log = jest.fn(); + const router = createRouter({log}); + console.log(router + ''); + console.log(router.fn('log.message') + ''); + console.log(router.fn('log.message').data); + console.log(router.fn('log.message').type + ''); + const result = await router.fn('log.message').exec({message: 'asdf'}); + expect(result.data).toEqual({time: expect.any(Number)}); +}); diff --git a/src/value/__tests__/ObjValue.fixtures.ts b/src/value/__tests__/ObjValue.fixtures.ts new file mode 100644 index 0000000..c981f8b --- /dev/null +++ b/src/value/__tests__/ObjValue.fixtures.ts @@ -0,0 +1,39 @@ +import {ObjType} from "../../type"; +import {TypeBuilder} from "../../type/TypeBuilder"; +import {ObjValue} from "../ObjValue"; + +interface Services { + log: (msg: string) => void; +} + +export interface RouteDeps { + svc: Services; + t: TypeBuilder; +} +export type RouterBase = ObjType; +export type Router = ObjValue; + +const addLogMessageRoute = ({t, svc}: RouteDeps) => + (r: Router) => { + return r.set('log.message', t.fn + .inp(t.Object( + t.Key('message', t.str), + )) + .out(t.object({ + time: t.num, + })) + .value(({message}) => { + svc.log(message); + return {time: Date.now()}; + }) + ) +}; + +export const createRouter = (svc: Services) => { + const base = ObjValue.new(); + const t = base.system.t; + const deps: RouteDeps = {svc, t}; + const router = addLogMessageRoute(deps)(base); + return router; +}; + diff --git a/yarn.lock b/yarn.lock index 4c64669..5327e8e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -889,7 +889,7 @@ __metadata: rxjs: "npm:^7.8.2" sonic-forest: "npm:^1.2.1" thingies: "npm:^2.5.0" - tree-dump: "npm:^1.0.3" + tree-dump: "npm:^1.1.0" ts-jest: "npm:^29.1.2" tslib: "npm:^2.7.0" typescript: "npm:^5.6.2" @@ -3530,7 +3530,7 @@ __metadata: languageName: node linkType: hard -"tree-dump@npm:^1.0.0, tree-dump@npm:^1.0.3": +"tree-dump@npm:^1.0.0": version: 1.0.3 resolution: "tree-dump@npm:1.0.3" peerDependencies: @@ -3539,6 +3539,15 @@ __metadata: languageName: node linkType: hard +"tree-dump@npm:^1.1.0": + version: 1.1.0 + resolution: "tree-dump@npm:1.1.0" + peerDependencies: + tslib: 2 + checksum: 10c0/079f0f0163b68ee2eedc65cab1de6fb121487eba9ae135c106a8bc5e4ab7906ae0b57d86016e4a7da8c0ee906da1eae8c6a1490cd6e2a5e5ccbca321e1f959ca + languageName: node + linkType: hard + "ts-jest@npm:^29.1.2": version: 29.4.1 resolution: "ts-jest@npm:29.4.1" From 51c4029f9fa683b1a60550361991ae90cfab1d41 Mon Sep 17 00:00:00 2001 From: streamich Date: Thu, 4 Sep 2025 14:23:30 +0200 Subject: [PATCH 4/5] =?UTF-8?q?style:=20=F0=9F=92=84=20fix=20linter=20issu?= =?UTF-8?q?es?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/type/classes/FnType.ts | 5 ++- src/value/ObjValue.ts | 5 ++- src/value/Value.ts | 13 ++++--- src/value/__tests__/ObjValue-router.spec.ts | 4 --- src/value/__tests__/ObjValue.fixtures.ts | 38 +++++++++++---------- 5 files changed, 37 insertions(+), 28 deletions(-) diff --git a/src/type/classes/FnType.ts b/src/type/classes/FnType.ts index 8998783..a8811aa 100644 --- a/src/type/classes/FnType.ts +++ b/src/type/classes/FnType.ts @@ -68,7 +68,10 @@ export class FnType extends A } public exec(input: schema.TypeOf>) { - const func = this.schema.default as schema.FunctionValue>, schema.TypeOf>>; + const func = this.schema.default as schema.FunctionValue< + schema.TypeOf>, + schema.TypeOf> + >; return func(input); } diff --git a/src/value/ObjValue.ts b/src/value/ObjValue.ts index d1397c0..5ad7245 100644 --- a/src/value/ObjValue.ts +++ b/src/value/ObjValue.ts @@ -46,7 +46,10 @@ export class ObjValue> extends Value implement public fn>>( key: K, ): FnValue< - Ensure>[K] extends classes.Type ? ObjValueToTypeMap>[K] : classes.Type, classes.FnType> + Ensure< + ObjValueToTypeMap>[K] extends classes.Type ? ObjValueToTypeMap>[K] : classes.Type, + classes.FnType + > > { const val = this.get(key); return new FnValue(val.data, val.type as any); diff --git a/src/value/Value.ts b/src/value/Value.ts index b8757c6..1cbc2e3 100644 --- a/src/value/Value.ts +++ b/src/value/Value.ts @@ -26,10 +26,15 @@ export class Value implements Printable { public toString(tab: string = ''): string { const type = this.type; - return this.name() + (type ? printTree(tab, [ - (tab) => type.toString(tab), - (tab) => printJson(tab, copyForPrint(this.data)).replace(/"__fN---"/g, 'fn()'), - ]) : ''); + return ( + this.name() + + (type + ? printTree(tab, [ + (tab) => type.toString(tab), + (tab) => printJson(tab, copyForPrint(this.data)).replace(/"__fN---"/g, 'fn()'), + ]) + : '') + ); } } diff --git a/src/value/__tests__/ObjValue-router.spec.ts b/src/value/__tests__/ObjValue-router.spec.ts index 41cbe9a..180d87d 100644 --- a/src/value/__tests__/ObjValue-router.spec.ts +++ b/src/value/__tests__/ObjValue-router.spec.ts @@ -3,10 +3,6 @@ import {createRouter} from './ObjValue.fixtures'; test('can retrieve field as Value', async () => { const log = jest.fn(); const router = createRouter({log}); - console.log(router + ''); - console.log(router.fn('log.message') + ''); - console.log(router.fn('log.message').data); - console.log(router.fn('log.message').type + ''); const result = await router.fn('log.message').exec({message: 'asdf'}); expect(result.data).toEqual({time: expect.any(Number)}); }); diff --git a/src/value/__tests__/ObjValue.fixtures.ts b/src/value/__tests__/ObjValue.fixtures.ts index c981f8b..970ba90 100644 --- a/src/value/__tests__/ObjValue.fixtures.ts +++ b/src/value/__tests__/ObjValue.fixtures.ts @@ -1,6 +1,6 @@ -import {ObjType} from "../../type"; -import {TypeBuilder} from "../../type/TypeBuilder"; -import {ObjValue} from "../ObjValue"; +import type {ObjType} from '../../type'; +import type {TypeBuilder} from '../../type/TypeBuilder'; +import {ObjValue} from '../ObjValue'; interface Services { log: (msg: string) => void; @@ -13,21 +13,24 @@ export interface RouteDeps { export type RouterBase = ObjType; export type Router = ObjValue; -const addLogMessageRoute = ({t, svc}: RouteDeps) => +const addLogMessageRoute = + ({t, svc}: RouteDeps) => (r: Router) => { - return r.set('log.message', t.fn - .inp(t.Object( - t.Key('message', t.str), - )) - .out(t.object({ - time: t.num, - })) - .value(({message}) => { - svc.log(message); - return {time: Date.now()}; - }) - ) -}; + return r.set( + 'log.message', + t.fn + .inp(t.Object(t.Key('message', t.str))) + .out( + t.object({ + time: t.num, + }), + ) + .value(({message}) => { + svc.log(message); + return {time: Date.now()}; + }), + ); + }; export const createRouter = (svc: Services) => { const base = ObjValue.new(); @@ -36,4 +39,3 @@ export const createRouter = (svc: Services) => { const router = addLogMessageRoute(deps)(base); return router; }; - From db35c95c88296552b3f2852899945d4ea383f01c Mon Sep 17 00:00:00 2001 From: streamich Date: Thu, 4 Sep 2025 14:26:27 +0200 Subject: [PATCH 5/5] =?UTF-8?q?test:=20=F0=9F=92=8D=20update=20snapshot?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/value/__tests__/__snapshots__/ObjValue.spec.ts.snap | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/value/__tests__/__snapshots__/ObjValue.spec.ts.snap b/src/value/__tests__/__snapshots__/ObjValue.spec.ts.snap index 447b4e2..f59eb69 100644 --- a/src/value/__tests__/__snapshots__/ObjValue.spec.ts.snap +++ b/src/value/__tests__/__snapshots__/ObjValue.spec.ts.snap @@ -2,7 +2,10 @@ exports[`can print to string 1`] = ` "ObjValue -└─ obj - └─ "foo" - └─ str" +├─ obj +│ └─ "foo" +│ └─ str +└─ { + "foo": "bar" + }" `;