@korajs/store 1.0.0-beta.11 → 1.0.0-beta.13

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.
Files changed (92) hide show
  1. package/README.md +29 -37
  2. package/dist/adapters/better-sqlite3.cjs +90 -4
  3. package/dist/adapters/better-sqlite3.cjs.map +1 -1
  4. package/dist/adapters/better-sqlite3.d.cts +21 -12
  5. package/dist/adapters/better-sqlite3.d.ts +21 -12
  6. package/dist/adapters/better-sqlite3.js +69 -5
  7. package/dist/adapters/better-sqlite3.js.map +1 -1
  8. package/dist/adapters/indexeddb.cjs +1316 -610
  9. package/dist/adapters/indexeddb.cjs.map +1 -1
  10. package/dist/adapters/indexeddb.d.cts +29 -3
  11. package/dist/adapters/indexeddb.d.ts +29 -3
  12. package/dist/adapters/indexeddb.js +247 -221
  13. package/dist/adapters/indexeddb.js.map +1 -1
  14. package/dist/adapters/sqlite-wasm-worker.cjs +803 -62
  15. package/dist/adapters/sqlite-wasm-worker.cjs.map +1 -1
  16. package/dist/adapters/sqlite-wasm-worker.js +480 -62
  17. package/dist/adapters/sqlite-wasm-worker.js.map +1 -1
  18. package/dist/adapters/sqlite-wasm.cjs +1032 -344
  19. package/dist/adapters/sqlite-wasm.cjs.map +1 -1
  20. package/dist/adapters/sqlite-wasm.d.cts +239 -18
  21. package/dist/adapters/sqlite-wasm.d.ts +239 -18
  22. package/dist/adapters/sqlite-wasm.js +111 -5
  23. package/dist/adapters/sqlite-wasm.js.map +1 -1
  24. package/dist/backup-4NUPDEMB.js +25 -0
  25. package/dist/chunk-32EDKWMI.js +876 -0
  26. package/dist/chunk-32EDKWMI.js.map +1 -0
  27. package/dist/{chunk-CYO3YKFL.js → chunk-5ZRRF6R2.js} +2 -2
  28. package/dist/chunk-A6GFH5IG.js +36 -0
  29. package/dist/chunk-A6GFH5IG.js.map +1 -0
  30. package/dist/chunk-DUKTWVZW.js +175 -0
  31. package/dist/chunk-DUKTWVZW.js.map +1 -0
  32. package/dist/chunk-HZTDBLDB.js +607 -0
  33. package/dist/chunk-HZTDBLDB.js.map +1 -0
  34. package/dist/{chunk-GSJ7EEUC.js → chunk-J5AGXF3D.js} +58 -9
  35. package/dist/chunk-J5AGXF3D.js.map +1 -0
  36. package/dist/chunk-MCQKEHOD.js +112 -0
  37. package/dist/chunk-MCQKEHOD.js.map +1 -0
  38. package/dist/chunk-P4V375FY.js +810 -0
  39. package/dist/chunk-P4V375FY.js.map +1 -0
  40. package/dist/chunk-QDUSJMFV.js +1095 -0
  41. package/dist/chunk-QDUSJMFV.js.map +1 -0
  42. package/dist/chunk-SJP2HTYZ.js +1003 -0
  43. package/dist/chunk-SJP2HTYZ.js.map +1 -0
  44. package/dist/chunk-W5F2LWG6.js +591 -0
  45. package/dist/chunk-W5F2LWG6.js.map +1 -0
  46. package/dist/chunk-Y5LRH7KI.js +5341 -0
  47. package/dist/chunk-Y5LRH7KI.js.map +1 -0
  48. package/dist/{export-audit-ODGQTQIK.js → export-audit-J2QXRHD2.js} +3 -3
  49. package/dist/index.cjs +9354 -4157
  50. package/dist/index.cjs.map +1 -1
  51. package/dist/index.d.cts +1064 -108
  52. package/dist/index.d.ts +1064 -108
  53. package/dist/index.js +249 -3092
  54. package/dist/index.js.map +1 -1
  55. package/dist/internal.cjs +580 -264
  56. package/dist/internal.cjs.map +1 -1
  57. package/dist/internal.d.cts +15 -85
  58. package/dist/internal.d.ts +15 -85
  59. package/dist/internal.js +10 -8
  60. package/dist/restore-FR7XIKT2.js +299 -0
  61. package/dist/restore-FR7XIKT2.js.map +1 -0
  62. package/dist/sql-builder-C4u55jKW.d.cts +608 -0
  63. package/dist/sql-builder-CGckdBDL.d.ts +608 -0
  64. package/dist/sqlite-wasm-channel-DVX93Uiq.d.cts +141 -0
  65. package/dist/sqlite-wasm-channel-DVX93Uiq.d.ts +141 -0
  66. package/dist/sqlite-wasm-channel-MLHLKEED.js +12 -0
  67. package/dist/store-IMWKZLWB.js +16 -0
  68. package/dist/store-IMWKZLWB.js.map +1 -0
  69. package/dist/{types-BslDOTk0.d.ts → types-Dh-xyUjP.d.cts} +99 -26
  70. package/dist/{types-BslDOTk0.d.cts → types-Dh-xyUjP.d.ts} +99 -26
  71. package/package.json +3 -2
  72. package/dist/backup-5S7K2Z35.js +0 -14
  73. package/dist/chunk-GSJ7EEUC.js.map +0 -1
  74. package/dist/chunk-HS6DBMST.js +0 -383
  75. package/dist/chunk-HS6DBMST.js.map +0 -1
  76. package/dist/chunk-RJIEKEN4.js +0 -751
  77. package/dist/chunk-RJIEKEN4.js.map +0 -1
  78. package/dist/chunk-TUR7RGEP.js +0 -84
  79. package/dist/chunk-TUR7RGEP.js.map +0 -1
  80. package/dist/chunk-VDBJQ75K.js +0 -78
  81. package/dist/chunk-VDBJQ75K.js.map +0 -1
  82. package/dist/chunk-YT7MRFCP.js +0 -687
  83. package/dist/chunk-YT7MRFCP.js.map +0 -1
  84. package/dist/sqlite-wasm-channel-GGHA2BXU.js +0 -12
  85. package/dist/sqlite-wasm-channel-L6o3bWQK.d.cts +0 -92
  86. package/dist/sqlite-wasm-channel-L6o3bWQK.d.ts +0 -92
  87. package/dist/types-BOO9EPt1.d.ts +0 -170
  88. package/dist/types-C_GDavG-.d.cts +0 -170
  89. /package/dist/{backup-5S7K2Z35.js.map → backup-4NUPDEMB.js.map} +0 -0
  90. /package/dist/{chunk-CYO3YKFL.js.map → chunk-5ZRRF6R2.js.map} +0 -0
  91. /package/dist/{export-audit-ODGQTQIK.js.map → export-audit-J2QXRHD2.js.map} +0 -0
  92. /package/dist/{sqlite-wasm-channel-GGHA2BXU.js.map → sqlite-wasm-channel-MLHLKEED.js.map} +0 -0
