@voidhash/mimic 0.0.9 → 1.0.0-beta.2

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.
@@ -1,5 +1,5 @@
1
1
 
2
- > @voidhash/mimic@0.0.9 build /home/runner/work/mimic/mimic/packages/mimic
2
+ > @voidhash/mimic@1.0.0-beta.2 build /home/runner/work/mimic/mimic/packages/mimic
3
3
  > tsdown
4
4
 
5
5
  ℹ tsdown v0.18.2 powered by rolldown v1.0.0-beta.55
@@ -31,8 +31,8 @@
31
31
  ℹ [CJS] dist/primitives/Literal.cjs  3.89 kB │ gzip: 1.06 kB
32
32
  ℹ [CJS] dist/client/StateMonitor.cjs  3.71 kB │ gzip: 1.27 kB
33
33
  ℹ [CJS] dist/primitives/shared.cjs  3.69 kB │ gzip: 1.25 kB
34
+ ℹ [CJS] dist/server/ServerDocument.cjs  3.66 kB │ gzip: 1.14 kB
34
35
  ℹ [CJS] dist/server/errors.cjs  3.29 kB │ gzip: 0.70 kB
35
- ℹ [CJS] dist/server/ServerDocument.cjs  3.16 kB │ gzip: 1.04 kB
36
36
  ℹ [CJS] dist/primitives/TreeNode.cjs  2.67 kB │ gzip: 0.92 kB
37
37
  ℹ [CJS] dist/Presence.cjs  2.36 kB │ gzip: 0.87 kB
38
38
  ℹ [CJS] dist/Primitive.cjs  2.34 kB │ gzip: 0.59 kB
@@ -52,7 +52,7 @@
52
52
  ℹ [CJS] dist/OperationDefinition.cjs  0.22 kB │ gzip: 0.16 kB
53
53
  ℹ [CJS] dist/client/Transport.cjs  0.21 kB │ gzip: 0.16 kB
54
54
  ℹ [CJS] dist/Transform.cjs  0.20 kB │ gzip: 0.16 kB
55
- ℹ [CJS] 44 files, total: 213.69 kB
55
+ ℹ [CJS] 44 files, total: 214.19 kB
56
56
  ℹ [CJS] dist/primitives/Tree.d.cts.map  6.90 kB │ gzip: 2.60 kB
57
57
  ℹ [CJS] dist/primitives/Struct.d.cts.map  4.62 kB │ gzip: 1.95 kB
58
58
  ℹ [CJS] dist/utils/tree-helpers.d.cts.map  3.61 kB │ gzip: 1.31 kB
@@ -70,9 +70,9 @@
70
70
  ℹ [CJS] dist/primitives/Lazy.d.cts.map  1.44 kB │ gzip: 0.69 kB
71
71
  ℹ [CJS] dist/client/Rebase.d.cts.map  1.02 kB │ gzip: 0.47 kB
72
72
  ℹ [CJS] dist/EffectSchema.d.cts.map  1.00 kB │ gzip: 0.48 kB
73
+ ℹ [CJS] dist/server/ServerDocument.d.cts.map  0.92 kB │ gzip: 0.46 kB
73
74
  ℹ [CJS] dist/Document.d.cts.map  0.85 kB │ gzip: 0.46 kB
74
75
  ℹ [CJS] dist/Operation.d.cts.map  0.81 kB │ gzip: 0.42 kB
75
- ℹ [CJS] dist/server/ServerDocument.d.cts.map  0.81 kB │ gzip: 0.44 kB
76
76
  ℹ [CJS] dist/OperationPath.d.cts.map  0.77 kB │ gzip: 0.43 kB
77
77
  ℹ [CJS] dist/client/errors.d.cts.map  0.74 kB │ gzip: 0.38 kB
78
78
  ℹ [CJS] dist/Presence.d.cts.map  0.69 kB │ gzip: 0.38 kB
@@ -87,7 +87,7 @@
87
87
  ℹ [CJS] dist/Primitive.d.cts.map  0.12 kB │ gzip: 0.11 kB
88
88
  ℹ [CJS] dist/index.d.cts  1.05 kB │ gzip: 0.28 kB
89
89
  ℹ [CJS] dist/client/index.d.cts  1.01 kB │ gzip: 0.29 kB
90
- ℹ [CJS] dist/server/index.d.cts  0.65 kB │ gzip: 0.23 kB
90
+ ℹ [CJS] dist/server/index.d.cts  0.68 kB │ gzip: 0.24 kB
91
91
  ℹ [CJS] dist/primitives/Tree.d.cts 13.50 kB │ gzip: 3.50 kB
92
92
  ℹ [CJS] dist/utils/tree-helpers.d.cts 12.18 kB │ gzip: 2.52 kB
93
93
  ℹ [CJS] dist/client/Transport.d.cts  7.30 kB │ gzip: 1.84 kB
@@ -96,11 +96,11 @@
96
96
  ℹ [CJS] dist/client/ClientDocument.d.cts  6.07 kB │ gzip: 1.68 kB
97
97
  ℹ [CJS] dist/primitives/Either.d.cts  5.44 kB │ gzip: 1.54 kB
98
98
  ℹ [CJS] dist/primitives/Array.d.cts  5.01 kB │ gzip: 1.44 kB
99
+ ℹ [CJS] dist/server/ServerDocument.d.cts  4.90 kB │ gzip: 1.55 kB
99
100
  ℹ [CJS] dist/client/Rebase.d.cts  4.88 kB │ gzip: 1.14 kB
100
101
  ℹ [CJS] dist/primitives/Union.d.cts  4.58 kB │ gzip: 1.26 kB
101
102
  ℹ [CJS] dist/primitives/TreeNode.d.cts  4.28 kB │ gzip: 1.29 kB
102
103
  ℹ [CJS] dist/Primitive.d.cts  3.79 kB │ gzip: 0.82 kB
103
- ℹ [CJS] dist/server/ServerDocument.d.cts  3.58 kB │ gzip: 1.19 kB
104
104
  ℹ [CJS] dist/client/StateMonitor.d.cts  3.33 kB │ gzip: 1.07 kB
105
105
  ℹ [CJS] dist/EffectSchema.d.cts  3.20 kB │ gzip: 0.92 kB
106
106
  ℹ [CJS] dist/primitives/String.d.cts  3.08 kB │ gzip: 0.85 kB
@@ -120,8 +120,8 @@
120
120
  ℹ [CJS] dist/client/WebSocketTransport.d.cts  1.22 kB │ gzip: 0.55 kB
121
121
  ℹ [CJS] dist/Transform.d.cts  0.48 kB │ gzip: 0.26 kB
122
122
  ℹ [CJS] dist/OperationDefinition.d.cts  0.36 kB │ gzip: 0.21 kB
123
- ℹ [CJS] 67 files, total: 181.04 kB
124
- ✔ Build complete in 5818ms
123
+ ℹ [CJS] 67 files, total: 182.50 kB
124
+ ✔ Build complete in 5745ms
125
125
  ℹ [ESM] dist/index.mjs  1.01 kB │ gzip: 0.28 kB
