@voidhash/mimic 0.0.1-alpha.1 → 0.0.1-alpha.10
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 +51 -0
- package/LICENSE.md +663 -0
- package/dist/Document-ChuFrTk1.cjs +571 -0
- package/dist/Document-CwiAFTIq.mjs +438 -0
- package/dist/Document-CwiAFTIq.mjs.map +1 -0
- package/dist/Presence-DKKP4v5X.d.cts +91 -0
- package/dist/Presence-DKKP4v5X.d.cts.map +1 -0
- package/dist/Presence-DdMVKcOv.mjs +110 -0
- package/dist/Presence-DdMVKcOv.mjs.map +1 -0
- package/dist/Presence-N8u7Eppr.d.mts +91 -0
- package/dist/Presence-N8u7Eppr.d.mts.map +1 -0
- package/dist/Presence-gWrmGBeu.cjs +126 -0
- package/dist/Primitive-CvFVxR8_.d.cts +1175 -0
- package/dist/Primitive-CvFVxR8_.d.cts.map +1 -0
- package/dist/Primitive-lEhQyGVL.d.mts +1175 -0
- package/dist/Primitive-lEhQyGVL.d.mts.map +1 -0
- package/dist/chunk-CLMFDpHK.mjs +18 -0
- package/dist/client/index.cjs +1456 -0
- package/dist/client/index.d.cts +692 -0
- package/dist/client/index.d.cts.map +1 -0
- package/dist/client/index.d.mts +692 -0
- package/dist/client/index.d.mts.map +1 -0
- package/dist/client/index.mjs +1413 -0
- package/dist/client/index.mjs.map +1 -0
- package/dist/index.cjs +2577 -0
- package/dist/index.d.cts +143 -0
- package/dist/index.d.cts.map +1 -0
- package/dist/index.d.mts +143 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +2526 -0
- package/dist/index.mjs.map +1 -0
- package/dist/server/index.cjs +191 -0
- package/dist/server/index.d.cts +148 -0
- package/dist/server/index.d.cts.map +1 -0
- package/dist/server/index.d.mts +148 -0
- package/dist/server/index.d.mts.map +1 -0
- package/dist/server/index.mjs +182 -0
- package/dist/server/index.mjs.map +1 -0
- package/package.json +25 -13
- package/src/EffectSchema.ts +374 -0
- package/src/Primitive.ts +3 -0
- package/src/client/ClientDocument.ts +1 -1
- package/src/client/errors.ts +10 -10
- package/src/index.ts +1 -0
- package/src/primitives/Array.ts +57 -22
- package/src/primitives/Boolean.ts +33 -19
- package/src/primitives/Either.ts +379 -0
- package/src/primitives/Lazy.ts +16 -2
- package/src/primitives/Literal.ts +33 -20
- package/src/primitives/Number.ts +39 -26
- package/src/primitives/String.ts +40 -25
- package/src/primitives/Struct.ts +126 -29
- package/src/primitives/Tree.ts +119 -32
- package/src/primitives/TreeNode.ts +77 -30
- package/src/primitives/Union.ts +56 -29
- package/src/primitives/shared.ts +111 -9
- package/src/server/errors.ts +6 -6
- package/tests/EffectSchema.test.ts +546 -0
- package/tests/primitives/Array.test.ts +108 -0
- package/tests/primitives/Either.test.ts +707 -0
- package/tests/primitives/Struct.test.ts +250 -0
- package/tests/primitives/Tree.test.ts +250 -0
- package/tsdown.config.ts +1 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.mjs","names":["Document.make","_transactionOrder: string[]","Transaction.isEmpty"],"sources":["../../src/server/ServerDocument.ts","../../src/server/errors.ts"],"sourcesContent":["import * as Document from \"../Document\";\nimport * as Transaction from \"../Transaction\";\nimport type * as Primitive from \"../Primitive\";\n\n// =============================================================================\n// Server Message Types (matching client's Transport expectations)\n// =============================================================================\n\n/**\n * Message sent when broadcasting a committed transaction.\n */\nexport interface TransactionMessage {\n readonly type: \"transaction\";\n readonly transaction: Transaction.Transaction;\n /** Server-assigned version number for ordering */\n readonly version: number;\n}\n\n/**\n * Message sent when a transaction is rejected.\n */\nexport interface ErrorMessage {\n readonly type: \"error\";\n readonly transactionId: string;\n readonly reason: string;\n}\n\n/**\n * Message sent as a full state snapshot.\n */\nexport interface SnapshotMessage {\n readonly type: \"snapshot\";\n readonly state: unknown;\n readonly version: number;\n}\n\n/**\n * Union of all server messages that can be broadcast.\n */\nexport type ServerMessage = TransactionMessage | ErrorMessage | SnapshotMessage;\n\n// =============================================================================\n// Submit Result Types\n// =============================================================================\n\n/**\n * Result of submitting a transaction to the server.\n */\nexport type SubmitResult =\n | { readonly success: true; readonly version: number }\n | { readonly success: false; readonly reason: string };\n\n// =============================================================================\n// Server Document Types\n// =============================================================================\n\n/**\n * Options for creating a ServerDocument.\n */\nexport interface ServerDocumentOptions<TSchema extends Primitive.AnyPrimitive> {\n /** The schema defining the document structure */\n readonly schema: TSchema;\n /** Initial state (optional, will use schema defaults if not provided) */\n readonly initialState?: Primitive.InferState<TSchema>;\n /** Initial version number (optional, defaults to 0) */\n readonly initialVersion?: number;\n /** Called when a transaction is successfully applied and should be broadcast */\n readonly onBroadcast: (message: TransactionMessage) => void;\n /** Called when a transaction is rejected (optional, for logging/metrics) */\n readonly onRejection?: (transactionId: string, reason: string) => void;\n /** Maximum number of processed transaction IDs to track for deduplication */\n readonly maxTransactionHistory?: number;\n}\n\n/**\n * A ServerDocument maintains the authoritative state and processes client transactions.\n */\nexport interface ServerDocument<TSchema extends Primitive.AnyPrimitive> {\n /** The schema defining this document's structure */\n readonly schema: TSchema;\n\n /** Returns the current authoritative state */\n get(): Primitive.InferState<TSchema> | undefined;\n\n /** Returns the current version number */\n getVersion(): number;\n\n /**\n * Submits a transaction for processing.\n * Validates and applies the transaction if valid, or rejects it with a reason.\n * @param transaction - The transaction to process\n * @returns SubmitResult indicating success with version or failure with reason\n */\n submit(transaction: Transaction.Transaction): SubmitResult;\n\n /**\n * Returns a snapshot of the current state and version.\n * Used to initialize new clients or resync after drift.\n */\n getSnapshot(): SnapshotMessage;\n\n /**\n * Checks if a transaction has already been processed.\n * @param transactionId - The transaction ID to check\n */\n hasProcessed(transactionId: string): boolean;\n}\n\n// =============================================================================\n// Server Document Implementation\n// =============================================================================\n\n/**\n * Creates a new ServerDocument for the given schema.\n */\nexport const make = <TSchema extends Primitive.AnyPrimitive>(\n options: ServerDocumentOptions<TSchema>\n): ServerDocument<TSchema> => {\n const {\n schema,\n initialState,\n initialVersion = 0,\n onBroadcast,\n onRejection,\n maxTransactionHistory = 1000,\n } = options;\n\n // ==========================================================================\n // Internal State\n // ==========================================================================\n\n // The authoritative document\n let _document = Document.make(schema, { initial: initialState });\n\n // Current version number (incremented on each successful transaction)\n let _version = initialVersion;\n\n // Track processed transaction IDs for deduplication\n const _processedTransactions = new Set<string>();\n const _transactionOrder: string[] = [];\n\n // ==========================================================================\n // Helper Functions\n // ==========================================================================\n\n /**\n * Records a transaction as processed, maintaining the history limit.\n */\n const recordTransaction = (transactionId: string): void => {\n _processedTransactions.add(transactionId);\n _transactionOrder.push(transactionId);\n\n // Evict oldest transactions if over limit\n while (_transactionOrder.length > maxTransactionHistory) {\n const oldest = _transactionOrder.shift();\n if (oldest) {\n _processedTransactions.delete(oldest);\n }\n }\n };\n\n /**\n * Validates that the transaction can be applied to the current state.\n * Creates a temporary document and attempts to apply the operations.\n */\n const validateTransaction = (\n transaction: Transaction.Transaction\n ): { valid: true } | { valid: false; reason: string } => {\n // Check for empty transaction\n if (Transaction.isEmpty(transaction)) {\n return { valid: false, reason: \"Transaction is empty\" };\n }\n\n // Check for duplicate transaction\n if (_processedTransactions.has(transaction.id)) {\n return { valid: false, reason: \"Transaction has already been processed\" };\n }\n\n // Create a temporary document with current state to test the operations\n const currentState = _document.get();\n const tempDoc = Document.make(schema, { initial: currentState });\n\n try {\n // Attempt to apply all operations\n tempDoc.apply(transaction.ops);\n return { valid: true };\n } catch (error) {\n // Operations failed to apply\n const message = error instanceof Error ? error.message : String(error);\n return { valid: false, reason: message };\n }\n };\n\n // ==========================================================================\n // Public API\n // ==========================================================================\n\n const serverDocument: ServerDocument<TSchema> = {\n schema,\n\n get: (): Primitive.InferState<TSchema> | undefined => {\n return _document.get();\n },\n\n getVersion: (): number => {\n return _version;\n },\n\n submit: (transaction: Transaction.Transaction): SubmitResult => {\n // Validate the transaction\n const validation = validateTransaction(transaction);\n\n if (!validation.valid) {\n // Notify rejection callback if provided\n onRejection?.(transaction.id, validation.reason);\n\n return {\n success: false,\n reason: validation.reason,\n };\n }\n\n // Apply the transaction to the authoritative state\n try {\n _document.apply(transaction.ops);\n } catch (error) {\n // This shouldn't happen since we validated, but handle gracefully\n const reason = error instanceof Error ? error.message : String(error);\n onRejection?.(transaction.id, reason);\n return { success: false, reason };\n }\n\n // Increment version\n _version += 1;\n\n // Record as processed\n recordTransaction(transaction.id);\n\n // Broadcast the confirmed transaction\n const message: TransactionMessage = {\n type: \"transaction\",\n transaction,\n version: _version,\n };\n onBroadcast(message);\n\n return {\n success: true,\n version: _version,\n };\n },\n\n getSnapshot: (): SnapshotMessage => {\n return {\n type: \"snapshot\",\n state: _document.get(),\n version: _version,\n };\n },\n\n hasProcessed: (transactionId: string): boolean => {\n return _processedTransactions.has(transactionId);\n },\n };\n\n return serverDocument;\n};\n","import type * as Transaction from \"../Transaction\";\n\n// =============================================================================\n// Server Errors\n// =============================================================================\n\n/**\n * Base error class for all mimic-server errors.\n */\nexport class MimicServerError extends Error {\n readonly _tag: string = \"MimicServerError\";\n constructor(message: string) {\n super(message);\n this.name = \"MimicServerError\";\n }\n}\n\n/**\n * Error thrown when a transaction fails validation.\n */\nexport class ValidationError extends MimicServerError {\n override readonly _tag = \"ValidationError\";\n readonly transactionId: string;\n\n constructor(transactionId: string, message: string) {\n super(`Transaction ${transactionId} validation failed: ${message}`);\n this.name = \"ValidationError\";\n this.transactionId = transactionId;\n }\n}\n\n/**\n * Error thrown when an operation is invalid for the current schema.\n */\nexport class InvalidOperationError extends MimicServerError {\n override readonly _tag = \"InvalidOperationError\";\n readonly operationKind: string;\n readonly path: string;\n\n constructor(operationKind: string, path: string, message: string) {\n super(`Invalid operation ${operationKind} at path \"${path}\": ${message}`);\n this.name = \"InvalidOperationError\";\n this.operationKind = operationKind;\n this.path = path;\n }\n}\n\n/**\n * Error thrown when an operation cannot be applied to the current state.\n */\nexport class StateValidationError extends MimicServerError {\n override readonly _tag = \"StateValidationError\";\n readonly transactionId: string;\n override readonly cause?: Error;\n\n constructor(transactionId: string, message: string, cause?: Error) {\n super(`Transaction ${transactionId} cannot be applied to current state: ${message}`);\n this.name = \"StateValidationError\";\n this.transactionId = transactionId;\n this.cause = cause;\n }\n}\n\n/**\n * Error thrown when attempting to apply an empty transaction.\n */\nexport class EmptyTransactionError extends MimicServerError {\n override readonly _tag = \"EmptyTransactionError\";\n readonly transactionId: string;\n\n constructor(transactionId: string) {\n super(`Transaction ${transactionId} is empty and cannot be applied`);\n this.name = \"EmptyTransactionError\";\n this.transactionId = transactionId;\n }\n}\n\n/**\n * Error thrown when a duplicate transaction is submitted.\n */\nexport class DuplicateTransactionError extends MimicServerError {\n override readonly _tag = \"DuplicateTransactionError\";\n readonly transactionId: string;\n\n constructor(transactionId: string) {\n super(`Transaction ${transactionId} has already been processed`);\n this.name = \"DuplicateTransactionError\";\n this.transactionId = transactionId;\n }\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@voidhash/mimic",
|
|
3
|
-
"version": "0.0.1-alpha.
|
|
3
|
+
"version": "0.0.1-alpha.10",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -9,25 +9,37 @@
|
|
|
9
9
|
},
|
|
10
10
|
"main": "./src/index.ts",
|
|
11
11
|
"exports": {
|
|
12
|
-
".":
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
"
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
12
|
+
".": {
|
|
13
|
+
"import": "./dist/index.mjs",
|
|
14
|
+
"types": "./dist/index.d.mts",
|
|
15
|
+
"require": "./dist/index.cjs"
|
|
16
|
+
},
|
|
17
|
+
"./server": {
|
|
18
|
+
"import": "./dist/server/index.mjs",
|
|
19
|
+
"types": "./dist/server/index.d.mts",
|
|
20
|
+
"require": "./dist/server/index.cjs"
|
|
21
|
+
},
|
|
22
|
+
"./client": {
|
|
23
|
+
"import": "./dist/client/index.mjs",
|
|
24
|
+
"types": "./dist/client/index.d.mts",
|
|
25
|
+
"require": "./dist/client/index.cjs"
|
|
26
|
+
}
|
|
21
27
|
},
|
|
22
28
|
"devDependencies": {
|
|
23
29
|
"@effect/vitest": "^0.27.0",
|
|
24
|
-
"@voidhash/tsconfig": "workspace:*",
|
|
25
30
|
"tsdown": "^0.18.2",
|
|
26
31
|
"typescript": "5.8.3",
|
|
27
32
|
"vite-tsconfig-paths": "^5.1.4",
|
|
28
|
-
"vitest": "^3.2.4"
|
|
33
|
+
"vitest": "^3.2.4",
|
|
34
|
+
"@voidhash/tsconfig": "0.0.1-alpha.10"
|
|
29
35
|
},
|
|
30
36
|
"peerDependencies": {
|
|
31
|
-
"effect": "
|
|
37
|
+
"effect": "^3.19.12"
|
|
38
|
+
},
|
|
39
|
+
"scripts": {
|
|
40
|
+
"build": "tsdown",
|
|
41
|
+
"lint": "biome check .",
|
|
42
|
+
"typecheck": "tsc --noEmit",
|
|
43
|
+
"test": "vitest run -c vitest.mts"
|
|
32
44
|
}
|
|
33
45
|
}
|
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Effect.Schema utilities for converting Mimic primitives to Effect.Schema schemas.
|
|
3
|
+
*
|
|
4
|
+
* @since 0.0.1
|
|
5
|
+
*/
|
|
6
|
+
import { Schema } from "effect";
|
|
7
|
+
import type { AnyPrimitive, InferSetInput, InferUpdateInput } from "./primitives/shared";
|
|
8
|
+
import type { LiteralPrimitive, LiteralValue } from "./primitives/Literal";
|
|
9
|
+
import type { StructPrimitive } from "./primitives/Struct";
|
|
10
|
+
import type { ArrayPrimitive } from "./primitives/Array";
|
|
11
|
+
import type { UnionPrimitive, UnionVariants } from "./primitives/Union";
|
|
12
|
+
import type { EitherPrimitive, ScalarPrimitive } from "./primitives/Either";
|
|
13
|
+
import type { LazyPrimitive } from "./primitives/Lazy";
|
|
14
|
+
import type { TreeNodePrimitive, AnyTreeNodePrimitive } from "./primitives/TreeNode";
|
|
15
|
+
|
|
16
|
+
// =============================================================================
|
|
17
|
+
// Type-level Schema Inference
|
|
18
|
+
// =============================================================================
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Infer the Effect.Schema type for a primitive's set input.
|
|
22
|
+
*/
|
|
23
|
+
export type ToSetSchema<T extends AnyPrimitive> = Schema.Schema<InferSetInput<T>>;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Infer the Effect.Schema type for a primitive's update input.
|
|
27
|
+
*/
|
|
28
|
+
export type ToUpdateSchema<T extends AnyPrimitive> = Schema.Schema<InferUpdateInput<T>>;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Type for TreeNode set schema - uses the node's data set input type
|
|
32
|
+
*/
|
|
33
|
+
export type ToTreeNodeSetSchema<T extends AnyTreeNodePrimitive> = Schema.Schema<InferSetInput<T["data"]>>;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Type for TreeNode update schema - uses the node's data update input type
|
|
37
|
+
*/
|
|
38
|
+
export type ToTreeNodeUpdateSchema<T extends AnyTreeNodePrimitive> = Schema.Schema<InferUpdateInput<T["data"]>>;
|
|
39
|
+
|
|
40
|
+
// =============================================================================
|
|
41
|
+
// Schema for TreeNodeState
|
|
42
|
+
// =============================================================================
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Schema for a tree node state (flat storage format).
|
|
46
|
+
*/
|
|
47
|
+
export const TreeNodeStateSchema = Schema.Struct({
|
|
48
|
+
id: Schema.String,
|
|
49
|
+
type: Schema.String,
|
|
50
|
+
parentId: Schema.NullOr(Schema.String),
|
|
51
|
+
pos: Schema.String,
|
|
52
|
+
data: Schema.Unknown,
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
// =============================================================================
|
|
56
|
+
// Internal type for primitives (including those that don't implement full Primitive interface)
|
|
57
|
+
// =============================================================================
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Internal type for anything that can be converted to a schema.
|
|
61
|
+
* This includes both AnyPrimitive and AnyTreeNodePrimitive.
|
|
62
|
+
*/
|
|
63
|
+
type ConvertiblePrimitive = { _tag: string };
|
|
64
|
+
|
|
65
|
+
// =============================================================================
|
|
66
|
+
// Runtime Conversion Functions
|
|
67
|
+
// =============================================================================
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Check if a field is required for set operations.
|
|
71
|
+
* A field is required if: TRequired is true AND THasDefault is false.
|
|
72
|
+
*
|
|
73
|
+
* We determine this by checking the primitive's schema properties.
|
|
74
|
+
*/
|
|
75
|
+
function isRequiredForSet(primitive: ConvertiblePrimitive): boolean {
|
|
76
|
+
// Access the private schema to check required and default status
|
|
77
|
+
const schema = (primitive as any)._schema;
|
|
78
|
+
if (!schema) return false;
|
|
79
|
+
|
|
80
|
+
return schema.required === true && schema.defaultValue === undefined;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Get the base Effect.Schema for a primitive type (without optional wrapper).
|
|
85
|
+
*/
|
|
86
|
+
function getBaseSchema(primitive: ConvertiblePrimitive): Schema.Schema<any> {
|
|
87
|
+
switch (primitive._tag) {
|
|
88
|
+
case "StringPrimitive":
|
|
89
|
+
return Schema.String;
|
|
90
|
+
|
|
91
|
+
case "NumberPrimitive":
|
|
92
|
+
return Schema.Number;
|
|
93
|
+
|
|
94
|
+
case "BooleanPrimitive":
|
|
95
|
+
return Schema.Boolean;
|
|
96
|
+
|
|
97
|
+
case "LiteralPrimitive": {
|
|
98
|
+
const literalPrimitive = primitive as unknown as LiteralPrimitive<LiteralValue, any, any>;
|
|
99
|
+
const literalValue = (literalPrimitive as any)._schema?.literal ?? (literalPrimitive as any).literal;
|
|
100
|
+
return Schema.Literal(literalValue);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
case "StructPrimitive": {
|
|
104
|
+
const structPrimitive = primitive as unknown as StructPrimitive<Record<string, AnyPrimitive>, any, any>;
|
|
105
|
+
return buildStructSetSchema(structPrimitive);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
case "ArrayPrimitive": {
|
|
109
|
+
const arrayPrimitive = primitive as unknown as ArrayPrimitive<AnyPrimitive, any, any>;
|
|
110
|
+
const elementSchema = buildElementSetSchema(arrayPrimitive.element);
|
|
111
|
+
return Schema.Array(elementSchema);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
case "UnionPrimitive": {
|
|
115
|
+
const unionPrimitive = primitive as unknown as UnionPrimitive<UnionVariants, any, any, any>;
|
|
116
|
+
return buildUnionSetSchema(unionPrimitive);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
case "EitherPrimitive": {
|
|
120
|
+
const eitherPrimitive = primitive as unknown as EitherPrimitive<readonly ScalarPrimitive[], any, any>;
|
|
121
|
+
return buildEitherSchema(eitherPrimitive);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
case "LazyPrimitive": {
|
|
125
|
+
const lazyPrimitive = primitive as unknown as LazyPrimitive<() => AnyPrimitive>;
|
|
126
|
+
// Resolve the lazy primitive and get its schema
|
|
127
|
+
const resolved = (lazyPrimitive as any)._resolve?.() ?? (lazyPrimitive as any)._thunk();
|
|
128
|
+
return getBaseSchema(resolved);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
case "TreeNodePrimitive": {
|
|
132
|
+
const treeNodePrimitive = primitive as unknown as TreeNodePrimitive<string, StructPrimitive<any>, any>;
|
|
133
|
+
// TreeNode delegates to its data struct
|
|
134
|
+
return buildStructSetSchema(treeNodePrimitive.data);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
case "TreePrimitive": {
|
|
138
|
+
// Tree returns an array of TreeNodeState
|
|
139
|
+
return Schema.Array(TreeNodeStateSchema);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
default:
|
|
143
|
+
return Schema.Unknown;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Build the set schema for a struct primitive.
|
|
149
|
+
* Required fields (required=true, no default) are non-optional.
|
|
150
|
+
* Other fields are wrapped with Schema.optional.
|
|
151
|
+
*/
|
|
152
|
+
function buildStructSetSchema(structPrimitive: StructPrimitive<Record<string, AnyPrimitive>, any, any>): Schema.Schema<any> {
|
|
153
|
+
const fields = structPrimitive.fields;
|
|
154
|
+
// Use any to avoid complex Schema type constraints
|
|
155
|
+
const schemaFields: Record<string, any> = {};
|
|
156
|
+
|
|
157
|
+
for (const key in fields) {
|
|
158
|
+
const fieldPrimitive = fields[key]!;
|
|
159
|
+
const baseSchema = getBaseSchema(fieldPrimitive);
|
|
160
|
+
|
|
161
|
+
if (isRequiredForSet(fieldPrimitive)) {
|
|
162
|
+
// Required field - use base schema directly
|
|
163
|
+
schemaFields[key] = baseSchema;
|
|
164
|
+
} else {
|
|
165
|
+
// Optional field - wrap with Schema.optional
|
|
166
|
+
schemaFields[key] = Schema.optional(baseSchema);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return Schema.Struct(schemaFields) as any;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Build the update schema for a struct primitive.
|
|
175
|
+
* All fields are optional for partial updates.
|
|
176
|
+
*/
|
|
177
|
+
function buildStructUpdateSchema(structPrimitive: StructPrimitive<Record<string, AnyPrimitive>, any, any>): Schema.Schema<any> {
|
|
178
|
+
const fields = structPrimitive.fields;
|
|
179
|
+
// Use any to avoid complex Schema type constraints
|
|
180
|
+
const schemaFields: Record<string, any> = {};
|
|
181
|
+
|
|
182
|
+
for (const key in fields) {
|
|
183
|
+
const fieldPrimitive = fields[key]!;
|
|
184
|
+
// For update, use the update schema for nested structs, otherwise base schema
|
|
185
|
+
let fieldSchema: Schema.Schema<any>;
|
|
186
|
+
|
|
187
|
+
if (fieldPrimitive._tag === "StructPrimitive") {
|
|
188
|
+
fieldSchema = buildStructUpdateSchema(fieldPrimitive as StructPrimitive<Record<string, AnyPrimitive>, any, any>);
|
|
189
|
+
} else {
|
|
190
|
+
fieldSchema = getBaseSchema(fieldPrimitive);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// All fields are optional in update
|
|
194
|
+
schemaFields[key] = Schema.optional(fieldSchema);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return Schema.Struct(schemaFields) as any;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Build the set schema for an array element.
|
|
202
|
+
* For struct elements, uses the struct's set input schema.
|
|
203
|
+
*/
|
|
204
|
+
function buildElementSetSchema(elementPrimitive: AnyPrimitive): Schema.Schema<any> {
|
|
205
|
+
if (elementPrimitive._tag === "StructPrimitive") {
|
|
206
|
+
return buildStructSetSchema(elementPrimitive as StructPrimitive<Record<string, AnyPrimitive>, any, any>);
|
|
207
|
+
}
|
|
208
|
+
return getBaseSchema(elementPrimitive);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Build the set schema for a union primitive.
|
|
213
|
+
* Creates a Schema.Union of all variant schemas.
|
|
214
|
+
*/
|
|
215
|
+
function buildUnionSetSchema(unionPrimitive: UnionPrimitive<UnionVariants, any, any, any>): Schema.Schema<any> {
|
|
216
|
+
const variants = unionPrimitive.variants;
|
|
217
|
+
const variantSchemas: Schema.Schema<any>[] = [];
|
|
218
|
+
|
|
219
|
+
for (const key in variants) {
|
|
220
|
+
const variantPrimitive = variants[key]!;
|
|
221
|
+
variantSchemas.push(buildStructSetSchema(variantPrimitive));
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
if (variantSchemas.length === 0) {
|
|
225
|
+
return Schema.Unknown;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
if (variantSchemas.length === 1) {
|
|
229
|
+
return variantSchemas[0]!;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
return Schema.Union(...variantSchemas as [Schema.Schema<any>, Schema.Schema<any>, ...Schema.Schema<any>[]]);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Build the schema for an either primitive.
|
|
237
|
+
* Creates a Schema.Union of all scalar variant types.
|
|
238
|
+
*/
|
|
239
|
+
function buildEitherSchema(eitherPrimitive: EitherPrimitive<readonly ScalarPrimitive[], any, any>): Schema.Schema<any> {
|
|
240
|
+
const variants = eitherPrimitive.variants;
|
|
241
|
+
const variantSchemas: Schema.Schema<any>[] = [];
|
|
242
|
+
|
|
243
|
+
for (const variant of variants) {
|
|
244
|
+
variantSchemas.push(getBaseSchema(variant as unknown as ConvertiblePrimitive));
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
if (variantSchemas.length === 0) {
|
|
248
|
+
return Schema.Unknown;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
if (variantSchemas.length === 1) {
|
|
252
|
+
return variantSchemas[0]!;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
return Schema.Union(...variantSchemas as [Schema.Schema<any>, Schema.Schema<any>, ...Schema.Schema<any>[]]);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Build the update schema for a union primitive.
|
|
260
|
+
* Creates a Schema.Union of all variant update schemas.
|
|
261
|
+
*/
|
|
262
|
+
function buildUnionUpdateSchema(unionPrimitive: UnionPrimitive<UnionVariants, any, any, any>): Schema.Schema<any> {
|
|
263
|
+
const variants = unionPrimitive.variants;
|
|
264
|
+
const variantSchemas: Schema.Schema<any>[] = [];
|
|
265
|
+
|
|
266
|
+
for (const key in variants) {
|
|
267
|
+
const variantPrimitive = variants[key]!;
|
|
268
|
+
variantSchemas.push(buildStructUpdateSchema(variantPrimitive));
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
if (variantSchemas.length === 0) {
|
|
272
|
+
return Schema.Unknown;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
if (variantSchemas.length === 1) {
|
|
276
|
+
return variantSchemas[0]!;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
return Schema.Union(...variantSchemas as [Schema.Schema<any>, Schema.Schema<any>, ...Schema.Schema<any>[]]);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Get the update schema for a primitive.
|
|
284
|
+
* For structs, all fields are optional (partial updates).
|
|
285
|
+
* For simple primitives, same as set schema.
|
|
286
|
+
*/
|
|
287
|
+
function getUpdateSchema(primitive: ConvertiblePrimitive): Schema.Schema<any> {
|
|
288
|
+
switch (primitive._tag) {
|
|
289
|
+
case "StructPrimitive": {
|
|
290
|
+
const structPrimitive = primitive as unknown as StructPrimitive<Record<string, AnyPrimitive>, any, any>;
|
|
291
|
+
return buildStructUpdateSchema(structPrimitive);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
case "UnionPrimitive": {
|
|
295
|
+
const unionPrimitive = primitive as unknown as UnionPrimitive<UnionVariants, any, any, any>;
|
|
296
|
+
return buildUnionUpdateSchema(unionPrimitive);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
case "TreeNodePrimitive": {
|
|
300
|
+
const treeNodePrimitive = primitive as unknown as TreeNodePrimitive<string, StructPrimitive<any>, any>;
|
|
301
|
+
// TreeNode update delegates to data struct's update schema (all fields optional)
|
|
302
|
+
return buildStructUpdateSchema(treeNodePrimitive.data);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
case "LazyPrimitive": {
|
|
306
|
+
const lazyPrimitive = primitive as unknown as LazyPrimitive<() => AnyPrimitive>;
|
|
307
|
+
const resolved = (lazyPrimitive as any)._resolve?.() ?? (lazyPrimitive as any)._thunk();
|
|
308
|
+
return getUpdateSchema(resolved);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
default:
|
|
312
|
+
// For simple primitives, update schema is same as set schema
|
|
313
|
+
return getBaseSchema(primitive);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// =============================================================================
|
|
318
|
+
// Public API
|
|
319
|
+
// =============================================================================
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Convert a Mimic primitive to an Effect.Schema for set operations.
|
|
323
|
+
*
|
|
324
|
+
* The resulting schema:
|
|
325
|
+
* - For structs: required fields (required=true, no default) are non-optional, others are optional
|
|
326
|
+
* - For arrays: uses the element's set schema
|
|
327
|
+
* - For unions: creates a Schema.Union of variant schemas
|
|
328
|
+
* - For TreeNode: delegates to the node's data struct schema
|
|
329
|
+
* - For Tree: returns Schema.Array of TreeNodeState
|
|
330
|
+
*
|
|
331
|
+
* @example
|
|
332
|
+
* ```typescript
|
|
333
|
+
* const UserSchema = Primitive.Struct({
|
|
334
|
+
* name: Primitive.String().required(),
|
|
335
|
+
* age: Primitive.Number().default(0),
|
|
336
|
+
* email: Primitive.String(),
|
|
337
|
+
* });
|
|
338
|
+
*
|
|
339
|
+
* const SetSchema = toSetSchema(UserSchema);
|
|
340
|
+
* // { name: string, age?: number, email?: string }
|
|
341
|
+
* ```
|
|
342
|
+
*/
|
|
343
|
+
export function toSetSchema<T extends AnyPrimitive>(primitive: T): ToSetSchema<T>;
|
|
344
|
+
export function toSetSchema<T extends AnyTreeNodePrimitive>(primitive: T): ToTreeNodeSetSchema<T>;
|
|
345
|
+
export function toSetSchema(primitive: ConvertiblePrimitive): Schema.Schema<any> {
|
|
346
|
+
return getBaseSchema(primitive);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* Convert a Mimic primitive to an Effect.Schema for update operations.
|
|
351
|
+
*
|
|
352
|
+
* The resulting schema:
|
|
353
|
+
* - For structs: all fields are optional (partial updates)
|
|
354
|
+
* - For unions: all variant fields are optional
|
|
355
|
+
* - For TreeNode: delegates to the node's data struct update schema
|
|
356
|
+
* - For simple primitives: same as set schema
|
|
357
|
+
*
|
|
358
|
+
* @example
|
|
359
|
+
* ```typescript
|
|
360
|
+
* const UserSchema = Primitive.Struct({
|
|
361
|
+
* name: Primitive.String().required(),
|
|
362
|
+
* age: Primitive.Number().default(0),
|
|
363
|
+
* email: Primitive.String(),
|
|
364
|
+
* });
|
|
365
|
+
*
|
|
366
|
+
* const UpdateSchema = toUpdateSchema(UserSchema);
|
|
367
|
+
* // { name?: string, age?: string, email?: string }
|
|
368
|
+
* ```
|
|
369
|
+
*/
|
|
370
|
+
export function toUpdateSchema<T extends AnyPrimitive>(primitive: T): ToUpdateSchema<T>;
|
|
371
|
+
export function toUpdateSchema<T extends AnyTreeNodePrimitive>(primitive: T): ToTreeNodeUpdateSchema<T>;
|
|
372
|
+
export function toUpdateSchema(primitive: ConvertiblePrimitive): Schema.Schema<any> {
|
|
373
|
+
return getUpdateSchema(primitive);
|
|
374
|
+
}
|
package/src/Primitive.ts
CHANGED
|
@@ -43,7 +43,7 @@ type InitState =
|
|
|
43
43
|
/**
|
|
44
44
|
* Listener for presence changes.
|
|
45
45
|
*/
|
|
46
|
-
export interface PresenceListener<
|
|
46
|
+
export interface PresenceListener<_TData> {
|
|
47
47
|
/** Called when any presence changes (self or others) */
|
|
48
48
|
readonly onPresenceChange?: () => void;
|
|
49
49
|
}
|
package/src/client/errors.ts
CHANGED
|
@@ -19,7 +19,7 @@ export class MimicClientError extends Error {
|
|
|
19
19
|
* Error thrown when a transaction is rejected by the server.
|
|
20
20
|
*/
|
|
21
21
|
export class TransactionRejectedError extends MimicClientError {
|
|
22
|
-
readonly _tag = "TransactionRejectedError";
|
|
22
|
+
override readonly _tag = "TransactionRejectedError";
|
|
23
23
|
readonly transaction: Transaction.Transaction;
|
|
24
24
|
readonly reason: string;
|
|
25
25
|
|
|
@@ -35,7 +35,7 @@ export class TransactionRejectedError extends MimicClientError {
|
|
|
35
35
|
* Error thrown when the transport is not connected.
|
|
36
36
|
*/
|
|
37
37
|
export class NotConnectedError extends MimicClientError {
|
|
38
|
-
readonly _tag = "NotConnectedError";
|
|
38
|
+
override readonly _tag = "NotConnectedError";
|
|
39
39
|
constructor() {
|
|
40
40
|
super("Transport is not connected");
|
|
41
41
|
this.name = "NotConnectedError";
|
|
@@ -46,8 +46,8 @@ export class NotConnectedError extends MimicClientError {
|
|
|
46
46
|
* Error thrown when connection to the server fails.
|
|
47
47
|
*/
|
|
48
48
|
export class ConnectionError extends MimicClientError {
|
|
49
|
-
readonly _tag = "ConnectionError";
|
|
50
|
-
readonly cause?: Error;
|
|
49
|
+
override readonly _tag = "ConnectionError";
|
|
50
|
+
override readonly cause?: Error;
|
|
51
51
|
|
|
52
52
|
constructor(message: string, cause?: Error) {
|
|
53
53
|
super(message);
|
|
@@ -60,7 +60,7 @@ export class ConnectionError extends MimicClientError {
|
|
|
60
60
|
* Error thrown when state drift is detected and cannot be recovered.
|
|
61
61
|
*/
|
|
62
62
|
export class StateDriftError extends MimicClientError {
|
|
63
|
-
readonly _tag = "StateDriftError";
|
|
63
|
+
override readonly _tag = "StateDriftError";
|
|
64
64
|
readonly expectedVersion: number;
|
|
65
65
|
readonly receivedVersion: number;
|
|
66
66
|
|
|
@@ -78,7 +78,7 @@ export class StateDriftError extends MimicClientError {
|
|
|
78
78
|
* Error thrown when a pending transaction times out waiting for confirmation.
|
|
79
79
|
*/
|
|
80
80
|
export class TransactionTimeoutError extends MimicClientError {
|
|
81
|
-
readonly _tag = "TransactionTimeoutError";
|
|
81
|
+
override readonly _tag = "TransactionTimeoutError";
|
|
82
82
|
readonly transaction: Transaction.Transaction;
|
|
83
83
|
readonly timeoutMs: number;
|
|
84
84
|
|
|
@@ -96,7 +96,7 @@ export class TransactionTimeoutError extends MimicClientError {
|
|
|
96
96
|
* Error thrown when rebasing operations fails.
|
|
97
97
|
*/
|
|
98
98
|
export class RebaseError extends MimicClientError {
|
|
99
|
-
readonly _tag = "RebaseError";
|
|
99
|
+
override readonly _tag = "RebaseError";
|
|
100
100
|
readonly transactionId: string;
|
|
101
101
|
|
|
102
102
|
constructor(transactionId: string, message: string) {
|
|
@@ -110,7 +110,7 @@ export class RebaseError extends MimicClientError {
|
|
|
110
110
|
* Error thrown when the client document is in an invalid state.
|
|
111
111
|
*/
|
|
112
112
|
export class InvalidStateError extends MimicClientError {
|
|
113
|
-
readonly _tag = "InvalidStateError";
|
|
113
|
+
override readonly _tag = "InvalidStateError";
|
|
114
114
|
constructor(message: string) {
|
|
115
115
|
super(message);
|
|
116
116
|
this.name = "InvalidStateError";
|
|
@@ -121,7 +121,7 @@ export class InvalidStateError extends MimicClientError {
|
|
|
121
121
|
* Error thrown when WebSocket connection or communication fails.
|
|
122
122
|
*/
|
|
123
123
|
export class WebSocketError extends MimicClientError {
|
|
124
|
-
readonly _tag = "WebSocketError";
|
|
124
|
+
override readonly _tag = "WebSocketError";
|
|
125
125
|
readonly code?: number;
|
|
126
126
|
readonly reason?: string;
|
|
127
127
|
|
|
@@ -137,7 +137,7 @@ export class WebSocketError extends MimicClientError {
|
|
|
137
137
|
* Error thrown when authentication fails.
|
|
138
138
|
*/
|
|
139
139
|
export class AuthenticationError extends MimicClientError {
|
|
140
|
-
readonly _tag = "AuthenticationError";
|
|
140
|
+
override readonly _tag = "AuthenticationError";
|
|
141
141
|
constructor(message: string) {
|
|
142
142
|
super(message);
|
|
143
143
|
this.name = "AuthenticationError";
|
package/src/index.ts
CHANGED