@stratasync/core 0.2.0

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 (90) hide show
  1. package/README.md +83 -0
  2. package/dist/index.d.ts +21 -0
  3. package/dist/index.d.ts.map +1 -0
  4. package/dist/index.js +14 -0
  5. package/dist/index.js.map +1 -0
  6. package/dist/model/base-model.d.ts +82 -0
  7. package/dist/model/base-model.d.ts.map +1 -0
  8. package/dist/model/base-model.js +219 -0
  9. package/dist/model/base-model.js.map +1 -0
  10. package/dist/model/cached-promise.d.ts +15 -0
  11. package/dist/model/cached-promise.d.ts.map +1 -0
  12. package/dist/model/cached-promise.js +50 -0
  13. package/dist/model/cached-promise.js.map +1 -0
  14. package/dist/model/collection.d.ts +33 -0
  15. package/dist/model/collection.d.ts.map +1 -0
  16. package/dist/model/collection.js +181 -0
  17. package/dist/model/collection.js.map +1 -0
  18. package/dist/model/hydration.d.ts +13 -0
  19. package/dist/model/hydration.d.ts.map +1 -0
  20. package/dist/model/hydration.js +2 -0
  21. package/dist/model/hydration.js.map +1 -0
  22. package/dist/model/observability.d.ts +23 -0
  23. package/dist/model/observability.d.ts.map +1 -0
  24. package/dist/model/observability.js +162 -0
  25. package/dist/model/observability.js.map +1 -0
  26. package/dist/reactivity/adapter.d.ts +145 -0
  27. package/dist/reactivity/adapter.d.ts.map +1 -0
  28. package/dist/reactivity/adapter.js +95 -0
  29. package/dist/reactivity/adapter.js.map +1 -0
  30. package/dist/schema/decorators.d.ts +8 -0
  31. package/dist/schema/decorators.d.ts.map +1 -0
  32. package/dist/schema/decorators.js +117 -0
  33. package/dist/schema/decorators.js.map +1 -0
  34. package/dist/schema/hash.d.ts +6 -0
  35. package/dist/schema/hash.d.ts.map +1 -0
  36. package/dist/schema/hash.js +76 -0
  37. package/dist/schema/hash.js.map +1 -0
  38. package/dist/schema/normalize.d.ts +5 -0
  39. package/dist/schema/normalize.d.ts.map +1 -0
  40. package/dist/schema/normalize.js +194 -0
  41. package/dist/schema/normalize.js.map +1 -0
  42. package/dist/schema/registry.d.ts +147 -0
  43. package/dist/schema/registry.d.ts.map +1 -0
  44. package/dist/schema/registry.js +304 -0
  45. package/dist/schema/registry.js.map +1 -0
  46. package/dist/schema/types.d.ts +215 -0
  47. package/dist/schema/types.d.ts.map +1 -0
  48. package/dist/schema/types.js +2 -0
  49. package/dist/schema/types.js.map +1 -0
  50. package/dist/store/types.d.ts +14 -0
  51. package/dist/store/types.d.ts.map +1 -0
  52. package/dist/store/types.js +2 -0
  53. package/dist/store/types.js.map +1 -0
  54. package/dist/sync/delta-applier.d.ts +52 -0
  55. package/dist/sync/delta-applier.d.ts.map +1 -0
  56. package/dist/sync/delta-applier.js +110 -0
  57. package/dist/sync/delta-applier.js.map +1 -0
  58. package/dist/sync/rebase.d.ts +57 -0
  59. package/dist/sync/rebase.d.ts.map +1 -0
  60. package/dist/sync/rebase.js +155 -0
  61. package/dist/sync/rebase.js.map +1 -0
  62. package/dist/sync/sync-id.d.ts +17 -0
  63. package/dist/sync/sync-id.d.ts.map +1 -0
  64. package/dist/sync/sync-id.js +26 -0
  65. package/dist/sync/sync-id.js.map +1 -0
  66. package/dist/sync/types.d.ts +152 -0
  67. package/dist/sync/types.d.ts.map +1 -0
  68. package/dist/sync/types.js +2 -0
  69. package/dist/sync/types.js.map +1 -0
  70. package/dist/transaction/archive.d.ts +16 -0
  71. package/dist/transaction/archive.d.ts.map +1 -0
  72. package/dist/transaction/archive.js +23 -0
  73. package/dist/transaction/archive.js.map +1 -0
  74. package/dist/transaction/create.d.ts +31 -0
  75. package/dist/transaction/create.d.ts.map +1 -0
  76. package/dist/transaction/create.js +121 -0
  77. package/dist/transaction/create.js.map +1 -0
  78. package/dist/transaction/types.d.ts +86 -0
  79. package/dist/transaction/types.d.ts.map +1 -0
  80. package/dist/transaction/types.js +2 -0
  81. package/dist/transaction/types.js.map +1 -0
  82. package/dist/utils/assign.d.ts +9 -0
  83. package/dist/utils/assign.d.ts.map +1 -0
  84. package/dist/utils/assign.js +20 -0
  85. package/dist/utils/assign.js.map +1 -0
  86. package/dist/utils/idempotency.d.ts +16 -0
  87. package/dist/utils/idempotency.d.ts.map +1 -0
  88. package/dist/utils/idempotency.js +39 -0
  89. package/dist/utils/idempotency.js.map +1 -0
  90. package/package.json +37 -0