126
126
  ℹ [ESM] dist/client/index.mjs  0.99 kB │ gzip: 0.28 kB
127
127
  ℹ [ESM] dist/server/index.mjs  0.41 kB │ gzip: 0.17 kB
@@ -140,11 +140,11 @@
140
140
  ℹ [ESM] dist/FractionalIndex.mjs 16.49 kB │ gzip: 3.36 kB
141
141
  ℹ [ESM] dist/client/Rebase.mjs.map 14.15 kB │ gzip: 3.21 kB
142
142
  ℹ [ESM] dist/utils/tree-helpers.mjs 13.31 kB │ gzip: 3.15 kB
143
+ ℹ [ESM] dist/server/ServerDocument.mjs.map 13.26 kB │ gzip: 3.59 kB
143
144
  ℹ [ESM] dist/primitives/shared.mjs.map 13.14 kB │ gzip: 3.69 kB
144
145
  ℹ [ESM] dist/primitives/Array.mjs 11.07 kB │ gzip: 2.64 kB
145
146
  ℹ [ESM] dist/Document.mjs.map 10.89 kB │ gzip: 3.31 kB
146
147
  ℹ [ESM] dist/client/WebSocketTransport.mjs 10.81 kB │ gzip: 2.68 kB
147
- ℹ [ESM] dist/server/ServerDocument.mjs.map 10.44 kB │ gzip: 2.95 kB
148
148
  ℹ [ESM] dist/client/StateMonitor.mjs.map 10.39 kB │ gzip: 3.04 kB
149
149
  ℹ [ESM] dist/primitives/String.mjs.map  9.70 kB │ gzip: 2.70 kB
150
150
  ℹ [ESM] dist/primitives/Struct.mjs  9.30 kB │ gzip: 2.31 kB
@@ -169,16 +169,16 @@
169
169
  ℹ [ESM] dist/Document.mjs  4.00 kB │ gzip: 1.40 kB
170
170
  ℹ [ESM] dist/primitives/Number.mjs  3.99 kB │ gzip: 1.19 kB
171
171
  ℹ [ESM] dist/server/errors.mjs.map  3.86 kB │ gzip: 0.96 kB
172
- ℹ [ESM] dist/types/index.mjs.map  3.74 kB │ gzip: 0.79 kB
172
+ ℹ [ESM] dist/types/index.mjs.map  3.71 kB │ gzip: 0.78 kB
173
173
  ℹ [ESM] dist/utils/tree-helpers.d.mts.map  3.61 kB │ gzip: 1.31 kB
174
174
  ℹ [ESM] dist/client/StateMonitor.mjs  3.60 kB │ gzip: 1.23 kB
175
175
  ℹ [ESM] dist/primitives/shared.mjs  3.55 kB │ gzip: 1.25 kB
176
+ ℹ [ESM] dist/server/ServerDocument.mjs  3.49 kB │ gzip: 1.11 kB
176
177
  ℹ [ESM] dist/primitives/Boolean.mjs  3.40 kB │ gzip: 1.04 kB
177
178
  ℹ [ESM] dist/primitives/Literal.mjs  3.31 kB │ gzip: 1.04 kB
178
179
  ℹ [ESM] dist/primitives/Array.d.mts.map  3.26 kB │ gzip: 1.35 kB
179
180
  ℹ [ESM] dist/primitives/Union.d.mts.map  3.15 kB │ gzip: 1.37 kB
180
181
  ℹ [ESM] dist/primitives/Either.d.mts.map  3.12 kB │ gzip: 1.26 kB
181
- ℹ [ESM] dist/server/ServerDocument.mjs  2.99 kB │ gzip: 1.01 kB
182
182
  ℹ [ESM] dist/server/errors.mjs  2.85 kB │ gzip: 0.70 kB
183
183
  ℹ [ESM] dist/Operation.mjs.map  2.75 kB │ gzip: 0.86 kB
184
184
  ℹ [ESM] dist/Transaction.mjs.map  2.65 kB │ gzip: 0.99 kB
@@ -204,9 +204,9 @@
204
204
  ℹ [ESM] dist/client/Rebase.d.mts.map  1.03 kB │ gzip: 0.48 kB
205
205
  ℹ [ESM] dist/EffectSchema.d.mts.map  1.00 kB │ gzip: 0.48 kB
206
206
  ℹ [ESM] dist/_virtual/_@oxc-project_runtime@0.103.0/helpers/objectSpread2.mjs  0.90 kB │ gzip: 0.42 kB
207
+ ℹ [ESM] dist/server/ServerDocument.d.mts.map  0.90 kB │ gzip: 0.46 kB
207
208
  ℹ [ESM] dist/Document.d.mts.map  0.84 kB │ gzip: 0.46 kB
208
209
  ℹ [ESM] dist/Operation.d.mts.map  0.81 kB │ gzip: 0.42 kB
209
- ℹ [ESM] dist/server/ServerDocument.d.mts.map  0.80 kB │ gzip: 0.44 kB
210
210
  ℹ [ESM] dist/OperationPath.d.mts.map  0.77 kB │ gzip: 0.43 kB
211
211
  ℹ [ESM] dist/client/errors.d.mts.map  0.74 kB │ gzip: 0.38 kB
212
212
  ℹ [ESM] dist/Presence.d.mts.map  0.69 kB │ gzip: 0.38 kB
@@ -233,7 +233,7 @@
233
233
  ℹ [ESM] dist/Primitive.d.mts.map  0.12 kB │ gzip: 0.11 kB
234
234
  ℹ [ESM] dist/index.d.mts  1.05 kB │ gzip: 0.28 kB
235
235
  ℹ [ESM] dist/client/index.d.mts  1.01 kB │ gzip: 0.29 kB
236
- ℹ [ESM] dist/server/index.d.mts  0.65 kB │ gzip: 0.23 kB
236
+ ℹ [ESM] dist/server/index.d.mts  0.68 kB │ gzip: 0.24 kB
237
237
  ℹ [ESM] dist/primitives/Tree.d.mts 13.50 kB │ gzip: 3.50 kB
238
238
  ℹ [ESM] dist/utils/tree-helpers.d.mts 12.18 kB │ gzip: 2.52 kB
239
239
  ℹ [ESM] dist/client/Transport.d.mts  7.30 kB │ gzip: 1.84 kB
@@ -242,11 +242,11 @@
242
242
  ℹ [ESM] dist/client/ClientDocument.d.mts  6.09 kB │ gzip: 1.69 kB
243
243
  ℹ [ESM] dist/primitives/Either.d.mts  5.44 kB │ gzip: 1.54 kB
244
244
  ℹ [ESM] dist/primitives/Array.d.mts  5.04 kB │ gzip: 1.45 kB
245
+ ℹ [ESM] dist/server/ServerDocument.d.mts  4.93 kB │ gzip: 1.56 kB
245
246
  ℹ [ESM] dist/client/Rebase.d.mts  4.91 kB │ gzip: 1.15 kB
246
247
  ℹ [ESM] dist/primitives/Union.d.mts  4.61 kB │ gzip: 1.27 kB
