@gialicus/smart-object 1.0.0 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +38 -1
- package/README.md +127 -19
- package/dist/errors.d.ts +20 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +40 -0
- package/dist/index.d.ts +4 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/smart-object/apply-operations.d.ts +5 -0
- package/dist/smart-object/apply-operations.d.ts.map +1 -0
- package/dist/smart-object/apply-operations.js +21 -0
- package/dist/smart-object/build-class.d.ts +3 -0
- package/dist/smart-object/build-class.d.ts.map +1 -0
- package/dist/smart-object/build-class.js +31 -0
- package/dist/smart-object/codecs.d.ts +15 -0
- package/dist/smart-object/codecs.d.ts.map +1 -0
- package/dist/smart-object/codecs.js +163 -0
- package/dist/smart-object/define-prototype.d.ts +4 -0
- package/dist/smart-object/define-prototype.d.ts.map +1 -0
- package/dist/smart-object/define-prototype.js +76 -0
- package/dist/{smart-object.d.ts → smart-object/factory.d.ts} +4 -2
- package/dist/smart-object/factory.d.ts.map +1 -0
- package/dist/smart-object/factory.js +27 -0
- package/dist/smart-object/index.d.ts +2 -0
- package/dist/smart-object/index.d.ts.map +1 -0
- package/dist/smart-object/index.js +1 -0
- package/dist/smart-object/instance-state.d.ts +9 -0
- package/dist/smart-object/instance-state.d.ts.map +1 -0
- package/dist/smart-object/instance-state.js +23 -0
- package/dist/smart-object/json-patch.d.ts +4 -0
- package/dist/smart-object/json-patch.d.ts.map +1 -0
- package/dist/smart-object/json-patch.js +27 -0
- package/dist/smart-object/read-field.d.ts +2 -0
- package/dist/smart-object/read-field.d.ts.map +1 -0
- package/dist/smart-object/read-field.js +13 -0
- package/dist/smart-object/setters/object-field.d.ts +5 -0
- package/dist/smart-object/setters/object-field.d.ts.map +1 -0
- package/dist/smart-object/setters/object-field.js +56 -0
- package/dist/smart-object/setters/record-field.d.ts +7 -0
- package/dist/smart-object/setters/record-field.d.ts.map +1 -0
- package/dist/smart-object/setters/record-field.js +133 -0
- package/dist/smart-object/setters/union-field.d.ts +4 -0
- package/dist/smart-object/setters/union-field.d.ts.map +1 -0
- package/dist/smart-object/setters/union-field.js +25 -0
- package/dist/smart-object/setters/variant-switch.d.ts +5 -0
- package/dist/smart-object/setters/variant-switch.d.ts.map +1 -0
- package/dist/smart-object/setters/variant-switch.js +37 -0
- package/dist/smart-object/union-variant.d.ts +5 -0
- package/dist/smart-object/union-variant.d.ts.map +1 -0
- package/dist/smart-object/union-variant.js +38 -0
- package/dist/types.d.ts +36 -3
- package/dist/types.d.ts.map +1 -1
- package/dist/zod-introspect.d.ts +50 -0
- package/dist/zod-introspect.d.ts.map +1 -0
- package/dist/zod-introspect.js +252 -0
- package/package.json +1 -1
- package/dist/smart-object.d.ts.map +0 -1
- package/dist/smart-object.js +0 -157
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { getDiscriminatedVariants, getDiscriminator, getFieldSchema, getRecordFieldsFromSchema, isObjectLikeRoot, isZodDiscriminatedUnion, isZodUnionRoot, toRecordEntryMethodPrefix, toSetterMethodName, } from "../zod-introspect.js";
|
|
2
|
+
import { readFieldValue } from "./read-field.js";
|
|
3
|
+
import { createObjectFieldSetter } from "./setters/object-field.js";
|
|
4
|
+
import { createRecordEntryDeleter, createRecordEntryGetter, createRecordEntrySetter, } from "./setters/record-field.js";
|
|
5
|
+
import { createUnionFieldSetter } from "./setters/union-field.js";
|
|
6
|
+
import { createSwitchToVariant, createSwitchVariant } from "./setters/variant-switch.js";
|
|
7
|
+
function defineRecordFieldMethods(prototype, state, zodSchema) {
|
|
8
|
+
for (const field of getRecordFieldsFromSchema(zodSchema)) {
|
|
9
|
+
const prefix = toRecordEntryMethodPrefix(field.fieldName);
|
|
10
|
+
Object.defineProperty(prototype, `get${prefix}Entry`, {
|
|
11
|
+
value: createRecordEntryGetter(state, field),
|
|
12
|
+
enumerable: false,
|
|
13
|
+
configurable: true,
|
|
14
|
+
writable: true,
|
|
15
|
+
});
|
|
16
|
+
Object.defineProperty(prototype, `set${prefix}Entry`, {
|
|
17
|
+
value: createRecordEntrySetter(state, zodSchema, field),
|
|
18
|
+
enumerable: false,
|
|
19
|
+
configurable: true,
|
|
20
|
+
writable: true,
|
|
21
|
+
});
|
|
22
|
+
Object.defineProperty(prototype, `delete${prefix}Entry`, {
|
|
23
|
+
value: createRecordEntryDeleter(state, zodSchema, field),
|
|
24
|
+
enumerable: false,
|
|
25
|
+
configurable: true,
|
|
26
|
+
writable: true,
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
function defineUnionRootMethods(prototype, state, zodSchema) {
|
|
31
|
+
Object.defineProperty(prototype, "switchVariant", {
|
|
32
|
+
value: createSwitchVariant(state, zodSchema),
|
|
33
|
+
enumerable: false,
|
|
34
|
+
configurable: true,
|
|
35
|
+
writable: true,
|
|
36
|
+
});
|
|
37
|
+
if (isZodDiscriminatedUnion(zodSchema)) {
|
|
38
|
+
const discriminator = getDiscriminator(zodSchema);
|
|
39
|
+
for (const variant of getDiscriminatedVariants(zodSchema)) {
|
|
40
|
+
Object.defineProperty(prototype, variant.methodName, {
|
|
41
|
+
value: createSwitchToVariant(state, zodSchema, variant.schema, variant.tag, discriminator),
|
|
42
|
+
enumerable: false,
|
|
43
|
+
configurable: true,
|
|
44
|
+
writable: true,
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
export function definePrototype(prototype, state, zodSchema, keys) {
|
|
50
|
+
const objectSchema = isObjectLikeRoot(zodSchema) ? zodSchema : undefined;
|
|
51
|
+
for (const key of keys) {
|
|
52
|
+
const setMethodName = toSetterMethodName(key);
|
|
53
|
+
const fieldSchema = getFieldSchema(zodSchema, key);
|
|
54
|
+
Object.defineProperty(prototype, key, {
|
|
55
|
+
get() {
|
|
56
|
+
return readFieldValue(state.getData(this)[key]);
|
|
57
|
+
},
|
|
58
|
+
enumerable: true,
|
|
59
|
+
configurable: true,
|
|
60
|
+
});
|
|
61
|
+
Object.defineProperty(prototype, setMethodName, {
|
|
62
|
+
value: fieldSchema && isObjectLikeRoot(zodSchema)
|
|
63
|
+
? createObjectFieldSetter(state, key, fieldSchema, zodSchema)
|
|
64
|
+
: createUnionFieldSetter(state, zodSchema, key),
|
|
65
|
+
enumerable: false,
|
|
66
|
+
configurable: true,
|
|
67
|
+
writable: true,
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
if (objectSchema) {
|
|
71
|
+
defineRecordFieldMethods(prototype, state, zodSchema);
|
|
72
|
+
}
|
|
73
|
+
if (isZodUnionRoot(zodSchema)) {
|
|
74
|
+
defineUnionRootMethods(prototype, state, zodSchema);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { z } from "zod";
|
|
2
|
-
import type { SmartObjectConstructor } from "
|
|
2
|
+
import type { SmartObjectConstructor } from "../types.js";
|
|
3
3
|
/**
|
|
4
4
|
* Builds a typed SmartObject class from a Zod schema.
|
|
5
5
|
*
|
|
@@ -33,4 +33,6 @@ import type { SmartObjectConstructor } from "./types.js";
|
|
|
33
33
|
export declare function SmartObject<T extends z.ZodObject>(zodSchema: T): SmartObjectConstructor<T>;
|
|
34
34
|
export declare function SmartObject<T extends z.ZodDiscriminatedUnion>(zodSchema: T): SmartObjectConstructor<T>;
|
|
35
35
|
export declare function SmartObject<T extends z.ZodUnion>(zodSchema: T): SmartObjectConstructor<T>;
|
|
36
|
-
|
|
36
|
+
export declare function SmartObject<T extends z.ZodIntersection>(zodSchema: T): SmartObjectConstructor<T>;
|
|
37
|
+
export declare function SmartObject<T extends z.ZodLazy>(zodSchema: T): SmartObjectConstructor<T>;
|
|
38
|
+
//# sourceMappingURL=factory.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"factory.d.ts","sourceRoot":"","sources":["../../src/smart-object/factory.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAE7B,OAAO,KAAK,EAAE,sBAAsB,EAAqB,MAAM,aAAa,CAAC;AAwC7E;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,wBAAgB,WAAW,CAAC,CAAC,SAAS,CAAC,CAAC,SAAS,EAAE,SAAS,EAAE,CAAC,GAAG,sBAAsB,CAAC,CAAC,CAAC,CAAC;AAC5F,wBAAgB,WAAW,CAAC,CAAC,SAAS,CAAC,CAAC,qBAAqB,EAC3D,SAAS,EAAE,CAAC,GACX,sBAAsB,CAAC,CAAC,CAAC,CAAC;AAC7B,wBAAgB,WAAW,CAAC,CAAC,SAAS,CAAC,CAAC,QAAQ,EAAE,SAAS,EAAE,CAAC,GAAG,sBAAsB,CAAC,CAAC,CAAC,CAAC;AAC3F,wBAAgB,WAAW,CAAC,CAAC,SAAS,CAAC,CAAC,eAAe,EAAE,SAAS,EAAE,CAAC,GAAG,sBAAsB,CAAC,CAAC,CAAC,CAAC;AAClG,wBAAgB,WAAW,CAAC,CAAC,SAAS,CAAC,CAAC,OAAO,EAAE,SAAS,EAAE,CAAC,GAAG,sBAAsB,CAAC,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { SmartObjectError } from "../errors.js";
|
|
2
|
+
import { isZodIntersection, isZodObject, isZodUnionOfObjects, isZodUnionRoot, resolveLazySchema, } from "../zod-introspect.js";
|
|
3
|
+
import { buildSmartObjectClass } from "./build-class.js";
|
|
4
|
+
function assertBuildableSchema(schema) {
|
|
5
|
+
const resolved = resolveLazySchema(schema);
|
|
6
|
+
if (isZodObject(resolved)) {
|
|
7
|
+
return;
|
|
8
|
+
}
|
|
9
|
+
if (isZodUnionRoot(resolved)) {
|
|
10
|
+
if (!isZodUnionOfObjects(resolved)) {
|
|
11
|
+
throw SmartObjectError.unsupportedSchema("SmartObject union root requires all options to be z.object(...)");
|
|
12
|
+
}
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
if (isZodIntersection(resolved)) {
|
|
16
|
+
const left = resolved._zod.def.left;
|
|
17
|
+
const right = resolved._zod.def.right;
|
|
18
|
+
assertBuildableSchema(left);
|
|
19
|
+
assertBuildableSchema(right);
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
throw SmartObjectError.unsupportedSchema("SmartObject requires a buildable z.object(...), z.union([...]), z.discriminatedUnion(...), z.intersection(...), or z.lazy(...) schema");
|
|
23
|
+
}
|
|
24
|
+
export function SmartObject(zodSchema) {
|
|
25
|
+
assertBuildableSchema(zodSchema);
|
|
26
|
+
return buildSmartObjectClass(zodSchema);
|
|
27
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/smart-object/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { SmartObject } from "./factory.js";
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Operation } from "fast-json-patch";
|
|
2
|
+
export type InstanceState<T> = {
|
|
3
|
+
getData(instance: object): T;
|
|
4
|
+
setData(instance: object, data: T): void;
|
|
5
|
+
getOperations(instance: object): Operation[];
|
|
6
|
+
initOperations(instance: object): void;
|
|
7
|
+
};
|
|
8
|
+
export declare function createInstanceState<T>(): InstanceState<T>;
|
|
9
|
+
//# sourceMappingURL=instance-state.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"instance-state.d.ts","sourceRoot":"","sources":["../../src/smart-object/instance-state.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAEjD,MAAM,MAAM,aAAa,CAAC,CAAC,IAAI;IAC7B,OAAO,CAAC,QAAQ,EAAE,MAAM,GAAG,CAAC,CAAC;IAC7B,OAAO,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,GAAG,IAAI,CAAC;IACzC,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,EAAE,CAAC;IAC7C,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;CACxC,CAAC;AAEF,wBAAgB,mBAAmB,CAAC,CAAC,KAAK,aAAa,CAAC,CAAC,CAAC,CA0BzD"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export function createInstanceState() {
|
|
2
|
+
const instanceData = new WeakMap();
|
|
3
|
+
const instanceOperations = new WeakMap();
|
|
4
|
+
return {
|
|
5
|
+
getData(instance) {
|
|
6
|
+
return instanceData.get(instance);
|
|
7
|
+
},
|
|
8
|
+
setData(instance, data) {
|
|
9
|
+
instanceData.set(instance, data);
|
|
10
|
+
},
|
|
11
|
+
getOperations(instance) {
|
|
12
|
+
let operations = instanceOperations.get(instance);
|
|
13
|
+
if (!operations) {
|
|
14
|
+
operations = [];
|
|
15
|
+
instanceOperations.set(instance, operations);
|
|
16
|
+
}
|
|
17
|
+
return operations;
|
|
18
|
+
},
|
|
19
|
+
initOperations(instance) {
|
|
20
|
+
instanceOperations.set(instance, []);
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"json-patch.d.ts","sourceRoot":"","sources":["../../src/smart-object/json-patch.ts"],"names":[],"mappings":"AAAA,OAAO,SAAS,MAAM,iBAAiB,CAAC;AAExC,eAAO,MAAQ,UAAU,+BAAE,OAAO,0BAAc,CAAC;AAEjD,wBAAgB,SAAS,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,GAAG,CAAC,CAkCxC"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import jsonPatch from "fast-json-patch";
|
|
2
|
+
export const { applyPatch, compare } = jsonPatch;
|
|
3
|
+
export function deepClone(value) {
|
|
4
|
+
if (value instanceof Date) {
|
|
5
|
+
return new Date(value.getTime());
|
|
6
|
+
}
|
|
7
|
+
if (typeof value === "bigint") {
|
|
8
|
+
return value;
|
|
9
|
+
}
|
|
10
|
+
if (value instanceof Map) {
|
|
11
|
+
return new Map([...value.entries()].map(([key, nestedValue]) => [key, deepClone(nestedValue)]));
|
|
12
|
+
}
|
|
13
|
+
if (value instanceof Set) {
|
|
14
|
+
return new Set([...value].map((item) => deepClone(item)));
|
|
15
|
+
}
|
|
16
|
+
if (Array.isArray(value)) {
|
|
17
|
+
return value.map((item) => deepClone(item));
|
|
18
|
+
}
|
|
19
|
+
if (typeof value === "object" && value !== null) {
|
|
20
|
+
const result = {};
|
|
21
|
+
for (const [key, nestedValue] of Object.entries(value)) {
|
|
22
|
+
result[key] = deepClone(nestedValue);
|
|
23
|
+
}
|
|
24
|
+
return result;
|
|
25
|
+
}
|
|
26
|
+
return value;
|
|
27
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"read-field.d.ts","sourceRoot":"","sources":["../../src/smart-object/read-field.ts"],"names":[],"mappings":"AAEA,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAetD"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { deepClone } from "./json-patch.js";
|
|
2
|
+
export function readFieldValue(value) {
|
|
3
|
+
if (value instanceof Date ||
|
|
4
|
+
value instanceof Map ||
|
|
5
|
+
value instanceof Set ||
|
|
6
|
+
typeof value === "bigint") {
|
|
7
|
+
return deepClone(value);
|
|
8
|
+
}
|
|
9
|
+
if (typeof value === "object" && value !== null) {
|
|
10
|
+
return deepClone(value);
|
|
11
|
+
}
|
|
12
|
+
return value;
|
|
13
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { z } from "zod";
|
|
2
|
+
import type { SmartObjectSchema } from "../../types.js";
|
|
3
|
+
import type { InstanceState } from "../instance-state.js";
|
|
4
|
+
export declare function createObjectFieldSetter<T>(state: InstanceState<T>, key: string, fieldSchema: z.ZodType, rootSchema: SmartObjectSchema): (this: object, value: unknown) => void;
|
|
5
|
+
//# sourceMappingURL=object-field.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"object-field.d.ts","sourceRoot":"","sources":["../../../src/smart-object/setters/object-field.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAE7B,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAGxD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AA8C1D,wBAAgB,uBAAuB,CAAC,CAAC,EACvC,KAAK,EAAE,aAAa,CAAC,CAAC,CAAC,EACvB,GAAG,EAAE,MAAM,EACX,WAAW,EAAE,CAAC,CAAC,OAAO,EACtB,UAAU,EAAE,iBAAiB,IAEZ,MAAM,MAAM,EAAE,OAAO,OAAO,UAoC9C"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { SmartObjectError } from "../../errors.js";
|
|
2
|
+
import { unwrapFieldSchema } from "../../zod-introspect.js";
|
|
3
|
+
import { serializeDataForPatch, serializeValue } from "../codecs.js";
|
|
4
|
+
import { compare, deepClone } from "../json-patch.js";
|
|
5
|
+
function normalizeFieldPatch(patch, key, fieldSchema, parsed, beforeSerialized) {
|
|
6
|
+
const inner = unwrapFieldSchema(fieldSchema);
|
|
7
|
+
const defType = inner._zod.def.type;
|
|
8
|
+
if (defType !== "set" && defType !== "map") {
|
|
9
|
+
return patch.map((operation) => {
|
|
10
|
+
if ("value" in operation && operation.path === `/${key}`) {
|
|
11
|
+
return {
|
|
12
|
+
...operation,
|
|
13
|
+
value: serializeValue(fieldSchema, parsed),
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
return operation;
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
const prefix = `/${key}`;
|
|
20
|
+
const touchesField = patch.some((operation) => operation.path === prefix || operation.path.startsWith(`${prefix}/`));
|
|
21
|
+
if (!touchesField) {
|
|
22
|
+
return patch;
|
|
23
|
+
}
|
|
24
|
+
const hadField = Object.hasOwn(beforeSerialized, key) && beforeSerialized[key] !== undefined;
|
|
25
|
+
return [
|
|
26
|
+
{
|
|
27
|
+
op: hadField ? "replace" : "add",
|
|
28
|
+
path: prefix,
|
|
29
|
+
value: serializeValue(fieldSchema, parsed),
|
|
30
|
+
},
|
|
31
|
+
];
|
|
32
|
+
}
|
|
33
|
+
export function createObjectFieldSetter(state, key, fieldSchema, rootSchema) {
|
|
34
|
+
return function (value) {
|
|
35
|
+
let parsed;
|
|
36
|
+
try {
|
|
37
|
+
parsed = fieldSchema.parse(value);
|
|
38
|
+
}
|
|
39
|
+
catch (cause) {
|
|
40
|
+
throw SmartObjectError.invalidValue(key, cause);
|
|
41
|
+
}
|
|
42
|
+
const data = state.getData(this);
|
|
43
|
+
const beforeData = deepClone(data);
|
|
44
|
+
const afterData = deepClone(data);
|
|
45
|
+
afterData[key] = parsed;
|
|
46
|
+
const beforeSerialized = serializeDataForPatch(beforeData, rootSchema);
|
|
47
|
+
const afterSerialized = serializeDataForPatch(afterData, rootSchema);
|
|
48
|
+
const patch = normalizeFieldPatch(compare(beforeSerialized, afterSerialized), key, fieldSchema, parsed, beforeSerialized);
|
|
49
|
+
if (patch.length === 0) {
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
afterData[key] = parsed;
|
|
53
|
+
state.setData(this, afterData);
|
|
54
|
+
state.getOperations(this).push(...patch);
|
|
55
|
+
};
|
|
56
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { SmartObjectSchema } from "../../types.js";
|
|
2
|
+
import type { RecordFieldInfo } from "../../zod-introspect.js";
|
|
3
|
+
import type { InstanceState } from "../instance-state.js";
|
|
4
|
+
export declare function createRecordEntryGetter<T>(state: InstanceState<T>, field: RecordFieldInfo): (this: object, key: string) => unknown;
|
|
5
|
+
export declare function createRecordEntrySetter<T>(state: InstanceState<T>, rootSchema: SmartObjectSchema, field: RecordFieldInfo): (this: object, key: string, value: unknown) => void;
|
|
6
|
+
export declare function createRecordEntryDeleter<T>(state: InstanceState<T>, rootSchema: SmartObjectSchema, field: RecordFieldInfo): (this: object, key: string) => void;
|
|
7
|
+
//# sourceMappingURL=record-field.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"record-field.d.ts","sourceRoot":"","sources":["../../../src/smart-object/setters/record-field.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AACxD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAE/D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAuH1D,wBAAgB,uBAAuB,CAAC,CAAC,EAAE,KAAK,EAAE,aAAa,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,eAAe,IACvE,MAAM,MAAM,EAAE,KAAK,MAAM,aAU3C;AAED,wBAAgB,uBAAuB,CAAC,CAAC,EACvC,KAAK,EAAE,aAAa,CAAC,CAAC,CAAC,EACvB,UAAU,EAAE,iBAAiB,EAC7B,KAAK,EAAE,eAAe,IAIL,MAAM,MAAM,EAAE,KAAK,MAAM,EAAE,OAAO,OAAO,UA0C3D;AAED,wBAAgB,wBAAwB,CAAC,CAAC,EACxC,KAAK,EAAE,aAAa,CAAC,CAAC,CAAC,EACvB,UAAU,EAAE,iBAAiB,EAC7B,KAAK,EAAE,eAAe,IAIL,MAAM,MAAM,EAAE,KAAK,MAAM,UA0B3C"}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { SmartObjectError } from "../../errors.js";
|
|
2
|
+
import { serializeDataForPatch, serializeEntryPatchValue } from "../codecs.js";
|
|
3
|
+
import { compare, deepClone } from "../json-patch.js";
|
|
4
|
+
import { readFieldValue } from "../read-field.js";
|
|
5
|
+
function getEntryContainer(data, fieldName, storage) {
|
|
6
|
+
const container = data[fieldName];
|
|
7
|
+
if (storage === "map") {
|
|
8
|
+
return container instanceof Map
|
|
9
|
+
? container
|
|
10
|
+
: new Map();
|
|
11
|
+
}
|
|
12
|
+
if (typeof container === "object" && container !== null && !Array.isArray(container)) {
|
|
13
|
+
return container;
|
|
14
|
+
}
|
|
15
|
+
return {};
|
|
16
|
+
}
|
|
17
|
+
function hasEntry(container, key, storage) {
|
|
18
|
+
if (storage === "map") {
|
|
19
|
+
return container instanceof Map && container.has(key);
|
|
20
|
+
}
|
|
21
|
+
return typeof container === "object" && container !== null && Object.hasOwn(container, key);
|
|
22
|
+
}
|
|
23
|
+
function getEntry(container, key, storage) {
|
|
24
|
+
if (storage === "map" && container instanceof Map) {
|
|
25
|
+
return container.get(key);
|
|
26
|
+
}
|
|
27
|
+
if (typeof container === "object" && container !== null) {
|
|
28
|
+
return container[key];
|
|
29
|
+
}
|
|
30
|
+
return undefined;
|
|
31
|
+
}
|
|
32
|
+
function cloneContainer(container, storage) {
|
|
33
|
+
if (storage === "map") {
|
|
34
|
+
return deepClone(container instanceof Map ? container : new Map());
|
|
35
|
+
}
|
|
36
|
+
return deepClone(typeof container === "object" && container !== null
|
|
37
|
+
? container
|
|
38
|
+
: {});
|
|
39
|
+
}
|
|
40
|
+
function setEntry(container, key, value, storage) {
|
|
41
|
+
if (storage === "map" && container instanceof Map) {
|
|
42
|
+
container.set(key, value);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
container[key] = value;
|
|
46
|
+
}
|
|
47
|
+
function deleteEntry(container, key, storage) {
|
|
48
|
+
if (storage === "map" && container instanceof Map) {
|
|
49
|
+
container.delete(key);
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
delete container[key];
|
|
53
|
+
}
|
|
54
|
+
function normalizeEntryPatches(patch, fieldName, fieldSchema, valueSchema, nextContainer, storage) {
|
|
55
|
+
const prefix = `/${fieldName}/`;
|
|
56
|
+
return patch.map((operation) => {
|
|
57
|
+
if (!("value" in operation) || !operation.path.startsWith(prefix)) {
|
|
58
|
+
return operation;
|
|
59
|
+
}
|
|
60
|
+
const remainder = operation.path.slice(prefix.length);
|
|
61
|
+
if (remainder.includes("/")) {
|
|
62
|
+
return operation;
|
|
63
|
+
}
|
|
64
|
+
const entryKey = remainder.replace(/~1/g, "/").replace(/~0/g, "~");
|
|
65
|
+
const entryValue = storage === "map" && nextContainer instanceof Map
|
|
66
|
+
? nextContainer.get(entryKey)
|
|
67
|
+
: nextContainer[entryKey];
|
|
68
|
+
return {
|
|
69
|
+
...operation,
|
|
70
|
+
value: serializeEntryPatchValue(fieldSchema, valueSchema, entryValue),
|
|
71
|
+
};
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
export function createRecordEntryGetter(state, field) {
|
|
75
|
+
return function (key) {
|
|
76
|
+
const data = state.getData(this);
|
|
77
|
+
const value = getEntry(data[field.fieldName], key, field.storage);
|
|
78
|
+
if (value === undefined) {
|
|
79
|
+
return undefined;
|
|
80
|
+
}
|
|
81
|
+
return readFieldValue(value);
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
export function createRecordEntrySetter(state, rootSchema, field) {
|
|
85
|
+
const { fieldName, fieldSchema, valueSchema, storage } = field;
|
|
86
|
+
return function (key, value) {
|
|
87
|
+
let parsed;
|
|
88
|
+
try {
|
|
89
|
+
parsed = valueSchema.parse(value);
|
|
90
|
+
}
|
|
91
|
+
catch (cause) {
|
|
92
|
+
throw SmartObjectError.invalidValue(`${fieldName}.${key}`, cause);
|
|
93
|
+
}
|
|
94
|
+
const data = state.getData(this);
|
|
95
|
+
const container = getEntryContainer(data, fieldName, storage);
|
|
96
|
+
const previousValue = getEntry(container, key, storage);
|
|
97
|
+
if (hasEntry(container, key, storage) && Object.is(previousValue, parsed)) {
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
const beforeData = deepClone(data);
|
|
101
|
+
const afterData = deepClone(data);
|
|
102
|
+
const nextContainer = cloneContainer(container, storage);
|
|
103
|
+
setEntry(nextContainer, key, parsed, storage);
|
|
104
|
+
afterData[fieldName] = nextContainer;
|
|
105
|
+
const patch = normalizeEntryPatches(compare(serializeDataForPatch(beforeData, rootSchema), serializeDataForPatch(afterData, rootSchema)), fieldName, fieldSchema, valueSchema, nextContainer, storage);
|
|
106
|
+
if (patch.length === 0) {
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
data[fieldName] = nextContainer;
|
|
110
|
+
state.getOperations(this).push(...patch);
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
export function createRecordEntryDeleter(state, rootSchema, field) {
|
|
114
|
+
const { fieldName, storage } = field;
|
|
115
|
+
return function (key) {
|
|
116
|
+
const data = state.getData(this);
|
|
117
|
+
const container = data[fieldName];
|
|
118
|
+
if (!hasEntry(container, key, storage)) {
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
const beforeData = deepClone(data);
|
|
122
|
+
const afterData = deepClone(data);
|
|
123
|
+
const nextContainer = cloneContainer(getEntryContainer(afterData, fieldName, storage), storage);
|
|
124
|
+
deleteEntry(nextContainer, key, storage);
|
|
125
|
+
afterData[fieldName] = nextContainer;
|
|
126
|
+
const patch = compare(serializeDataForPatch(beforeData, rootSchema), serializeDataForPatch(afterData, rootSchema));
|
|
127
|
+
if (patch.length === 0) {
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
data[fieldName] = nextContainer;
|
|
131
|
+
state.getOperations(this).push(...patch);
|
|
132
|
+
};
|
|
133
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { ZodUnionRootLike } from "../../zod-introspect.js";
|
|
2
|
+
import type { InstanceState } from "../instance-state.js";
|
|
3
|
+
export declare function createUnionFieldSetter<T>(state: InstanceState<T>, schema: ZodUnionRootLike, key: string): (this: object, value: unknown) => void;
|
|
4
|
+
//# sourceMappingURL=union-field.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"union-field.d.ts","sourceRoot":"","sources":["../../../src/smart-object/setters/union-field.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAChE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAI1D,wBAAgB,sBAAsB,CAAC,CAAC,EACtC,KAAK,EAAE,aAAa,CAAC,CAAC,CAAC,EACvB,MAAM,EAAE,gBAAgB,EACxB,GAAG,EAAE,MAAM,IAEM,MAAM,MAAM,EAAE,OAAO,OAAO,UAyB9C"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { SmartObjectError } from "../../errors.js";
|
|
2
|
+
import { compare, deepClone } from "../json-patch.js";
|
|
3
|
+
import { assertKeyAllowedOnMatchingVariants, getMatchingVariantObjects } from "../union-variant.js";
|
|
4
|
+
export function createUnionFieldSetter(state, schema, key) {
|
|
5
|
+
return function (value) {
|
|
6
|
+
const matchingVariants = getMatchingVariantObjects(state, this, schema);
|
|
7
|
+
assertKeyAllowedOnMatchingVariants(matchingVariants, key);
|
|
8
|
+
const data = state.getData(this);
|
|
9
|
+
const beforeData = deepClone(data);
|
|
10
|
+
const candidate = { ...deepClone(data), [key]: value };
|
|
11
|
+
let parsed;
|
|
12
|
+
try {
|
|
13
|
+
parsed = schema.parse(candidate);
|
|
14
|
+
}
|
|
15
|
+
catch (cause) {
|
|
16
|
+
throw SmartObjectError.invalidValue(key, cause);
|
|
17
|
+
}
|
|
18
|
+
const patch = compare(beforeData, parsed);
|
|
19
|
+
if (patch.length === 0) {
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
state.setData(this, parsed);
|
|
23
|
+
state.getOperations(this).push(...patch);
|
|
24
|
+
};
|
|
25
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { ZodDiscriminatedUnionLike, ZodObjectLike, ZodUnionRootLike } from "../../zod-introspect.js";
|
|
2
|
+
import type { InstanceState } from "../instance-state.js";
|
|
3
|
+
export declare function createSwitchVariant<T>(state: InstanceState<T>, schema: ZodUnionRootLike): (this: object, value: unknown) => void;
|
|
4
|
+
export declare function createSwitchToVariant<T>(state: InstanceState<T>, schema: ZodDiscriminatedUnionLike, variantSchema: ZodObjectLike, tagValue: string | number | boolean, discriminator: string): (this: object, partial: Record<string, unknown>) => void;
|
|
5
|
+
//# sourceMappingURL=variant-switch.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"variant-switch.d.ts","sourceRoot":"","sources":["../../../src/smart-object/setters/variant-switch.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,yBAAyB,EACzB,aAAa,EACb,gBAAgB,EACjB,MAAM,yBAAyB,CAAC;AACjC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AA8B1D,wBAAgB,mBAAmB,CAAC,CAAC,EAAE,KAAK,EAAE,aAAa,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,gBAAgB,IACrE,MAAM,MAAM,EAAE,OAAO,OAAO,UAG9C;AAED,wBAAgB,qBAAqB,CAAC,CAAC,EACrC,KAAK,EAAE,aAAa,CAAC,CAAC,CAAC,EACvB,MAAM,EAAE,yBAAyB,EACjC,aAAa,EAAE,aAAa,EAC5B,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,EACnC,aAAa,EAAE,MAAM,IAEJ,MAAM,MAAM,EAAE,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,UAahE"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { SmartObjectError } from "../../errors.js";
|
|
2
|
+
import { compare, deepClone } from "../json-patch.js";
|
|
3
|
+
function applyVariantSwitch(state, instance, schema, value) {
|
|
4
|
+
const data = state.getData(instance);
|
|
5
|
+
const beforeData = deepClone(data);
|
|
6
|
+
let parsed;
|
|
7
|
+
try {
|
|
8
|
+
parsed = schema.parse(value);
|
|
9
|
+
}
|
|
10
|
+
catch (cause) {
|
|
11
|
+
throw SmartObjectError.invalidValue("variant", cause);
|
|
12
|
+
}
|
|
13
|
+
const patch = compare(beforeData, parsed);
|
|
14
|
+
if (patch.length === 0) {
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
state.setData(instance, parsed);
|
|
18
|
+
state.getOperations(instance).push(...patch);
|
|
19
|
+
}
|
|
20
|
+
export function createSwitchVariant(state, schema) {
|
|
21
|
+
return function (value) {
|
|
22
|
+
applyVariantSwitch(state, this, schema, value);
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
export function createSwitchToVariant(state, schema, variantSchema, tagValue, discriminator) {
|
|
26
|
+
return function (partial) {
|
|
27
|
+
const candidate = { ...partial, [discriminator]: tagValue };
|
|
28
|
+
let parsed;
|
|
29
|
+
try {
|
|
30
|
+
parsed = variantSchema.parse(candidate);
|
|
31
|
+
}
|
|
32
|
+
catch (cause) {
|
|
33
|
+
throw SmartObjectError.invalidValue("variant", cause);
|
|
34
|
+
}
|
|
35
|
+
applyVariantSwitch(state, this, schema, parsed);
|
|
36
|
+
};
|
|
37
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { type ZodObjectLike, type ZodUnionRootLike } from "../zod-introspect.js";
|
|
2
|
+
import type { InstanceState } from "./instance-state.js";
|
|
3
|
+
export declare function getMatchingVariantObjects<T>(state: InstanceState<T>, instance: object, schema: ZodUnionRootLike): ZodObjectLike[];
|
|
4
|
+
export declare function assertKeyAllowedOnMatchingVariants(matchingVariants: ZodObjectLike[], key: string): void;
|
|
5
|
+
//# sourceMappingURL=union-variant.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"union-variant.d.ts","sourceRoot":"","sources":["../../src/smart-object/union-variant.ts"],"names":[],"mappings":"AACA,OAAO,EAKL,KAAK,aAAa,EAClB,KAAK,gBAAgB,EACtB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAuBzD,wBAAgB,yBAAyB,CAAC,CAAC,EACzC,KAAK,EAAE,aAAa,CAAC,CAAC,CAAC,EACvB,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,gBAAgB,GACvB,aAAa,EAAE,CASjB;AAED,wBAAgB,kCAAkC,CAChD,gBAAgB,EAAE,aAAa,EAAE,EACjC,GAAG,EAAE,MAAM,GACV,IAAI,CAeN"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { SmartObjectError } from "../errors.js";
|
|
2
|
+
import { getDiscriminator, isZodDiscriminatedUnion, isZodObject, } from "../zod-introspect.js";
|
|
3
|
+
function getActiveVariantViaDiscriminator(schema, data) {
|
|
4
|
+
const discriminator = getDiscriminator(schema);
|
|
5
|
+
const tag = data[discriminator];
|
|
6
|
+
for (const option of schema.options) {
|
|
7
|
+
if (!isZodObject(option)) {
|
|
8
|
+
continue;
|
|
9
|
+
}
|
|
10
|
+
const tagSchema = option.shape[discriminator];
|
|
11
|
+
if (tagSchema?.safeParse(tag).success) {
|
|
12
|
+
return option;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
return undefined;
|
|
16
|
+
}
|
|
17
|
+
export function getMatchingVariantObjects(state, instance, schema) {
|
|
18
|
+
const data = state.getData(instance);
|
|
19
|
+
if (isZodDiscriminatedUnion(schema)) {
|
|
20
|
+
const activeVariant = getActiveVariantViaDiscriminator(schema, data);
|
|
21
|
+
return activeVariant ? [activeVariant] : [];
|
|
22
|
+
}
|
|
23
|
+
return schema.options.filter(isZodObject).filter((option) => option.safeParse(data).success);
|
|
24
|
+
}
|
|
25
|
+
export function assertKeyAllowedOnMatchingVariants(matchingVariants, key) {
|
|
26
|
+
if (matchingVariants.length === 0) {
|
|
27
|
+
throw SmartObjectError.invalidUnionState();
|
|
28
|
+
}
|
|
29
|
+
if (matchingVariants.length === 1) {
|
|
30
|
+
if (!(key in matchingVariants[0].shape)) {
|
|
31
|
+
throw SmartObjectError.invalidUnionField(key);
|
|
32
|
+
}
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
if (!matchingVariants.some((variant) => key in variant.shape)) {
|
|
36
|
+
throw SmartObjectError.invalidUnionField(key);
|
|
37
|
+
}
|
|
38
|
+
}
|