Skip to content

Commit 7b6909c

Browse files
committed
feat: 🎸 allow "key" type in tuples
1 parent 7df727d commit 7b6909c

File tree

9 files changed

+90
-81
lines changed

9 files changed

+90
-81
lines changed

src/__tests__/fixtures.ts

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -33,29 +33,29 @@ export const primitiveSchemas = {
3333
export const compositeSchemas = {
3434
simpleArray: s.Array(s.String()),
3535
arrayWithBounds: s.Array(s.Number(), {min: 2, max: 5}),
36-
simpleObject: s.Object([s.prop('id', s.String()), s.prop('name', s.String()), s.prop('active', s.Boolean())]),
36+
simpleObject: s.Object([s.Key('id', s.String()), s.Key('name', s.String()), s.Key('active', s.Boolean())]),
3737
objectWithOptionalFields: s.Object([
38-
s.prop('id', s.String()),
39-
s.propOpt('name', s.String()),
40-
s.propOpt('count', s.Number()),
38+
s.Key('id', s.String()),
39+
s.KeyOpt('name', s.String()),
40+
s.KeyOpt('count', s.Number()),
4141
]),
4242
nestedObject: s.Object([
43-
s.prop(
43+
s.Key(
4444
'user',
4545
s.Object([
46-
s.prop('id', s.Number()),
47-
s.prop('profile', s.Object([s.prop('name', s.String()), s.prop('email', s.String())])),
46+
s.Key('id', s.Number()),
47+
s.Key('profile', s.Object([s.Key('name', s.String()), s.Key('email', s.String())])),
4848
]),
4949
),
50-
s.prop('tags', s.Array(s.String())),
50+
s.Key('tags', s.Array(s.String())),
5151
]),
5252
tuple: s.Tuple([s.String(), s.Number(), s.Boolean()]),
5353
map: s.Map(s.String()),
54-
mapWithComplexValue: s.Map(s.Object([s.prop('value', s.Number()), s.prop('label', s.String())])),
54+
mapWithComplexValue: s.Map(s.Object([s.Key('value', s.Number()), s.Key('label', s.String())])),
5555
union: s.Or(s.String(), s.Number(), s.Boolean()),
5656
complexUnion: s.Or(
5757
s.String(),
58-
s.Object([s.prop('type', s.Const('object' as const)), s.prop('data', s.Any())]),
58+
s.Object([s.Key('type', s.Const('object' as const)), s.Key('data', s.Any())]),
5959
s.Array(s.Number()),
6060
),
6161
binary: s.bin,

src/codegen/json/__tests__/json.spec.ts

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -89,21 +89,21 @@ describe('"arr" type', () => {
8989

9090
describe('"obj" type', () => {
9191
test('serializes object with required fields', () => {
92-
const type = s.Object([s.prop('a', s.num), s.prop('b', s.str)]);
92+
const type = s.Object([s.Key('a', s.num), s.Key('b', s.str)]);
9393
exec(type, {a: 123, b: 'asdf'});
9494
});
9595

9696
test('serializes object with constant string with required fields', () => {
97-
const type = s.Object([s.prop('a', s.num), s.prop('b', s.Const<'asdf'>('asdf'))]);
97+
const type = s.Object([s.Key('a', s.num), s.Key('b', s.Const<'asdf'>('asdf'))]);
9898
exec(type, {a: 123, b: 'asdf'});
9999
});
100100

101101
test('can serialize optional fields', () => {
102102
const type = s.Object([
103-
s.prop('a', s.num),
104-
s.prop('b', s.Const<'asdf'>('asdf')),
105-
s.propOpt('c', s.str),
106-
s.propOpt('d', s.num),
103+
s.Key('a', s.num),
104+
s.Key('b', s.Const<'asdf'>('asdf')),
105+
s.KeyOpt('c', s.str),
106+
s.KeyOpt('d', s.num),
107107
]);
108108
exec(type, {a: 123, b: 'asdf'});
109109
exec(type, {a: 123, b: 'asdf', c: 'qwerty'});
@@ -112,7 +112,7 @@ describe('"obj" type', () => {
112112

113113
test('can serialize object with unknown fields', () => {
114114
const type = s.Object(
115-
[s.prop('a', s.num), s.prop('b', s.Const<'asdf'>('asdf')), s.propOpt('c', s.str), s.propOpt('d', s.num)],
115+
[s.Key('a', s.num), s.Key('b', s.Const<'asdf'>('asdf')), s.KeyOpt('c', s.str), s.KeyOpt('d', s.num)],
116116
{encodeUnknownKeys: true},
117117
);
118118
exec(type, {a: 123, b: 'asdf'});
@@ -155,19 +155,19 @@ describe('general', () => {
155155
test('serializes according to schema a POJO object', () => {
156156
const type = s.Object({
157157
keys: [
158-
s.prop('a', s.num),
159-
s.prop('b', s.str),
160-
s.prop('c', s.nil),
161-
s.prop('d', s.bool),
162-
s.prop(
158+
s.Key('a', s.num),
159+
s.Key('b', s.str),
160+
s.Key('c', s.nil),
161+
s.Key('d', s.bool),
162+
s.Key(
163163
'arr',
164164
s.Array(
165165
s.Object({
166-
keys: [s.prop('foo', s.Array(s.num)), s.prop('.!@#', s.str)],
166+
keys: [s.Key('foo', s.Array(s.num)), s.Key('.!@#', s.str)],
167167
}),
168168
),
169169
),
170-
s.prop('bin', s.bin),
170+
s.Key('bin', s.bin),
171171
],
172172
});
173173
const json = {
@@ -196,7 +196,7 @@ describe('general', () => {
196196
});
197197

198198
test('can encode binary', () => {
199-
const type = s.Object([s.prop('bin', s.bin)]);
199+
const type = s.Object([s.Key('bin', s.bin)]);
200200
const json = {
201201
bin: new Uint8Array([1, 2, 3]),
202202
};
@@ -211,7 +211,7 @@ describe('"ref" type', () => {
211211
test('can serialize reference by resolving to type', () => {
212212
const system = new ModuleType();
213213
system.alias('ID', system.t.str);
214-
const schema = s.Object([s.prop('name', s.str), s.prop('id', s.Ref<any>('ID')), s.prop('createdAt', s.num)]);
214+
const schema = s.Object([s.Key('name', s.str), s.Key('id', s.Ref<any>('ID')), s.Key('createdAt', s.num)]);
215215
const type = system.t.import(schema);
216216
const fn = JsonTextCodegen.get(type);
217217
const json = {

src/codegen/validator/__tests__/codegen.spec.ts

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -33,24 +33,24 @@ test('validates according to schema a POJO object', () => {
3333
const type = s.Object({
3434
decodeUnknownKeys: false,
3535
keys: [
36-
s.prop(
36+
s.Key(
3737
'collection',
3838
s.Object({
3939
decodeUnknownKeys: false,
4040
keys: [
41-
s.prop('id', s.str),
42-
s.prop('ts', s.num),
43-
s.prop('cid', s.str),
44-
s.prop('prid', s.str),
45-
s.propOpt('slug', s.str),
46-
s.propOpt('name', s.str),
47-
s.propOpt('src', s.str),
48-
s.propOpt('authz', s.str),
49-
s.prop('tags', s.Array(s.str)),
41+
s.Key('id', s.str),
42+
s.Key('ts', s.num),
43+
s.Key('cid', s.str),
44+
s.Key('prid', s.str),
45+
s.KeyOpt('slug', s.str),
46+
s.KeyOpt('name', s.str),
47+
s.KeyOpt('src', s.str),
48+
s.KeyOpt('authz', s.str),
49+
s.Key('tags', s.Array(s.str)),
5050
],
5151
}),
5252
),
53-
s.prop('bin.', s.bin),
53+
s.Key('bin.', s.bin),
5454
],
5555
});
5656
const json = {
@@ -922,7 +922,7 @@ describe('"obj" type', () => {
922922

923923
test('object can have a field of any type', () => {
924924
const type = s.Object({
925-
keys: [s.prop('foo', s.any)],
925+
keys: [s.Key('foo', s.any)],
926926
});
927927
exec(type, {foo: 123}, null);
928928
exec(type, {foo: null}, null);
@@ -941,7 +941,7 @@ describe('"obj" type', () => {
941941

942942
test('can detect extra properties in object', () => {
943943
const type = s.Object({
944-
keys: [s.prop('foo', s.any), s.propOpt('zup', s.any)],
944+
keys: [s.Key('foo', s.any), s.KeyOpt('zup', s.any)],
945945
});
946946
exec(type, {foo: 123}, null);
947947
exec(type, {foo: 123, zup: 'asdf'}, null);
@@ -960,7 +960,7 @@ describe('"obj" type', () => {
960960

961961
test('can disable extra property check', () => {
962962
const type = s.Object({
963-
keys: [s.prop('foo', s.any), s.propOpt('zup', s.any)],
963+
keys: [s.Key('foo', s.any), s.KeyOpt('zup', s.any)],
964964
});
965965
exec(type, {foo: 123}, null, {skipObjectExtraFieldsCheck: true});
966966
exec(type, {foo: 123, zup: 'asdf'}, null, {
@@ -1035,7 +1035,7 @@ describe('"or" type', () => {
10351035
});
10361036

10371037
test('checks inner type', () => {
1038-
const type = s.Or(s.Object(s.prop('type', s.Const<'num'>('num')), s.prop('foo', s.num)), s.num);
1038+
const type = s.Or(s.Object(s.Key('type', s.Const<'num'>('num')), s.Key('foo', s.num)), s.num);
10391039
exec(type, {type: 'num', foo: 123}, null);
10401040
exec(
10411041
type,
@@ -1052,7 +1052,7 @@ describe('"or" type', () => {
10521052
test('object key can be of multiple types', () => {
10531053
const type = s.Object({
10541054
keys: [
1055-
s.prop('foo', {
1055+
s.Key('foo', {
10561056
...s.Or(s.num, s.str),
10571057
discriminator: [
10581058
'if',
@@ -1080,7 +1080,7 @@ describe('"or" type', () => {
10801080
test('array can be of multiple types', () => {
10811081
const type = s.Object({
10821082
keys: [
1083-
s.prop(
1083+
s.Key(
10841084
'gg',
10851085
s.Array({
10861086
...s.Or(s.num, s.str),

src/schema/SchemaBuilder.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ export class SchemaBuilder {
137137
}
138138

139139
/** Declares an object property. */
140-
public prop<K extends string, V extends _.Schema>(
140+
public Key<K extends string, V extends _.Schema>(
141141
key: K,
142142
value: V,
143143
options: Omit<_.NoT<_.KeySchema<K, V>>, 'key' | 'value' | 'optional'> = {},
@@ -151,7 +151,7 @@ export class SchemaBuilder {
151151
}
152152

153153
/** Declares an optional object property. */
154-
public propOpt<K extends string, V extends _.Schema>(
154+
public KeyOpt<K extends string, V extends _.Schema>(
155155
key: K,
156156
value: V,
157157
options: Omit<_.NoT<_.KeySchema<K, V>>, 'key' | 'value' | 'optional'> = {},

src/schema/__tests__/SchemaBuilder.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ describe('object', () => {
3434
});
3535

3636
test('can specify types', () => {
37-
const type = s.Object([s.prop('id', s.String()), s.prop('name', s.str)]);
37+
const type = s.Object([s.Key('id', s.String()), s.Key('name', s.str)]);
3838
expect(type).toEqual({
3939
kind: 'obj',
4040
keys: [

src/schema/__tests__/TypeOf.spec.ts

Lines changed: 21 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,13 @@ describe('"arr" kind', () => {
100100
const arr1: T1 = ['foo', 1] satisfies [string, number];
101101
const arr2: [string, number] = ['foo', 1] satisfies T1;
102102
});
103+
104+
test('named tuple members', () => {
105+
const schema1 = s.Tuple([s.Key('foo', s.str), s.num]);
106+
type T1 = TypeOf<typeof schema1>;
107+
const arr1: T1 = ['foo', 1] satisfies [string, number];
108+
const arr2: [string, number] = ['foo', 1] satisfies T1;
109+
});
103110
});
104111

105112
test('can infer a simple "const" type', () => {
@@ -127,11 +134,11 @@ test('can infer a simple "tuple" type', () => {
127134

128135
test('can infer a simple "obj" type', () => {
129136
const schema1 = s.obj;
130-
const schema2 = s.Object(s.prop('foo', s.str), s.propOpt('bar', s.num));
137+
const schema2 = s.Object(s.Key('foo', s.str), s.KeyOpt('bar', s.num));
131138
const schema3 = s.Object({
132-
keys: <const>[s.prop('bar', s.bool)],
139+
keys: <const>[s.Key('bar', s.bool)],
133140
});
134-
const schema4 = s.Object([s.prop('baz', s.num), s.propOpt('bazOptional', s.bool), s.propOpt('z', s.str)], {});
141+
const schema4 = s.Object([s.Key('baz', s.num), s.KeyOpt('bazOptional', s.bool), s.KeyOpt('z', s.str)], {});
135142
type T1 = TypeOf<typeof schema1>;
136143
type T2 = TypeOf<typeof schema2>;
137144
type T3 = TypeOf<typeof schema3>;
@@ -174,7 +181,7 @@ test('can infer a simple "or" type', () => {
174181

175182
test('can infer a simple "ref" type', () => {
176183
const schema1 = s.str;
177-
const schema2 = s.Object(s.prop('foo', s.Ref<typeof schema1>('another-str')));
184+
const schema2 = s.Object(s.Key('foo', s.Ref<typeof schema1>('another-str')));
178185
type T1 = TypeOf<typeof schema1>;
179186
type T2 = TypeOf<typeof schema2>;
180187
const val1: T1 = 'foo';
@@ -204,9 +211,9 @@ test('can infer a simple "fn$" type', () => {
204211
});
205212

206213
test('can infer a complex "fn" type', () => {
207-
const arr = s.Array(s.Object(s.prop('op', s.str), s.prop('path', s.str)));
208-
const req = s.Object(s.prop('id', s.str), s.prop('age', s.num), s.prop('patch', s.Object(s.prop('ops', arr))));
209-
const res = s.Object(s.prop('id', s.String()));
214+
const arr = s.Array(s.Object(s.Key('op', s.str), s.Key('path', s.str)));
215+
const req = s.Object(s.Key('id', s.str), s.Key('age', s.num), s.Key('patch', s.Object(s.Key('ops', arr))));
216+
const res = s.Object(s.Key('id', s.String()));
210217
const schema1 = s.Function(req, res);
211218
type T1 = TypeOf<typeof schema1>;
212219
const val1: T1 = async ({patch, id}) => {
@@ -217,12 +224,12 @@ test('can infer a complex "fn" type', () => {
217224

218225
test('can infer a realistic schema', () => {
219226
const schema = s.Object(
220-
s.prop('id', s.str),
221-
s.prop('age', s.num),
222-
s.prop('tags', s.Array(s.Or(s.str, s.num))),
223-
s.prop('data', s.Object(s.prop('foo', s.str), s.prop('bar', s.num))),
224-
s.prop('approved', s.bool),
225-
s.prop('meta', s.any),
227+
s.Key('id', s.str),
228+
s.Key('age', s.num),
229+
s.Key('tags', s.Array(s.Or(s.str, s.num))),
230+
s.Key('data', s.Object(s.Key('foo', s.str), s.Key('bar', s.num))),
231+
s.Key('approved', s.bool),
232+
s.Key('meta', s.any),
226233
);
227234
type T = TypeOf<typeof schema>;
228235
const val: T = {
@@ -239,7 +246,7 @@ test('can infer a realistic schema', () => {
239246
});
240247

241248
test('can specify an optional fields', () => {
242-
const schema = s.Object(s.propOpt('meta', s.Object(s.prop('foo', s.str), s.propOpt('bar', s.num))));
249+
const schema = s.Object(s.KeyOpt('meta', s.Object(s.Key('foo', s.str), s.KeyOpt('bar', s.num))));
243250
type T = TypeOf<typeof schema>;
244251
const val0: T = {};
245252
const val1: T = {

src/schema/__tests__/type.spec.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,16 @@ test('can generate any type', () => {
55
kind: 'obj',
66
title: 'User address',
77
description: 'Various address fields for user',
8-
keys: [...s.Object(s.prop('street', s.String()), s.prop('zip', s.String())).keys],
8+
keys: [...s.Object(s.Key('street', s.String()), s.Key('zip', s.String())).keys],
99
};
1010
const userType = s.Object(
11-
s.prop('id', s.Number({format: 'i'})),
12-
s.prop('alwaysOne', s.Const<1>(1)),
13-
s.prop('name', s.String()),
14-
s.prop('address', address),
15-
s.prop('timeCreated', s.Number()),
16-
s.prop('tags', s.Array(s.Or(s.Number(), s.String()))),
17-
s.prop('elements', s.Map(s.str)),
11+
s.Key('id', s.Number({format: 'i'})),
12+
s.Key('alwaysOne', s.Const<1>(1)),
13+
s.Key('name', s.String()),
14+
s.Key('address', address),
15+
s.Key('timeCreated', s.Number()),
16+
s.Key('tags', s.Array(s.Or(s.Number(), s.String()))),
17+
s.Key('elements', s.Map(s.str)),
1818
);
1919

2020
expect(userType).toMatchObject({

src/schema/schema.ts

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -479,17 +479,19 @@ export type TypeOfValue<T> = T extends BoolSchema
479479
]
480480
: T extends ConSchema<infer U>
481481
? U
482-
: T extends ObjSchema<infer F>
483-
? NoEmptyInterface<TypeFields<Mutable<F>>>
484-
: T extends MapSchema<infer U>
485-
? Record<string, TypeOf<U>>
486-
: T extends BinSchema
487-
? Uint8Array
488-
: T extends FnSchema<infer Req, infer Res, infer Ctx>
489-
? (req: TypeOf<Req>, ctx: Ctx) => UndefToVoid<TypeOf<Res>> | Promise<UndefToVoid<TypeOf<Res>>>
490-
: T extends FnRxSchema<infer Req, infer Res, infer Ctx>
491-
? (req$: Observable<TypeOf<Req>>, ctx: Ctx) => Observable<UndefToVoid<TypeOf<Res>>>
492-
: never;
482+
: T extends KeySchema<infer K, infer V>
483+
? TypeOf<V>
484+
: T extends ObjSchema<infer F>
485+
? NoEmptyInterface<TypeFields<Mutable<F>>>
486+
: T extends MapSchema<infer U>
487+
? Record<string, TypeOf<U>>
488+
: T extends BinSchema
489+
? Uint8Array
490+
: T extends FnSchema<infer Req, infer Res, infer Ctx>
491+
? (req: TypeOf<Req>, ctx: Ctx) => UndefToVoid<TypeOf<Res>> | Promise<UndefToVoid<TypeOf<Res>>>
492+
: T extends FnRxSchema<infer Req, infer Res, infer Ctx>
493+
? (req$: Observable<TypeOf<Req>>, ctx: Ctx) => Observable<UndefToVoid<TypeOf<Res>>>
494+
: never;
493495

494496
export type TypeOfMap<M extends Record<string, Schema>> = {
495497
[K in keyof M]: TypeOf<M[K]>;

src/type/classes/ObjType.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ export class ObjKeyType<K extends string, V extends Type> extends AbsType<schema
1111
public readonly key: K,
1212
public readonly val: V,
1313
) {
14-
super(schema.s.prop(key, schema.s.any) as any);
14+
super(schema.s.Key(key, schema.s.any) as any);
1515
}
1616

1717
public getSchema(): schema.KeySchema<K, SchemaOf<V>> {
@@ -43,7 +43,7 @@ export class ObjKeyOptType<K extends string, V extends Type> extends ObjKeyType<
4343
public readonly val: V,
4444
) {
4545
super(key, val);
46-
(this as any).schema = schema.s.propOpt(key, schema.s.any) as any;
46+
(this as any).schema = schema.s.KeyOpt(key, schema.s.any) as any;
4747
}
4848

4949
protected toStringTitle(): string {

0 commit comments

Comments
 (0)