Skip to content

Commit d8b6c1b

Browse files
committed
feat(schema): add object type methods
1 parent e136132 commit d8b6c1b

File tree

9 files changed

+329
-82
lines changed

9 files changed

+329
-82
lines changed

.changeset/bumpy-tigers-visit.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@dreamkit/schema": patch
3+
---
4+
5+
Add `fit` and `createWithAsync` methods

packages/schema/src/flags.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,10 +38,9 @@ export namespace TypeFlag {
3838
export type Nullish<F> = TypeFlag.Merge<F, Object[Name.Nullish]> & {};
3939
export type Required<F> = TypeFlag.Merge<F, Object[Name.Required]> & {};
4040

41-
export type CheckTypeFlags<
42-
F1 extends TypeFlag.Query | undefined,
43-
F2 extends TypeFlag.Options,
44-
> = [F1] extends [undefined]
41+
export type CheckTypeFlags<F1, F2 extends TypeFlag.Options> = [F1] extends [
42+
undefined,
43+
]
4544
? true
4645
: keyof {
4746
[K in keyof F1 as [F1[K]] extends [true]

packages/schema/src/index.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,8 @@ export {
99
type TypeAssertErrorData,
1010
} from "./validation.js";
1111
export { Schema, type SchemaOptions } from "./schema.js";
12+
export type {
13+
DeepMergeFlags,
14+
ConvertObjectType,
15+
ObjectTypeMask,
16+
} from "./utils/object-type.js";

packages/schema/src/types/ObjectType.ts

Lines changed: 106 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ export type IsEmptyObjectTypeProps<
5050

5151
export type QueryObjectType<
5252
T extends MinimalObjectType,
53-
Q extends $.TypeFlag.Query,
53+
Q,
5454
P extends ObjectTypeProps = T["props"],
5555
> =
5656
unknown extends Any<T>
@@ -101,6 +101,29 @@ export class MinimalObjectType<
101101
override readonly kind = "object" as const;
102102
}
103103

104+
type ObjectTypeIteratorItem = {
105+
type: $.Type;
106+
objectType: ObjectType | undefined;
107+
name: string;
108+
path: string[];
109+
value: any;
110+
};
111+
112+
type ObjectTypeIteratorOptions = {
113+
data?: any;
114+
parentPath?: string[];
115+
followData?: boolean;
116+
};
117+
118+
type ObjectTypeCreatorOptions = ObjectTypeIteratorOptions & {
119+
map?: (
120+
data: ObjectTypeIteratorItem & {
121+
pathName: string;
122+
},
123+
) => any;
124+
output?: Record<string, any>;
125+
};
126+
104127
export class ObjectType<
105128
P extends ObjectTypeProps = ObjectTypeProps,
106129
F extends $.TypeFlag.Options = {},
@@ -168,41 +191,82 @@ export class ObjectType<
168191
const { props, ...otherOptions } = options;
169192
return new ObjectType(props, otherOptions);
170193
}
171-
createWith(
172-
data: any,
173-
mapper?: (data: {
174-
name: string;
175-
selfName: string;
176-
path: string[];
177-
type: $.Type;
178-
value: any;
179-
}) => any,
180-
parentPath?: string[],
181-
output: Record<string, any> = {},
182-
): any {
183-
for (const name in this.props) {
194+
195+
private *createIterator(
196+
options: ObjectTypeIteratorOptions = {},
197+
): Generator<ObjectTypeIteratorItem, void, unknown> {
198+
const entry = options.followData ? options.data : this.props;
199+
for (const name in entry) {
184200
const type = this.props[name] as any as $.Type;
185-
const path = [...(parentPath || []), name];
186-
const value = data?.[name];
201+
if (!type) continue;
202+
const path = [...(options.parentPath || []), name];
203+
const value = options.data?.[name];
204+
yield {
205+
type,
206+
path,
207+
name,
208+
value,
209+
objectType: kindOf(type, ObjectType) ? type : undefined,
210+
};
211+
}
212+
}
213+
createWith(options: ObjectTypeCreatorOptions = {}): any {
214+
const output = options.output ?? {};
215+
const it = this.createIterator(options);
216+
for (const item of it) {
217+
if (
218+
item.objectType &&
219+
(!!item.value ||
220+
(!item.type.options.nullable && !item.type.options.optional))
221+
) {
222+
output[item.name] = item.objectType.createWith({
223+
...options,
224+
data: item.value,
225+
output: undefined,
226+
parentPath: item.path,
227+
});
228+
} else {
229+
let pathName: string | undefined;
230+
const newValue = options.map
231+
? options.map({
232+
...item,
233+
get pathName() {
234+
return pathName || (pathName = item.path.join("."));
235+
},
236+
})
237+
: item.value;
238+
if (newValue !== undefined) output[item.name] = newValue;
239+
}
240+
}
241+
return output;
242+
}
243+
244+
async createWithAsync(options: ObjectTypeCreatorOptions = {}): Promise<any> {
245+
const output = options.output ?? {};
246+
const it = this.createIterator(options);
247+
for (const item of it) {
187248
if (
188-
kindOf(type, ObjectType) &&
189-
(!!value || (!type.options.nullable && !type.options.optional))
249+
item.objectType &&
250+
(!!item.value ||
251+
(!item.type.options.nullable && !item.type.options.optional))
190252
) {
191-
output[name] = type.createWith(value, mapper, path);
253+
output[item.name] = await item.objectType.createWithAsync({
254+
...options,
255+
data: item.value,
256+
output: undefined,
257+
parentPath: item.path,
258+
});
192259
} else {
193260
let pathName: string | undefined;
194-
const newValue = mapper
195-
? mapper({
196-
path,
197-
type,
198-
value,
199-
selfName: name,
200-
get name() {
201-
return pathName || (pathName = path.join("."));
261+
const newValue = options.map
262+
? await options.map({
263+
...item,
264+
get pathName() {
265+
return pathName || (pathName = item.path.join("."));
202266
},
203267
})
204-
: value;
205-
if (newValue !== undefined) output[name] = newValue;
268+
: item.value;
269+
if (newValue !== undefined) output[item.name] = newValue;
206270
}
207271
}
208272
return output;
@@ -268,6 +332,13 @@ export class ObjectType<
268332
),
269333
};
270334
}
335+
fit(data: InferType<this>): InferType<this> {
336+
return this.createWith({
337+
data,
338+
followData: true,
339+
map: ({ value }) => value,
340+
});
341+
}
271342
pick<Input extends ObjectTypeMask<P>>(
272343
input: Input,
273344
): DeepProjectObjectType<this, Input> {
@@ -450,14 +521,13 @@ export class ObjectType<
450521
}
451522
iterateProps(
452523
cb: (name: string, type: $.Type) => void | false,
453-
parentNames: string[] = [],
524+
parentPath: string[] = [],
454525
) {
455-
for (const name in this.props) {
456-
const type = this.props[name] as any as $.Type;
457-
const names = [...parentNames, name];
458-
if (kindOf(type, ObjectType)) {
459-
type.iterateProps(cb, names);
460-
} else if (cb(names.join("."), type) === false) {
526+
const it = this.createIterator({ parentPath });
527+
for (const item of it) {
528+
if (item.objectType) {
529+
item.objectType.iterateProps(cb, item.path);
530+
} else if (cb(item.path.join("."), item.type) === false) {
461531
break;
462532
}
463533
}

packages/schema/src/types/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ export { BoolType, type MinimalBoolType } from "./BoolType.js";
77
export { NumberType, type MinimalNumberType } from "./NumberType.js";
88
export {
99
ObjectType,
10+
type QueryObjectType,
1011
type ObjectTypeProps,
1112
type MinimalObjectType,
1213
type InferObjectProps,

packages/schema/src/utils/object-type.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ export type DeepProjectObjectType<
7474
export type DeepMergeFlags<
7575
T extends $.MinimalType,
7676
F extends $.TypeFlag.Name,
77-
Q extends $.TypeFlag.Query | undefined = undefined,
77+
Q = undefined,
7878
Self extends boolean = false,
7979
> = F extends keyof T
8080
? T extends MinimalObjectType

0 commit comments

Comments
 (0)