@voidhash/mimic 1.0.0-beta.13 → 1.0.0-beta.15
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/.turbo/turbo-build.log +27 -27
- package/dist/Primitive.cjs +1 -0
- package/dist/Primitive.d.cts +2 -2
- package/dist/Primitive.d.mts +2 -2
- package/dist/Primitive.mjs +2 -1
- package/dist/client/ClientDocument.cjs +36 -7
- package/dist/client/ClientDocument.d.mts.map +1 -1
- package/dist/client/ClientDocument.mjs +36 -7
- package/dist/client/ClientDocument.mjs.map +1 -1
- package/dist/primitives/Struct.cjs +38 -13
- package/dist/primitives/Struct.d.cts +1 -0
- package/dist/primitives/Struct.d.cts.map +1 -1
- package/dist/primitives/Struct.d.mts +1 -0
- package/dist/primitives/Struct.d.mts.map +1 -1
- package/dist/primitives/Struct.mjs +39 -14
- package/dist/primitives/Struct.mjs.map +1 -1
- package/dist/primitives/shared.cjs +28 -4
- package/dist/primitives/shared.d.cts +6 -1
- package/dist/primitives/shared.d.cts.map +1 -1
- package/dist/primitives/shared.d.mts +6 -1
- package/dist/primitives/shared.d.mts.map +1 -1
- package/dist/primitives/shared.mjs +28 -5
- package/dist/primitives/shared.mjs.map +1 -1
- package/package.json +2 -2
- package/src/client/ClientDocument.ts +40 -3
- package/src/primitives/Struct.ts +52 -18
- package/src/primitives/shared.ts +51 -5
- package/tests/client/ClientDocument.test.ts +99 -0
- package/tests/primitives/Struct.test.ts +141 -4
- package/tests/primitives/Tree.test.ts +143 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Struct.mjs","names":["OperationDefinition.make","snapshot: Record<string, unknown>","Operation.fromDefinition","newState: InferStructState<TFields>","initialState: Record<string, unknown>","OperationPath.pathsOverlap","partialFields: Record<string, AnyPrimitive>"],"sources":["../../src/primitives/Struct.ts"],"sourcesContent":["import { Schema } from \"effect\";\nimport * as OperationDefinition from \"../OperationDefinition\";\nimport * as Operation from \"../Operation\";\nimport * as OperationPath from \"../OperationPath\";\nimport * as ProxyEnvironment from \"../ProxyEnvironment\";\nimport * as Transform from \"../Transform\";\nimport type { Primitive, PrimitiveInternal, MaybeUndefined, AnyPrimitive, Validator, InferState, InferProxy, InferSnapshot, NeedsValue, InferUpdateInput, InferSetInput } from \"../Primitive\";\nimport { ValidationError } from \"../Primitive\";\nimport { runValidators, applyDefaults } from \"./shared\";\n\n// =============================================================================\n// Struct Set Input Types\n// =============================================================================\n\n/**\n * Determines if a field is required for set() operations.\n * A field is required if: TRequired is true AND THasDefault is false\n */\ntype IsRequiredForSet<T> = T extends Primitive<any, any, true, false> ? true : false;\n\n/**\n * Extract keys of fields that are required for set() (required without default).\n */\ntype RequiredSetKeys<TFields extends Record<string, AnyPrimitive>> = {\n [K in keyof TFields]: IsRequiredForSet<TFields[K]> extends true ? K : never;\n}[keyof TFields];\n\n/**\n * Extract keys of fields that are optional for set() (has default OR not required).\n */\ntype OptionalSetKeys<TFields extends Record<string, AnyPrimitive>> = {\n [K in keyof TFields]: IsRequiredForSet<TFields[K]> extends true ? never : K;\n}[keyof TFields];\n\n/**\n * Compute the input type for set() operations on a struct.\n * Required fields (required without default) must be provided.\n * Optional fields (has default or not required) can be omitted.\n * Uses each field's TSetInput type to handle nested structs correctly.\n */\nexport type StructSetInput<TFields extends Record<string, AnyPrimitive>> = \n { readonly [K in RequiredSetKeys<TFields>]: InferSetInput<TFields[K]> } &\n { readonly [K in OptionalSetKeys<TFields>]?: InferSetInput<TFields[K]> };\n\n/**\n * Input type for set() - respects required/default status of the struct.\n * If the struct is required without a default, the value must be provided.\n * The value itself uses StructSetInput which handles field-level required/default logic.\n */\ntype InferStructSetInput<TFields extends Record<string, AnyPrimitive>, TRequired extends boolean, THasDefault extends boolean> = \n NeedsValue<StructSetInput<TFields>, TRequired, THasDefault>;\n\n/**\n * Input type for update() - always partial since update only modifies specified fields.\n * For nested structs, allows recursive partial updates.\n */\ntype InferStructUpdateInput<TFields extends Record<string, AnyPrimitive>> = StructUpdateValue<TFields>;\n\n\n/**\n * Maps a schema definition to its state type.\n * { name: StringPrimitive, age: NumberPrimitive } -> { name: string, age: number }\n */\nexport type InferStructState<TFields extends Record<string, AnyPrimitive>> = {\n readonly [K in keyof TFields]: InferState<TFields[K]>;\n};\n\n/**\n * Maps a schema definition to its snapshot type.\n * Each field's snapshot type is inferred from the field primitive.\n */\nexport type InferStructSnapshot<TFields extends Record<string, AnyPrimitive>> = {\n readonly [K in keyof TFields]: InferSnapshot<TFields[K]>;\n};\n\n/**\n * Maps a schema definition to a partial update type.\n * Uses each field's TUpdateInput type, which handles nested updates recursively.\n */\nexport type StructUpdateValue<TFields extends Record<string, AnyPrimitive>> = {\n readonly [K in keyof TFields]?: InferUpdateInput<TFields[K]>;\n};\n\n/**\n * Transforms a primitive type to make it optional (TRequired = false).\n * Preserves the primitive structure while changing the required flag.\n */\nexport type MakeOptional<T extends AnyPrimitive> = T extends Primitive<infer S, infer P, any, infer H, infer SI, infer UI>\n ? Primitive<S, P, false, H, SI, UI>\n : T;\n\n/**\n * Maps each field in a struct to its optional version.\n * All fields become optional (TRequired = false).\n */\nexport type PartialFields<TFields extends Record<string, AnyPrimitive>> = {\n [K in keyof TFields]: MakeOptional<TFields[K]>;\n};\n\n/**\n * Maps a schema definition to its proxy type.\n * Provides nested field access + get()/set()/toSnapshot() methods for the whole struct.\n */\nexport type StructProxy<TFields extends Record<string, AnyPrimitive>, TRequired extends boolean = false, THasDefault extends boolean = false> = {\n readonly [K in keyof TFields]: InferProxy<TFields[K]>;\n} & {\n /** Gets the entire struct value */\n get(): MaybeUndefined<InferStructState<TFields>, TRequired, THasDefault>;\n /** Sets the entire struct value (only fields that are required without defaults must be provided) */\n set(value: InferStructSetInput<TFields, TRequired, THasDefault>): void;\n /** Updates only the specified fields (partial update, handles nested structs recursively) */\n update(value: InferStructUpdateInput<TFields>): void;\n /** Returns a readonly snapshot of the struct for rendering */\n toSnapshot(): MaybeUndefined<InferStructSnapshot<TFields>, TRequired, THasDefault>;\n};\n\ninterface StructPrimitiveSchema<TFields extends Record<string, AnyPrimitive>> {\n readonly required: boolean;\n readonly defaultValue: InferStructState<TFields> | undefined;\n readonly fields: TFields;\n readonly validators: readonly Validator<InferStructState<TFields>>[];\n}\n\nexport class StructPrimitive<TFields extends Record<string, AnyPrimitive>, TRequired extends boolean = false, THasDefault extends boolean = false>\n implements Primitive<InferStructState<TFields>, StructProxy<TFields, TRequired, THasDefault>, TRequired, THasDefault, InferStructSetInput<TFields, TRequired, THasDefault>, InferStructUpdateInput<TFields>>\n{\n readonly _tag = \"StructPrimitive\" as const;\n readonly _State!: InferStructState<TFields>;\n readonly _Proxy!: StructProxy<TFields, TRequired, THasDefault>;\n readonly _TRequired!: TRequired;\n readonly _THasDefault!: THasDefault;\n readonly TSetInput!: InferStructSetInput<TFields, TRequired, THasDefault>;\n readonly TUpdateInput!: InferStructUpdateInput<TFields>;\n\n private readonly _schema: StructPrimitiveSchema<TFields>;\n\n private readonly _opDefinitions = {\n set: OperationDefinition.make({\n kind: \"struct.set\" as const,\n payload: Schema.Unknown,\n target: Schema.Unknown,\n apply: (payload) => payload,\n deduplicable: true,\n }),\n };\n\n constructor(schema: StructPrimitiveSchema<TFields>) {\n this._schema = schema;\n }\n\n /** Mark this struct as required */\n required(): StructPrimitive<TFields, true, THasDefault> {\n return new StructPrimitive({\n ...this._schema,\n required: true,\n });\n }\n\n /** Set a default value for this struct */\n default(defaultValue: StructSetInput<TFields>): StructPrimitive<TFields, TRequired, true> {\n // Apply defaults to the provided value\n const merged = applyDefaults(this as AnyPrimitive, defaultValue as Partial<InferStructState<TFields>>) as InferStructState<TFields>;\n return new StructPrimitive({\n ...this._schema,\n defaultValue: merged,\n });\n }\n\n /** Get the fields schema */\n get fields(): TFields {\n return this._schema.fields;\n }\n\n /** Add a custom validation rule (useful for cross-field validation) */\n refine(fn: (value: InferStructState<TFields>) => boolean, message: string): StructPrimitive<TFields, TRequired, THasDefault> {\n return new StructPrimitive({\n ...this._schema,\n validators: [...this._schema.validators, { validate: fn, message }],\n });\n }\n\n /** Extend this struct with additional fields */\n extend<TNewFields extends Record<string, AnyPrimitive>>(\n newFields: TNewFields\n ): StructPrimitive<TFields & TNewFields, TRequired, THasDefault> {\n return new StructPrimitive<TFields & TNewFields, TRequired, THasDefault>({\n required: this._schema.required,\n defaultValue: undefined,\n fields: { ...this._schema.fields, ...newFields } as TFields & TNewFields,\n validators: [],\n });\n }\n\n /** Make all properties of this struct optional (TRequired = false for all fields) */\n partial(): StructPrimitive<PartialFields<TFields>, TRequired, THasDefault> {\n const partialFields: Record<string, AnyPrimitive> = {};\n for (const key in this._schema.fields) {\n const field = this._schema.fields[key]!;\n // Create a new field that is not required (optional)\n partialFields[key] = this._makeFieldOptional(field);\n }\n return new StructPrimitive<PartialFields<TFields>, TRequired, THasDefault>({\n required: this._schema.required,\n defaultValue: undefined,\n fields: partialFields as PartialFields<TFields>,\n validators: [],\n });\n }\n\n private _makeFieldOptional(field: AnyPrimitive): AnyPrimitive {\n // Create a new primitive with required: false\n // We access the _schema property if available, otherwise return as-is\n const fieldAny = field as any;\n if (fieldAny._schema && typeof fieldAny._schema === \"object\") {\n const Constructor = field.constructor as new (schema: any) => AnyPrimitive;\n return new Constructor({\n ...fieldAny._schema,\n required: false,\n });\n }\n return field;\n }\n\n readonly _internal: PrimitiveInternal<InferStructState<TFields>, StructProxy<TFields, TRequired, THasDefault>> = {\n createProxy: (env: ProxyEnvironment.ProxyEnvironment, operationPath: OperationPath.OperationPath): StructProxy<TFields, TRequired, THasDefault> => {\n const fields = this._schema.fields;\n const defaultValue = this._schema.defaultValue;\n\n // Helper to build a snapshot by calling toSnapshot on each field\n const buildSnapshot = (): InferStructSnapshot<TFields> | undefined => {\n const state = env.getState(operationPath);\n \n // Build snapshot from field proxies (they handle their own defaults)\n const snapshot: Record<string, unknown> = {};\n let hasAnyDefinedField = false;\n \n for (const key in fields) {\n const fieldPrimitive = fields[key]!;\n const fieldPath = operationPath.append(key);\n const fieldProxy = fieldPrimitive._internal.createProxy(env, fieldPath);\n const fieldSnapshot = (fieldProxy as { toSnapshot(): unknown }).toSnapshot();\n snapshot[key] = fieldSnapshot;\n if (fieldSnapshot !== undefined) {\n hasAnyDefinedField = true;\n }\n }\n \n // Return undefined only if there's no state, no struct default, and no field snapshots\n if (state === undefined && defaultValue === undefined && !hasAnyDefinedField) {\n return undefined;\n }\n \n return snapshot as InferStructSnapshot<TFields>;\n };\n\n // Create the base object with get/set/update/toSnapshot methods\n const base = {\n get: (): MaybeUndefined<InferStructState<TFields>, TRequired, THasDefault> => {\n const state = env.getState(operationPath) as InferStructState<TFields> | undefined;\n return (state ?? defaultValue) as MaybeUndefined<InferStructState<TFields>, TRequired, THasDefault>;\n },\n set: (value: InferStructSetInput<TFields, TRequired, THasDefault>) => {\n // Apply defaults for missing fields\n const merged = applyDefaults(this as AnyPrimitive, value as Partial<InferStructState<TFields>>) as InferStructState<TFields>;\n env.addOperation(\n Operation.fromDefinition(operationPath, this._opDefinitions.set, merged)\n );\n },\n update: (value: InferStructUpdateInput<TFields>) => {\n for (const key in value) {\n if (Object.prototype.hasOwnProperty.call(value, key)) {\n const fieldValue = value[key as keyof TFields];\n if (fieldValue === undefined) continue; // Skip undefined values\n\n const fieldPrimitive = fields[key as keyof TFields];\n if (!fieldPrimitive) continue; // Skip unknown fields\n\n const fieldPath = operationPath.append(key);\n const fieldProxy = fieldPrimitive._internal.createProxy(env, fieldPath);\n\n // Check if this is a nested struct and value is a plain object (partial update)\n if (\n fieldPrimitive._tag === \"StructPrimitive\" &&\n typeof fieldValue === \"object\" &&\n fieldValue !== null &&\n !Array.isArray(fieldValue)\n ) {\n // Recursively update nested struct\n (fieldProxy as { update: (v: unknown) => void }).update(fieldValue);\n } else {\n // Set the field value directly\n (fieldProxy as { set: (v: unknown) => void }).set(fieldValue);\n }\n }\n }\n },\n toSnapshot: (): MaybeUndefined<InferStructSnapshot<TFields>, TRequired, THasDefault> => {\n const snapshot = buildSnapshot();\n return snapshot as MaybeUndefined<InferStructSnapshot<TFields>, TRequired, THasDefault>;\n },\n };\n\n // Use a JavaScript Proxy to intercept field access\n return new globalThis.Proxy(base as StructProxy<TFields, TRequired, THasDefault>, {\n get: (target, prop, _receiver) => {\n // Return base methods (get, set, update, toSnapshot)\n if (prop === \"get\") {\n return target.get;\n }\n if (prop === \"set\") {\n return target.set;\n }\n if (prop === \"update\") {\n return target.update;\n }\n if (prop === \"toSnapshot\") {\n return target.toSnapshot;\n }\n\n // Handle symbol properties (like Symbol.toStringTag)\n if (typeof prop === \"symbol\") {\n return undefined;\n }\n\n // Check if prop is a field in the schema\n if (prop in fields) {\n const fieldPrimitive = fields[prop as keyof TFields]!;\n const fieldPath = operationPath.append(prop as string);\n return fieldPrimitive._internal.createProxy(env, fieldPath);\n }\n\n return undefined;\n },\n has: (_target, prop) => {\n if (prop === \"get\" || prop === \"set\" || prop === \"update\" || prop === \"toSnapshot\") return true;\n if (typeof prop === \"string\" && prop in fields) return true;\n return false;\n },\n });\n },\n\n applyOperation: (\n state: InferStructState<TFields> | undefined,\n operation: Operation.Operation<any, any, any>\n ): InferStructState<TFields> => {\n const path = operation.path;\n const tokens = path.toTokens().filter((t: string) => t !== \"\");\n\n let newState: InferStructState<TFields>;\n\n // If path is empty or root, this is a struct.set operation\n if (tokens.length === 0) {\n if (operation.kind !== \"struct.set\") {\n throw new ValidationError(`StructPrimitive root cannot apply operation of kind: ${operation.kind}`);\n }\n\n const payload = operation.payload;\n if (typeof payload !== \"object\" || payload === null) {\n throw new ValidationError(`StructPrimitive.set requires an object payload`);\n }\n\n newState = payload as InferStructState<TFields>;\n } else {\n // Otherwise, delegate to the appropriate field primitive\n const fieldName = tokens[0] as keyof TFields;\n if (!(fieldName in this._schema.fields)) {\n throw new ValidationError(`Unknown field: ${globalThis.String(fieldName)}`);\n }\n\n const fieldPrimitive = this._schema.fields[fieldName]!;\n const remainingPath = path.shift();\n const fieldOperation = {\n ...operation,\n path: remainingPath,\n };\n\n // Get the current field state\n const currentState = state ?? ({} as InferStructState<TFields>);\n const currentFieldState = currentState[fieldName] as InferState<typeof fieldPrimitive> | undefined;\n\n // Apply the operation to the field\n const newFieldState = fieldPrimitive._internal.applyOperation(currentFieldState, fieldOperation);\n\n // Build updated state\n newState = {\n ...currentState,\n [fieldName]: newFieldState,\n };\n }\n\n // Run validators on the new state\n runValidators(newState, this._schema.validators);\n\n return newState;\n },\n\n getInitialState: (): InferStructState<TFields> | undefined => {\n if (this._schema.defaultValue !== undefined) {\n return this._schema.defaultValue;\n }\n\n // Build initial state from field defaults\n const fields = this._schema.fields;\n const initialState: Record<string, unknown> = {};\n let hasAnyDefault = false;\n\n for (const key in fields) {\n const fieldDefault = fields[key]!._internal.getInitialState();\n if (fieldDefault !== undefined) {\n initialState[key] = fieldDefault;\n hasAnyDefault = true;\n }\n }\n\n return hasAnyDefault ? (initialState as InferStructState<TFields>) : undefined;\n },\n\n transformOperation: (\n clientOp: Operation.Operation<any, any, any>,\n serverOp: Operation.Operation<any, any, any>\n ): Transform.TransformResult => {\n const clientPath = clientOp.path;\n const serverPath = serverOp.path;\n\n // If paths don't overlap at all, no transformation needed\n if (!OperationPath.pathsOverlap(clientPath, serverPath)) {\n return { type: \"transformed\", operation: clientOp };\n }\n\n const clientTokens = clientPath.toTokens().filter((t: string) => t !== \"\");\n const serverTokens = serverPath.toTokens().filter((t: string) => t !== \"\");\n\n // If both are at root level (struct.set operations)\n if (clientTokens.length === 0 && serverTokens.length === 0) {\n // Client wins (last-write-wins)\n return { type: \"transformed\", operation: clientOp };\n }\n\n // If server set entire struct and client is updating a field\n if (serverTokens.length === 0 && serverOp.kind === \"struct.set\") {\n // Client's field operation proceeds - optimistic update\n // Server will validate/reject if needed\n return { type: \"transformed\", operation: clientOp };\n }\n\n // If client set entire struct and server is updating a field\n if (clientTokens.length === 0 && clientOp.kind === \"struct.set\") {\n // Client's struct.set supersedes server's field update\n return { type: \"transformed\", operation: clientOp };\n }\n\n // Both operations target fields\n if (clientTokens.length > 0 && serverTokens.length > 0) {\n const clientField = clientTokens[0] as keyof TFields;\n const serverField = serverTokens[0] as keyof TFields;\n\n // Different fields - no conflict\n if (clientField !== serverField) {\n return { type: \"transformed\", operation: clientOp };\n }\n\n // Same field - delegate to field primitive\n const fieldPrimitive = this._schema.fields[clientField];\n if (!fieldPrimitive) {\n return { type: \"transformed\", operation: clientOp };\n }\n\n const clientOpForField = {\n ...clientOp,\n path: clientOp.path.shift(),\n };\n const serverOpForField = {\n ...serverOp,\n path: serverOp.path.shift(),\n };\n\n const result = fieldPrimitive._internal.transformOperation(clientOpForField, serverOpForField);\n\n if (result.type === \"transformed\") {\n // Restore the original path\n return {\n type: \"transformed\",\n operation: {\n ...result.operation,\n path: clientOp.path,\n },\n };\n }\n\n return result;\n }\n\n // Default: no transformation needed\n return { type: \"transformed\", operation: clientOp };\n },\n };\n}\n\n/** Creates a new StructPrimitive with the given fields */\nexport const Struct = <TFields extends Record<string, AnyPrimitive>>(\n fields: TFields\n): StructPrimitive<TFields, false, false> =>\n new StructPrimitive({ required: false, defaultValue: undefined, fields, validators: [] });\n\n"],"mappings":";;;;;;;;;;AA2HA,IAAa,kBAAb,MAAa,gBAEb;CAqBE,YAAY,QAAwC;wBApB3C,QAAO;wBACP;wBACA;wBACA;wBACA;wBACA;wBACA;wBAEQ;wBAEA,kBAAiB,EAChC,KAAKA,KAAyB;GAC5B,MAAM;GACN,SAAS,OAAO;GAChB,QAAQ,OAAO;GACf,QAAQ,YAAY;GACpB,cAAc;GACf,CAAC,EACH;wBA+EQ,aAAwG;GAC/G,cAAc,KAAwC,kBAA6F;IACjJ,MAAM,SAAS,KAAK,QAAQ;IAC5B,MAAM,eAAe,KAAK,QAAQ;IAGlC,MAAM,sBAAgE;KACpE,MAAM,QAAQ,IAAI,SAAS,cAAc;KAGzC,MAAMC,WAAoC,EAAE;KAC5C,IAAI,qBAAqB;AAEzB,UAAK,MAAM,OAAO,QAAQ;MACxB,MAAM,iBAAiB,OAAO;MAC9B,MAAM,YAAY,cAAc,OAAO,IAAI;MAE3C,MAAM,gBADa,eAAe,UAAU,YAAY,KAAK,UAAU,CACP,YAAY;AAC5E,eAAS,OAAO;AAChB,UAAI,kBAAkB,OACpB,sBAAqB;;AAKzB,SAAI,UAAU,UAAa,iBAAiB,UAAa,CAAC,mBACxD;AAGF,YAAO;;AAmDT,WAAO,IAAI,WAAW,MA/CT;KACX,WAA8E;MAC5E,MAAM,QAAQ,IAAI,SAAS,cAAc;AACzC,aAAQ,6CAAS;;KAEnB,MAAM,UAAgE;MAEpE,MAAM,SAAS,cAAc,MAAsB,MAA4C;AAC/F,UAAI,aACFC,eAAyB,eAAe,KAAK,eAAe,KAAK,OAAO,CACzE;;KAEH,SAAS,UAA2C;AAClD,WAAK,MAAM,OAAO,MAChB,KAAI,OAAO,UAAU,eAAe,KAAK,OAAO,IAAI,EAAE;OACpD,MAAM,aAAa,MAAM;AACzB,WAAI,eAAe,OAAW;OAE9B,MAAM,iBAAiB,OAAO;AAC9B,WAAI,CAAC,eAAgB;OAErB,MAAM,YAAY,cAAc,OAAO,IAAI;OAC3C,MAAM,aAAa,eAAe,UAAU,YAAY,KAAK,UAAU;AAGvE,WACE,eAAe,SAAS,qBACxB,OAAO,eAAe,YACtB,eAAe,QACf,CAAC,MAAM,QAAQ,WAAW,CAG1B,CAAC,WAAgD,OAAO,WAAW;WAGnE,CAAC,WAA6C,IAAI,WAAW;;;KAKrE,kBAAwF;AAEtF,aADiB,eAAe;;KAGnC,EAGiF;KAChF,MAAM,QAAQ,MAAM,cAAc;AAEhC,UAAI,SAAS,MACX,QAAO,OAAO;AAEhB,UAAI,SAAS,MACX,QAAO,OAAO;AAEhB,UAAI,SAAS,SACX,QAAO,OAAO;AAEhB,UAAI,SAAS,aACX,QAAO,OAAO;AAIhB,UAAI,OAAO,SAAS,SAClB;AAIF,UAAI,QAAQ,QAAQ;OAClB,MAAM,iBAAiB,OAAO;OAC9B,MAAM,YAAY,cAAc,OAAO,KAAe;AACtD,cAAO,eAAe,UAAU,YAAY,KAAK,UAAU;;;KAK/D,MAAM,SAAS,SAAS;AACtB,UAAI,SAAS,SAAS,SAAS,SAAS,SAAS,YAAY,SAAS,aAAc,QAAO;AAC3F,UAAI,OAAO,SAAS,YAAY,QAAQ,OAAQ,QAAO;AACvD,aAAO;;KAEV,CAAC;;GAGJ,iBACE,OACA,cAC8B;IAC9B,MAAM,OAAO,UAAU;IACvB,MAAM,SAAS,KAAK,UAAU,CAAC,QAAQ,MAAc,MAAM,GAAG;IAE9D,IAAIC;AAGJ,QAAI,OAAO,WAAW,GAAG;AACvB,SAAI,UAAU,SAAS,aACrB,OAAM,IAAI,gBAAgB,wDAAwD,UAAU,OAAO;KAGrG,MAAM,UAAU,UAAU;AAC1B,SAAI,OAAO,YAAY,YAAY,YAAY,KAC7C,OAAM,IAAI,gBAAgB,iDAAiD;AAG7E,gBAAW;WACN;KAEL,MAAM,YAAY,OAAO;AACzB,SAAI,EAAE,aAAa,KAAK,QAAQ,QAC9B,OAAM,IAAI,gBAAgB,kBAAkB,WAAW,OAAO,UAAU,GAAG;KAG7E,MAAM,iBAAiB,KAAK,QAAQ,OAAO;KAC3C,MAAM,gBAAgB,KAAK,OAAO;KAClC,MAAM,mDACD,kBACH,MAAM;KAIR,MAAM,eAAe,6CAAU,EAAE;KACjC,MAAM,oBAAoB,aAAa;KAGvC,MAAM,gBAAgB,eAAe,UAAU,eAAe,mBAAmB,eAAe;AAGhG,kDACK,sBACF,YAAY;;AAKjB,kBAAc,UAAU,KAAK,QAAQ,WAAW;AAEhD,WAAO;;GAGT,uBAA8D;AAC5D,QAAI,KAAK,QAAQ,iBAAiB,OAChC,QAAO,KAAK,QAAQ;IAItB,MAAM,SAAS,KAAK,QAAQ;IAC5B,MAAMC,eAAwC,EAAE;IAChD,IAAI,gBAAgB;AAEpB,SAAK,MAAM,OAAO,QAAQ;KACxB,MAAM,eAAe,OAAO,KAAM,UAAU,iBAAiB;AAC7D,SAAI,iBAAiB,QAAW;AAC9B,mBAAa,OAAO;AACpB,sBAAgB;;;AAIpB,WAAO,gBAAiB,eAA6C;;GAGvE,qBACE,UACA,aAC8B;IAC9B,MAAM,aAAa,SAAS;IAC5B,MAAM,aAAa,SAAS;AAG5B,QAAI,CAACC,aAA2B,YAAY,WAAW,CACrD,QAAO;KAAE,MAAM;KAAe,WAAW;KAAU;IAGrD,MAAM,eAAe,WAAW,UAAU,CAAC,QAAQ,MAAc,MAAM,GAAG;IAC1E,MAAM,eAAe,WAAW,UAAU,CAAC,QAAQ,MAAc,MAAM,GAAG;AAG1E,QAAI,aAAa,WAAW,KAAK,aAAa,WAAW,EAEvD,QAAO;KAAE,MAAM;KAAe,WAAW;KAAU;AAIrD,QAAI,aAAa,WAAW,KAAK,SAAS,SAAS,aAGjD,QAAO;KAAE,MAAM;KAAe,WAAW;KAAU;AAIrD,QAAI,aAAa,WAAW,KAAK,SAAS,SAAS,aAEjD,QAAO;KAAE,MAAM;KAAe,WAAW;KAAU;AAIrD,QAAI,aAAa,SAAS,KAAK,aAAa,SAAS,GAAG;KACtD,MAAM,cAAc,aAAa;AAIjC,SAAI,gBAHgB,aAAa,GAI/B,QAAO;MAAE,MAAM;MAAe,WAAW;MAAU;KAIrD,MAAM,iBAAiB,KAAK,QAAQ,OAAO;AAC3C,SAAI,CAAC,eACH,QAAO;MAAE,MAAM;MAAe,WAAW;MAAU;KAGrD,MAAM,qDACD,iBACH,MAAM,SAAS,KAAK,OAAO;KAE7B,MAAM,qDACD,iBACH,MAAM,SAAS,KAAK,OAAO;KAG7B,MAAM,SAAS,eAAe,UAAU,mBAAmB,kBAAkB,iBAAiB;AAE9F,SAAI,OAAO,SAAS,cAElB,QAAO;MACL,MAAM;MACN,6CACK,OAAO,kBACV,MAAM,SAAS;MAElB;AAGH,YAAO;;AAIT,WAAO;KAAE,MAAM;KAAe,WAAW;KAAU;;GAEtD;AA5VC,OAAK,UAAU;;;CAIjB,WAAwD;AACtD,SAAO,IAAI,kDACN,KAAK,gBACR,UAAU,QACV;;;CAIJ,QAAQ,cAAkF;EAExF,MAAM,SAAS,cAAc,MAAsB,aAAmD;AACtG,SAAO,IAAI,kDACN,KAAK,gBACR,cAAc,UACd;;;CAIJ,IAAI,SAAkB;AACpB,SAAO,KAAK,QAAQ;;;CAItB,OAAO,IAAmD,SAAmE;AAC3H,SAAO,IAAI,kDACN,KAAK,gBACR,YAAY,CAAC,GAAG,KAAK,QAAQ,YAAY;GAAE,UAAU;GAAI;GAAS,CAAC,IACnE;;;CAIJ,OACE,WAC+D;AAC/D,SAAO,IAAI,gBAA8D;GACvE,UAAU,KAAK,QAAQ;GACvB,cAAc;GACd,0CAAa,KAAK,QAAQ,SAAW;GACrC,YAAY,EAAE;GACf,CAAC;;;CAIJ,UAA2E;EACzE,MAAMC,gBAA8C,EAAE;AACtD,OAAK,MAAM,OAAO,KAAK,QAAQ,QAAQ;GACrC,MAAM,QAAQ,KAAK,QAAQ,OAAO;AAElC,iBAAc,OAAO,KAAK,mBAAmB,MAAM;;AAErD,SAAO,IAAI,gBAAgE;GACzE,UAAU,KAAK,QAAQ;GACvB,cAAc;GACd,QAAQ;GACR,YAAY,EAAE;GACf,CAAC;;CAGJ,AAAQ,mBAAmB,OAAmC;EAG5D,MAAM,WAAW;AACjB,MAAI,SAAS,WAAW,OAAO,SAAS,YAAY,UAAU;GAC5D,MAAM,cAAc,MAAM;AAC1B,UAAO,IAAI,8CACN,SAAS,gBACZ,UAAU,SACV;;AAEJ,SAAO;;;;AAuRX,MAAa,UACX,WAEA,IAAI,gBAAgB;CAAE,UAAU;CAAO,cAAc;CAAW;CAAQ,YAAY,EAAE;CAAE,CAAC"}
|
|
1
|
+
{"version":3,"file":"Struct.mjs","names":["OperationDefinition.make","snapshot: Record<string, unknown>","Operation.fromDefinition","newState: InferStructState<TFields>","initialState: Record<string, unknown>","OperationPath.pathsOverlap","partialFields: Record<string, AnyPrimitive>"],"sources":["../../src/primitives/Struct.ts"],"sourcesContent":["import { Schema } from \"effect\";\nimport * as OperationDefinition from \"../OperationDefinition\";\nimport * as Operation from \"../Operation\";\nimport * as OperationPath from \"../OperationPath\";\nimport * as ProxyEnvironment from \"../ProxyEnvironment\";\nimport * as Transform from \"../Transform\";\nimport type { Primitive, PrimitiveInternal, MaybeUndefined, AnyPrimitive, Validator, InferState, InferProxy, InferSnapshot, NeedsValue, InferUpdateInput, InferSetInput } from \"../Primitive\";\nimport { ValidationError } from \"../Primitive\";\nimport { runValidators, applyDefaults, primitiveAllowsNullValue } from \"./shared\";\n\n// =============================================================================\n// Struct Set Input Types\n// =============================================================================\n\n/**\n * Determines if a field is required for set() operations.\n * A field is required if: TRequired is true AND THasDefault is false\n */\ntype IsRequiredForSet<T> = T extends Primitive<any, any, true, false> ? true : false;\n\n/**\n * Extract keys of fields that are required for set() (required without default).\n */\ntype RequiredSetKeys<TFields extends Record<string, AnyPrimitive>> = {\n [K in keyof TFields]: IsRequiredForSet<TFields[K]> extends true ? K : never;\n}[keyof TFields];\n\n/**\n * Extract keys of fields that are optional for set() (has default OR not required).\n */\ntype OptionalSetKeys<TFields extends Record<string, AnyPrimitive>> = {\n [K in keyof TFields]: IsRequiredForSet<TFields[K]> extends true ? never : K;\n}[keyof TFields];\n\n/**\n * Compute the input type for set() operations on a struct.\n * Required fields (required without default) must be provided.\n * Optional fields (has default or not required) can be omitted.\n * Uses each field's TSetInput type to handle nested structs correctly.\n */\nexport type StructSetInput<TFields extends Record<string, AnyPrimitive>> = \n { readonly [K in RequiredSetKeys<TFields>]: InferSetInput<TFields[K]> } &\n { readonly [K in OptionalSetKeys<TFields>]?: InferSetInput<TFields[K]> };\n\n/**\n * Input type for set() - respects required/default status of the struct.\n * If the struct is required without a default, the value must be provided.\n * The value itself uses StructSetInput which handles field-level required/default logic.\n */\ntype InferStructSetInput<TFields extends Record<string, AnyPrimitive>, TRequired extends boolean, THasDefault extends boolean> = \n NeedsValue<StructSetInput<TFields>, TRequired, THasDefault>;\n\n/**\n * Input type for update() - always partial since update only modifies specified fields.\n * For nested structs, allows recursive partial updates.\n */\ntype InferStructUpdateInput<TFields extends Record<string, AnyPrimitive>> = StructUpdateValue<TFields>;\n\n\n/**\n * Maps a schema definition to its state type.\n * { name: StringPrimitive, age: NumberPrimitive } -> { name: string, age: number }\n */\nexport type InferStructState<TFields extends Record<string, AnyPrimitive>> = {\n readonly [K in keyof TFields]: InferState<TFields[K]>;\n};\n\n/**\n * Maps a schema definition to its snapshot type.\n * Each field's snapshot type is inferred from the field primitive.\n */\nexport type InferStructSnapshot<TFields extends Record<string, AnyPrimitive>> = {\n readonly [K in keyof TFields]: InferSnapshot<TFields[K]>;\n};\n\n/**\n * Maps a schema definition to a partial update type.\n * Uses each field's TUpdateInput type, which handles nested updates recursively.\n */\nexport type StructUpdateValue<TFields extends Record<string, AnyPrimitive>> = {\n readonly [K in keyof TFields]?: InferUpdateInput<TFields[K]>;\n};\n\n/**\n * Transforms a primitive type to make it optional (TRequired = false).\n * Preserves the primitive structure while changing the required flag.\n */\nexport type MakeOptional<T extends AnyPrimitive> = T extends Primitive<infer S, infer P, any, infer H, infer SI, infer UI>\n ? Primitive<S, P, false, H, SI, UI>\n : T;\n\n/**\n * Maps each field in a struct to its optional version.\n * All fields become optional (TRequired = false).\n */\nexport type PartialFields<TFields extends Record<string, AnyPrimitive>> = {\n [K in keyof TFields]: MakeOptional<TFields[K]>;\n};\n\n/**\n * Maps a schema definition to its proxy type.\n * Provides nested field access + get()/set()/toSnapshot() methods for the whole struct.\n */\nexport type StructProxy<TFields extends Record<string, AnyPrimitive>, TRequired extends boolean = false, THasDefault extends boolean = false> = {\n readonly [K in keyof TFields]: InferProxy<TFields[K]>;\n} & {\n /** Gets the entire struct value */\n get(): MaybeUndefined<InferStructState<TFields>, TRequired, THasDefault>;\n /** Sets the entire struct value (only fields that are required without defaults must be provided) */\n set(value: InferStructSetInput<TFields, TRequired, THasDefault>): void;\n /** Updates only the specified fields (partial update, handles nested structs recursively) */\n update(value: InferStructUpdateInput<TFields>): void;\n /** Returns a readonly snapshot of the struct for rendering */\n toSnapshot(): MaybeUndefined<InferStructSnapshot<TFields>, TRequired, THasDefault>;\n};\n\ninterface StructPrimitiveSchema<TFields extends Record<string, AnyPrimitive>> {\n readonly required: boolean;\n readonly defaultValue: InferStructState<TFields> | undefined;\n readonly fields: TFields;\n readonly validators: readonly Validator<InferStructState<TFields>>[];\n}\n\nexport class StructPrimitive<TFields extends Record<string, AnyPrimitive>, TRequired extends boolean = false, THasDefault extends boolean = false>\n implements Primitive<InferStructState<TFields>, StructProxy<TFields, TRequired, THasDefault>, TRequired, THasDefault, InferStructSetInput<TFields, TRequired, THasDefault>, InferStructUpdateInput<TFields>>\n{\n readonly _tag = \"StructPrimitive\" as const;\n readonly _State!: InferStructState<TFields>;\n readonly _Proxy!: StructProxy<TFields, TRequired, THasDefault>;\n readonly _TRequired!: TRequired;\n readonly _THasDefault!: THasDefault;\n readonly TSetInput!: InferStructSetInput<TFields, TRequired, THasDefault>;\n readonly TUpdateInput!: InferStructUpdateInput<TFields>;\n\n private readonly _schema: StructPrimitiveSchema<TFields>;\n\n private readonly _opDefinitions = {\n set: OperationDefinition.make({\n kind: \"struct.set\" as const,\n payload: Schema.Unknown,\n target: Schema.Unknown,\n apply: (payload) => payload,\n deduplicable: true,\n }),\n unset: OperationDefinition.make({\n kind: \"struct.unset\" as const,\n payload: Schema.Unknown,\n target: Schema.Unknown,\n apply: (payload) => payload,\n deduplicable: true,\n }),\n };\n\n constructor(schema: StructPrimitiveSchema<TFields>) {\n this._schema = schema;\n }\n\n /** Mark this struct as required */\n required(): StructPrimitive<TFields, true, THasDefault> {\n return new StructPrimitive({\n ...this._schema,\n required: true,\n });\n }\n\n /** Set a default value for this struct */\n default(defaultValue: StructSetInput<TFields>): StructPrimitive<TFields, TRequired, true> {\n // Apply defaults to the provided value\n const merged = applyDefaults(this as AnyPrimitive, defaultValue as Partial<InferStructState<TFields>>) as InferStructState<TFields>;\n return new StructPrimitive({\n ...this._schema,\n defaultValue: merged,\n });\n }\n\n /** Get the fields schema */\n get fields(): TFields {\n return this._schema.fields;\n }\n\n /** Add a custom validation rule (useful for cross-field validation) */\n refine(fn: (value: InferStructState<TFields>) => boolean, message: string): StructPrimitive<TFields, TRequired, THasDefault> {\n return new StructPrimitive({\n ...this._schema,\n validators: [...this._schema.validators, { validate: fn, message }],\n });\n }\n\n /** Extend this struct with additional fields */\n extend<TNewFields extends Record<string, AnyPrimitive>>(\n newFields: TNewFields\n ): StructPrimitive<TFields & TNewFields, TRequired, THasDefault> {\n return new StructPrimitive<TFields & TNewFields, TRequired, THasDefault>({\n required: this._schema.required,\n defaultValue: undefined,\n fields: { ...this._schema.fields, ...newFields } as TFields & TNewFields,\n validators: [],\n });\n }\n\n /** Make all properties of this struct optional (TRequired = false for all fields) */\n partial(): StructPrimitive<PartialFields<TFields>, TRequired, THasDefault> {\n const partialFields: Record<string, AnyPrimitive> = {};\n for (const key in this._schema.fields) {\n const field = this._schema.fields[key]!;\n // Create a new field that is not required (optional)\n partialFields[key] = this._makeFieldOptional(field);\n }\n return new StructPrimitive<PartialFields<TFields>, TRequired, THasDefault>({\n required: this._schema.required,\n defaultValue: undefined,\n fields: partialFields as PartialFields<TFields>,\n validators: [],\n });\n }\n\n private _makeFieldOptional(field: AnyPrimitive): AnyPrimitive {\n // Create a new primitive with required: false\n // We access the _schema property if available, otherwise return as-is\n const fieldAny = field as any;\n if (fieldAny._schema && typeof fieldAny._schema === \"object\") {\n const Constructor = field.constructor as new (schema: any) => AnyPrimitive;\n return new Constructor({\n ...fieldAny._schema,\n required: false,\n });\n }\n return field;\n }\n\n private _isRequiredWithoutDefault(field: AnyPrimitive): boolean {\n const fieldDefault = field._internal.getInitialState();\n return (\n (field as { _schema?: { required?: boolean } })._schema?.required === true &&\n fieldDefault === undefined\n );\n }\n\n readonly _internal: PrimitiveInternal<InferStructState<TFields>, StructProxy<TFields, TRequired, THasDefault>> = {\n createProxy: (env: ProxyEnvironment.ProxyEnvironment, operationPath: OperationPath.OperationPath): StructProxy<TFields, TRequired, THasDefault> => {\n const fields = this._schema.fields;\n const defaultValue = this._schema.defaultValue;\n\n // Helper to build a snapshot by calling toSnapshot on each field\n const buildSnapshot = (): InferStructSnapshot<TFields> | undefined => {\n const state = env.getState(operationPath);\n \n // Build snapshot from field proxies (they handle their own defaults)\n const snapshot: Record<string, unknown> = {};\n let hasAnyDefinedField = false;\n \n for (const key in fields) {\n const fieldPrimitive = fields[key]!;\n const fieldPath = operationPath.append(key);\n const fieldProxy = fieldPrimitive._internal.createProxy(env, fieldPath);\n const fieldSnapshot = (fieldProxy as { toSnapshot(): unknown }).toSnapshot();\n snapshot[key] = fieldSnapshot;\n if (fieldSnapshot !== undefined) {\n hasAnyDefinedField = true;\n }\n }\n \n // Return undefined only if there's no state, no struct default, and no field snapshots\n if (state === undefined && defaultValue === undefined && !hasAnyDefinedField) {\n return undefined;\n }\n \n return snapshot as InferStructSnapshot<TFields>;\n };\n\n // Create the base object with get/set/update/toSnapshot methods\n const base = {\n get: (): MaybeUndefined<InferStructState<TFields>, TRequired, THasDefault> => {\n const state = env.getState(operationPath) as InferStructState<TFields> | undefined;\n return (state ?? defaultValue) as MaybeUndefined<InferStructState<TFields>, TRequired, THasDefault>;\n },\n set: (value: InferStructSetInput<TFields, TRequired, THasDefault>) => {\n // Apply defaults for missing fields\n const merged = applyDefaults(this as AnyPrimitive, value as Partial<InferStructState<TFields>>) as InferStructState<TFields>;\n env.addOperation(\n Operation.fromDefinition(operationPath, this._opDefinitions.set, merged)\n );\n },\n update: (value: InferStructUpdateInput<TFields>) => {\n for (const key in value) {\n if (Object.prototype.hasOwnProperty.call(value, key)) {\n const fieldValue = value[key as keyof TFields];\n const fieldPrimitive = fields[key as keyof TFields];\n if (!fieldPrimitive) continue; // Skip unknown fields\n\n const fieldPath = operationPath.append(key);\n\n const shouldUnset =\n fieldValue === undefined ||\n (fieldValue === null && !primitiveAllowsNullValue(fieldPrimitive));\n if (shouldUnset) {\n if (this._isRequiredWithoutDefault(fieldPrimitive)) {\n throw new ValidationError(`Field \"${key}\" is required and cannot be null or undefined`);\n }\n env.addOperation(\n Operation.fromDefinition(fieldPath, this._opDefinitions.unset, null)\n );\n continue;\n }\n\n const fieldProxy = fieldPrimitive._internal.createProxy(env, fieldPath);\n\n // Check if this is a nested struct and value is a plain object (partial update)\n if (\n fieldPrimitive._tag === \"StructPrimitive\" &&\n typeof fieldValue === \"object\" &&\n fieldValue !== null &&\n !Array.isArray(fieldValue)\n ) {\n // Recursively update nested struct\n (fieldProxy as { update: (v: unknown) => void }).update(fieldValue);\n } else {\n // Set the field value directly\n (fieldProxy as { set: (v: unknown) => void }).set(fieldValue);\n }\n }\n }\n },\n toSnapshot: (): MaybeUndefined<InferStructSnapshot<TFields>, TRequired, THasDefault> => {\n const snapshot = buildSnapshot();\n return snapshot as MaybeUndefined<InferStructSnapshot<TFields>, TRequired, THasDefault>;\n },\n };\n\n // Use a JavaScript Proxy to intercept field access\n return new globalThis.Proxy(base as StructProxy<TFields, TRequired, THasDefault>, {\n get: (target, prop, _receiver) => {\n // Return base methods (get, set, update, toSnapshot)\n if (prop === \"get\") {\n return target.get;\n }\n if (prop === \"set\") {\n return target.set;\n }\n if (prop === \"update\") {\n return target.update;\n }\n if (prop === \"toSnapshot\") {\n return target.toSnapshot;\n }\n\n // Handle symbol properties (like Symbol.toStringTag)\n if (typeof prop === \"symbol\") {\n return undefined;\n }\n\n // Check if prop is a field in the schema\n if (prop in fields) {\n const fieldPrimitive = fields[prop as keyof TFields]!;\n const fieldPath = operationPath.append(prop as string);\n return fieldPrimitive._internal.createProxy(env, fieldPath);\n }\n\n return undefined;\n },\n has: (_target, prop) => {\n if (prop === \"get\" || prop === \"set\" || prop === \"update\" || prop === \"toSnapshot\") return true;\n if (typeof prop === \"string\" && prop in fields) return true;\n return false;\n },\n });\n },\n\n applyOperation: (\n state: InferStructState<TFields> | undefined,\n operation: Operation.Operation<any, any, any>\n ): InferStructState<TFields> => {\n const path = operation.path;\n const tokens = path.toTokens().filter((t: string) => t !== \"\");\n\n let newState: InferStructState<TFields>;\n\n // If path is empty or root, this is a struct.set operation\n if (tokens.length === 0) {\n if (operation.kind !== \"struct.set\") {\n throw new ValidationError(`StructPrimitive root cannot apply operation of kind: ${operation.kind}`);\n }\n\n const payload = operation.payload;\n if (typeof payload !== \"object\" || payload === null) {\n throw new ValidationError(`StructPrimitive.set requires an object payload`);\n }\n\n newState = payload as InferStructState<TFields>;\n } else {\n // Otherwise, delegate to the appropriate field primitive\n const fieldName = tokens[0] as keyof TFields;\n if (!(fieldName in this._schema.fields)) {\n throw new ValidationError(`Unknown field: ${globalThis.String(fieldName)}`);\n }\n\n const fieldPrimitive = this._schema.fields[fieldName]!;\n // Get the current field state\n const currentState = state ?? ({} as InferStructState<TFields>);\n if (operation.kind === \"struct.unset\" && tokens.length === 1) {\n if (this._isRequiredWithoutDefault(fieldPrimitive)) {\n throw new ValidationError(`Field \"${globalThis.String(fieldName)}\" is required and cannot be removed`);\n }\n const mutableState = { ...currentState } as Record<string, unknown>;\n delete mutableState[globalThis.String(fieldName)];\n newState = mutableState as InferStructState<TFields>;\n } else {\n const remainingPath = path.shift();\n const fieldOperation = {\n ...operation,\n path: remainingPath,\n };\n const currentFieldState = currentState[fieldName] as InferState<typeof fieldPrimitive> | undefined;\n\n // Apply the operation to the field\n const newFieldState = fieldPrimitive._internal.applyOperation(currentFieldState, fieldOperation);\n\n // Build updated state\n newState = {\n ...currentState,\n [fieldName]: newFieldState,\n };\n }\n }\n\n // Run validators on the new state\n runValidators(newState, this._schema.validators);\n\n return newState;\n },\n\n getInitialState: (): InferStructState<TFields> | undefined => {\n if (this._schema.defaultValue !== undefined) {\n return this._schema.defaultValue;\n }\n\n // Build initial state from field defaults\n const fields = this._schema.fields;\n const initialState: Record<string, unknown> = {};\n let hasAnyDefault = false;\n\n for (const key in fields) {\n const fieldDefault = fields[key]!._internal.getInitialState();\n if (fieldDefault !== undefined) {\n initialState[key] = fieldDefault;\n hasAnyDefault = true;\n }\n }\n\n return hasAnyDefault ? (initialState as InferStructState<TFields>) : undefined;\n },\n\n transformOperation: (\n clientOp: Operation.Operation<any, any, any>,\n serverOp: Operation.Operation<any, any, any>\n ): Transform.TransformResult => {\n const clientPath = clientOp.path;\n const serverPath = serverOp.path;\n\n // If paths don't overlap at all, no transformation needed\n if (!OperationPath.pathsOverlap(clientPath, serverPath)) {\n return { type: \"transformed\", operation: clientOp };\n }\n\n const clientTokens = clientPath.toTokens().filter((t: string) => t !== \"\");\n const serverTokens = serverPath.toTokens().filter((t: string) => t !== \"\");\n\n // If both are at root level (struct.set operations)\n if (clientTokens.length === 0 && serverTokens.length === 0) {\n // Client wins (last-write-wins)\n return { type: \"transformed\", operation: clientOp };\n }\n\n // If server set entire struct and client is updating a field\n if (serverTokens.length === 0 && serverOp.kind === \"struct.set\") {\n // Client's field operation proceeds - optimistic update\n // Server will validate/reject if needed\n return { type: \"transformed\", operation: clientOp };\n }\n\n // If client set entire struct and server is updating a field\n if (clientTokens.length === 0 && clientOp.kind === \"struct.set\") {\n // Client's struct.set supersedes server's field update\n return { type: \"transformed\", operation: clientOp };\n }\n\n // Both operations target fields\n if (clientTokens.length > 0 && serverTokens.length > 0) {\n const clientField = clientTokens[0] as keyof TFields;\n const serverField = serverTokens[0] as keyof TFields;\n\n // Different fields - no conflict\n if (clientField !== serverField) {\n return { type: \"transformed\", operation: clientOp };\n }\n\n // Same field - delegate to field primitive\n const fieldPrimitive = this._schema.fields[clientField];\n if (!fieldPrimitive) {\n return { type: \"transformed\", operation: clientOp };\n }\n\n const clientOpForField = {\n ...clientOp,\n path: clientOp.path.shift(),\n };\n const serverOpForField = {\n ...serverOp,\n path: serverOp.path.shift(),\n };\n\n const result = fieldPrimitive._internal.transformOperation(clientOpForField, serverOpForField);\n\n if (result.type === \"transformed\") {\n // Restore the original path\n return {\n type: \"transformed\",\n operation: {\n ...result.operation,\n path: clientOp.path,\n },\n };\n }\n\n return result;\n }\n\n // Default: no transformation needed\n return { type: \"transformed\", operation: clientOp };\n },\n };\n}\n\n/** Creates a new StructPrimitive with the given fields */\nexport const Struct = <TFields extends Record<string, AnyPrimitive>>(\n fields: TFields\n): StructPrimitive<TFields, false, false> =>\n new StructPrimitive({ required: false, defaultValue: undefined, fields, validators: [] });\n"],"mappings":"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"}
|
|
@@ -16,6 +16,19 @@ function runValidators(value, validators) {
|
|
|
16
16
|
for (const validator of validators) if (!validator.validate(value)) throw new ValidationError(validator.message);
|
|
17
17
|
}
|
|
18
18
|
/**
|
|
19
|
+
* Returns true if a primitive can represent null as a meaningful value.
|
|
20
|
+
* This is used to avoid pruning explicit null values for null-capable scalar unions.
|
|
21
|
+
*/
|
|
22
|
+
function primitiveAllowsNullValue(primitive) {
|
|
23
|
+
if (primitive._tag === "LiteralPrimitive") return primitive.literal === null;
|
|
24
|
+
if (primitive._tag === "EitherPrimitive") {
|
|
25
|
+
var _schema;
|
|
26
|
+
const variants = (_schema = primitive._schema) === null || _schema === void 0 ? void 0 : _schema.variants;
|
|
27
|
+
return Array.isArray(variants) && variants.some((variant) => primitiveAllowsNullValue(variant));
|
|
28
|
+
}
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
19
32
|
* Checks if an operation is compatible with the given operation definitions.
|
|
20
33
|
* @param operation - The operation to check.
|
|
21
34
|
* @param operationDefinitions - The operation definitions to check against.
|
|
@@ -41,13 +54,23 @@ function applyDefaults(primitive, value) {
|
|
|
41
54
|
var _structPrimitive$_int;
|
|
42
55
|
const structPrimitive = primitive;
|
|
43
56
|
const result = require_objectSpread2._objectSpread2(require_objectSpread2._objectSpread2({}, (_structPrimitive$_int = structPrimitive._internal.getInitialState()) !== null && _structPrimitive$_int !== void 0 ? _structPrimitive$_int : {}), value);
|
|
57
|
+
const inputObject = typeof value === "object" && value !== null ? value : void 0;
|
|
44
58
|
for (const key in structPrimitive.fields) {
|
|
59
|
+
var _schema2;
|
|
45
60
|
const fieldPrimitive = structPrimitive.fields[key];
|
|
46
|
-
|
|
47
|
-
|
|
61
|
+
const hasExplicitKey = inputObject !== void 0 && Object.prototype.hasOwnProperty.call(inputObject, key);
|
|
62
|
+
const explicitValue = hasExplicitKey ? inputObject[key] : void 0;
|
|
63
|
+
const fieldDefault = fieldPrimitive._internal.getInitialState();
|
|
64
|
+
const isRequiredWithoutDefault = ((_schema2 = fieldPrimitive._schema) === null || _schema2 === void 0 ? void 0 : _schema2.required) === true && fieldDefault === void 0;
|
|
65
|
+
if (hasExplicitKey && (explicitValue === void 0 || explicitValue === null && !primitiveAllowsNullValue(fieldPrimitive))) {
|
|
66
|
+
if (isRequiredWithoutDefault) throw new ValidationError(`Field "${key}" is required and cannot be null or undefined`);
|
|
67
|
+
delete result[key];
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
if (!hasExplicitKey && result[key] === void 0) {
|
|
48
71
|
if (fieldDefault !== void 0) result[key] = fieldDefault;
|
|
49
|
-
} else if (typeof
|
|
50
|
-
if (fieldPrimitive._tag === "StructPrimitive" || fieldPrimitive._tag === "UnionPrimitive") result[key] = applyDefaults(fieldPrimitive,
|
|
72
|
+
} else if (hasExplicitKey && typeof explicitValue === "object" && explicitValue !== null) {
|
|
73
|
+
if (fieldPrimitive._tag === "StructPrimitive" || fieldPrimitive._tag === "UnionPrimitive") result[key] = applyDefaults(fieldPrimitive, explicitValue);
|
|
51
74
|
}
|
|
52
75
|
}
|
|
53
76
|
return result;
|
|
@@ -81,4 +104,5 @@ function applyDefaults(primitive, value) {
|
|
|
81
104
|
exports.ValidationError = ValidationError;
|
|
82
105
|
exports.applyDefaults = applyDefaults;
|
|
83
106
|
exports.isCompatibleOperation = isCompatibleOperation;
|
|
107
|
+
exports.primitiveAllowsNullValue = primitiveAllowsNullValue;
|
|
84
108
|
exports.runValidators = runValidators;
|
|
@@ -131,6 +131,11 @@ declare function runValidators<T>(value: T, validators: readonly {
|
|
|
131
131
|
validate: (value: T) => boolean;
|
|
132
132
|
message: string;
|
|
133
133
|
}[]): void;
|
|
134
|
+
/**
|
|
135
|
+
* Returns true if a primitive can represent null as a meaningful value.
|
|
136
|
+
* This is used to avoid pruning explicit null values for null-capable scalar unions.
|
|
137
|
+
*/
|
|
138
|
+
declare function primitiveAllowsNullValue(primitive: AnyPrimitive): boolean;
|
|
134
139
|
/**
|
|
135
140
|
* Checks if an operation is compatible with the given operation definitions.
|
|
136
141
|
* @param operation - The operation to check.
|
|
@@ -152,5 +157,5 @@ declare function isCompatibleOperation(operation: Operation<any, any, any>, oper
|
|
|
152
157
|
*/
|
|
153
158
|
declare function applyDefaults<T extends AnyPrimitive>(primitive: T, value: Partial<InferState<T>>): InferState<T>;
|
|
154
159
|
//#endregion
|
|
155
|
-
export { AnyPrimitive, HasDefault, InferProxy, InferSetInput, InferSnapshot, InferState, InferUpdateInput, IsDefined, IsRequired, MaybeUndefined, NeedsValue, Optional, Primitive, PrimitiveInternal, ValidationError, Validator, applyDefaults, isCompatibleOperation, runValidators };
|
|
160
|
+
export { AnyPrimitive, HasDefault, InferProxy, InferSetInput, InferSnapshot, InferState, InferUpdateInput, IsDefined, IsRequired, MaybeUndefined, NeedsValue, Optional, Primitive, PrimitiveInternal, ValidationError, Validator, applyDefaults, isCompatibleOperation, primitiveAllowsNullValue, runValidators };
|
|
156
161
|
//# sourceMappingURL=shared.d.cts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"shared.d.cts","names":[],"sources":["../../src/primitives/shared.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;AAmBA;;;;;;;AAMwB,UANP,SAMO,CAAA,MAAA,EAAA,MAAA,EAAA,kBAAA,OAAA,GAAA,KAAA,EAAA,oBAAA,OAAA,GAAA,KAAA,EAAA,YAAA,OAAA,EAAA,eAAA,OAAA,CAAA,CAAA;EACA,SAAA,IAAA,EAAA,MAAA;EACG,SAAA,MAAA,EANN,MAMM;EAAY,SAAA,MAAA,EALlB,MAKkB;EAMpB,SAAA,UAAA,EAVM,SAUW;EAEJ,SAAA,YAAA,EAXL,WAWK;EAAyC,SAAA,SAAA,EAVjD,iBAUiD,CAV/B,MAU+B,EAVvB,MAUuB,CAAA;EAAgC,SAAA,SAAA,EATjF,SASiF;EAEpE,SAAA,YAAA,EAVV,YAUU;;;;;AAqBrB,UAzBG,iBAyBH,CAAA,MAAA,EAAA,MAAA,CAAA,CAAA;EACA;EACP,SAAA,WAAA,EAAA,CAAA,GAAA,EAzBuB,gBAyBvB,EAAA,IAAA,EAzBgE,aAyBhE,EAAA,GAzBgG,MAyBhG;EAAyB;EAMpB,SAAA,cAAY,EAAA,CAAA,KAAG,EA7BQ,MA6BC,GAAA,SAAA,EAAA,SAAA,EA7B8B,SA6B9B,CAAA,GAAA,EAAA,GAAA,EAAA,GAAA,CAAA,EAAA,GA7BqE,MA6BrE;EAKxB;EAKA,SAAA,eAAU,
|
|
1
|
+
{"version":3,"file":"shared.d.cts","names":[],"sources":["../../src/primitives/shared.ts"],"sourcesContent":[],"mappings":"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"}
|
|
@@ -131,6 +131,11 @@ declare function runValidators<T>(value: T, validators: readonly {
|
|
|
131
131
|
validate: (value: T) => boolean;
|
|
132
132
|
message: string;
|
|
133
133
|
}[]): void;
|
|
134
|
+
/**
|
|
135
|
+
* Returns true if a primitive can represent null as a meaningful value.
|
|
136
|
+
* This is used to avoid pruning explicit null values for null-capable scalar unions.
|
|
137
|
+
*/
|
|
138
|
+
declare function primitiveAllowsNullValue(primitive: AnyPrimitive): boolean;
|
|
134
139
|
/**
|
|
135
140
|
* Checks if an operation is compatible with the given operation definitions.
|
|
136
141
|
* @param operation - The operation to check.
|
|
@@ -152,5 +157,5 @@ declare function isCompatibleOperation(operation: Operation<any, any, any>, oper
|
|
|
152
157
|
*/
|
|
153
158
|
declare function applyDefaults<T extends AnyPrimitive>(primitive: T, value: Partial<InferState<T>>): InferState<T>;
|
|
154
159
|
//#endregion
|
|
155
|
-
export { AnyPrimitive, HasDefault, InferProxy, InferSetInput, InferSnapshot, InferState, InferUpdateInput, IsDefined, IsRequired, MaybeUndefined, NeedsValue, Optional, Primitive, PrimitiveInternal, ValidationError, Validator, applyDefaults, isCompatibleOperation, runValidators };
|
|
160
|
+
export { AnyPrimitive, HasDefault, InferProxy, InferSetInput, InferSnapshot, InferState, InferUpdateInput, IsDefined, IsRequired, MaybeUndefined, NeedsValue, Optional, Primitive, PrimitiveInternal, ValidationError, Validator, applyDefaults, isCompatibleOperation, primitiveAllowsNullValue, runValidators };
|
|
156
161
|
//# sourceMappingURL=shared.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"shared.d.mts","names":[],"sources":["../../src/primitives/shared.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;AAmBA;;;;;;;AAMwB,UANP,SAMO,CAAA,MAAA,EAAA,MAAA,EAAA,kBAAA,OAAA,GAAA,KAAA,EAAA,oBAAA,OAAA,GAAA,KAAA,EAAA,YAAA,OAAA,EAAA,eAAA,OAAA,CAAA,CAAA;EACA,SAAA,IAAA,EAAA,MAAA;EACG,SAAA,MAAA,EANN,MAMM;EAAY,SAAA,MAAA,EALlB,MAKkB;EAMpB,SAAA,UAAA,EAVM,SAUW;EAEJ,SAAA,YAAA,EAXL,WAWK;EAAyC,SAAA,SAAA,EAVjD,iBAUiD,CAV/B,MAU+B,EAVvB,MAUuB,CAAA;EAAgC,SAAA,SAAA,EATjF,SASiF;EAEpE,SAAA,YAAA,EAVV,YAUU;;;;;AAqBrB,UAzBG,iBAyBH,CAAA,MAAA,EAAA,MAAA,CAAA,CAAA;EACA;EACP,SAAA,WAAA,EAAA,CAAA,GAAA,EAzBuB,gBAyBvB,EAAA,IAAA,EAzBgE,aAyBhE,EAAA,GAzBgG,MAyBhG;EAAyB;EAMpB,SAAA,cAAY,EAAA,CAAA,KAAG,EA7BQ,MA6BC,GAAA,SAAA,EAAA,SAAA,EA7B8B,SA6B9B,CAAA,GAAA,EAAA,GAAA,EAAA,GAAA,CAAA,EAAA,GA7BqE,MA6BrE;EAKxB;EAKA,SAAA,eAAU,EAAA,GAAM,GArCM,MAqCI,GAAS,SAAA;EAMnC;;;;;AASZ;;;EAEE,SAAA,sBAAA,CAAA,EAAA,CAAA,KAAA,EAAA,OAAA,EAAA,GA7CsD,MA6CtD;EAAC;AAQH;;;;;;;EAAiK,SAAA,kBAAA,EAAA,CAAA,QAAA,EA3CnJ,SA2CmJ,CAAA,GAAA,EAAA,GAAA,EAAA,GAAA,CAAA,EAAA,QAAA,EA1CnJ,SA0CmJ,CAAA,GAAA,EAAA,GAAA,EAAA,GAAA,CAAA,EAAA,GAzC1J,eAyC0J;AAEjK;AAOA;;;AAAyI,KA5C7H,YAAA,GAAe,SA4C8G,CAAA,GAAA,EAAA,GAAA,EAAA,GAAA,EAAA,GAAA,CAAA;;;;AAAkB,KAvC/I,UAuC+I,CAAA,CAAA,CAAA,GAvC/H,CAuC+H,SAvCrH,SAuCqH,CAAA,KAAA,EAAA,EAAA,GAAA,EAAA,GAAA,EAAA,GAAA,CAAA,GAAA,CAAA,GAAA,KAAA;;AAM3J;AAOA;AAKY,KApDA,UAoDS,CAAA,CAAA,CAAA,GApDO,CAoDP,SApDiB,SAoDQ,CAAA,GAAA,EAAA,KAAA,EAAA,EAAA,GAAA,EAAA,GAAA,CAAA,GAAA,CAAA,GAAA,KAAA;AAM9C;AAOA;AAeA;AASF;
|
|
1
|
+
{"version":3,"file":"shared.d.mts","names":[],"sources":["../../src/primitives/shared.ts"],"sourcesContent":[],"mappings":"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"}
|
|
@@ -16,6 +16,19 @@ function runValidators(value, validators) {
|
|
|
16
16
|
for (const validator of validators) if (!validator.validate(value)) throw new ValidationError(validator.message);
|
|
17
17
|
}
|
|
18
18
|
/**
|
|
19
|
+
* Returns true if a primitive can represent null as a meaningful value.
|
|
20
|
+
* This is used to avoid pruning explicit null values for null-capable scalar unions.
|
|
21
|
+
*/
|
|
22
|
+
function primitiveAllowsNullValue(primitive) {
|
|
23
|
+
if (primitive._tag === "LiteralPrimitive") return primitive.literal === null;
|
|
24
|
+
if (primitive._tag === "EitherPrimitive") {
|
|
25
|
+
var _schema;
|
|
26
|
+
const variants = (_schema = primitive._schema) === null || _schema === void 0 ? void 0 : _schema.variants;
|
|
27
|
+
return Array.isArray(variants) && variants.some((variant) => primitiveAllowsNullValue(variant));
|
|
28
|
+
}
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
19
32
|
* Checks if an operation is compatible with the given operation definitions.
|
|
20
33
|
* @param operation - The operation to check.
|
|
21
34
|
* @param operationDefinitions - The operation definitions to check against.
|
|
@@ -41,13 +54,23 @@ function applyDefaults(primitive, value) {
|
|
|
41
54
|
var _structPrimitive$_int;
|
|
42
55
|
const structPrimitive = primitive;
|
|
43
56
|
const result = _objectSpread2(_objectSpread2({}, (_structPrimitive$_int = structPrimitive._internal.getInitialState()) !== null && _structPrimitive$_int !== void 0 ? _structPrimitive$_int : {}), value);
|
|
57
|
+
const inputObject = typeof value === "object" && value !== null ? value : void 0;
|
|
44
58
|
for (const key in structPrimitive.fields) {
|
|
59
|
+
var _schema2;
|
|
45
60
|
const fieldPrimitive = structPrimitive.fields[key];
|
|
46
|
-
|
|
47
|
-
|
|
61
|
+
const hasExplicitKey = inputObject !== void 0 && Object.prototype.hasOwnProperty.call(inputObject, key);
|
|
62
|
+
const explicitValue = hasExplicitKey ? inputObject[key] : void 0;
|
|
63
|
+
const fieldDefault = fieldPrimitive._internal.getInitialState();
|
|
64
|
+
const isRequiredWithoutDefault = ((_schema2 = fieldPrimitive._schema) === null || _schema2 === void 0 ? void 0 : _schema2.required) === true && fieldDefault === void 0;
|
|
65
|
+
if (hasExplicitKey && (explicitValue === void 0 || explicitValue === null && !primitiveAllowsNullValue(fieldPrimitive))) {
|
|
66
|
+
if (isRequiredWithoutDefault) throw new ValidationError(`Field "${key}" is required and cannot be null or undefined`);
|
|
67
|
+
delete result[key];
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
if (!hasExplicitKey && result[key] === void 0) {
|
|
48
71
|
if (fieldDefault !== void 0) result[key] = fieldDefault;
|
|
49
|
-
} else if (typeof
|
|
50
|
-
if (fieldPrimitive._tag === "StructPrimitive" || fieldPrimitive._tag === "UnionPrimitive") result[key] = applyDefaults(fieldPrimitive,
|
|
72
|
+
} else if (hasExplicitKey && typeof explicitValue === "object" && explicitValue !== null) {
|
|
73
|
+
if (fieldPrimitive._tag === "StructPrimitive" || fieldPrimitive._tag === "UnionPrimitive") result[key] = applyDefaults(fieldPrimitive, explicitValue);
|
|
51
74
|
}
|
|
52
75
|
}
|
|
53
76
|
return result;
|
|
@@ -78,5 +101,5 @@ function applyDefaults(primitive, value) {
|
|
|
78
101
|
}
|
|
79
102
|
|
|
80
103
|
//#endregion
|
|
81
|
-
export { ValidationError, applyDefaults, isCompatibleOperation, runValidators };
|
|
104
|
+
export { ValidationError, applyDefaults, isCompatibleOperation, primitiveAllowsNullValue, runValidators };
|
|
82
105
|
//# sourceMappingURL=shared.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"shared.mjs","names":["result: Record<string, unknown>","matchingVariantKey: string | undefined"],"sources":["../../src/primitives/shared.ts"],"sourcesContent":["import * as Operation from \"../Operation\";\nimport * as OperationDefinition from \"../OperationDefinition\";\nimport * as ProxyEnvironment from \"../ProxyEnvironment\";\nimport * as OperationPath from \"../OperationPath\";\nimport * as Transform from \"../Transform\";\n\n// =============================================================================\n// Primitive Interface & Type Utilities\n// =============================================================================\n\n/**\n * Base interface that all primitives must implement.\n * Provides type inference helpers and internal operations.\n * \n * @typeParam TState - The state type this primitive holds\n * @typeParam TProxy - The proxy type for interacting with this primitive\n * @typeParam TDefined - Whether the value is guaranteed to be defined (via required() or default())\n * @typeParam THasDefault - Whether this primitive has a default value\n */\nexport interface Primitive<TState, TProxy, TRequired extends boolean = false, THasDefault extends boolean = false, TSetInput = unknown, TUpdateInput = unknown> {\n readonly _tag: string;\n readonly _State: TState;\n readonly _Proxy: TProxy;\n readonly _TRequired: TRequired;\n readonly _THasDefault: THasDefault;\n readonly _internal: PrimitiveInternal<TState, TProxy>;\n readonly TSetInput: TSetInput;\n readonly TUpdateInput: TUpdateInput;\n }\n \n /**\n * Internal operations that each primitive must provide.\n */\n export interface PrimitiveInternal<TState, TProxy> {\n /** Creates a proxy for generating operations */\n readonly createProxy: (env: ProxyEnvironment.ProxyEnvironment, path: OperationPath.OperationPath) => TProxy;\n /** Applies an operation to the current state, returning the new state */\n readonly applyOperation: (state: TState | undefined, operation: Operation.Operation<any, any, any>) => TState;\n /** Returns the initial/default state for this primitive */\n readonly getInitialState: () => TState | undefined;\n /**\n * Converts a set input value to state format.\n * For most primitives, this is a simple pass-through with defaults applied.\n * For Tree primitives, this converts nested input to flat TreeState.\n *\n * @param input - The set input value\n * @returns The corresponding state value\n */\n readonly convertSetInputToState?: (input: unknown) => TState;\n /**\n * Transforms a client operation against a server operation.\n * Used for operational transformation (OT) conflict resolution.\n *\n * @param clientOp - The client's operation to transform\n * @param serverOp - The server's operation that has already been applied\n * @returns TransformResult indicating how the client operation should be handled\n */\n readonly transformOperation: (\n clientOp: Operation.Operation<any, any, any>,\n serverOp: Operation.Operation<any, any, any>\n ) => Transform.TransformResult;\n }\n \n /**\n * Any primitive type - used for generic constraints.\n */\n export type AnyPrimitive = Primitive<any, any, any, any>;\n \n /**\n * Infer the state type from a primitive.\n */\n export type InferState<T> = T extends Primitive<infer S, any, any, any> ? S : never;\n \n /**\n * Infer the proxy type from a primitive.\n */\n export type InferProxy<T> = T extends Primitive<any, infer P, any, any> ? P : never;\n\n /**\n * Infer the SetInput type from a primitive.\n * Works with both Primitive interface types and types with a TSetInput property (like TreeNodePrimitive).\n */\n export type InferSetInput<T> = \n T extends Primitive<any, any, any, any, infer S, any> ? S : \n T extends { TSetInput: infer S } ? S : \n never;\n\n /**\n * Infer the UpdateInput type from a primitive.\n * Works with both Primitive interface types and types with a TUpdateInput property (like TreeNodePrimitive).\n */\n export type InferUpdateInput<T> = \n T extends Primitive<any, any, any, any, any, infer U> ? U : \n T extends { TUpdateInput: infer U } ? U : \n never;\n \n /**\n * Helper type to conditionally add undefined based on TRequired and THasDefault.\n * When TRequired is false and THasDefault is false, the value may be undefined.\n * Otherwise, the value is guaranteed to be defined.\n */\n export type MaybeUndefined<T, TRequired extends boolean, THasDefault extends boolean> = TRequired extends false ? THasDefault extends false ? Optional<T> : T : T;\n\n export type Optional<T> = T | undefined;\n\n /**\n * Helper type to conditionally add undefined based on TRequired and THasDefault.\n * When TRequired is true and THasDefault is false, the value must be provided.\n * Otherwise, the value may be undefined.\n */\n export type NeedsValue<T, TRequired extends boolean, THasDefault extends boolean> = TRequired extends true ? THasDefault extends false ? T : Optional<T> : Optional<T>;\n \n /**\n * Infer the snapshot type from a primitive.\n * The snapshot is a readonly, type-safe structure suitable for rendering.\n */\n export type InferSnapshot<T> = T extends Primitive<any, infer P, any, any>\n ? P extends { toSnapshot(): infer S } ? S : never\n : never;\n\n /**\n * Extract THasDefault from a primitive.\n */\n export type HasDefault<T> = T extends Primitive<any, any, any, infer H> ? H : false;\n\n /**\n * Extract TDefined from a primitive.\n */\n export type IsDefined<T> = T extends Primitive<any, any, infer D, any> ? D : false;\n\n /**\n * Infer whether a primitive is required.\n * Alias for IsDefined for clarity.\n */\n export type IsRequired<T> = IsDefined<T>;\n\n\n // =============================================================================\n // Validation Errors\n // =============================================================================\n \n export class ValidationError extends Error {\n readonly _tag = \"ValidationError\";\n constructor(message: string) {\n super(message);\n this.name = \"ValidationError\";\n }\n }\n \n // =============================================================================\n // Validation Infrastructure\n // =============================================================================\n \n /**\n * A validator that checks a value and returns whether it's valid.\n */\n export interface Validator<T> {\n readonly validate: (value: T) => boolean;\n readonly message: string;\n }\n \n\n/**\n * Runs all validators against a value, throwing ValidationError if any fail.\n */\nexport function runValidators<T>(value: T, validators: readonly { validate: (value: T) => boolean; message: string }[]): void {\n for (const validator of validators) {\n if (!validator.validate(value)) {\n throw new ValidationError(validator.message);\n }\n }\n}\n\n/**\n * Checks if an operation is compatible with the given operation definitions.\n * @param operation - The operation to check.\n * @param operationDefinitions - The operation definitions to check against.\n * @returns True if the operation is compatible, false otherwise.\n */\nexport function isCompatibleOperation(operation: Operation.Operation<any, any, any>, operationDefinitions: Record<string, OperationDefinition.OperationDefinition<any, any, any>>) {\n const values = Object.values(operationDefinitions);\n return values.some(value => value.kind === operation.kind);\n}\n\n// =============================================================================\n// Default Value Utilities\n// =============================================================================\n\n/**\n * Applies default values to a partial input, recursively handling nested structs and unions.\n * \n * Uses a two-layer approach:\n * 1. First, get the struct's initial state (which includes struct-level defaults)\n * 2. Then, layer the provided values on top\n * 3. Finally, ensure nested structs and unions are recursively processed\n * \n * @param primitive - The primitive definition containing field information\n * @param value - The partial value provided by the user\n * @returns The value with defaults applied for missing fields\n */\nexport function applyDefaults<T extends AnyPrimitive>(\n primitive: T,\n value: Partial<InferState<T>>\n): InferState<T> {\n // Handle StructPrimitive\n if (primitive._tag === \"StructPrimitive\") {\n const structPrimitive = primitive as unknown as { \n fields: Record<string, AnyPrimitive>;\n _internal: { getInitialState: () => Record<string, unknown> | undefined };\n };\n \n // Start with the struct's initial state (struct-level default or field defaults)\n const structInitialState = structPrimitive._internal.getInitialState() ?? {};\n \n // Layer the provided values on top of initial state\n const result: Record<string, unknown> = { ...structInitialState, ...value };\n \n for (const key in structPrimitive.fields) {\n const fieldPrimitive = structPrimitive.fields[key]!;\n \n if (result[key] === undefined) {\n // Field still not provided after merging - try individual field default\n const fieldDefault = fieldPrimitive._internal.getInitialState();\n if (fieldDefault !== undefined) {\n result[key] = fieldDefault;\n }\n } else if (typeof result[key] === \"object\" && result[key] !== null) {\n // Recursively apply defaults to nested structs and unions\n if (fieldPrimitive._tag === \"StructPrimitive\" || fieldPrimitive._tag === \"UnionPrimitive\") {\n result[key] = applyDefaults(fieldPrimitive, result[key] as Partial<InferState<typeof fieldPrimitive>>);\n }\n }\n }\n \n return result as InferState<T>;\n }\n \n // Handle UnionPrimitive\n if (primitive._tag === \"UnionPrimitive\") {\n const unionPrimitive = primitive as unknown as {\n _schema: {\n discriminator: string;\n variants: Record<string, AnyPrimitive>;\n };\n };\n \n // Validate that value is an object\n if (typeof value !== \"object\" || value === null) {\n return value as InferState<T>;\n }\n \n const discriminator = unionPrimitive._schema.discriminator;\n const discriminatorValue = (value as Record<string, unknown>)[discriminator];\n \n // Find the matching variant based on discriminator value\n let matchingVariantKey: string | undefined;\n for (const variantKey in unionPrimitive._schema.variants) {\n const variant = unionPrimitive._schema.variants[variantKey]!;\n // Variants are structs - check if the discriminator field's literal matches\n if (variant._tag === \"StructPrimitive\") {\n const variantStruct = variant as unknown as {\n fields: Record<string, AnyPrimitive>;\n };\n const discriminatorField = variantStruct.fields[discriminator];\n if (discriminatorField && discriminatorField._tag === \"LiteralPrimitive\") {\n const literalPrimitive = discriminatorField as unknown as { literal: unknown };\n if (literalPrimitive.literal === discriminatorValue) {\n matchingVariantKey = variantKey;\n break;\n }\n }\n }\n }\n \n if (!matchingVariantKey) {\n // No matching variant found - return value as-is\n return value as InferState<T>;\n }\n \n // Apply defaults using the matching variant's struct\n const variantPrimitive = unionPrimitive._schema.variants[matchingVariantKey]!;\n return applyDefaults(variantPrimitive, value as Partial<InferState<typeof variantPrimitive>>) as InferState<T>;\n }\n \n // For other primitives, return the value as-is\n return value as InferState<T>;\n}\n\n"],"mappings":";;;;AA6IE,IAAa,kBAAb,cAAqC,MAAM;CAEzC,YAAY,SAAiB;AAC3B,QAAM,QAAQ;wBAFP,QAAO;AAGd,OAAK,OAAO;;;;;;AAoBlB,SAAgB,cAAiB,OAAU,YAAmF;AAC5H,MAAK,MAAM,aAAa,WACtB,KAAI,CAAC,UAAU,SAAS,MAAM,CAC5B,OAAM,IAAI,gBAAgB,UAAU,QAAQ;;;;;;;;AAWlD,SAAgB,sBAAsB,WAA+C,sBAA8F;AAEjL,QADe,OAAO,OAAO,qBAAqB,CACpC,MAAK,UAAS,MAAM,SAAS,UAAU,KAAK;;;;;;;;;;;;;;AAmB5D,SAAgB,cACd,WACA,OACe;AAEf,KAAI,UAAU,SAAS,mBAAmB;;EACxC,MAAM,kBAAkB;EASxB,MAAMA,oEAHqB,gBAAgB,UAAU,iBAAiB,yEAAI,EAAE,GAGR;AAEpE,OAAK,MAAM,OAAO,gBAAgB,QAAQ;GACxC,MAAM,iBAAiB,gBAAgB,OAAO;AAE9C,OAAI,OAAO,SAAS,QAAW;IAE7B,MAAM,eAAe,eAAe,UAAU,iBAAiB;AAC/D,QAAI,iBAAiB,OACnB,QAAO,OAAO;cAEP,OAAO,OAAO,SAAS,YAAY,OAAO,SAAS,MAE5D;QAAI,eAAe,SAAS,qBAAqB,eAAe,SAAS,iBACvE,QAAO,OAAO,cAAc,gBAAgB,OAAO,KAAmD;;;AAK5G,SAAO;;AAIT,KAAI,UAAU,SAAS,kBAAkB;EACvC,MAAM,iBAAiB;AAQvB,MAAI,OAAO,UAAU,YAAY,UAAU,KACzC,QAAO;EAGT,MAAM,gBAAgB,eAAe,QAAQ;EAC7C,MAAM,qBAAsB,MAAkC;EAG9D,IAAIC;AACJ,OAAK,MAAM,cAAc,eAAe,QAAQ,UAAU;GACxD,MAAM,UAAU,eAAe,QAAQ,SAAS;AAEhD,OAAI,QAAQ,SAAS,mBAAmB;IAItC,MAAM,qBAHgB,QAGmB,OAAO;AAChD,QAAI,sBAAsB,mBAAmB,SAAS,oBAEpD;SADyB,mBACJ,YAAY,oBAAoB;AACnD,2BAAqB;AACrB;;;;;AAMR,MAAI,CAAC,mBAEH,QAAO;EAIT,MAAM,mBAAmB,eAAe,QAAQ,SAAS;AACzD,SAAO,cAAc,kBAAkB,MAAsD;;AAI/F,QAAO"}
|
|
1
|
+
{"version":3,"file":"shared.mjs","names":["result: Record<string, unknown>","matchingVariantKey: string | undefined"],"sources":["../../src/primitives/shared.ts"],"sourcesContent":["import * as Operation from \"../Operation\";\nimport * as OperationDefinition from \"../OperationDefinition\";\nimport * as ProxyEnvironment from \"../ProxyEnvironment\";\nimport * as OperationPath from \"../OperationPath\";\nimport * as Transform from \"../Transform\";\n\n// =============================================================================\n// Primitive Interface & Type Utilities\n// =============================================================================\n\n/**\n * Base interface that all primitives must implement.\n * Provides type inference helpers and internal operations.\n * \n * @typeParam TState - The state type this primitive holds\n * @typeParam TProxy - The proxy type for interacting with this primitive\n * @typeParam TDefined - Whether the value is guaranteed to be defined (via required() or default())\n * @typeParam THasDefault - Whether this primitive has a default value\n */\nexport interface Primitive<TState, TProxy, TRequired extends boolean = false, THasDefault extends boolean = false, TSetInput = unknown, TUpdateInput = unknown> {\n readonly _tag: string;\n readonly _State: TState;\n readonly _Proxy: TProxy;\n readonly _TRequired: TRequired;\n readonly _THasDefault: THasDefault;\n readonly _internal: PrimitiveInternal<TState, TProxy>;\n readonly TSetInput: TSetInput;\n readonly TUpdateInput: TUpdateInput;\n }\n \n /**\n * Internal operations that each primitive must provide.\n */\n export interface PrimitiveInternal<TState, TProxy> {\n /** Creates a proxy for generating operations */\n readonly createProxy: (env: ProxyEnvironment.ProxyEnvironment, path: OperationPath.OperationPath) => TProxy;\n /** Applies an operation to the current state, returning the new state */\n readonly applyOperation: (state: TState | undefined, operation: Operation.Operation<any, any, any>) => TState;\n /** Returns the initial/default state for this primitive */\n readonly getInitialState: () => TState | undefined;\n /**\n * Converts a set input value to state format.\n * For most primitives, this is a simple pass-through with defaults applied.\n * For Tree primitives, this converts nested input to flat TreeState.\n *\n * @param input - The set input value\n * @returns The corresponding state value\n */\n readonly convertSetInputToState?: (input: unknown) => TState;\n /**\n * Transforms a client operation against a server operation.\n * Used for operational transformation (OT) conflict resolution.\n *\n * @param clientOp - The client's operation to transform\n * @param serverOp - The server's operation that has already been applied\n * @returns TransformResult indicating how the client operation should be handled\n */\n readonly transformOperation: (\n clientOp: Operation.Operation<any, any, any>,\n serverOp: Operation.Operation<any, any, any>\n ) => Transform.TransformResult;\n }\n \n /**\n * Any primitive type - used for generic constraints.\n */\n export type AnyPrimitive = Primitive<any, any, any, any>;\n \n /**\n * Infer the state type from a primitive.\n */\n export type InferState<T> = T extends Primitive<infer S, any, any, any> ? S : never;\n \n /**\n * Infer the proxy type from a primitive.\n */\n export type InferProxy<T> = T extends Primitive<any, infer P, any, any> ? P : never;\n\n /**\n * Infer the SetInput type from a primitive.\n * Works with both Primitive interface types and types with a TSetInput property (like TreeNodePrimitive).\n */\n export type InferSetInput<T> = \n T extends Primitive<any, any, any, any, infer S, any> ? S : \n T extends { TSetInput: infer S } ? S : \n never;\n\n /**\n * Infer the UpdateInput type from a primitive.\n * Works with both Primitive interface types and types with a TUpdateInput property (like TreeNodePrimitive).\n */\n export type InferUpdateInput<T> = \n T extends Primitive<any, any, any, any, any, infer U> ? U : \n T extends { TUpdateInput: infer U } ? U : \n never;\n \n /**\n * Helper type to conditionally add undefined based on TRequired and THasDefault.\n * When TRequired is false and THasDefault is false, the value may be undefined.\n * Otherwise, the value is guaranteed to be defined.\n */\n export type MaybeUndefined<T, TRequired extends boolean, THasDefault extends boolean> = TRequired extends false ? THasDefault extends false ? Optional<T> : T : T;\n\n export type Optional<T> = T | undefined;\n\n /**\n * Helper type to conditionally add undefined based on TRequired and THasDefault.\n * When TRequired is true and THasDefault is false, the value must be provided.\n * Otherwise, the value may be undefined.\n */\n export type NeedsValue<T, TRequired extends boolean, THasDefault extends boolean> = TRequired extends true ? THasDefault extends false ? T : Optional<T> : Optional<T>;\n \n /**\n * Infer the snapshot type from a primitive.\n * The snapshot is a readonly, type-safe structure suitable for rendering.\n */\n export type InferSnapshot<T> = T extends Primitive<any, infer P, any, any>\n ? P extends { toSnapshot(): infer S } ? S : never\n : never;\n\n /**\n * Extract THasDefault from a primitive.\n */\n export type HasDefault<T> = T extends Primitive<any, any, any, infer H> ? H : false;\n\n /**\n * Extract TDefined from a primitive.\n */\n export type IsDefined<T> = T extends Primitive<any, any, infer D, any> ? D : false;\n\n /**\n * Infer whether a primitive is required.\n * Alias for IsDefined for clarity.\n */\n export type IsRequired<T> = IsDefined<T>;\n\n\n // =============================================================================\n // Validation Errors\n // =============================================================================\n \n export class ValidationError extends Error {\n readonly _tag = \"ValidationError\";\n constructor(message: string) {\n super(message);\n this.name = \"ValidationError\";\n }\n }\n \n // =============================================================================\n // Validation Infrastructure\n // =============================================================================\n \n /**\n * A validator that checks a value and returns whether it's valid.\n */\n export interface Validator<T> {\n readonly validate: (value: T) => boolean;\n readonly message: string;\n }\n \n\n/**\n * Runs all validators against a value, throwing ValidationError if any fail.\n */\nexport function runValidators<T>(value: T, validators: readonly { validate: (value: T) => boolean; message: string }[]): void {\n for (const validator of validators) {\n if (!validator.validate(value)) {\n throw new ValidationError(validator.message);\n }\n }\n}\n\n/**\n * Returns true if a primitive can represent null as a meaningful value.\n * This is used to avoid pruning explicit null values for null-capable scalar unions.\n */\nexport function primitiveAllowsNullValue(primitive: AnyPrimitive): boolean {\n if (primitive._tag === \"LiteralPrimitive\") {\n return (primitive as { literal: unknown }).literal === null;\n }\n\n if (primitive._tag === \"EitherPrimitive\") {\n const variants = (primitive as { _schema?: { variants?: readonly AnyPrimitive[] } })._schema?.variants;\n return Array.isArray(variants) && variants.some((variant) => primitiveAllowsNullValue(variant));\n }\n\n return false;\n}\n\n/**\n * Checks if an operation is compatible with the given operation definitions.\n * @param operation - The operation to check.\n * @param operationDefinitions - The operation definitions to check against.\n * @returns True if the operation is compatible, false otherwise.\n */\nexport function isCompatibleOperation(operation: Operation.Operation<any, any, any>, operationDefinitions: Record<string, OperationDefinition.OperationDefinition<any, any, any>>) {\n const values = Object.values(operationDefinitions);\n return values.some(value => value.kind === operation.kind);\n}\n\n// =============================================================================\n// Default Value Utilities\n// =============================================================================\n\n/**\n * Applies default values to a partial input, recursively handling nested structs and unions.\n * \n * Uses a two-layer approach:\n * 1. First, get the struct's initial state (which includes struct-level defaults)\n * 2. Then, layer the provided values on top\n * 3. Finally, ensure nested structs and unions are recursively processed\n * \n * @param primitive - The primitive definition containing field information\n * @param value - The partial value provided by the user\n * @returns The value with defaults applied for missing fields\n */\nexport function applyDefaults<T extends AnyPrimitive>(\n primitive: T,\n value: Partial<InferState<T>>\n): InferState<T> {\n // Handle StructPrimitive\n if (primitive._tag === \"StructPrimitive\") {\n const structPrimitive = primitive as unknown as { \n fields: Record<string, AnyPrimitive>;\n _internal: { getInitialState: () => Record<string, unknown> | undefined };\n };\n \n // Start with the struct's initial state (struct-level default or field defaults)\n const structInitialState = structPrimitive._internal.getInitialState() ?? {};\n \n // Layer the provided values on top of initial state\n const result: Record<string, unknown> = { ...structInitialState, ...value };\n const inputObject =\n typeof value === \"object\" && value !== null\n ? (value as Record<string, unknown>)\n : undefined;\n \n for (const key in structPrimitive.fields) {\n const fieldPrimitive = structPrimitive.fields[key]!;\n const hasExplicitKey = inputObject !== undefined && Object.prototype.hasOwnProperty.call(inputObject, key);\n const explicitValue = hasExplicitKey ? inputObject[key] : undefined;\n const fieldDefault = fieldPrimitive._internal.getInitialState();\n const isRequiredWithoutDefault =\n (fieldPrimitive as { _schema?: { required?: boolean } })._schema?.required === true &&\n fieldDefault === undefined;\n\n // Explicit undefined values always prune optional keys.\n // Explicit null values prune optional keys unless null is a valid semantic value for this field.\n // Required fields without defaults reject nullish values.\n const shouldPruneExplicitNullish =\n hasExplicitKey &&\n (\n explicitValue === undefined ||\n (explicitValue === null && !primitiveAllowsNullValue(fieldPrimitive))\n );\n if (shouldPruneExplicitNullish) {\n if (isRequiredWithoutDefault) {\n throw new ValidationError(`Field \"${key}\" is required and cannot be null or undefined`);\n }\n delete result[key];\n continue;\n }\n \n if (!hasExplicitKey && result[key] === undefined) {\n // Field still not provided after merging - try individual field default\n if (fieldDefault !== undefined) {\n result[key] = fieldDefault;\n }\n } else if (\n hasExplicitKey &&\n typeof explicitValue === \"object\" &&\n explicitValue !== null\n ) {\n // Recursively apply defaults to nested structs and unions\n if (fieldPrimitive._tag === \"StructPrimitive\" || fieldPrimitive._tag === \"UnionPrimitive\") {\n result[key] = applyDefaults(fieldPrimitive, explicitValue as Partial<InferState<typeof fieldPrimitive>>);\n }\n }\n }\n \n return result as InferState<T>;\n }\n \n // Handle UnionPrimitive\n if (primitive._tag === \"UnionPrimitive\") {\n const unionPrimitive = primitive as unknown as {\n _schema: {\n discriminator: string;\n variants: Record<string, AnyPrimitive>;\n };\n };\n \n // Validate that value is an object\n if (typeof value !== \"object\" || value === null) {\n return value as InferState<T>;\n }\n \n const discriminator = unionPrimitive._schema.discriminator;\n const discriminatorValue = (value as Record<string, unknown>)[discriminator];\n \n // Find the matching variant based on discriminator value\n let matchingVariantKey: string | undefined;\n for (const variantKey in unionPrimitive._schema.variants) {\n const variant = unionPrimitive._schema.variants[variantKey]!;\n // Variants are structs - check if the discriminator field's literal matches\n if (variant._tag === \"StructPrimitive\") {\n const variantStruct = variant as unknown as {\n fields: Record<string, AnyPrimitive>;\n };\n const discriminatorField = variantStruct.fields[discriminator];\n if (discriminatorField && discriminatorField._tag === \"LiteralPrimitive\") {\n const literalPrimitive = discriminatorField as unknown as { literal: unknown };\n if (literalPrimitive.literal === discriminatorValue) {\n matchingVariantKey = variantKey;\n break;\n }\n }\n }\n }\n \n if (!matchingVariantKey) {\n // No matching variant found - return value as-is\n return value as InferState<T>;\n }\n \n // Apply defaults using the matching variant's struct\n const variantPrimitive = unionPrimitive._schema.variants[matchingVariantKey]!;\n return applyDefaults(variantPrimitive, value as Partial<InferState<typeof variantPrimitive>>) as InferState<T>;\n }\n \n // For other primitives, return the value as-is\n return value as InferState<T>;\n}\n"],"mappings":";;;;AA6IE,IAAa,kBAAb,cAAqC,MAAM;CAEzC,YAAY,SAAiB;AAC3B,QAAM,QAAQ;wBAFP,QAAO;AAGd,OAAK,OAAO;;;;;;AAoBlB,SAAgB,cAAiB,OAAU,YAAmF;AAC5H,MAAK,MAAM,aAAa,WACtB,KAAI,CAAC,UAAU,SAAS,MAAM,CAC5B,OAAM,IAAI,gBAAgB,UAAU,QAAQ;;;;;;AASlD,SAAgB,yBAAyB,WAAkC;AACzE,KAAI,UAAU,SAAS,mBACrB,QAAQ,UAAmC,YAAY;AAGzD,KAAI,UAAU,SAAS,mBAAmB;;EACxC,MAAM,sBAAY,UAAmE,2DAAS;AAC9F,SAAO,MAAM,QAAQ,SAAS,IAAI,SAAS,MAAM,YAAY,yBAAyB,QAAQ,CAAC;;AAGjG,QAAO;;;;;;;;AAST,SAAgB,sBAAsB,WAA+C,sBAA8F;AAEjL,QADe,OAAO,OAAO,qBAAqB,CACpC,MAAK,UAAS,MAAM,SAAS,UAAU,KAAK;;;;;;;;;;;;;;AAmB5D,SAAgB,cACd,WACA,OACe;AAEf,KAAI,UAAU,SAAS,mBAAmB;;EACxC,MAAM,kBAAkB;EASxB,MAAMA,oEAHqB,gBAAgB,UAAU,iBAAiB,yEAAI,EAAE,GAGR;EACpE,MAAM,cACJ,OAAO,UAAU,YAAY,UAAU,OAClC,QACD;AAEN,OAAK,MAAM,OAAO,gBAAgB,QAAQ;;GACxC,MAAM,iBAAiB,gBAAgB,OAAO;GAC9C,MAAM,iBAAiB,gBAAgB,UAAa,OAAO,UAAU,eAAe,KAAK,aAAa,IAAI;GAC1G,MAAM,gBAAgB,iBAAiB,YAAY,OAAO;GAC1D,MAAM,eAAe,eAAe,UAAU,iBAAiB;GAC/D,MAAM,wCACH,eAAwD,6DAAS,cAAa,QAC/E,iBAAiB;AAWnB,OALE,mBAEE,kBAAkB,UACjB,kBAAkB,QAAQ,CAAC,yBAAyB,eAAe,GAExC;AAC9B,QAAI,yBACF,OAAM,IAAI,gBAAgB,UAAU,IAAI,+CAA+C;AAEzF,WAAO,OAAO;AACd;;AAGF,OAAI,CAAC,kBAAkB,OAAO,SAAS,QAErC;QAAI,iBAAiB,OACnB,QAAO,OAAO;cAGhB,kBACA,OAAO,kBAAkB,YACzB,kBAAkB,MAGlB;QAAI,eAAe,SAAS,qBAAqB,eAAe,SAAS,iBACvE,QAAO,OAAO,cAAc,gBAAgB,cAA4D;;;AAK9G,SAAO;;AAIT,KAAI,UAAU,SAAS,kBAAkB;EACvC,MAAM,iBAAiB;AAQvB,MAAI,OAAO,UAAU,YAAY,UAAU,KACzC,QAAO;EAGT,MAAM,gBAAgB,eAAe,QAAQ;EAC7C,MAAM,qBAAsB,MAAkC;EAG9D,IAAIC;AACJ,OAAK,MAAM,cAAc,eAAe,QAAQ,UAAU;GACxD,MAAM,UAAU,eAAe,QAAQ,SAAS;AAEhD,OAAI,QAAQ,SAAS,mBAAmB;IAItC,MAAM,qBAHgB,QAGmB,OAAO;AAChD,QAAI,sBAAsB,mBAAmB,SAAS,oBAEpD;SADyB,mBACJ,YAAY,oBAAoB;AACnD,2BAAqB;AACrB;;;;;AAMR,MAAI,CAAC,mBAEH,QAAO;EAIT,MAAM,mBAAmB,eAAe,QAAQ,SAAS;AACzD,SAAO,cAAc,kBAAkB,MAAsD;;AAI/F,QAAO"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@voidhash/mimic",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
3
|
+
"version": "1.0.0-beta.15",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"typescript": "5.8.3",
|
|
32
32
|
"vite-tsconfig-paths": "^5.1.4",
|
|
33
33
|
"vitest": "^3.2.4",
|
|
34
|
-
"@voidhash/tsconfig": "1.0.0-beta.
|
|
34
|
+
"@voidhash/tsconfig": "1.0.0-beta.15"
|
|
35
35
|
},
|
|
36
36
|
"peerDependencies": {
|
|
37
37
|
"effect": "^3.19.12"
|
|
@@ -558,6 +558,9 @@ export const make = <
|
|
|
558
558
|
ops: tx.ops,
|
|
559
559
|
pendingCount: _pending.length + 1,
|
|
560
560
|
isConnected: transport.isConnected(),
|
|
561
|
+
activeDraftCount: _drafts.size,
|
|
562
|
+
activeDraftIds: Array.from(_drafts.keys()),
|
|
563
|
+
callStack: new Error().stack?.split("\n").slice(1, 6).join("\n"),
|
|
561
564
|
});
|
|
562
565
|
|
|
563
566
|
const pending: PendingTransaction = {
|
|
@@ -1002,6 +1005,9 @@ export const make = <
|
|
|
1002
1005
|
isConnected: transport.isConnected(),
|
|
1003
1006
|
isReady: _initState.type === "ready",
|
|
1004
1007
|
pendingCount: _pending.length,
|
|
1008
|
+
activeDraftCount: _drafts.size,
|
|
1009
|
+
activeDraftIds: Array.from(_drafts.keys()),
|
|
1010
|
+
callStack: new Error().stack?.split("\n").slice(1, 6).join("\n"),
|
|
1005
1011
|
});
|
|
1006
1012
|
|
|
1007
1013
|
// Allow transactions even when disconnected - they will be queued
|
|
@@ -1176,6 +1182,13 @@ export const make = <
|
|
|
1176
1182
|
id: draftId,
|
|
1177
1183
|
|
|
1178
1184
|
update: (fn: (root: Primitive.InferProxy<TSchema>) => void): void => {
|
|
1185
|
+
debugLog("draft.update: starting", {
|
|
1186
|
+
draftId,
|
|
1187
|
+
consumed,
|
|
1188
|
+
currentOpsCount: draftState.ops.size,
|
|
1189
|
+
pendingCount: _pending.length,
|
|
1190
|
+
});
|
|
1191
|
+
|
|
1179
1192
|
if (consumed) {
|
|
1180
1193
|
throw new InvalidStateError("Draft has already been committed or discarded.");
|
|
1181
1194
|
}
|
|
@@ -1213,7 +1226,12 @@ export const make = <
|
|
|
1213
1226
|
}
|
|
1214
1227
|
}
|
|
1215
1228
|
|
|
1216
|
-
debugLog("draft.update", {
|
|
1229
|
+
debugLog("draft.update: complete", {
|
|
1230
|
+
draftId,
|
|
1231
|
+
newOpsCount: tx.ops.length,
|
|
1232
|
+
totalOps: draftState.ops.size,
|
|
1233
|
+
note: "Ops stored in draft - NOT sent to server",
|
|
1234
|
+
});
|
|
1217
1235
|
|
|
1218
1236
|
// Recompute optimistic state to reflect draft changes
|
|
1219
1237
|
recomputeOptimisticState();
|
|
@@ -1221,6 +1239,12 @@ export const make = <
|
|
|
1221
1239
|
},
|
|
1222
1240
|
|
|
1223
1241
|
commit: (): void => {
|
|
1242
|
+
debugLog("draft.commit: starting", {
|
|
1243
|
+
draftId,
|
|
1244
|
+
consumed,
|
|
1245
|
+
opsCount: draftState.ops.size,
|
|
1246
|
+
});
|
|
1247
|
+
|
|
1224
1248
|
if (consumed) {
|
|
1225
1249
|
throw new InvalidStateError("Draft has already been committed or discarded.");
|
|
1226
1250
|
}
|
|
@@ -1229,7 +1253,11 @@ export const make = <
|
|
|
1229
1253
|
const ops = Array.from(draftState.ops.values());
|
|
1230
1254
|
_drafts.delete(draftId);
|
|
1231
1255
|
|
|
1232
|
-
debugLog("draft.commit", {
|
|
1256
|
+
debugLog("draft.commit: submitting", {
|
|
1257
|
+
draftId,
|
|
1258
|
+
opsCount: ops.length,
|
|
1259
|
+
note: ops.length > 0 ? "Will call submitTransaction" : "Empty draft - no transaction",
|
|
1260
|
+
});
|
|
1233
1261
|
|
|
1234
1262
|
if (ops.length > 0) {
|
|
1235
1263
|
const tx = Transaction.make(ops);
|
|
@@ -1243,6 +1271,12 @@ export const make = <
|
|
|
1243
1271
|
},
|
|
1244
1272
|
|
|
1245
1273
|
discard: (): void => {
|
|
1274
|
+
debugLog("draft.discard: starting", {
|
|
1275
|
+
draftId,
|
|
1276
|
+
consumed,
|
|
1277
|
+
opsCount: draftState.ops.size,
|
|
1278
|
+
});
|
|
1279
|
+
|
|
1246
1280
|
if (consumed) {
|
|
1247
1281
|
throw new InvalidStateError("Draft has already been committed or discarded.");
|
|
1248
1282
|
}
|
|
@@ -1250,7 +1284,10 @@ export const make = <
|
|
|
1250
1284
|
|
|
1251
1285
|
_drafts.delete(draftId);
|
|
1252
1286
|
|
|
1253
|
-
debugLog("draft.discard", {
|
|
1287
|
+
debugLog("draft.discard: complete", {
|
|
1288
|
+
draftId,
|
|
1289
|
+
note: "Draft discarded - no transaction sent to server",
|
|
1290
|
+
});
|
|
1254
1291
|
|
|
1255
1292
|
recomputeOptimisticState();
|
|
1256
1293
|
notifyDraftChange();
|
package/src/primitives/Struct.ts
CHANGED
|
@@ -6,7 +6,7 @@ import * as ProxyEnvironment from "../ProxyEnvironment";
|
|
|
6
6
|
import * as Transform from "../Transform";
|
|
7
7
|
import type { Primitive, PrimitiveInternal, MaybeUndefined, AnyPrimitive, Validator, InferState, InferProxy, InferSnapshot, NeedsValue, InferUpdateInput, InferSetInput } from "../Primitive";
|
|
8
8
|
import { ValidationError } from "../Primitive";
|
|
9
|
-
import { runValidators, applyDefaults } from "./shared";
|
|
9
|
+
import { runValidators, applyDefaults, primitiveAllowsNullValue } from "./shared";
|
|
10
10
|
|
|
11
11
|
// =============================================================================
|
|
12
12
|
// Struct Set Input Types
|
|
@@ -142,6 +142,13 @@ export class StructPrimitive<TFields extends Record<string, AnyPrimitive>, TRequ
|
|
|
142
142
|
apply: (payload) => payload,
|
|
143
143
|
deduplicable: true,
|
|
144
144
|
}),
|
|
145
|
+
unset: OperationDefinition.make({
|
|
146
|
+
kind: "struct.unset" as const,
|
|
147
|
+
payload: Schema.Unknown,
|
|
148
|
+
target: Schema.Unknown,
|
|
149
|
+
apply: (payload) => payload,
|
|
150
|
+
deduplicable: true,
|
|
151
|
+
}),
|
|
145
152
|
};
|
|
146
153
|
|
|
147
154
|
constructor(schema: StructPrimitiveSchema<TFields>) {
|
|
@@ -221,6 +228,14 @@ export class StructPrimitive<TFields extends Record<string, AnyPrimitive>, TRequ
|
|
|
221
228
|
return field;
|
|
222
229
|
}
|
|
223
230
|
|
|
231
|
+
private _isRequiredWithoutDefault(field: AnyPrimitive): boolean {
|
|
232
|
+
const fieldDefault = field._internal.getInitialState();
|
|
233
|
+
return (
|
|
234
|
+
(field as { _schema?: { required?: boolean } })._schema?.required === true &&
|
|
235
|
+
fieldDefault === undefined
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
|
|
224
239
|
readonly _internal: PrimitiveInternal<InferStructState<TFields>, StructProxy<TFields, TRequired, THasDefault>> = {
|
|
225
240
|
createProxy: (env: ProxyEnvironment.ProxyEnvironment, operationPath: OperationPath.OperationPath): StructProxy<TFields, TRequired, THasDefault> => {
|
|
226
241
|
const fields = this._schema.fields;
|
|
@@ -270,12 +285,24 @@ export class StructPrimitive<TFields extends Record<string, AnyPrimitive>, TRequ
|
|
|
270
285
|
for (const key in value) {
|
|
271
286
|
if (Object.prototype.hasOwnProperty.call(value, key)) {
|
|
272
287
|
const fieldValue = value[key as keyof TFields];
|
|
273
|
-
if (fieldValue === undefined) continue; // Skip undefined values
|
|
274
|
-
|
|
275
288
|
const fieldPrimitive = fields[key as keyof TFields];
|
|
276
289
|
if (!fieldPrimitive) continue; // Skip unknown fields
|
|
277
290
|
|
|
278
291
|
const fieldPath = operationPath.append(key);
|
|
292
|
+
|
|
293
|
+
const shouldUnset =
|
|
294
|
+
fieldValue === undefined ||
|
|
295
|
+
(fieldValue === null && !primitiveAllowsNullValue(fieldPrimitive));
|
|
296
|
+
if (shouldUnset) {
|
|
297
|
+
if (this._isRequiredWithoutDefault(fieldPrimitive)) {
|
|
298
|
+
throw new ValidationError(`Field "${key}" is required and cannot be null or undefined`);
|
|
299
|
+
}
|
|
300
|
+
env.addOperation(
|
|
301
|
+
Operation.fromDefinition(fieldPath, this._opDefinitions.unset, null)
|
|
302
|
+
);
|
|
303
|
+
continue;
|
|
304
|
+
}
|
|
305
|
+
|
|
279
306
|
const fieldProxy = fieldPrimitive._internal.createProxy(env, fieldPath);
|
|
280
307
|
|
|
281
308
|
// Check if this is a nested struct and value is a plain object (partial update)
|
|
@@ -368,24 +395,32 @@ export class StructPrimitive<TFields extends Record<string, AnyPrimitive>, TRequ
|
|
|
368
395
|
}
|
|
369
396
|
|
|
370
397
|
const fieldPrimitive = this._schema.fields[fieldName]!;
|
|
371
|
-
const remainingPath = path.shift();
|
|
372
|
-
const fieldOperation = {
|
|
373
|
-
...operation,
|
|
374
|
-
path: remainingPath,
|
|
375
|
-
};
|
|
376
|
-
|
|
377
398
|
// Get the current field state
|
|
378
399
|
const currentState = state ?? ({} as InferStructState<TFields>);
|
|
379
|
-
|
|
400
|
+
if (operation.kind === "struct.unset" && tokens.length === 1) {
|
|
401
|
+
if (this._isRequiredWithoutDefault(fieldPrimitive)) {
|
|
402
|
+
throw new ValidationError(`Field "${globalThis.String(fieldName)}" is required and cannot be removed`);
|
|
403
|
+
}
|
|
404
|
+
const mutableState = { ...currentState } as Record<string, unknown>;
|
|
405
|
+
delete mutableState[globalThis.String(fieldName)];
|
|
406
|
+
newState = mutableState as InferStructState<TFields>;
|
|
407
|
+
} else {
|
|
408
|
+
const remainingPath = path.shift();
|
|
409
|
+
const fieldOperation = {
|
|
410
|
+
...operation,
|
|
411
|
+
path: remainingPath,
|
|
412
|
+
};
|
|
413
|
+
const currentFieldState = currentState[fieldName] as InferState<typeof fieldPrimitive> | undefined;
|
|
380
414
|
|
|
381
|
-
|
|
382
|
-
|
|
415
|
+
// Apply the operation to the field
|
|
416
|
+
const newFieldState = fieldPrimitive._internal.applyOperation(currentFieldState, fieldOperation);
|
|
383
417
|
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
418
|
+
// Build updated state
|
|
419
|
+
newState = {
|
|
420
|
+
...currentState,
|
|
421
|
+
[fieldName]: newFieldState,
|
|
422
|
+
};
|
|
423
|
+
}
|
|
389
424
|
}
|
|
390
425
|
|
|
391
426
|
// Run validators on the new state
|
|
@@ -501,4 +536,3 @@ export const Struct = <TFields extends Record<string, AnyPrimitive>>(
|
|
|
501
536
|
fields: TFields
|
|
502
537
|
): StructPrimitive<TFields, false, false> =>
|
|
503
538
|
new StructPrimitive({ required: false, defaultValue: undefined, fields, validators: [] });
|
|
504
|
-
|