247
248
  ℹ [ESM] dist/primitives/TreeNode.d.mts  4.31 kB │ gzip: 1.29 kB
248
249
  ℹ [ESM] dist/Primitive.d.mts  3.79 kB │ gzip: 0.82 kB
249
- ℹ [ESM] dist/server/ServerDocument.d.mts  3.61 kB │ gzip: 1.19 kB
250
250
  ℹ [ESM] dist/client/StateMonitor.d.mts  3.33 kB │ gzip: 1.07 kB
251
251
  ℹ [ESM] dist/EffectSchema.d.mts  3.20 kB │ gzip: 0.92 kB
252
252
  ℹ [ESM] dist/primitives/String.d.mts  3.08 kB │ gzip: 0.85 kB
@@ -266,5 +266,5 @@
266
266
  ℹ [ESM] dist/client/WebSocketTransport.d.mts  1.22 kB │ gzip: 0.55 kB
267
267
  ℹ [ESM] dist/Transform.d.mts  0.48 kB │ gzip: 0.26 kB
268
268
  ℹ [ESM] dist/OperationDefinition.d.mts  0.36 kB │ gzip: 0.22 kB
269
- ℹ [ESM] 144 files, total: 845.46 kB
270
- ✔ Build complete in 5846ms
269
+ ℹ [ESM] 144 files, total: 850.21 kB
270
+ ✔ Build complete in 5795ms
@@ -1 +1 @@
1
- {"version":3,"file":"Presence.d.mts","names":[],"sources":["../src/Presence.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;UAeiB;;;mBAGE,MAAA,CAAO,OAAO;;EAHhB,SAAA,KAAQ,EAKP,KALO;;;;;AAWR,UAAA,eAAe,CAAA,KAEC,CAAA,CAAA;EAMrB;EAKA,SAAA,MAAW,EAXJ,MAAA,CAAO,MAWA,CAXO,KAWC,CAAA;AASlC;AA8BA;;;AAAwE,KA5C5D,KA4C4D,CAAA,UA5C5C,QA4C4C,CAAA,GAAA,CAAA,CAAA,GA5C3B,CA4C2B,CAAA,OAAA,CAAA;;;AAmBxE;AACqB,KA3DT,WAAA,GAAc,QA2DL,CAAA,GAAA,CAAA;;;;AAcR,UAhEI,aAyEhB,CAAA,QAAA,OAAA,CAAA,CAAA;EARoB;EAAT,SAAA,IAAA,EA/DK,KA+DL;EAET;EAAK,SAAA,MAAA,CAAA,EAAA,MAAA;AAeR;;;;;;;;;;;;;;;;;;;;cApDa,uBAAyB,gBAAgB,WAAS,SAAS;;;;;;;;;;cAmB3D,4BACD,SAAS,0BAElB;;;;;;;;;cAYU,gCACD,SAAS,0BAElB;;;;;;;;cAeU,2BACD,SAAS,kCAEV"}
1
+ {"version":3,"file":"Presence.d.mts","names":[],"sources":["../src/Presence.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;UAeiB;;;mBAGE,MAAA,CAAO,OAAO;;EAHhB,SAAA,KAAQ,EAKP,KALO;;;;;AAWR,UAAA,eAAe,CAAA,KAAA,CAEC,CAAA;EAMrB;EAKA,SAAA,MAAW,EAXJ,MAAA,CAAO,MAWA,CAXO,KAWC,CAAA;AASlC;AA8BA;;;AAAwE,KA5C5D,KA4C4D,CAAA,UA5C5C,QA4C4C,CAAA,GAAA,CAAA,CAAA,GA5C3B,CA4C2B,CAAA,OAAA,CAAA;;;AAmBxE;AACqB,KA3DT,WAAA,GAAc,QA2DL,CAAA,GAAA,CAAA;;;;AAcR,UAhEI,aAyEhB,CAAA,QAAA,OAAA,CAAA,CAAA;EARoB;EAAT,SAAA,IAAA,EA/DK,KA+DL;EAET;EAAK,SAAA,MAAA,CAAA,EAAA,MAAA;AAeR;;;;;;;;;;;;;;;;;;;;cApDa,uBAAyB,gBAAgB,WAAS,SAAS;;;;;;;;;;cAmB3D,4BACD,SAAS,0BAElB;;;;;;;;;cAYU,gCACD,SAAS,0BAElB;;;;;;;;cAeU,2BACD,SAAS,kCAEV"}
@@ -49,6 +49,20 @@ const make = (options) => {
49
49
  };
50
50
  }
51
51
  };