@@ -0,0 +1,110 @@
1
+ import { createArchivePayload, createUnarchivePatch, readArchivedAt, } from "../transaction/archive.js";
2
+ import { maxSyncId, ZERO_SYNC_ID } from "./sync-id.js";
3
+ /**
4
+ * Merges data into an existing row and writes it back.
5
+ */
6
+ const mergeAndPut = async (target, modelName, modelId, data, overrides) => {
7
+ const existing = await target.get(modelName, modelId);
8
+ await target.put(modelName, modelId, {
9
+ ...existing,
10
+ ...data,
11
+ ...overrides,
12
+ });
13
+ };
14
+ /**
15
+ * Applies a single sync action to the target
16
+ */
17
+ const applySingleAction = async (action, target, options) => {
18
+ const { modelName, modelId, data } = action;
19
+ switch (action.action) {
20
+ case "I": {
21
+ await target.put(modelName, modelId, data);
22
+ break;
23
+ }
24
+ case "U": {
25
+ if (options.mergeUpdates) {
26
+ await target.patch(modelName, modelId, data);
27
+ }
28
+ else {
29
+ const existing = await target.get(modelName, modelId);
30
+ await target.put(modelName, modelId, existing ? { ...existing, ...data } : data);
31
+ }
32
+ break;
33
+ }
34
+ case "D": {
35
+ await target.delete(modelName, modelId);
36
+ break;
37
+ }
38
+ case "A": {
39
+ await mergeAndPut(target, modelName, modelId, data, createArchivePayload(readArchivedAt(data)));
40
+ break;
41
+ }
42
+ case "V": {
43
+ await mergeAndPut(target, modelName, modelId, data, createUnarchivePatch());
44
+ break;
45
+ }
46
+ default: {
47
+ break;
48
+ }
49
+ }
50
+ };
51
+ /**
52
+ * Updates the result counters based on action type
53
+ */
54
+ const updateResult = (result, action) => {
55
+ const actionSyncId = action.id;
56
+ result.lastSyncId = maxSyncId(result.lastSyncId, actionSyncId);
57
+ switch (action.action) {
58
+ case "I": {
59
+ result.inserts += 1;
60
+ break;
61
+ }
62
+ case "U": {
63
+ result.updates += 1;
64
+ break;
65
+ }
66
+ case "D": {
67
+ result.deletes += 1;
68
+ break;
69
+ }
70
+ case "A": {
71
+ result.archives += 1;
72
+ break;
73
+ }
74
+ case "V": {
75
+ result.unarchives += 1;
76
+ break;
77
+ }
78
+ default: {
79
+ break;
80
+ }
81
+ }
82
+ };
83
+ export const applyDeltas = async (packet, target, registry, options = {}) => {
84
+ const result = {
85
+ archives: 0,
86
+ deletes: 0,
87
+ inserts: 0,
88
+ lastSyncId: ZERO_SYNC_ID,
89
+ skipped: 0,
90
+ unarchives: 0,
91
+ updates: 0,
92
+ };
93
+ for (const action of packet.actions) {
94
+ // Skip actions from our own client (we already applied them optimistically)
95
+ const actionSyncId = action.id;
96
+ if (options.clientId && action.clientId === options.clientId) {
97
+ result.skipped += 1;
98
+ result.lastSyncId = maxSyncId(result.lastSyncId, actionSyncId);
99
+ continue;
100
+ }
101
+ // Verify model exists in schema
102
+ if (!registry.hasModel(action.modelName)) {
103
+ continue;
104
+ }
105
+ await applySingleAction(action, target, options);
106
+ updateResult(result, action);
107
+ }
108
+ return result;
109
+ };
110
+ //# sourceMappingURL=delta-applier.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"delta-applier.js","sourceRoot":"","sources":["../../src/sync/delta-applier.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,57 @@
1
+ import type { Transaction } from "../transaction/types.js";
2
+ import type { SyncAction } from "./types.js";
3
+ /**
4
+ * Result of rebasing pending transactions against server deltas
5
+ */
6
+ export interface RebaseResult {
7
+ /** Transactions that should remain pending */
8
+ pending: Transaction[];
9
+ /** Transactions that were confirmed by the server */
10
+ confirmed: Transaction[];
11
+ /** Transactions that conflict with server changes */
12
+ conflicts: RebaseConflict[];
13
+ }
14
+ /**
15
+ * A conflict between a pending transaction and a server change
16
+ */
17
+ export interface RebaseConflict {
18
+ /** The local pending transaction */
19
+ localTransaction: Transaction;
20
+ /** The conflicting server action */
21
+ serverAction: SyncAction;
22
+ /** Type of conflict */
23
+ conflictType: ConflictType;
24
+ /** Suggested resolution */
25
+ resolution: ConflictResolution;
26
+ }
27
+ /**
28
+ * Types of conflicts that can occur
29
+ */
30
+ type ConflictType = "update-update" | "update-delete" | "delete-update" | "insert-insert";
31
+ /**
32
+ * Possible conflict resolutions
33
+ */
34
+ export type ConflictResolution = "server-wins" | "client-wins" | "merge" | "manual";
35
+ /**
36
+ * Options for rebase operation
37
+ */
38
+ export interface RebaseOptions {
39
+ /** Client ID to identify own transactions */
40
+ clientId: string;
41
+ /** Default conflict resolution strategy */
42
+ defaultResolution?: ConflictResolution;
43
+ /** Field-level conflict detection */
44
+ fieldLevelConflicts?: boolean;
45
+ }
46
+ /**
47
+ * Rebases pending transactions against server deltas
48
+ *
49
+ * This is the core algorithm for handling concurrent edits:
50
+ * 1. For each server action, check if we have a pending transaction for the same model/id
51
+ * 2. If the server action is our own transaction (matched by clientTxId), mark as confirmed
52
+ * 3. If the server action conflicts with our pending transaction, detect the conflict type
53
+ * 4. Apply the appropriate resolution strategy
54
+ */
55
+ export declare const rebaseTransactions: (pending: Transaction[], serverActions: SyncAction[], options: RebaseOptions) => RebaseResult;
56
+ export {};
57
+ //# sourceMappingURL=rebase.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rebase.d.ts","sourceRoot":"","sources":["../../src/sync/rebase.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAC3D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAE7C;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,8CAA8C;IAC9C,OAAO,EAAE,WAAW,EAAE,CAAC;IACvB,qDAAqD;IACrD,SAAS,EAAE,WAAW,EAAE,CAAC;IACzB,qDAAqD;IACrD,SAAS,EAAE,cAAc,EAAE,CAAC;CAC7B;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,oCAAoC;IACpC,gBAAgB,EAAE,WAAW,CAAC;IAC9B,oCAAoC;IACpC,YAAY,EAAE,UAAU,CAAC;IACzB,uBAAuB;IACvB,YAAY,EAAE,YAAY,CAAC;IAC3B,2BAA2B;IAC3B,UAAU,EAAE,kBAAkB,CAAC;CAChC;AAED;;GAEG;AACH,KAAK,YAAY,GAEb,eAAe,GAEf,eAAe,GAEf,eAAe,GAEf,eAAe,CAAC;AAEpB;;GAEG;AACH,MAAM,MAAM,kBAAkB,GAE1B,aAAa,GAEb,aAAa,GAEb,OAAO,GAEP,QAAQ,CAAC;AAEb;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,6CAA6C;IAC7C,QAAQ,EAAE,MAAM,CAAC;IACjB,2CAA2C;IAC3C,iBAAiB,CAAC,EAAE,kBAAkB,CAAC;IACvC,qCAAqC;IACrC,mBAAmB,CAAC,EAAE,OAAO,CAAC;CAC/B;AAoHD;;;;;;;;GAQG;AACH,eAAO,MAAM,kBAAkB,GAC7B,SAAS,WAAW,EAAE,EACtB,eAAe,UAAU,EAAE,EAC3B,SAAS,aAAa,KACrB,YA2DF,CAAC"}
@@ -0,0 +1,155 @@
1
+ /**
2
+ * Normalizes Archive (A) and Unarchive (V) actions to Update (U)
3
+ * since they are semantically update-like operations.
4
+ */
5
+ const normalizeAction = (action) => {
6
+ switch (action) {
7
+ case "I": {
8
+ return "I";
9
+ }
10
+ case "U":
11
+ case "A":
12
+ case "V": {
13
+ return "U";
14
+ }
15
+ case "D": {
16
+ return "D";
17
+ }
18
+ default: {
19
+ return null;
20
+ }
21
+ }
22
+ };
23
+ const getLocalConflictFields = (tx) => {
24
+ if (tx.action === "A" || tx.action === "V") {
25
+ return ["archivedAt"];
26
+ }
27
+ return Object.keys(tx.payload);
28
+ };
29
+ const getServerConflictFields = (action) => {
30
+ if (action.action === "A" || action.action === "V") {
31
+ const dataKeys = Object.keys(action.data).filter((key) => key !== "archivedAt");
32
+ return ["archivedAt", ...dataKeys];
33
+ }
34
+ return Object.keys(action.data);
35
+ };
36
+ /**
37
+ * Determines the resolution strategy for a conflict
38
+ */
39
+ const resolveConflict = (conflictType, options) => {
40
+ if (options.defaultResolution) {
41
+ return options.defaultResolution;
42
+ }
43
+ // insert-insert is rare (likely a bug) and requires manual resolution.
44
+ // All other conflict types default to server-wins (last-write-wins).
45
+ if (conflictType === "insert-insert") {
46
+ return "manual";
47
+ }
48
+ return "server-wins";
49
+ };
50
+ /**
51
+ * Detects if there's a conflict between a local transaction and server action
52
+ */
53
+ const detectConflict = (tx, action, options) => {
54
+ const normalizedServer = normalizeAction(action.action);
55
+ const normalizedLocal = normalizeAction(tx.action);
56
+ if (!(normalizedServer && normalizedLocal)) {
57
+ return null;
58
+ }
59
+ // modelName and modelId match is guaranteed by the caller's key-based lookup
60
+ let conflictType;
61
+ if (normalizedLocal === "I" && normalizedServer === "I") {
62
+ conflictType = "insert-insert";
63
+ }
64
+ else if (normalizedLocal === "D" && normalizedServer === "U") {
65
+ conflictType = "delete-update";
66
+ }
67
+ else if (normalizedLocal === "U" && normalizedServer === "D") {
68
+ conflictType = "update-delete";
69
+ }
70
+ else if (normalizedLocal === "U" && normalizedServer === "U") {
71
+ // Check for field-level conflicts
72
+ if (options.fieldLevelConflicts) {
73
+ const localFields = getLocalConflictFields(tx);
74
+ const serverFields = getServerConflictFields(action);
75
+ const overlap = localFields.some((f) => serverFields.includes(f));
76
+ if (!overlap) {
77
+ // No overlapping fields, can merge
78
+ return null;
79
+ }
80
+ }
81
+ conflictType = "update-update";
82
+ }
83
+ else {
84
+ // No conflict for other combinations
85
+ return null;
86
+ }
87
+ // Determine resolution
88
+ const resolution = resolveConflict(conflictType, options);
89
+ return {
90
+ conflictType,
91
+ localTransaction: tx,
92
+ resolution,
93
+ serverAction: action,
94
+ };
95
+ };
96
+ /**
97
+ * Rebases pending transactions against server deltas
98
+ *
99
+ * This is the core algorithm for handling concurrent edits:
100
+ * 1. For each server action, check if we have a pending transaction for the same model/id
101
+ * 2. If the server action is our own transaction (matched by clientTxId), mark as confirmed
102
+ * 3. If the server action conflicts with our pending transaction, detect the conflict type
103
+ * 4. Apply the appropriate resolution strategy
104
+ */
105
+ export const rebaseTransactions = (pending, serverActions, options) => {
106
+ const result = {
107
+ confirmed: [],
108
+ conflicts: [],
109
+ pending: [],
110
+ };
111
+ // Index pending transactions by model+id for fast lookup
112
+ const pendingByKey = new Map();
113
+ for (const tx of pending) {
114
+ const key = `${tx.modelName}:${tx.modelId}`;
115
+ const existing = pendingByKey.get(key) ?? [];
116
+ existing.push(tx);
117
+ pendingByKey.set(key, existing);
118
+ }
119
+ // Track which transactions have been processed
120
+ const processed = new Set();
121
+ // Process each server action
122
+ for (const action of serverActions) {
123
+ const key = `${action.modelName}:${action.modelId}`;
124
+ const relatedTxs = pendingByKey.get(key) ?? [];
125
+ for (const tx of relatedTxs) {
126
+ if (processed.has(tx.clientTxId)) {
127
+ continue;
128
+ }
129
+ // Check if this server action is our own transaction
130
+ if (action.clientTxId === tx.clientTxId &&
131
+ action.clientId === options.clientId) {
132
+ result.confirmed.push(tx);
133
+ processed.add(tx.clientTxId);
134
+ // Our own echo — don't conflict-check remaining pending txs against it.
135
+ // Subsequent pending mutations (e.g. undo) were created on top of this
136
+ // change and should not be rolled back by its server confirmation.
137
+ break;
138
+ }
139
+ // Check for conflicts
140
+ const conflict = detectConflict(tx, action, options);
141
+ if (conflict) {
142
+ result.conflicts.push(conflict);
143
+ processed.add(tx.clientTxId);
144
+ }
145
+ }
146
+ }
147
+ // Remaining unprocessed transactions stay pending
148
+ for (const tx of pending) {
149
+ if (!processed.has(tx.clientTxId)) {
150
+ result.pending.push(tx);
151
+ }
152
+ }
153
+ return result;
154
+ };
155
+ //# sourceMappingURL=rebase.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rebase.js","sourceRoot":"","sources":["../../src/sync/rebase.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Sync ID type — string on the wire, represents a monotonically increasing integer.
3
+ * Using string avoids precision loss for values exceeding Number.MAX_SAFE_INTEGER.
4
+ */
5
+ export type SyncId = string;
6
+ /** Zero sync ID constant */
7
+ export declare const ZERO_SYNC_ID: SyncId;
8
+ /**
9
+ * Compares two string-encoded sync IDs numerically.
10
+ * Returns negative if a < b, 0 if equal, positive if a > b.
11
+ */
12
+ export declare const compareSyncId: (a: SyncId, b: SyncId) => number;
13
+ /** Returns the larger of two sync IDs */
14
+ export declare const maxSyncId: (a: SyncId, b: SyncId) => SyncId;
15
+ /** Checks if sync ID a is greater than sync ID b */
16
+ export declare const isSyncIdGreaterThan: (a: SyncId, b: SyncId) => boolean;
17
+ //# sourceMappingURL=sync-id.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sync-id.d.ts","sourceRoot":"","sources":["../../src/sync/sync-id.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,MAAM,MAAM,MAAM,GAAG,MAAM,CAAC;AAE5B,4BAA4B;AAC5B,eAAO,MAAM,YAAY,EAAE,MAAY,CAAC;AAIxC;;;GAGG;AACH,eAAO,MAAM,aAAa,GAAI,GAAG,MAAM,EAAE,GAAG,MAAM,KAAG,MAcpD,CAAC;AAEF,yCAAyC;AACzC,eAAO,MAAM,SAAS,GAAI,GAAG,MAAM,EAAE,GAAG,MAAM,KAAG,MACf,CAAC;AAEnC,oDAAoD;AACpD,eAAO,MAAM,mBAAmB,GAAI,GAAG,MAAM,EAAE,GAAG,MAAM,KAAG,OAClC,CAAC"}
@@ -0,0 +1,26 @@
1
+ /** Zero sync ID constant */
2
+ export const ZERO_SYNC_ID = "0";
3
+ const LEADING_ZEROS = /^0+/;
4
+ /**
5
+ * Compares two string-encoded sync IDs numerically.
6
+ * Returns negative if a < b, 0 if equal, positive if a > b.
7
+ */
8
+ export const compareSyncId = (a, b) => {
9
+ const aStripped = a.replace(LEADING_ZEROS, "") || "0";
10
+ const bStripped = b.replace(LEADING_ZEROS, "") || "0";
11
+ if (aStripped.length !== bStripped.length) {
12
+ return aStripped.length - bStripped.length;
13
+ }
14
+ if (aStripped < bStripped) {
15
+ return -1;
16
+ }
17
+ if (aStripped > bStripped) {
18
+ return 1;
19
+ }
20
+ return 0;
21
+ };
22
+ /** Returns the larger of two sync IDs */
23
+ export const maxSyncId = (a, b) => compareSyncId(a, b) >= 0 ? a : b;
24
+ /** Checks if sync ID a is greater than sync ID b */
25
+ export const isSyncIdGreaterThan = (a, b) => compareSyncId(a, b) > 0;
26
+ //# sourceMappingURL=sync-id.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sync-id.js","sourceRoot":"","sources":["../../src/sync/sync-id.ts"],"names":[],"mappings":"AAMA,4BAA4B;AAC5B,MAAM,CAAC,MAAM,YAAY,GAAW,GAAG,CAAC;AAExC,MAAM,aAAa,GAAG,KAAK,CAAC;AAE5B;;;GAGG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,CAAS,EAAE,CAAS,EAAU,EAAE;IAC5D,MAAM,SAAS,GAAG,CAAC,CAAC,OAAO,CAAC,aAAa,EAAE,EAAE,CAAC,IAAI,GAAG,CAAC;IACtD,MAAM,SAAS,GAAG,CAAC,CAAC,OAAO,CAAC,aAAa,EAAE,EAAE,CAAC,IAAI,GAAG,CAAC;IAEtD,IAAI,SAAS,CAAC,MAAM,KAAK,SAAS,CAAC,MAAM,EAAE,CAAC;QAC1C,OAAO,SAAS,CAAC,MAAM,GAAG,SAAS,CAAC,MAAM,CAAC;IAC7C,CAAC;IACD,IAAI,SAAS,GAAG,SAAS,EAAE,CAAC;QAC1B,OAAO,CAAC,CAAC,CAAC;IACZ,CAAC;IACD,IAAI,SAAS,GAAG,SAAS,EAAE,CAAC;QAC1B,OAAO,CAAC,CAAC;IACX,CAAC;IACD,OAAO,CAAC,CAAC;AACX,CAAC,CAAC;AAEF,yCAAyC;AACzC,MAAM,CAAC,MAAM,SAAS,GAAG,CAAC,CAAS,EAAE,CAAS,EAAU,EAAE,CACxD,aAAa,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAEnC,oDAAoD;AACpD,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,CAAS,EAAE,CAAS,EAAW,EAAE,CACnE,aAAa,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC"}
@@ -0,0 +1,152 @@
1
+ import type { TransactionAction } from "../schema/types.js";
2
+ import type { SyncId } from "./sync-id.js";
3
+ /**
4
+ * Sync action types emitted by the server.
5
+ * Includes mutation actions plus sync-group / coverage signals.
6
+ */
7
+ export type SyncActionType = TransactionAction | "C" | "G" | "S";
8
+ /**
9
+ * A sync action represents a single change from the server
10
+ */
11
+ export interface SyncAction {
12
+ /** Monotonically increasing sync ID (string-encoded for BigInt safety) */
13
+ id: SyncId;
14
+ /** Name of the model that changed */
15
+ modelName: string;
16
+ /** ID of the model instance */
17
+ modelId: string;
18
+ /** Type of change */
19
+ action: SyncActionType;
20
+ /** Full or partial data for the model */
21
+ data: Record<string, unknown>;
22
+ /** Group ID for multi-tenancy filtering */
23
+ groupId?: string;
24
+ /** Group IDs for multi-tenancy filtering */
25
+ groups?: string[];
26
+ /** Client transaction ID if this was a client mutation */
27
+ clientTxId?: string;
28
+ /** Client ID that originated the change */
29
+ clientId?: string;
30
+ /** Timestamp when the change was created */
31
+ createdAt?: Date;
32
+ /** Class name marker (Done payloads include __class) */
33
+ __class?: "SyncAction";
34
+ }
35
+ /**
36
+ * A packet of delta changes from the server
37
+ */
38
+ export interface DeltaPacket {
39
+ /** Highest sync ID in this packet */
40
+ lastSyncId: SyncId;
41
+ /** Array of sync actions */
42
+ actions: SyncAction[];
43
+ /** Whether there are more deltas available */
44
+ hasMore?: boolean;
45
+ }
46
+ /**
47
+ * Bootstrap metadata for initial sync
48
+ */
49
+ export interface BootstrapMetadata {
50
+ /** Latest sync ID at bootstrap completion (may be omitted for partial/batch payloads) */
51
+ lastSyncId?: SyncId;
52
+ /** Groups returned by the server for this user */
53
+ subscribedSyncGroups: string[];
54
+ /** Count of returned models (by model name) */
55
+ returnedModelsCount?: Record<string, number>;
56
+ /** Schema hash returned by server (if provided) */
57
+ schemaHash?: string;
58
+ /** Server database version (if provided) */
59
+ databaseVersion?: number;
60
+ /** Additional metadata fields (if provided) */
61
+ raw?: Record<string, unknown>;
62
+ }
63
+ /**
64
+ * A row of model data during bootstrap
65
+ */
66
+ export interface ModelRow {
67
+ /** Model name */
68
+ modelName: string;
69
+ /** Row data */
70
+ data: Record<string, unknown>;
71
+ }
72
+ /**
73
+ * State of the sync client
74
+ */
75
+ export type SyncClientState = "disconnected" | "connecting" | "bootstrapping" | "syncing" | "error";
76
+ /**
77
+ * Connection state for transport
78
+ */
79
+ export type ConnectionState = "disconnected" | "connecting" | "connected" | "reconnecting" | "error";
80
+ /**
81
+ * Options for bootstrap operation
82
+ */
83
+ export interface BootstrapOptions {
84
+ /** Bootstrap type (full or partial) */
85
+ type?: "full" | "partial";
86
+ /** Models to include in bootstrap */
87
+ onlyModels?: string[];
88
+ /** Schema hash for validation/caching (optional) */
89
+ schemaHash?: string;
90
+ /** First sync ID for partial bootstrap (optional) */
91
+ firstSyncId?: SyncId;
92
+ /** Sync groups to bootstrap (optional) */
93
+ syncGroups?: string[];
94
+ /** Skip sync packets during partial bootstrap (optional) */
95
+ noSyncPackets?: boolean;
96
+ /** Enable CDN caching (optional) */
97
+ useCFCaching?: boolean;
98
+ /** Disable cache (optional) */
99
+ noCache?: boolean;
100
+ /** Models hash for cache validation (optional) */
101
+ modelsHash?: string;
102
+ }
103
+ /**
104
+ * Options for delta subscription
105
+ */
106
+ export interface SubscribeOptions {
107
+ /** Start after this sync ID */
108
+ afterSyncId: SyncId;
109
+ /** Groups to subscribe to */
110
+ groups: string[];
111
+ }
112
+ /**
113
+ * Delta subscription handle
114
+ */
115
+ export interface DeltaSubscription {
116
+ /** Async iterator of delta packets */
117
+ [Symbol.asyncIterator](): AsyncIterator<DeltaPacket>;
118
+ /** Unsubscribe and close connection */
119
+ unsubscribe(): void;
120
+ }
121
+ /**
122
+ * Batch load request for lazy-loaded models
123
+ */
124
+ export type BatchRequest = {
125
+ /** Model name */
126
+ modelName: string;
127
+ /** Indexed key to filter by */
128
+ indexedKey: string;
129
+ /** Indexed key value */
130
+ keyValue: string;
131
+ /** Disallow groupId for indexed requests */
132
+ groupId?: never;
133
+ } | {
134
+ /** Model name */
135
+ modelName: string;
136
+ /** Sync group ID */
137
+ groupId: string;
138
+ /** Disallow indexed key filters for group requests */
139
+ indexedKey?: never;
140
+ /** Disallow indexed key values for group requests */
141
+ keyValue?: never;
142
+ };
143
+ /**
144
+ * Batch load options for partial hydration
145
+ */
146
+ export interface BatchLoadOptions {
147
+ /** First sync ID from full bootstrap */
148
+ firstSyncId: SyncId;
149
+ /** Batch requests to load */
150
+ requests: BatchRequest[];
151
+ }
152
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/sync/types.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/sync/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,16 @@
1
+ export interface ArchiveState extends Record<string, unknown> {
2
+ archivedAt?: number | null;
3
+ }
4
+ export interface ArchiveTransactionOptions {
5
+ original?: Record<string, unknown>;
6
+ archivedAt?: number;
7
+ }
8
+ export interface UnarchiveTransactionOptions {
9
+ original?: Record<string, unknown>;
10
+ }
11
+ export declare const readArchivedAt: (record: Record<string, unknown> | ArchiveState | undefined) => number | undefined;
12
+ export declare const captureArchiveState: (record: Record<string, unknown> | ArchiveState | undefined) => ArchiveState;
13
+ export declare const createArchivePayload: (archivedAt?: number) => ArchiveState;
14
+ export declare const createUnarchivePatch: () => ArchiveState;
15
+ export declare const createUnarchivePayload: () => Record<string, unknown>;
16
+ //# sourceMappingURL=archive.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"archive.d.ts","sourceRoot":"","sources":["../../src/transaction/archive.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,YAAa,SAAQ,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAC3D,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5B;AAED,MAAM,WAAW,yBAAyB;IACxC,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACnC,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,2BAA2B;IAC1C,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACpC;AAID,eAAO,MAAM,cAAc,GACzB,QAAQ,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,YAAY,GAAG,SAAS,KACzD,MAAM,GAAG,SAgBX,CAAC;AAEF,eAAO,MAAM,mBAAmB,GAC9B,QAAQ,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,YAAY,GAAG,SAAS,KACzD,YAAgE,CAAC;AAEpE,eAAO,MAAM,oBAAoB,GAAI,aAAa,MAAM,KAAG,YAEzD,CAAC;AAEH,eAAO,MAAM,oBAAoB,QAAO,YAAsC,CAAC;AAE/E,eAAO,MAAM,sBAAsB,QAAO,MAAM,CAAC,MAAM,EAAE,OAAO,CAAS,CAAC"}
@@ -0,0 +1,23 @@
1
+ const ISO_ARCHIVE_TIMESTAMP_REGEX = /^\d{4}-\d{2}-\d{2}(?:[T ][0-9:.+Z-]+)?$/;
2
+ export const readArchivedAt = (record) => {
3
+ const archivedAt = record?.archivedAt;
4
+ if (typeof archivedAt === "number") {
5
+ return archivedAt;
6
+ }
7
+ if (typeof archivedAt === "string") {
8
+ const isIsoTimestamp = ISO_ARCHIVE_TIMESTAMP_REGEX.test(archivedAt);
9
+ if (!isIsoTimestamp) {
10
+ return undefined;
11
+ }
12
+ const parsed = Date.parse(archivedAt);
13
+ return Number.isNaN(parsed) ? undefined : parsed;
14
+ }
15
+ return undefined;
16
+ };
17
+ export const captureArchiveState = (record) => ({ archivedAt: readArchivedAt(record) ?? null });
18
+ export const createArchivePayload = (archivedAt) => ({
19
+ archivedAt: archivedAt ?? Date.now(),
20
+ });
21
+ export const createUnarchivePatch = () => ({ archivedAt: null });
22
+ export const createUnarchivePayload = () => ({});
23
+ //# sourceMappingURL=archive.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"archive.js","sourceRoot":"","sources":["../../src/transaction/archive.ts"],"names":[],"mappings":"AAaA,MAAM,2BAA2B,GAAG,yCAAyC,CAAC;AAE9E,MAAM,CAAC,MAAM,cAAc,GAAG,CAC5B,MAA0D,EACtC,EAAE;IACtB,MAAM,UAAU,GAAG,MAAM,EAAE,UAAU,CAAC;IACtC,IAAI,OAAO,UAAU,KAAK,QAAQ,EAAE,CAAC;QACnC,OAAO,UAAU,CAAC;IACpB,CAAC;IAED,IAAI,OAAO,UAAU,KAAK,QAAQ,EAAE,CAAC;QACnC,MAAM,cAAc,GAAG,2BAA2B,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QACpE,IAAI,CAAC,cAAc,EAAE,CAAC;YACpB,OAAO,SAAS,CAAC;QACnB,CAAC;QACD,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;QACtC,OAAO,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC;IACnD,CAAC;IAED,OAAO,SAAS,CAAC;AACnB,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,mBAAmB,GAAG,CACjC,MAA0D,EAC5C,EAAE,CAAC,CAAC,EAAE,UAAU,EAAE,cAAc,CAAC,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC;AAEpE,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAAC,UAAmB,EAAgB,EAAE,CAAC,CAAC;IAC1E,UAAU,EAAE,UAAU,IAAI,IAAI,CAAC,GAAG,EAAE;CACrC,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,oBAAoB,GAAG,GAAiB,EAAE,CAAC,CAAC,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC,CAAC;AAE/E,MAAM,CAAC,MAAM,sBAAsB,GAAG,GAA4B,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC"}
@@ -0,0 +1,31 @@
1
+ import type { ArchiveTransactionOptions, UnarchiveTransactionOptions } from "./archive.js";
2
+ import type { Transaction, TransactionBatch } from "./types.js";
3
+ /**
4
+ * Creates a transaction batch from an array of transactions
5
+ */
6
+ export declare const createTransactionBatch: (transactions: Transaction[]) => TransactionBatch;
7
+ /**
8
+ * Creates an INSERT transaction for a new model instance
9
+ */
10
+ export declare const createInsertTransaction: (clientId: string, modelName: string, modelId: string, data: Record<string, unknown>) => Transaction;
11
+ /**
12
+ * Creates an UPDATE transaction for an existing model instance
13
+ */
14
+ export declare const createUpdateTransaction: (clientId: string, modelName: string, modelId: string, changes: Record<string, unknown>, original: Record<string, unknown>) => Transaction;
15
+ /**
16
+ * Creates a DELETE transaction for removing a model instance
17
+ */
18
+ export declare const createDeleteTransaction: (clientId: string, modelName: string, modelId: string, original: Record<string, unknown>) => Transaction;
19
+ /**
20
+ * Creates an ARCHIVE transaction for soft-deleting a model instance
21
+ */
22
+ export declare const createArchiveTransaction: (clientId: string, modelName: string, modelId: string, options?: ArchiveTransactionOptions) => Transaction;
23
+ /**
24
+ * Creates an UNARCHIVE transaction for restoring a soft-deleted model instance
25
+ */
26
+ export declare const createUnarchiveTransaction: (clientId: string, modelName: string, modelId: string, options?: UnarchiveTransactionOptions) => Transaction;
27
+ /**
28
+ * Creates an undo transaction for a given transaction.
29
+ */
30
+ export declare const createUndoTransaction: (tx: Transaction, clientId?: string) => Transaction | null;
31
+ //# sourceMappingURL=create.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"create.d.ts","sourceRoot":"","sources":["../../src/transaction/create.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,yBAAyB,EACzB,2BAA2B,EAC5B,MAAM,cAAc,CAAC;AAQtB,OAAO,KAAK,EAEV,WAAW,EACX,gBAAgB,EACjB,MAAM,YAAY,CAAC;AAyBpB;;GAEG;AACH,eAAO,MAAM,sBAAsB,GACjC,cAAc,WAAW,EAAE,KAC1B,gBAID,CAAC;AAEH;;GAEG;AACH,eAAO,MAAM,uBAAuB,GAClC,UAAU,MAAM,EAChB,WAAW,MAAM,EACjB,SAAS,MAAM,EACf,MAAM,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAC5B,WAOC,CAAC;AAEL;;GAEG;AACH,eAAO,MAAM,uBAAuB,GAClC,UAAU,MAAM,EAChB,WAAW,MAAM,EACjB,SAAS,MAAM,EACf,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAChC,UAAU,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAChC,WAQC,CAAC;AAEL;;GAEG;AACH,eAAO,MAAM,uBAAuB,GAClC,UAAU,MAAM,EAChB,WAAW,MAAM,EACjB,SAAS,MAAM,EACf,UAAU,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAChC,WAQC,CAAC;AAEL;;GAEG;AACH,eAAO,MAAM,wBAAwB,GACnC,UAAU,MAAM,EAChB,WAAW,MAAM,EACjB,SAAS,MAAM,EACf,UAAS,yBAA8B,KACtC,WAQC,CAAC;AAEL;;GAEG;AACH,eAAO,MAAM,0BAA0B,GACrC,UAAU,MAAM,EAChB,WAAW,MAAM,EACjB,SAAS,MAAM,EACf,UAAS,2BAAgC,KACxC,WAQC,CAAC;AAEL;;GAEG;AACH,eAAO,MAAM,qBAAqB,GAChC,IAAI,WAAW,EACf,WAAU,MAAoB,KAC7B,WAAW,GAAG,IAgDhB,CAAC"}