Skip to content

Commit 69b422a

Browse files
authored
Merge pull request #54 from jsonjoy-com/obj-type-inference
`ObjValue` inference improvements
2 parents 877b81a + db35c95 commit 69b422a

File tree

9 files changed

+135
-17
lines changed

9 files changed

+135
-17
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@
6464
"@jsonjoy.com/util": "^1.9.0",
6565
"sonic-forest": "^1.2.1",
6666
"thingies": "^2.5.0",
67-
"tree-dump": "^1.0.3"
67+
"tree-dump": "^1.1.0"
6868
},
6969
"devDependencies": {
7070
"@biomejs/biome": "^1.9.3",

src/type/classes/FnType.ts

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,7 @@
11
import {printTree} from 'tree-dump/lib/printTree';
22
import * as schema from '../../schema';
3-
import type {SchemaOf, Type} from '../types';
43
import {AbsType} from './AbsType';
5-
6-
const fnNotImplemented: schema.FunctionValue<any, any> = async () => {
7-
throw new Error('NOT_IMPLEMENTED');
8-
};
4+
import type {SchemaOf, Type} from '../types';
95

106
const toStringTree = (tab: string = '', type: FnType<Type, Type, any> | FnRxType<Type, Type, any>) => {
117
return printTree(tab, [
@@ -71,6 +67,14 @@ export class FnType<Req extends Type, Res extends Type, Ctx = unknown> extends A
7167
return this;
7268
}
7369

70+
public exec(input: schema.TypeOf<SchemaOf<Req>>) {
71+
const func = this.schema.default as schema.FunctionValue<
72+
schema.TypeOf<SchemaOf<Req>>,
73+
schema.TypeOf<SchemaOf<Res>>
74+
>;
75+
return func(input);
76+
}
77+
7478
public toString(tab: string = ''): string {
7579
return super.toString(tab) + toStringTree(tab, this);
7680
}

src/value/FnValue.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import {Value} from './Value';
2+
import type {Printable} from 'tree-dump/lib/types';
3+
import type * as classes from '../type';
4+
5+
export class FnValue<T extends classes.FnType<any, any, any>> extends Value<T> implements Printable {
6+
public async exec(input: classes.ResolveType<T['req']>, ctx?: unknown): Promise<Value<T['res']>> {
7+
const fn = this.data as any;
8+
const output = await fn(input, ctx);
9+
return new Value(output, this.type!.res);
10+
}
11+
12+
public name(): string {
13+
return 'FnValue';
14+
}
15+
}

src/value/ObjValue.ts

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
import {printTree} from 'tree-dump/lib/printTree';
1+
import {ModuleType} from '../type/classes/ModuleType';
2+
import {Value} from './Value';
3+
import {FnValue} from './FnValue';
24
import type {Printable} from 'tree-dump/lib/types';
35
import type * as classes from '../type';
46
import type {TypeBuilder} from '../type/TypeBuilder';
5-
import {ModuleType} from '../type/classes/ModuleType';
6-
import {Value} from './Value';
77

88
export type UnObjType<T> = T extends classes.ObjType<infer U> ? U : never;
99
export type UnObjValue<T> = T extends ObjValue<infer U> ? U : never;
@@ -14,6 +14,8 @@ export type ObjValueToTypeMap<F> = ToObject<{
1414
[K in keyof F]: ObjFieldToTuple<F[K]>;
1515
}>;
1616

17+
export type Ensure<T, X> = T extends X ? T : X;
18+
1719
export class ObjValue<T extends classes.ObjType<any>> extends Value<T> implements Printable {
1820
public static new = (system: ModuleType = new ModuleType()) => new ObjValue({}, system.t.obj);
1921

@@ -41,6 +43,18 @@ export class ObjValue<T extends classes.ObjType<any>> extends Value<T> implement
4143
return new Value(data, field.val) as any;
4244
}
4345

46+
public fn<K extends keyof ObjValueToTypeMap<UnObjType<T>>>(
47+
key: K,
48+
): FnValue<
49+
Ensure<
50+
ObjValueToTypeMap<UnObjType<T>>[K] extends classes.Type ? ObjValueToTypeMap<UnObjType<T>>[K] : classes.Type,
51+
classes.FnType<any, any, any>
52+
>
53+
> {
54+
const val = this.get(key);
55+
return new FnValue(val.data, val.type as any);
56+
}
57+
4458
public field<F extends classes.KeyType<any, any>>(
4559
field: F | ((t: TypeBuilder) => F),
4660
data: classes.ResolveType<UnObjFieldTypeVal<F>>,
@@ -78,7 +92,7 @@ export class ObjValue<T extends classes.ObjType<any>> extends Value<T> implement
7892
return this as any;
7993
}
8094

81-
public toString(tab: string = ''): string {
82-
return 'ObjValue' + printTree(tab, [(tab) => this.type!.toString(tab)]);
95+
public name(): string {
96+
return 'ObjValue';
8397
}
8498
}

src/value/Value.ts

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,40 @@
11
import {printTree} from 'tree-dump/lib/printTree';
2+
import {printJson} from 'tree-dump/lib/printJson';
23
import type {Printable} from 'tree-dump';
34
import type {ResolveType, Type} from '../type/types';
45

6+
const copyForPrint = (data: unknown): unknown => {
7+
if (typeof data === 'function') return '__fN---';
8+
if (Array.isArray(data)) return data.map(copyForPrint);
9+
if (data && typeof data === 'object') {
10+
const res: Record<string, unknown> = {};
11+
for (const k in data) res[k] = copyForPrint((data as any)[k]);
12+
return res;
13+
}
14+
return data;
15+
};
16+
517
export class Value<T extends Type = Type> implements Printable {
618
constructor(
719
public data: ResolveType<T>,
820
public type?: T,
921
) {}
1022

23+
public name(): string {
24+
return 'Value';
25+
}
26+
1127
public toString(tab: string = ''): string {
1228
const type = this.type;
13-
return 'Value' + (type ? printTree(tab, [(tab) => type.toString(tab)]) : '');
29+
return (
30+
this.name() +
31+
(type
32+
? printTree(tab, [
33+
(tab) => type.toString(tab),
34+
(tab) => printJson(tab, copyForPrint(this.data)).replace(/"__fN---"/g, 'fn()'),
35+
])
36+
: '')
37+
);
1438
}
1539
}
1640

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import {createRouter} from './ObjValue.fixtures';
2+
3+
test('can retrieve field as Value', async () => {
4+
const log = jest.fn();
5+
const router = createRouter({log});
6+
const result = await router.fn('log.message').exec({message: 'asdf'});
7+
expect(result.data).toEqual({time: expect.any(Number)});
8+
});
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import type {ObjType} from '../../type';
2+
import type {TypeBuilder} from '../../type/TypeBuilder';
3+
import {ObjValue} from '../ObjValue';
4+
5+
interface Services {
6+
log: (msg: string) => void;
7+
}
8+
9+
export interface RouteDeps {
10+
svc: Services;
11+
t: TypeBuilder;
12+
}
13+
export type RouterBase = ObjType<any>;
14+
export type Router<R extends RouterBase> = ObjValue<R>;
15+
16+
const addLogMessageRoute =
17+
({t, svc}: RouteDeps) =>
18+
<R extends RouterBase>(r: Router<R>) => {
19+
return r.set(
20+
'log.message',
21+
t.fn
22+
.inp(t.Object(t.Key('message', t.str)))
23+
.out(
24+
t.object({
25+
time: t.num,
26+
}),
27+
)
28+
.value(({message}) => {
29+
svc.log(message);
30+
return {time: Date.now()};
31+
}),
32+
);
33+
};
34+
35+
export const createRouter = (svc: Services) => {
36+
const base = ObjValue.new();
37+
const t = base.system.t;
38+
const deps: RouteDeps = {svc, t};
39+
const router = addLogMessageRoute(deps)(base);
40+
return router;
41+
};

src/value/__tests__/__snapshots__/ObjValue.spec.ts.snap

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@
22

33
exports[`can print to string 1`] = `
44
"ObjValue
5-
└─ obj
6-
└─ "foo"
7-
└─ str"
5+
├─ obj
6+
│ └─ "foo"
7+
│ └─ str
8+
└─ {
9+
"foo": "bar"
10+
}"
811
`;

yarn.lock

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -889,7 +889,7 @@ __metadata:
889889
rxjs: "npm:^7.8.2"
890890
sonic-forest: "npm:^1.2.1"
891891
thingies: "npm:^2.5.0"
892-
tree-dump: "npm:^1.0.3"
892+
tree-dump: "npm:^1.1.0"
893893
ts-jest: "npm:^29.1.2"
894894
tslib: "npm:^2.7.0"
895895
typescript: "npm:^5.6.2"
@@ -3530,7 +3530,7 @@ __metadata:
35303530
languageName: node
35313531
linkType: hard
35323532

3533-
"tree-dump@npm:^1.0.0, tree-dump@npm:^1.0.3":
3533+
"tree-dump@npm:^1.0.0":
35343534
version: 1.0.3
35353535
resolution: "tree-dump@npm:1.0.3"
35363536
peerDependencies:
@@ -3539,6 +3539,15 @@ __metadata:
35393539
languageName: node
35403540
linkType: hard
35413541

3542+
"tree-dump@npm:^1.1.0":
3543+
version: 1.1.0
3544+
resolution: "tree-dump@npm:1.1.0"
3545+
peerDependencies:
3546+
tslib: 2
3547+
checksum: 10c0/079f0f0163b68ee2eedc65cab1de6fb121487eba9ae135c106a8bc5e4ab7906ae0b57d86016e4a7da8c0ee906da1eae8c6a1490cd6e2a5e5ccbca321e1f959ca
3548+
languageName: node
3549+
linkType: hard
3550+
35423551
"ts-jest@npm:^29.1.2":
35433552
version: 29.4.1
35443553
resolution: "ts-jest@npm:29.4.1"

0 commit comments

Comments
 (0)