52
+ /**
53
+ * Internal function to apply a transaction and broadcast.
54
+ * Called by both apply() and submit().
55
+ */
56
+ const applyAndBroadcast = (transaction) => {
57
+ _document.apply(transaction.ops);
58
+ _version += 1;
59
+ recordTransaction(transaction.id);
60
+ onBroadcast({
61
+ type: "transaction",
62
+ transaction,
63
+ version: _version
64
+ });
65
+ };
52
66
  return {
53
67
  schema,
54
68
  get: () => {
@@ -57,6 +71,20 @@ const make = (options) => {
57
71
  getVersion: () => {
58
72
  return _version;
59
73
  },
74
+ validate: (transaction) => {
75
+ const validation = validateTransaction(transaction);
76
+ if (!validation.valid) return {
77
+ valid: false,
78
+ reason: validation.reason
79
+ };
80
+ return {
81
+ valid: true,
82
+ nextVersion: _version + 1
83
+ };
84
+ },
85
+ apply: (transaction) => {
86
+ applyAndBroadcast(transaction);
87
+ },
60
88
  submit: (transaction) => {
61
89
  const validation = validateTransaction(transaction);
62
90
  if (!validation.valid) {
@@ -67,7 +95,7 @@ const make = (options) => {
67
95
  };
68
96
  }
69
97
  try {
70
- _document.apply(transaction.ops);
98
+ applyAndBroadcast(transaction);
71
99
  } catch (error) {
72
100
  const reason = error instanceof Error ? error.message : String(error);
73
101
  onRejection === null || onRejection === void 0 || onRejection(transaction.id, reason);
@@ -76,13 +104,6 @@ const make = (options) => {
76
104
  reason
77
105
  };
78
106
  }
79
- _version += 1;
80
- recordTransaction(transaction.id);
81
- onBroadcast({
82
- type: "transaction",
83
- transaction,
84
- version: _version
85
- });
86
107
  return {
87
108
  success: true,
88
109
  version: _version
@@ -3,7 +3,7 @@ import { AnyPrimitive, InferSetInput, InferState } from "../primitives/shared.cj
3
3
 
4
4
  //#region src/server/ServerDocument.d.ts
5
5
  declare namespace ServerDocument_d_exports {
6
- export { ErrorMessage, ServerDocument, ServerDocumentOptions, ServerMessage, SnapshotMessage, SubmitResult, TransactionMessage, make };
6
+ export { ErrorMessage, ServerDocument, ServerDocumentOptions, ServerMessage, SnapshotMessage, SubmitResult, TransactionMessage, ValidateResult, make };
7
7
  }
8
8
  /**
9
9
  * Message sent when broadcasting a committed transaction.
@@ -44,6 +44,17 @@ type SubmitResult = {
44
44
  readonly success: false;
45
45
  readonly reason: string;
46
46
  };
47
+ /**
48
+ * Result of validating a transaction (two-phase commit: phase 1).
49
+ * If valid, returns the version this transaction will get when applied.
50
+ */
51
+ type ValidateResult = {
52
+ readonly valid: true;
53
+ readonly nextVersion: number;
54
+ } | {
55
+ readonly valid: false;
56
+ readonly reason: string;
57
+ };
47
58
  /**
48
59
  * Options for creating a ServerDocument.
49
60
  */
@@ -72,8 +83,30 @@ interface ServerDocument<TSchema extends AnyPrimitive> {
72
83
  /** Returns the current version number */
73
84
  getVersion(): number;
74
85
  /**
75
- * Submits a transaction for processing.
86
+ * Phase 1 of two-phase commit: Validates a transaction without side effects.
87
+ * Returns the version this transaction would get if applied.
88
+ * Does NOT modify state, increment version, record transaction, or broadcast.
89
+ *
90
+ * Use this to validate before writing to WAL, then call apply() after WAL success.
91
+ *
92
+ * @param transaction - The transaction to validate
93
+ * @returns ValidateResult with nextVersion if valid, or reason if invalid
94
+ */
95
+ validate(transaction: Transaction): ValidateResult;
96
+ /**
97
+ * Phase 2 of two-phase commit: Applies a pre-validated transaction.
98
+ * MUST only be called after validate() succeeded AND WAL write succeeded.
99
+ * Mutates state, increments version, records transaction ID, and broadcasts.
100
+ *
101
+ * @param transaction - The transaction to apply (must have been validated first)
102
+ */
103
+ apply(transaction: Transaction): void;
104
+ /**
105
+ * Submits a transaction for processing (combines validate + apply).
76
106
  * Validates and applies the transaction if valid, or rejects it with a reason.
107
+ *
108
+ * For two-phase commit with WAL, use validate() then apply() instead.
109
+ *
77
110
  * @param transaction - The transaction to process
78
111
  * @returns SubmitResult indicating success with version or failure with reason
79
112
  */
@@ -94,5 +127,5 @@ interface ServerDocument<TSchema extends AnyPrimitive> {
94
127
  */
95
128
  declare const make: <TSchema extends AnyPrimitive>(options: ServerDocumentOptions<TSchema>) => ServerDocument<TSchema>;
96
129
  //#endregion
97
- export { ErrorMessage, ServerDocumentOptions, ServerDocument_d_exports, ServerMessage, SnapshotMessage, SubmitResult, TransactionMessage };
130
+ export { ErrorMessage, ServerDocumentOptions, ServerDocument_d_exports, ServerMessage, SnapshotMessage, SubmitResult, TransactionMessage, ValidateResult };
98
131
  //# sourceMappingURL=ServerDocument.d.cts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ServerDocument.d.cts","names":[],"sources":["../../src/server/ServerDocument.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;UAWiB,kBAAA;;wBAEO;;;;;;;UAQP,YAAA;EAVA,SAAA,IAAA,EAAA,OAAkB;EAUlB,SAAA,aAAY,EAAA,MAAA;EASZ,SAAA,MAAA,EAAA,MAAe;AAShC;;;;AAA+E,UAT9D,eAAA,CAS8D;EASnE,SAAA,IAAA,EAAY,UAAA;EAWP,SAAA,KAAA,EAAA,OAAA;EAAsC,SAAA,OAAA,EAAA,MAAA;;;;;AAQH,KA5BxC,aAAA,GAAgB,kBA4BwB,GA5BH,YA4BG,GA5BY,eA4BZ;AAUpD;;;AAK8B,KAlClB,YAAA,GAkCkB;EAArB,SAAA,OAAA,EAAA,IAAA;EAWa,SAAA,OAAA,EAAA,MAAA;CAA0B,GAAA;EAM/B,SAAA,OAAA,EAAA,KAAA;EAAe,SAAA,MAAA,EAAA,MAAA;AAgBhC,CAAA;;;;AAEkB,UA1DD,qBA0DC,CAAA,gBA1DqC,YA0DrC,CAAA,CAAA;EAAf;EAAc,SAAA,MAAA,EAxDE,OAwDF;;0BAtDS,cAAwB;;;;kCAIhB;;;;;;;;;UAUjB,+BAA+B;;mBAE7B;;SAGV,WAAqB;;;;;;;;;sBAWR,cAA0B;;;;;iBAM/B;;;;;;;;;;cAgBJ,uBAAwB,uBAC1B,sBAAsB,aAC9B,eAAe"}
1
+ {"version":3,"file":"ServerDocument.d.cts","names":[],"sources":["../../src/server/ServerDocument.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;UAWiB,kBAAA;;wBAEO;;;;;;;UAQP,YAAA;;EAVA,SAAA,aAAkB,EAAA,MAAA;EAUlB,SAAA,MAAY,EAAA,MAAA;AAS7B;AASA;;;AAAgE,UAT/C,eAAA,CAS+C;EAAe,SAAA,IAAA,EAAA,UAAA;EASnE,SAAA,KAAA,EAAY,OAAA;EAQZ,SAAA,OAAA,EAAc,MAAA;AAW1B;;;;AAI0B,KAhCd,aAAA,GAAgB,kBAgCF,GAhCuB,YAgCvB,GAhCsC,eAgCtC;;;AAc1B;AAAgD,KArCpC,YAAA,GAqCoC;EAE7B,SAAA,OAAA,EAAA,IAAA;EAGW,SAAA,OAAA,EAAA,MAAA;CAArB,GAAA;EAee,SAAA,OAAA,EAAA,KAAA;EAA0B,SAAA,MAAA,EAAA,MAAA;CAS7B;;;;;AAiCR,KA3FD,cAAA,GAyRX;EA9LoC,SAAA,KAAA,EAAA,IAAA;EACJ,SAAA,WAAA,EAAA,MAAA;CAAtB,GAAA;EACO,SAAA,KAAA,EAAA,KAAA;EAAf,SAAA,MAAA,EAAA,MAAA;CAAc;;;;UAlFA,sCAAsC;;mBAEpC;;0BAEO,cAAwB;;;;kCAIhB;;;;;;;;;UAUjB,+BAA+B;;mBAE7B;;SAGV,WAAqB;;;;;;;;;;;;;wBAeN,cAA0B;;;;;;;;qBAS7B;;;;;;;;;;sBAWC,cAA0B;;;;;iBAM/B;;;;;;;;;;cAgBJ,uBAAwB,uBAC1B,sBAAsB,aAC9B,eAAe"}
@@ -4,7 +4,7 @@ import "../Primitive.mjs";
4
4
 
5
5
  //#region src/server/ServerDocument.d.ts
6
6
  declare namespace ServerDocument_d_exports {
7
- export { ErrorMessage, ServerDocument, ServerDocumentOptions, ServerMessage, SnapshotMessage, SubmitResult, TransactionMessage, make };
7
+ export { ErrorMessage, ServerDocument, ServerDocumentOptions, ServerMessage, SnapshotMessage, SubmitResult, TransactionMessage, ValidateResult, make };
8
8
  }
9
9
  /**
10
10
  * Message sent when broadcasting a committed transaction.
@@ -45,6 +45,17 @@ type SubmitResult = {
45
45
  readonly success: false;
46
46
  readonly reason: string;
47
47
  };
48
+ /**
49
+ * Result of validating a transaction (two-phase commit: phase 1).
50
+ * If valid, returns the version this transaction will get when applied.
51
+ */
52
+ type ValidateResult = {
53
+ readonly valid: true;
54
+ readonly nextVersion: number;
55
+ } | {
56
+ readonly valid: false;
57
+ readonly reason: string;
58
+ };
48
59
  /**
49
60
  * Options for creating a ServerDocument.
50
61
  */
@@ -73,8 +84,30 @@ interface ServerDocument<TSchema extends AnyPrimitive> {
73
84
  /** Returns the current version number */
74
85
  getVersion(): number;
75
86
  /**
76
- * Submits a transaction for processing.
87
+ * Phase 1 of two-phase commit: Validates a transaction without side effects.
88
+ * Returns the version this transaction would get if applied.
89
+ * Does NOT modify state, increment version, record transaction, or broadcast.
90
+ *
91
+ * Use this to validate before writing to WAL, then call apply() after WAL success.
92
+ *
93
+ * @param transaction - The transaction to validate
94
+ * @returns ValidateResult with nextVersion if valid, or reason if invalid
95
+ */
96
+ validate(transaction: Transaction): ValidateResult;
97
+ /**
98
+ * Phase 2 of two-phase commit: Applies a pre-validated transaction.
99
+ * MUST only be called after validate() succeeded AND WAL write succeeded.
100
+ * Mutates state, increments version, records transaction ID, and broadcasts.
101
+ *
102
+ * @param transaction - The transaction to apply (must have been validated first)
103
+ */
104
+ apply(transaction: Transaction): void;
105
+ /**
106
+ * Submits a transaction for processing (combines validate + apply).
77
107
  * Validates and applies the transaction if valid, or rejects it with a reason.
108
+ *
109
+ * For two-phase commit with WAL, use validate() then apply() instead.
110
+ *
78
111
  * @param transaction - The transaction to process
79
112
  * @returns SubmitResult indicating success with version or failure with reason
80
113
  */
@@ -95,5 +128,5 @@ interface ServerDocument<TSchema extends AnyPrimitive> {
95
128
  */
96
129
  declare const make: <TSchema extends AnyPrimitive>(options: ServerDocumentOptions<TSchema>) => ServerDocument<TSchema>;
97
130
  //#endregion
98
- export { ErrorMessage, ServerDocumentOptions, ServerDocument_d_exports, ServerMessage, SnapshotMessage, SubmitResult, TransactionMessage };
131
+ export { ErrorMessage, ServerDocumentOptions, ServerDocument_d_exports, ServerMessage, SnapshotMessage, SubmitResult, TransactionMessage, ValidateResult };
99
132
  //# sourceMappingURL=ServerDocument.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ServerDocument.d.mts","names":[],"sources":["../../src/server/ServerDocument.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;;UAWiB,kBAAA;;wBAEO;;;;;;;UAQP,YAAA;;EAVA,SAAA,aAAkB,EAAA,MAAA;EAUlB,SAAA,MAAY,EAAA,MAAA;AAS7B;AASA;;;AAAgE,UAT/C,eAAA,CAS+C;EAAe,SAAA,IAAA,EAAA,UAAA;EASnE,SAAA,KAAA,EAAY,OAAA;EAWP,SAAA,OAAA,EAAA,MAAqB;;;;;AAQJ,KA5BtB,aAAA,GAAgB,kBA4BM,GA5Be,YA4Bf,GA5B8B,eA4B9B;;AAUlC;;AAEmB,KA/BP,YAAA,GA+BO;EAGW,SAAA,OAAA,EAAA,IAAA;EAArB,SAAA,OAAA,EAAA,MAAA;CAWa,GAAA;EAA0B,SAAA,OAAA,EAAA,KAAA;EAM/B,SAAA,MAAA,EAAA,MAAA;CAAe;AAgBhC;;;AACW,UAzDM,qBAyDN,CAAA,gBAzD4C,YAyD5C,CAAA,CAAA;EACO;EAAf,SAAA,MAAA,EAxDgB,OAwDhB;EAAc;0BAtDS,cAAwB;;;;kCAIhB;;;;;;;;;UAUjB,+BAA+B;;mBAE7B;;SAGV,WAAqB;;;;;;;;;sBAWR,cAA0B;;;;;iBAM/B;;;;;;;;;;cAgBJ,uBAAwB,uBAC1B,sBAAsB,aAC9B,eAAe"}
1
+ {"version":3,"file":"ServerDocument.d.mts","names":[],"sources":["../../src/server/ServerDocument.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;;UAWiB,kBAAA;;wBAEO;;;;;;;UAQP,YAAA;;;EAVA,SAAA,MAAA,EAAA,MAAkB;AAUnC;AASA;AASA;;AAAiD,UAThC,eAAA,CASgC;EAAe,SAAA,IAAA,EAAA,UAAA;EAAe,SAAA,KAAA,EAAA,OAAA;EASnE,SAAA,OAAY,EAAA,MAAA;AAQxB;AAWA;;;AAIkD,KAhCtC,aAAA,GAAgB,kBAgCsB,GAhCD,YAgCC,GAhCc,eAgCd;;;;AAcjC,KArCL,YAAA,GAqCmB;EAAiB,SAAA,OAAA,EAAA,IAAA;EAE7B,SAAA,OAAA,EAAA,MAAA;CAGW,GAAA;EAArB,SAAA,OAAA,EAAA,KAAA;EAee,SAAA,MAAA,EAAA,MAAA;CAA0B;;;;;AA0BlB,KA3EpB,cAAA,GA2EoB;EAgBnB,SA8LZ,KAAA,EAAA,IAAA;EA9LoC,SAAA,WAAA,EAAA,MAAA;CACJ,GAAA;EAAtB,SAAA,KAAA,EAAA,KAAA;EACO,SAAA,MAAA,EAAA,MAAA;CAAf;;;;UAlFc,sCAAsC;;mBAEpC;;0BAEO,cAAwB;;;;kCAIhB;;;;;;;;;UAUjB,+BAA+B;;mBAE7B;;SAGV,WAAqB;;;;;;;;;;;;;wBAeN,cAA0B;;;;;;;;qBAS7B;;;;;;;;;;sBAWC,cAA0B;;;;;iBAM/B;;;;;;;;;;cAgBJ,uBAAwB,uBAC1B,sBAAsB,aAC9B,eAAe"}
@@ -49,6 +49,20 @@ const make = (options) => {
49
49
  };
50
50
  }
51
51
  };
52
+ /**
53
+ * Internal function to apply a transaction and broadcast.
54
+ * Called by both apply() and submit().
55
+ */
56
+ const applyAndBroadcast = (transaction) => {
57
+ _document.apply(transaction.ops);
58
+ _version += 1;
59
+ recordTransaction(transaction.id);
60
+ onBroadcast({
61
+ type: "transaction",
62
+ transaction,
63
+ version: _version
64
+ });
65
+ };
52
66
  return {
53
67
  schema,
54
68
  get: () => {
@@ -57,6 +71,20 @@ const make = (options) => {
57
71
  getVersion: () => {
58
72
  return _version;
59
73
  },
74
+ validate: (transaction) => {
75
+ const validation = validateTransaction(transaction);
76
+ if (!validation.valid) return {
77
+ valid: false,
78
+ reason: validation.reason
79
+ };
80
+ return {
81
+ valid: true,
82
+ nextVersion: _version + 1
83
+ };
84
+ },
85
+ apply: (transaction) => {
86
+ applyAndBroadcast(transaction);
87
+ },
60
88
  submit: (transaction) => {
61
89
  const validation = validateTransaction(transaction);
62
90
  if (!validation.valid) {
@@ -67,7 +95,7 @@ const make = (options) => {
67
95
  };
68
96
  }
69
97
  try {
70
- _document.apply(transaction.ops);
98
+ applyAndBroadcast(transaction);
71
99
  } catch (error) {
72
100
  const reason = error instanceof Error ? error.message : String(error);
73
101
  onRejection === null || onRejection === void 0 || onRejection(transaction.id, reason);
@@ -76,13 +104,6 @@ const make = (options) => {
76
104
  reason
77
105
  };
78
106
  }
79
- _version += 1;
80
- recordTransaction(transaction.id);
81
- onBroadcast({
82
- type: "transaction",
83
- transaction,
84
- version: _version
85
- });
86
107
  return {
87
108
  success: true,
88
109
  version: _version
@@ -1 +1 @@
1
- {"version":3,"file":"ServerDocument.mjs","names":["Document.make","_transactionOrder: string[]","Transaction.isEmpty"],"sources":["../../src/server/ServerDocument.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 value (optional, will use schema defaults if not provided) - uses set input format */\n readonly initialState?: Primitive.InferSetInput<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 // Use initialState (not initial) since currentState is already in flat state format\n const currentState = _document.get();\n const tempDoc = Document.make(schema, { initialState: 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"],"mappings":";;;;;;;;;AAmHA,MAAa,QACX,YAC4B;CAC5B,MAAM,EACJ,QACA,cACA,iBAAiB,GACjB,aACA,aACA,wBAAwB,QACtB;CAOJ,IAAI,YAAYA,OAAc,QAAQ,EAAE,SAAS,cAAc,CAAC;CAGhE,IAAI,WAAW;CAGf,MAAM,yCAAyB,IAAI,KAAa;CAChD,MAAMC,oBAA8B,EAAE;;;;CAStC,MAAM,qBAAqB,kBAAgC;AACzD,yBAAuB,IAAI,cAAc;AACzC,oBAAkB,KAAK,cAAc;AAGrC,SAAO,kBAAkB,SAAS,uBAAuB;GACvD,MAAM,SAAS,kBAAkB,OAAO;AACxC,OAAI,OACF,wBAAuB,OAAO,OAAO;;;;;;;CAS3C,MAAM,uBACJ,gBACuD;AAEvD,MAAIC,QAAoB,YAAY,CAClC,QAAO;GAAE,OAAO;GAAO,QAAQ;GAAwB;AAIzD,MAAI,uBAAuB,IAAI,YAAY,GAAG,CAC5C,QAAO;GAAE,OAAO;GAAO,QAAQ;GAA0C;EAK3E,MAAM,eAAe,UAAU,KAAK;EACpC,MAAM,UAAUF,OAAc,QAAQ,EAAE,cAAc,cAAc,CAAC;AAErE,MAAI;AAEF,WAAQ,MAAM,YAAY,IAAI;AAC9B,UAAO,EAAE,OAAO,MAAM;WACf,OAAO;AAGd,UAAO;IAAE,OAAO;IAAO,QADP,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM;IAC9B;;;AA4E5C,QApEgD;EAC9C;EAEA,WAAsD;AACpD,UAAO,UAAU,KAAK;;EAGxB,kBAA0B;AACxB,UAAO;;EAGT,SAAS,gBAAuD;GAE9D,MAAM,aAAa,oBAAoB,YAAY;AAEnD,OAAI,CAAC,WAAW,OAAO;AAErB,kEAAc,YAAY,IAAI,WAAW,OAAO;AAEhD,WAAO;KACL,SAAS;KACT,QAAQ,WAAW;KACpB;;AAIH,OAAI;AACF,cAAU,MAAM,YAAY,IAAI;YACzB,OAAO;IAEd,MAAM,SAAS,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM;AACrE,kEAAc,YAAY,IAAI,OAAO;AACrC,WAAO;KAAE,SAAS;KAAO;KAAQ;;AAInC,eAAY;AAGZ,qBAAkB,YAAY,GAAG;AAQjC,eALoC;IAClC,MAAM;IACN;IACA,SAAS;IACV,CACmB;AAEpB,UAAO;IACL,SAAS;IACT,SAAS;IACV;;EAGH,mBAAoC;AAClC,UAAO;IACL,MAAM;IACN,OAAO,UAAU,KAAK;IACtB,SAAS;IACV;;EAGH,eAAe,kBAAmC;AAChD,UAAO,uBAAuB,IAAI,cAAc;;EAEnD"}
1
+ {"version":3,"file":"ServerDocument.mjs","names":["Document.make","_transactionOrder: string[]","Transaction.isEmpty"],"sources":["../../src/server/ServerDocument.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 * Result of validating a transaction (two-phase commit: phase 1).\n * If valid, returns the version this transaction will get when applied.\n */\nexport type ValidateResult =\n | { readonly valid: true; readonly nextVersion: number }\n | { readonly valid: 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 value (optional, will use schema defaults if not provided) - uses set input format */\n readonly initialState?: Primitive.InferSetInput<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 * Phase 1 of two-phase commit: Validates a transaction without side effects.\n * Returns the version this transaction would get if applied.\n * Does NOT modify state, increment version, record transaction, or broadcast.\n *\n * Use this to validate before writing to WAL, then call apply() after WAL success.\n *\n * @param transaction - The transaction to validate\n * @returns ValidateResult with nextVersion if valid, or reason if invalid\n */\n validate(transaction: Transaction.Transaction): ValidateResult;\n\n /**\n * Phase 2 of two-phase commit: Applies a pre-validated transaction.\n * MUST only be called after validate() succeeded AND WAL write succeeded.\n * Mutates state, increments version, records transaction ID, and broadcasts.\n *\n * @param transaction - The transaction to apply (must have been validated first)\n */\n apply(transaction: Transaction.Transaction): void;\n\n /**\n * Submits a transaction for processing (combines validate + apply).\n * Validates and applies the transaction if valid, or rejects it with a reason.\n *\n * For two-phase commit with WAL, use validate() then apply() instead.\n *\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 // Use initialState (not initial) since currentState is already in flat state format\n const currentState = _document.get();\n const tempDoc = Document.make(schema, { initialState: 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 // Internal Apply Logic\n // ==========================================================================\n\n /**\n * Internal function to apply a transaction and broadcast.\n * Called by both apply() and submit().\n */\n const applyAndBroadcast = (transaction: Transaction.Transaction): void => {\n // Apply the transaction to the authoritative state\n _document.apply(transaction.ops);\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\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 validate: (transaction: Transaction.Transaction): ValidateResult => {\n // Use internal validation helper\n const validation = validateTransaction(transaction);\n\n if (!validation.valid) {\n return {\n valid: false,\n reason: validation.reason,\n };\n }\n\n // Return the version this transaction will get when applied\n return {\n valid: true,\n nextVersion: _version + 1,\n };\n },\n\n apply: (transaction: Transaction.Transaction): void => {\n // Apply and broadcast\n // Note: This assumes validate() was called first and WAL write succeeded\n // We don't re-validate here for performance - caller is responsible\n applyAndBroadcast(transaction);\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 applyAndBroadcast(transaction);\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 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"],"mappings":";;;;;;;;;AAmJA,MAAa,QACX,YAC4B;CAC5B,MAAM,EACJ,QACA,cACA,iBAAiB,GACjB,aACA,aACA,wBAAwB,QACtB;CAOJ,IAAI,YAAYA,OAAc,QAAQ,EAAE,SAAS,cAAc,CAAC;CAGhE,IAAI,WAAW;CAGf,MAAM,yCAAyB,IAAI,KAAa;CAChD,MAAMC,oBAA8B,EAAE;;;;CAStC,MAAM,qBAAqB,kBAAgC;AACzD,yBAAuB,IAAI,cAAc;AACzC,oBAAkB,KAAK,cAAc;AAGrC,SAAO,kBAAkB,SAAS,uBAAuB;GACvD,MAAM,SAAS,kBAAkB,OAAO;AACxC,OAAI,OACF,wBAAuB,OAAO,OAAO;;;;;;;CAS3C,MAAM,uBACJ,gBACuD;AAEvD,MAAIC,QAAoB,YAAY,CAClC,QAAO;GAAE,OAAO;GAAO,QAAQ;GAAwB;AAIzD,MAAI,uBAAuB,IAAI,YAAY,GAAG,CAC5C,QAAO;GAAE,OAAO;GAAO,QAAQ;GAA0C;EAK3E,MAAM,eAAe,UAAU,KAAK;EACpC,MAAM,UAAUF,OAAc,QAAQ,EAAE,cAAc,cAAc,CAAC;AAErE,MAAI;AAEF,WAAQ,MAAM,YAAY,IAAI;AAC9B,UAAO,EAAE,OAAO,MAAM;WACf,OAAO;AAGd,UAAO;IAAE,OAAO;IAAO,QADP,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM;IAC9B;;;;;;;CAY5C,MAAM,qBAAqB,gBAA+C;AAExE,YAAU,MAAM,YAAY,IAAI;AAGhC,cAAY;AAGZ,oBAAkB,YAAY,GAAG;AAQjC,cALoC;GAClC,MAAM;GACN;GACA,SAAS;GACV,CACmB;;AAsFtB,QA/EgD;EAC9C;EAEA,WAAsD;AACpD,UAAO,UAAU,KAAK;;EAGxB,kBAA0B;AACxB,UAAO;;EAGT,WAAW,gBAAyD;GAElE,MAAM,aAAa,oBAAoB,YAAY;AAEnD,OAAI,CAAC,WAAW,MACd,QAAO;IACL,OAAO;IACP,QAAQ,WAAW;IACpB;AAIH,UAAO;IACL,OAAO;IACP,aAAa,WAAW;IACzB;;EAGH,QAAQ,gBAA+C;AAIrD,qBAAkB,YAAY;;EAGhC,SAAS,gBAAuD;GAE9D,MAAM,aAAa,oBAAoB,YAAY;AAEnD,OAAI,CAAC,WAAW,OAAO;AAErB,kEAAc,YAAY,IAAI,WAAW,OAAO;AAEhD,WAAO;KACL,SAAS;KACT,QAAQ,WAAW;KACpB;;AAIH,OAAI;AACF,sBAAkB,YAAY;YACvB,OAAO;IAEd,MAAM,SAAS,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM;AACrE,kEAAc,YAAY,IAAI,OAAO;AACrC,WAAO;KAAE,SAAS;KAAO;KAAQ;;AAGnC,UAAO;IACL,SAAS;IACT,SAAS;IACV;;EAGH,mBAAoC;AAClC,UAAO;IACL,MAAM;IACN,OAAO,UAAU,KAAK;IACtB,SAAS;IACV;;EAGH,eAAe,kBAAmC;AAChD,UAAO,uBAAuB,IAAI,cAAc;;EAEnD"}
@@ -1,3 +1,3 @@
1
- import { ErrorMessage, ServerDocumentOptions, ServerDocument_d_exports, ServerMessage, SnapshotMessage, SubmitResult, TransactionMessage } from "./ServerDocument.cjs";
1
+ import { ErrorMessage, ServerDocumentOptions, ServerDocument_d_exports, ServerMessage, SnapshotMessage, SubmitResult, TransactionMessage, ValidateResult } from "./ServerDocument.cjs";
2
2
  import { DuplicateTransactionError, EmptyTransactionError, InvalidOperationError, MimicServerError, StateValidationError, ValidationError } from "./errors.cjs";
3
- export { DuplicateTransactionError, EmptyTransactionError, type ErrorMessage, InvalidOperationError, MimicServerError, ServerDocument_d_exports as ServerDocument, type ServerDocumentOptions, type ServerMessage, type SnapshotMessage, StateValidationError, type SubmitResult, type TransactionMessage, ValidationError };
3
+ export { DuplicateTransactionError, EmptyTransactionError, type ErrorMessage, InvalidOperationError, MimicServerError, ServerDocument_d_exports as ServerDocument, type ServerDocumentOptions, type ServerMessage, type SnapshotMessage, StateValidationError, type SubmitResult, type TransactionMessage, type ValidateResult, ValidationError };
@@ -1,3 +1,3 @@
1
- import { ErrorMessage, ServerDocumentOptions, ServerDocument_d_exports, ServerMessage, SnapshotMessage, SubmitResult, TransactionMessage } from "./ServerDocument.mjs";
1
+ import { ErrorMessage, ServerDocumentOptions, ServerDocument_d_exports, ServerMessage, SnapshotMessage, SubmitResult, TransactionMessage, ValidateResult } from "./ServerDocument.mjs";
2
2
  import { DuplicateTransactionError, EmptyTransactionError, InvalidOperationError, MimicServerError, StateValidationError, ValidationError } from "./errors.mjs";
3
- export { DuplicateTransactionError, EmptyTransactionError, type ErrorMessage, InvalidOperationError, MimicServerError, ServerDocument_d_exports as ServerDocument, type ServerDocumentOptions, type ServerMessage, type SnapshotMessage, StateValidationError, type SubmitResult, type TransactionMessage, ValidationError };
3
+ export { DuplicateTransactionError, EmptyTransactionError, type ErrorMessage, InvalidOperationError, MimicServerError, ServerDocument_d_exports as ServerDocument, type ServerDocumentOptions, type ServerMessage, type SnapshotMessage, StateValidationError, type SubmitResult, type TransactionMessage, type ValidateResult, ValidationError };
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../../src/types/index.ts"],"sourcesContent":["/**\n * Consolidated type inference utilities for all primitives.\n *\n * This module re-exports all inference types from a single location\n * for convenient access across the mimic package.\n *\n * @since 0.0.1\n */\n\n// =============================================================================\n// Core Inference Types (from shared.ts)\n// =============================================================================\n\nexport type {\n // Core primitive types\n Primitive,\n AnyPrimitive,\n PrimitiveInternal,\n\n // State and proxy inference\n InferState,\n InferProxy,\n\n // Input inference\n InferSetInput,\n InferUpdateInput,\n\n // Snapshot inference\n InferSnapshot,\n\n // Required/default status inference\n HasDefault,\n IsDefined,\n IsRequired,\n\n // Utility types\n MaybeUndefined,\n NeedsValue,\n Optional,\n\n // Validator type\n Validator,\n} from \"../primitives/shared.js\";\n\n// =============================================================================\n// Struct Inference Types\n// =============================================================================\n\nexport type {\n InferStructState,\n InferStructSnapshot,\n StructSetInput,\n StructUpdateValue,\n StructProxy,\n} from \"../primitives/Struct.js\";\n\n// =============================================================================\n// Array Inference Types\n// =============================================================================\n\nexport type {\n ArrayState,\n ArraySnapshot,\n ArrayEntrySnapshot,\n ArrayEntry,\n ArraySetInput,\n ArrayUpdateInput,\n ArrayElementSetInput,\n ArrayProxy,\n} from \"../primitives/Array.js\";\n\n// =============================================================================\n// Tree Inference Types\n// =============================================================================\n\nexport type {\n TreeState,\n TreeNodeState,\n TypedTreeNodeState,\n TreeNodeSnapshot,\n InferTreeSnapshot,\n TreeSetInput,\n TreeUpdateInput,\n TreeNodeUpdateValue,\n TreeNodeDataSetInput,\n TreeProxy,\n TypedNodeProxy,\n TreeNodeProxyBase,\n} from \"../primitives/Tree.js\";\n\n// =============================================================================\n// TreeNode Inference Types\n// =============================================================================\n\nexport type {\n AnyTreeNodePrimitive,\n InferTreeNodeDataState,\n InferTreeNodeType,\n InferTreeNodeChildren,\n TreeNodeSelfType,\n TreeNodeConfig,\n TreeNodeChildrenInput,\n} from \"../primitives/TreeNode.js\";\n\nexport { TreeNodePrimitive, TreeNodeSelf } from \"../primitives/TreeNode.js\";\n\n// =============================================================================\n// Union Inference Types\n// =============================================================================\n\nexport type {\n InferUnionState,\n InferUnionSnapshot,\n UnionVariants,\n} from \"../primitives/Union.js\";\n\n// =============================================================================\n// Either Inference Types\n// =============================================================================\n\nexport type {\n InferEitherState,\n InferEitherSnapshot,\n ScalarPrimitive,\n} from \"../primitives/Either.js\";\n\n// =============================================================================\n// Lazy Inference Types\n// =============================================================================\n\nexport type {\n InferLazyState,\n InferLazyProxy,\n InferLazySnapshot,\n InferLazySetInput,\n InferLazyUpdateInput,\n} from \"../primitives/Lazy.js\";\n"],"mappings":""}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../../src/types/index.ts"],"sourcesContent":["/**\n * Consolidated type inference utilities for all primitives.\n *\n * This module re-exports all inference types from a single location\n * for convenient access across the mimic package.\n *\n * @since 0.0.1\n */\n\n// =============================================================================\n// Core Inference Types (from shared.ts)\n// =============================================================================\n\nexport type {\n // Core primitive types\n Primitive,\n AnyPrimitive,\n PrimitiveInternal,\n\n // State and proxy inference\n InferState,\n InferProxy,\n\n // Input inference\n InferSetInput,\n InferUpdateInput,\n\n // Snapshot inference\n InferSnapshot,\n\n // Required/default status inference\n HasDefault,\n IsDefined,\n IsRequired,\n\n // Utility types\n MaybeUndefined,\n NeedsValue,\n Optional,\n\n // Validator type\n Validator,\n} from \"../primitives/shared\";\n\n// =============================================================================\n// Struct Inference Types\n// =============================================================================\n\nexport type {\n InferStructState,\n InferStructSnapshot,\n StructSetInput,\n StructUpdateValue,\n StructProxy,\n} from \"../primitives/Struct\";\n\n// =============================================================================\n// Array Inference Types\n// =============================================================================\n\nexport type {\n ArrayState,\n ArraySnapshot,\n ArrayEntrySnapshot,\n ArrayEntry,\n ArraySetInput,\n ArrayUpdateInput,\n ArrayElementSetInput,\n ArrayProxy,\n} from \"../primitives/Array\";\n\n// =============================================================================\n// Tree Inference Types\n// =============================================================================\n\nexport type {\n TreeState,\n TreeNodeState,\n TypedTreeNodeState,\n TreeNodeSnapshot,\n InferTreeSnapshot,\n TreeSetInput,\n TreeUpdateInput,\n TreeNodeUpdateValue,\n TreeNodeDataSetInput,\n TreeProxy,\n TypedNodeProxy,\n TreeNodeProxyBase,\n} from \"../primitives/Tree\";\n\n// =============================================================================\n// TreeNode Inference Types\n// =============================================================================\n\nexport type {\n AnyTreeNodePrimitive,\n InferTreeNodeDataState,\n InferTreeNodeType,\n InferTreeNodeChildren,\n TreeNodeSelfType,\n TreeNodeConfig,\n TreeNodeChildrenInput,\n} from \"../primitives/TreeNode\";\n\nexport { TreeNodePrimitive, TreeNodeSelf } from \"../primitives/TreeNode\";\n\n// =============================================================================\n// Union Inference Types\n// =============================================================================\n\nexport type {\n InferUnionState,\n InferUnionSnapshot,\n UnionVariants,\n} from \"../primitives/Union\";\n\n// =============================================================================\n// Either Inference Types\n// =============================================================================\n\nexport type {\n InferEitherState,\n InferEitherSnapshot,\n ScalarPrimitive,\n} from \"../primitives/Either\";\n\n// =============================================================================\n// Lazy Inference Types\n// =============================================================================\n\nexport type {\n InferLazyState,\n InferLazyProxy,\n InferLazySnapshot,\n InferLazySetInput,\n InferLazyUpdateInput,\n} from \"../primitives/Lazy\";\n"],"mappings":""}