@@ -1,687 +0,0 @@
1
- import {
2
- QueryError,
3
- RecordNotFoundError
4
- } from "./chunk-TUR7RGEP.js";
5
- import {
6
- deserializeRecord,
7
- encodeRichtextFieldsForOpData,
8
- serializeOperation,
9
- serializeRecord
10
- } from "./chunk-GSJ7EEUC.js";
11
-
12
- // src/lww/row-version.ts
13
- import { HybridLogicalClock } from "@korajs/core";
14
- var EMPTY_ROW_VERSION = "";
15
- function serializeRowVersion(timestamp) {
16
- return HybridLogicalClock.serialize(timestamp);
17
- }
18
- function rowVersionFromRecord(row) {
19
- const version = row._version;
20
- if (typeof version === "string" && version.length > 0) {
21
- return HybridLogicalClock.deserialize(version);
22
- }
23
- return { wallTime: row._updated_at, logical: 0, nodeId: "" };
24
- }
25
- function isIncomingNewerThanRow(incoming, row) {
26
- return HybridLogicalClock.compare(incoming, rowVersionFromRecord(row)) > 0;
27
- }
28
- function lwwVersionWhereClause(remoteVersion) {
29
- return {
30
- sql: "(_version = ? OR _version < ?)",
31
- params: [EMPTY_ROW_VERSION, remoteVersion]
32
- };
33
- }
34
-
35
- // src/query/sql-builder.ts
36
- import { quoteIdent } from "@korajs/core";
37
- function buildSelectQuery(descriptor, fields) {
38
- const params = [];
39
- const parts = [`SELECT * FROM ${quoteIdent(descriptor.collection)}`];
40
- const whereClause = buildWhereClauseParts(descriptor.where, fields, params);
41
- const deletedFilter = "_deleted = 0";
42
- if (whereClause) {
43
- parts.push(`WHERE ${deletedFilter} AND ${whereClause}`);
44
- } else {
45
- parts.push(`WHERE ${deletedFilter}`);
46
- }
47
- if (descriptor.orderBy.length > 0) {
48
- const orderParts = descriptor.orderBy.map((o) => {
49
- validateFieldName(o.field, fields);
50
- return `${quoteIdent(o.field)} ${o.direction.toUpperCase()}`;
51
- });
52
- parts.push(`ORDER BY ${orderParts.join(", ")}`);
53
- }
54
- if (descriptor.limit !== void 0) {
55
- parts.push(`LIMIT ${descriptor.limit}`);
56
- }
57
- if (descriptor.offset !== void 0) {
58
- parts.push(`OFFSET ${descriptor.offset}`);
59
- }
60
- return { sql: parts.join(" "), params };
61
- }
62
- function buildCountQuery(descriptor, fields) {
63
- const params = [];
64
- const parts = [`SELECT COUNT(*) as count FROM ${quoteIdent(descriptor.collection)}`];
65
- const whereClause = buildWhereClauseParts(descriptor.where, fields, params);
66
- const deletedFilter = "_deleted = 0";
67
- if (whereClause) {
68
- parts.push(`WHERE ${deletedFilter} AND ${whereClause}`);
69
- } else {
70
- parts.push(`WHERE ${deletedFilter}`);
71
- }
72
- return { sql: parts.join(" "), params };
73
- }
74
- function buildInsertQuery(collection, record) {
75
- const columns = Object.keys(record);
76
- const placeholders = columns.map(() => "?");
77
- const params = Object.values(record);
78
- const quotedColumns = columns.map((col) => quoteIdent(col));
79
- const sql = `INSERT INTO ${quoteIdent(collection)} (${quotedColumns.join(", ")}) VALUES (${placeholders.join(", ")})`;
80
- return { sql, params };
81
- }
82
- function buildUpdateQuery(collection, id, changes) {
83
- const setClauses = Object.keys(changes).map((col) => `${quoteIdent(col)} = ?`);
84
- const params = [...Object.values(changes), id];
85
- const sql = `UPDATE ${quoteIdent(collection)} SET ${setClauses.join(", ")} WHERE id = ?`;
86
- return { sql, params };
87
- }
88
- function buildFieldFastForwardUpdateQuery(collection, id, fieldChanges, remoteVersion, wallTime, options) {
89
- const fieldClauses = Object.keys(fieldChanges).map((col) => `${quoteIdent(col)} = ?`);
90
- const setClauses = [
91
- ...fieldClauses,
92
- "_version = CASE WHEN _version >= ? THEN _version ELSE ? END",
93
- "_updated_at = CASE WHEN _updated_at >= ? THEN _updated_at ELSE ? END"
94
- ];
95
- const params = [
96
- ...Object.values(fieldChanges),
97
- remoteVersion,
98
- remoteVersion,
99
- wallTime,
100
- wallTime
101
- ];
102
- if (options?.maxCreatedAt !== void 0) {
103
- setClauses.push("_created_at = CASE WHEN _created_at >= ? THEN _created_at ELSE ? END");
104
- params.push(options.maxCreatedAt, options.maxCreatedAt);
105
- }
106
- params.push(id);
107
- const sql = `UPDATE ${quoteIdent(collection)} SET ${setClauses.join(", ")} WHERE id = ?`;
108
- return { sql, params };
109
- }
110
- function buildSoftDeleteQuery(collection, id, updatedAt, version) {
111
- if (version !== void 0) {
112
- return {
113
- sql: `UPDATE ${quoteIdent(collection)} SET _deleted = 1, _updated_at = ?, _version = ? WHERE id = ?`,
114
- params: [updatedAt, version, id]
115
- };
116
- }
117
- return {
118
- sql: `UPDATE ${quoteIdent(collection)} SET _deleted = 1, _updated_at = ? WHERE id = ?`,
119
- params: [updatedAt, id]
120
- };
121
- }
122
- function buildLwwUpdateQuery(collection, id, changes, remoteVersion) {
123
- const setClauses = Object.keys(changes).map((col) => `${quoteIdent(col)} = ?`);
124
- const lww = lwwVersionWhereClause(remoteVersion);
125
- const sql = `UPDATE ${quoteIdent(collection)} SET ${setClauses.join(", ")} WHERE id = ? AND ${lww.sql}`;
126
- const params = [...Object.values(changes), id, ...lww.params];
127
- return { sql, params };
128
- }
129
- function buildLwwSoftDeleteQuery(collection, id, updatedAt, version) {
130
- const lww = lwwVersionWhereClause(version);
131
- return {
132
- sql: `UPDATE ${quoteIdent(collection)} SET _deleted = 1, _updated_at = ?, _version = ? WHERE id = ? AND ${lww.sql}`,
133
- params: [updatedAt, version, id, ...lww.params]
134
- };
135
- }
136
- function buildWhereClause(where, fields) {
137
- const params = [];
138
- const result = buildWhereClauseParts(where, fields, params);
139
- if (!result) return null;
140
- return { sql: result, params };
141
- }
142
- var VALID_OPERATORS = /* @__PURE__ */ new Set(["$eq", "$ne", "$gt", "$gte", "$lt", "$lte", "$in"]);
143
- function buildWhereClauseParts(where, fields, params) {
144
- const conditions = [];
145
- for (const [fieldName, value] of Object.entries(where)) {
146
- validateFieldName(fieldName, fields);
147
- const descriptor = fields[fieldName];
148
- if (value !== null && typeof value === "object" && !Array.isArray(value)) {
149
- const ops = value;
150
- for (const [op, opValue] of Object.entries(ops)) {
151
- if (!VALID_OPERATORS.has(op)) {
152
- throw new QueryError(`Unknown operator "${op}" on field "${fieldName}"`, {
153
- field: fieldName,
154
- operator: op,
155
- validOperators: [...VALID_OPERATORS]
156
- });
157
- }
158
- conditions.push(buildOperatorCondition(fieldName, op, opValue, descriptor, params));
159
- }
160
- } else {
161
- conditions.push(buildOperatorCondition(fieldName, "$eq", value, descriptor, params));
162
- }
163
- }
164
- if (conditions.length === 0) return null;
165
- return conditions.join(" AND ");
166
- }
167
- function buildOperatorCondition(fieldName, operator, value, descriptor, params) {
168
- const sqlValue = descriptor?.kind === "boolean" && typeof value === "boolean" ? value ? 1 : 0 : value;
169
- const column = quoteIdent(fieldName);
170
- switch (operator) {
171
- case "$eq":
172
- if (sqlValue === null) {
173
- return `${column} IS NULL`;
174
- }
175
- params.push(sqlValue);
176
- return `${column} = ?`;
177
- case "$ne":
178
- if (sqlValue === null) {
179
- return `${column} IS NOT NULL`;
180
- }
181
- params.push(sqlValue);
182
- return `${column} != ?`;
183
- case "$gt":
184
- params.push(sqlValue);
185
- return `${column} > ?`;
186
- case "$gte":
187
- params.push(sqlValue);
188
- return `${column} >= ?`;
189
- case "$lt":
190
- params.push(sqlValue);
191
- return `${column} < ?`;
192
- case "$lte":
193
- params.push(sqlValue);
194
- return `${column} <= ?`;
195
- case "$in": {
196
- if (!Array.isArray(sqlValue)) {
197
- throw new QueryError(`$in operator requires an array value for field "${fieldName}"`, {
198
- field: fieldName,
199
- received: typeof sqlValue
200
- });
201
- }
202
- const placeholders = sqlValue.map(() => "?");
203
- for (const item of sqlValue) {
204
- params.push(
205
- descriptor?.kind === "boolean" && typeof item === "boolean" ? item ? 1 : 0 : item
206
- );
207
- }
208
- return `${column} IN (${placeholders.join(", ")})`;
209
- }
210
- default:
211
- throw new QueryError(`Unknown operator "${operator}"`, { operator });
212
- }
213
- }
214
- function validateFieldName(fieldName, fields) {
215
- const allowedFields = /* @__PURE__ */ new Set([
216
- ...Object.keys(fields),
217
- "id",
218
- "createdAt",
219
- "updatedAt",
220
- "_created_at",
221
- "_updated_at"
222
- ]);
223
- if (!allowedFields.has(fieldName)) {
224
- throw new QueryError(
225
- `Unknown field "${fieldName}" in query. Available fields: ${[...allowedFields].join(", ")}`,
226
- { field: fieldName }
227
- );
228
- }
229
- }
230
-
231
- // src/mutations/resolve-causal-deps.ts
232
- function resolveCausalDeps(ctx) {
233
- const merged = [];
234
- for (const id of ctx.extraCausalDeps ?? []) {
235
- if (!merged.includes(id)) {
236
- merged.push(id);
237
- }
238
- }
239
- for (const id of ctx.causalTracker?.nextCausalDeps(ctx.collection, ctx.inTransaction) ?? []) {
240
- if (!merged.includes(id)) {
241
- merged.push(id);
242
- }
243
- }
244
- return merged;
245
- }
246
-
247
- // src/mutations/execute-delete.ts
248
- import { createOperation, quoteIdent as quoteIdent2 } from "@korajs/core";
249
-
250
- // src/store/sequence-allocator.ts
251
- async function allocateNextSequenceNumber(adapter, nodeId) {
252
- const rows = await adapter.query(
253
- `INSERT INTO _kora_version_vector (node_id, sequence_number) VALUES (?, 1)
254
- ON CONFLICT(node_id) DO UPDATE SET sequence_number = sequence_number + 1
255
- RETURNING sequence_number`,
256
- [nodeId]
257
- );
258
- const seq = rows[0]?.sequence_number;
259
- if (seq === void 0) {
260
- throw new Error(`Failed to allocate sequence number for node "${nodeId}"`);
261
- }
262
- return seq;
263
- }
264
- async function allocateNextSequenceInTransaction(tx, nodeId) {
265
- const rows = await tx.query(
266
- `INSERT INTO _kora_version_vector (node_id, sequence_number) VALUES (?, 1)
267
- ON CONFLICT(node_id) DO UPDATE SET sequence_number = sequence_number + 1
268
- RETURNING sequence_number`,
269
- [nodeId]
270
- );
271
- const seq = rows[0]?.sequence_number;
272
- if (seq === void 0) {
273
- throw new Error(`Failed to allocate sequence number for node "${nodeId}" in transaction`);
274
- }
275
- return seq;
276
- }
277
- async function readSequenceNumber(adapter, nodeId) {
278
- const rows = await adapter.query(
279
- "SELECT sequence_number FROM _kora_version_vector WHERE node_id = ?",
280
- [nodeId]
281
- );
282
- return rows[0]?.sequence_number ?? 0;
283
- }
284
-
285
- // src/mutations/execute-delete.ts
286
- async function executeDelete(ctx, id, options) {
287
- const currentRows = await ctx.adapter.query(
288
- `SELECT * FROM ${quoteIdent2(ctx.collection)} WHERE id = ? AND _deleted = 0`,
289
- [id]
290
- );
291
- if (!currentRows[0]) {
292
- throw new RecordNotFoundError(ctx.collection, id);
293
- }
294
- const causalDeps = resolveCausalDeps(ctx);
295
- let operation = options?.operation;
296
- const cascadedOps = [];
297
- await ctx.adapter.transaction(async (tx) => {
298
- if (!operation) {
299
- const sequenceNumber = await allocateNextSequenceInTransaction(tx, ctx.nodeId);
300
- operation = await createOperation(
301
- {
302
- nodeId: ctx.nodeId,
303
- type: "delete",
304
- collection: ctx.collection,
305
- recordId: id,
306
- data: null,
307
- previousData: null,
308
- sequenceNumber,
309
- causalDeps,
310
- schemaVersion: ctx.schema.version
311
- },
312
- ctx.clock
313
- );
314
- ctx.causalTracker?.afterOperation(ctx.collection, operation.id, ctx.inTransaction);
315
- }
316
- if (ctx.relationEnforcer && !options?.skipReferentialEnforcement) {
317
- const enforcementResult = await ctx.relationEnforcer.enforceDelete(ctx.collection, id, tx, [
318
- operation.id
319
- ]);
320
- cascadedOps.push(...enforcementResult.operations);
321
- }
322
- const version = serializeRowVersion(operation.timestamp);
323
- const deleteQuery = buildSoftDeleteQuery(
324
- ctx.collection,
325
- id,
326
- operation.timestamp.wallTime,
327
- version
328
- );
329
- const opInsert = buildInsertQuery(
330
- `_kora_ops_${ctx.collection}`,
331
- serializeOperation(operation)
332
- );
333
- await tx.execute(deleteQuery.sql, deleteQuery.params);
334
- await tx.execute(opInsert.sql, opInsert.params);
335
- });
336
- if (!operation) {
337
- throw new Error("Delete operation was not created");
338
- }
339
- ctx.onMutation(ctx.collection, operation);
340
- for (const cascadedOp of cascadedOps) {
341
- ctx.onMutation(cascadedOp.collection, cascadedOp);
342
- }
343
- return [operation, ...cascadedOps];
344
- }
345
-
346
- // src/mutations/execute-insert.ts
347
- import {
348
- createOperation as createOperation2,
349
- generateUUIDv7,
350
- transformSecretFieldsForWrite,
351
- validateRecord
352
- } from "@korajs/core";
353
-
354
- // src/lww/field-versions.ts
355
- function parseFieldVersions(raw) {
356
- if (typeof raw !== "string" || raw.length === 0) {
357
- return {};
358
- }
359
- try {
360
- const parsed = JSON.parse(raw);
361
- if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
362
- return {};
363
- }
364
- const result = {};
365
- for (const [key, value] of Object.entries(parsed)) {
366
- if (typeof value === "string") {
367
- result[key] = value;
368
- }
369
- }
370
- return result;
371
- } catch {
372
- return {};
373
- }
374
- }
375
- function serializeFieldVersions(versions) {
376
- return JSON.stringify(versions);
377
- }
378
- function fieldVersionsForFields(fieldNames, version) {
379
- const versions = {};
380
- for (const field of fieldNames) {
381
- versions[field] = version;
382
- }
383
- return versions;
384
- }
385
- function effectiveFieldVersion(current, field, rowVersion) {
386
- const own = current[field];
387
- if (own !== void 0) {
388
- return own;
389
- }
390
- return rowVersion ?? EMPTY_ROW_VERSION;
391
- }
392
- function resolvePerFieldLww(current, incomingFields, incomingVersion, rowVersion) {
393
- const winners = [];
394
- const merged = { ...current };
395
- for (const field of incomingFields) {
396
- const stored = effectiveFieldVersion(current, field, rowVersion);
397
- if (incomingVersion > stored) {
398
- winners.push(field);
399
- merged[field] = incomingVersion;
400
- }
401
- }
402
- return { winners, merged };
403
- }
404
-
405
- // src/mutations/execute-insert.ts
406
- async function executeInsert(ctx, data) {
407
- const validated = validateRecord(ctx.collection, ctx.definition, data, "insert");
408
- const recordId = generateUUIDv7();
409
- for (const [fieldName, descriptor] of Object.entries(ctx.definition.fields)) {
410
- if (descriptor.auto && descriptor.kind === "timestamp") {
411
- validated[fieldName] = Date.now();
412
- }
413
- }
414
- const writeData = await transformSecretFieldsForWrite(
415
- validated,
416
- ctx.definition,
417
- ctx.secretKeyProvider
418
- );
419
- const causalDeps = resolveCausalDeps(ctx);
420
- let operation;
421
- let record;
422
- await ctx.adapter.transaction(async (tx) => {
423
- const sequenceNumber = await allocateNextSequenceInTransaction(tx, ctx.nodeId);
424
- operation = await createOperation2(
425
- {
426
- nodeId: ctx.nodeId,
427
- type: "insert",
428
- collection: ctx.collection,
429
- recordId,
430
- // Binary richtext values are tagged as canonical JSON BEFORE the
431
- // operation is content-hashed, so the hash input, persisted JSON,
432
- // and wire payload are the identical value.
433
- data: encodeRichtextFieldsForOpData(writeData, ctx.definition.fields),
434
- previousData: null,
435
- sequenceNumber,
436
- causalDeps,
437
- schemaVersion: ctx.schema.version
438
- },
439
- ctx.clock
440
- );
441
- ctx.causalTracker?.afterOperation(ctx.collection, operation.id, ctx.inTransaction);
442
- const serializedData = serializeRecord(writeData, ctx.definition.fields);
443
- const version = serializeRowVersion(operation.timestamp);
444
- record = {
445
- id: recordId,
446
- ...serializedData,
447
- _created_at: operation.timestamp.wallTime,
448
- _updated_at: operation.timestamp.wallTime,
449
- _version: version,
450
- // Stamp every inserted field with this operation's version so later
451
- // per-field LWW compares against a real writer, not the fallback.
452
- _field_versions: serializeFieldVersions(
453
- fieldVersionsForFields(Object.keys(serializedData), version)
454
- )
455
- };
456
- const builtInsert = buildInsertQuery(ctx.collection, record);
457
- const opInsert = buildInsertQuery(
458
- `_kora_ops_${ctx.collection}`,
459
- serializeOperation(operation)
460
- );
461
- await tx.execute(builtInsert.sql, builtInsert.params);
462
- await tx.execute(opInsert.sql, opInsert.params);
463
- });
464
- ctx.onMutation(ctx.collection, operation);
465
- return {
466
- id: recordId,
467
- ...writeData,
468
- createdAt: operation.timestamp.wallTime,
469
- updatedAt: operation.timestamp.wallTime
470
- };
471
- }
472
-
473
- // src/state-machine/state-validator.ts
474
- import { KoraError, validateTransition } from "@korajs/core";
475
- var InvalidStateTransitionError = class extends KoraError {
476
- constructor(collection, recordId, field, fromState, toState, allowedStates) {
477
- super(
478
- `Invalid state transition in collection "${collection}": cannot transition field "${field}" from "${fromState}" to "${toState}". Allowed transitions from "${fromState}": ${allowedStates.length > 0 ? allowedStates.join(", ") : "(none -- terminal state)"}`,
479
- "INVALID_STATE_TRANSITION",
480
- { collection, recordId, field, fromState, toState, allowedStates }
481
- );
482
- this.collection = collection;
483
- this.recordId = recordId;
484
- this.field = field;
485
- this.fromState = fromState;
486
- this.toState = toState;
487
- this.allowedStates = allowedStates;
488
- this.name = "InvalidStateTransitionError";
489
- }
490
- collection;
491
- recordId;
492
- field;
493
- fromState;
494
- toState;
495
- allowedStates;
496
- };
497
- function validateStateTransition(collectionName, recordId, stateMachine, currentState, newState) {
498
- if (currentState === null) {
499
- return { valid: true, allowedStates: [] };
500
- }
501
- if (currentState === newState) {
502
- return { valid: true, allowedStates: stateMachine.transitions[currentState] ?? [] };
503
- }
504
- const constraint = {
505
- field: stateMachine.field,
506
- collection: collectionName,
507
- transitions: stateMachine.transitions
508
- };
509
- const transitionResult = validateTransition(constraint, currentState, newState);
510
- if (transitionResult.valid) {
511
- return { valid: true, allowedStates: transitionResult.allowedTargets };
512
- }
513
- const allowedStates = transitionResult.allowedTargets;
514
- if (stateMachine.onInvalidTransition === "reject") {
515
- throw new InvalidStateTransitionError(
516
- collectionName,
517
- recordId,
518
- stateMachine.field,
519
- currentState,
520
- newState,
521
- allowedStates
522
- );
523
- }
524
- return { valid: false, allowedStates };
525
- }
526
- function validateUpdateStateMachine(collectionName, recordId, collectionDef, currentRecord, updateData) {
527
- const stateMachine = collectionDef.stateMachine;
528
- if (stateMachine === void 0) {
529
- return updateData;
530
- }
531
- const stateField = stateMachine.field;
532
- if (!(stateField in updateData)) {
533
- return updateData;
534
- }
535
- const currentState = currentRecord[stateField];
536
- const newState = updateData[stateField];
537
- if (typeof currentState !== "string" || typeof newState !== "string") {
538
- return updateData;
539
- }
540
- const result = validateStateTransition(
541
- collectionName,
542
- recordId,
543
- stateMachine,
544
- currentState,
545
- newState
546
- );
547
- if (result.valid) {
548
- return updateData;
549
- }
550
- const filtered = { ...updateData };
551
- delete filtered[stateField];
552
- return filtered;
553
- }
554
-
555
- // src/mutations/execute-update.ts
556
- import {
557
- createOperation as createOperation3,
558
- isAtomicOp,
559
- quoteIdent as quoteIdent3,
560
- resolveAtomicOp,
561
- toAtomicOp,
562
- transformSecretFieldsForWrite as transformSecretFieldsForWrite2,
563
- validateRecord as validateRecord2
564
- } from "@korajs/core";
565
- async function executeUpdate(ctx, id, data) {
566
- const currentRows = await ctx.adapter.query(
567
- `SELECT * FROM ${quoteIdent3(ctx.collection)} WHERE id = ? AND _deleted = 0`,
568
- [id]
569
- );
570
- const currentRow = currentRows[0];
571
- if (!currentRow) {
572
- throw new RecordNotFoundError(ctx.collection, id);
573
- }
574
- let validated = validateRecord2(ctx.collection, ctx.definition, data, "update");
575
- const currentRecord = deserializeRecord(currentRow, ctx.definition.fields);
576
- validated = validateUpdateStateMachine(
577
- ctx.collection,
578
- id,
579
- ctx.definition,
580
- currentRecord,
581
- validated
582
- );
583
- if (Object.keys(validated).length === 0) {
584
- return { ...currentRecord };
585
- }
586
- const previousData = {};
587
- const resolvedData = {};
588
- const atomicOps = {};
589
- for (const key of Object.keys(validated)) {
590
- const value = validated[key];
591
- previousData[key] = currentRecord[key];
592
- if (isAtomicOp(value)) {
593
- resolvedData[key] = resolveAtomicOp(currentRecord[key], value);
594
- atomicOps[key] = toAtomicOp(value);
595
- } else {
596
- resolvedData[key] = value;
597
- }
598
- }
599
- const writeData = await transformSecretFieldsForWrite2(
600
- resolvedData,
601
- ctx.definition,
602
- ctx.secretKeyProvider
603
- );
604
- const hasAtomicOps = Object.keys(atomicOps).length > 0;
605
- const causalDeps = resolveCausalDeps(ctx);
606
- let operation;
607
- await ctx.adapter.transaction(async (tx) => {
608
- const sequenceNumber = await allocateNextSequenceInTransaction(tx, ctx.nodeId);
609
- operation = await createOperation3(
610
- {
611
- nodeId: ctx.nodeId,
612
- type: "update",
613
- collection: ctx.collection,
614
- recordId: id,
615
- // Binary richtext values (new and previous) are tagged as canonical
616
- // JSON BEFORE the operation is content-hashed, so the hash input,
617
- // persisted JSON, and wire payload are the identical value.
618
- data: encodeRichtextFieldsForOpData(writeData, ctx.definition.fields),
619
- previousData: encodeRichtextFieldsForOpData(previousData, ctx.definition.fields),
620
- sequenceNumber,
621
- causalDeps,
622
- schemaVersion: ctx.schema.version,
623
- ...hasAtomicOps ? { atomicOps } : {}
624
- },
625
- ctx.clock
626
- );
627
- ctx.causalTracker?.afterOperation(ctx.collection, operation.id, ctx.inTransaction);
628
- const serializedChanges = serializeRecord(writeData, ctx.definition.fields);
629
- const version = serializeRowVersion(operation.timestamp);
630
- const mergedFieldVersions = parseFieldVersions(currentRow._field_versions);
631
- for (const changedField of Object.keys(serializedChanges)) {
632
- mergedFieldVersions[changedField] = version;
633
- }
634
- const updateQuery = buildUpdateQuery(ctx.collection, id, {
635
- ...serializedChanges,
636
- _updated_at: operation.timestamp.wallTime,
637
- _version: version,
638
- _field_versions: serializeFieldVersions(mergedFieldVersions)
639
- });
640
- const opInsert = buildInsertQuery(
641
- `_kora_ops_${ctx.collection}`,
642
- serializeOperation(operation)
643
- );
644
- await tx.execute(updateQuery.sql, updateQuery.params);
645
- await tx.execute(opInsert.sql, opInsert.params);
646
- });
647
- ctx.onMutation(ctx.collection, operation);
648
- const rows = await ctx.adapter.query(
649
- `SELECT * FROM ${quoteIdent3(ctx.collection)} WHERE id = ? AND _deleted = 0`,
650
- [id]
651
- );
652
- const row = rows[0];
653
- if (!row) {
654
- throw new RecordNotFoundError(ctx.collection, id);
655
- }
656
- return deserializeRecord(row, ctx.definition.fields);
657
- }
658
-
659
- export {
660
- serializeRowVersion,
661
- rowVersionFromRecord,
662
- isIncomingNewerThanRow,
663
- buildSelectQuery,
664
- buildCountQuery,
665
- buildInsertQuery,
666
- buildUpdateQuery,
667
- buildFieldFastForwardUpdateQuery,
668
- buildSoftDeleteQuery,
669
- buildLwwUpdateQuery,
670
- buildLwwSoftDeleteQuery,
671
- buildWhereClause,
672
- allocateNextSequenceNumber,
673
- allocateNextSequenceInTransaction,
674
- readSequenceNumber,
675
- resolveCausalDeps,
676
- executeDelete,
677
- parseFieldVersions,
678
- serializeFieldVersions,
679
- fieldVersionsForFields,
680
- resolvePerFieldLww,
681
- executeInsert,
682
- InvalidStateTransitionError,
683
- validateStateTransition,
684
- validateUpdateStateMachine,
685
- executeUpdate
686
- };
687
- //# sourceMappingURL=chunk-YT7MRFCP.js.map