@interocitor/core 0.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.
- package/LICENSE +21 -0
- package/README.md +178 -0
- package/dist/adapters/cloudflare.d.ts +72 -0
- package/dist/adapters/cloudflare.d.ts.map +1 -0
- package/dist/adapters/cloudflare.js +227 -0
- package/dist/adapters/cloudflare.js.map +1 -0
- package/dist/adapters/google-drive.d.ts +64 -0
- package/dist/adapters/google-drive.d.ts.map +1 -0
- package/dist/adapters/google-drive.js +340 -0
- package/dist/adapters/google-drive.js.map +1 -0
- package/dist/adapters/memory.d.ts +45 -0
- package/dist/adapters/memory.d.ts.map +1 -0
- package/dist/adapters/memory.js +129 -0
- package/dist/adapters/memory.js.map +1 -0
- package/dist/adapters/webdav.d.ts +59 -0
- package/dist/adapters/webdav.d.ts.map +1 -0
- package/dist/adapters/webdav.js +247 -0
- package/dist/adapters/webdav.js.map +1 -0
- package/dist/core/codec.d.ts +20 -0
- package/dist/core/codec.d.ts.map +1 -0
- package/dist/core/codec.js +66 -0
- package/dist/core/codec.js.map +1 -0
- package/dist/core/compaction.d.ts +37 -0
- package/dist/core/compaction.d.ts.map +1 -0
- package/dist/core/compaction.js +134 -0
- package/dist/core/compaction.js.map +1 -0
- package/dist/core/crdt.d.ts +33 -0
- package/dist/core/crdt.d.ts.map +1 -0
- package/dist/core/crdt.js +188 -0
- package/dist/core/crdt.js.map +1 -0
- package/dist/core/flush.d.ts +9 -0
- package/dist/core/flush.d.ts.map +1 -0
- package/dist/core/flush.js +41 -0
- package/dist/core/flush.js.map +1 -0
- package/dist/core/hlc.d.ts +25 -0
- package/dist/core/hlc.d.ts.map +1 -0
- package/dist/core/hlc.js +76 -0
- package/dist/core/hlc.js.map +1 -0
- package/dist/core/internals.d.ts +25 -0
- package/dist/core/internals.d.ts.map +1 -0
- package/dist/core/internals.js +54 -0
- package/dist/core/internals.js.map +1 -0
- package/dist/core/manifest.d.ts +31 -0
- package/dist/core/manifest.d.ts.map +1 -0
- package/dist/core/manifest.js +111 -0
- package/dist/core/manifest.js.map +1 -0
- package/dist/core/pull.d.ts +26 -0
- package/dist/core/pull.d.ts.map +1 -0
- package/dist/core/pull.js +98 -0
- package/dist/core/pull.js.map +1 -0
- package/dist/core/row-id.d.ts +12 -0
- package/dist/core/row-id.d.ts.map +1 -0
- package/dist/core/row-id.js +12 -0
- package/dist/core/row-id.js.map +1 -0
- package/dist/core/schema-types.d.ts +13 -0
- package/dist/core/schema-types.d.ts.map +1 -0
- package/dist/core/schema-types.js +18 -0
- package/dist/core/schema-types.js.map +1 -0
- package/dist/core/schema-types.type-test.d.ts +2 -0
- package/dist/core/schema-types.type-test.d.ts.map +1 -0
- package/dist/core/schema-types.type-test.js +149 -0
- package/dist/core/schema-types.type-test.js.map +1 -0
- package/dist/core/sync-engine.d.ts +158 -0
- package/dist/core/sync-engine.d.ts.map +1 -0
- package/dist/core/sync-engine.js +714 -0
- package/dist/core/sync-engine.js.map +1 -0
- package/dist/core/table.d.ts +60 -0
- package/dist/core/table.d.ts.map +1 -0
- package/dist/core/table.js +106 -0
- package/dist/core/table.js.map +1 -0
- package/dist/core/types.d.ts +478 -0
- package/dist/core/types.d.ts.map +1 -0
- package/dist/core/types.js +7 -0
- package/dist/core/types.js.map +1 -0
- package/dist/crypto/encryption.d.ts +57 -0
- package/dist/crypto/encryption.d.ts.map +1 -0
- package/dist/crypto/encryption.js +195 -0
- package/dist/crypto/encryption.js.map +1 -0
- package/dist/crypto/keys.d.ts +48 -0
- package/dist/crypto/keys.d.ts.map +1 -0
- package/dist/crypto/keys.js +55 -0
- package/dist/crypto/keys.js.map +1 -0
- package/dist/handshake/channel.d.ts +117 -0
- package/dist/handshake/channel.d.ts.map +1 -0
- package/dist/handshake/channel.js +246 -0
- package/dist/handshake/channel.js.map +1 -0
- package/dist/handshake/index.d.ts +213 -0
- package/dist/handshake/index.d.ts.map +1 -0
- package/dist/handshake/index.js +182 -0
- package/dist/handshake/index.js.map +1 -0
- package/dist/handshake/qr.d.ts +100 -0
- package/dist/handshake/qr.d.ts.map +1 -0
- package/dist/handshake/qr.js +103 -0
- package/dist/handshake/qr.js.map +1 -0
- package/dist/index.d.ts +46 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +46 -0
- package/dist/index.js.map +1 -0
- package/dist/storage/credential-store.d.ts +99 -0
- package/dist/storage/credential-store.d.ts.map +1 -0
- package/dist/storage/credential-store.js +309 -0
- package/dist/storage/credential-store.js.map +1 -0
- package/dist/storage/local-store.d.ts +56 -0
- package/dist/storage/local-store.d.ts.map +1 -0
- package/dist/storage/local-store.js +411 -0
- package/dist/storage/local-store.js.map +1 -0
- package/package.json +68 -0
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CRDT Merge Engine — configurable per-column merge strategy.
|
|
3
|
+
*
|
|
4
|
+
* Each column in each row carries its own HLC.
|
|
5
|
+
* The merge strategy determines which value wins on conflict:
|
|
6
|
+
*
|
|
7
|
+
* - `'remote-wins'` — Remote always overwrites local. (Default)
|
|
8
|
+
* - `'lww'` — Last-Writer-Wins. Highest HLC wins.
|
|
9
|
+
* - `'local-wins'` — Keep local value when both exist.
|
|
10
|
+
* - custom function — `(local, remote, ctx) => ColumnEntry`
|
|
11
|
+
*
|
|
12
|
+
* Deletes are soft (tombstone with HLC) and always use LWW.
|
|
13
|
+
*/
|
|
14
|
+
import { hlcCompareStr } from "./hlc.js";
|
|
15
|
+
/** Reserved keys that are not user columns */
|
|
16
|
+
const META_KEYS = new Set(['_table', '_rowId', '_deleted', '_deletedHlc', '_schemaVersion']);
|
|
17
|
+
/**
|
|
18
|
+
* Resolve the merge strategy for a specific column.
|
|
19
|
+
*
|
|
20
|
+
* Resolution order (first defined wins):
|
|
21
|
+
* field-level → table-level → database-level → 'lww'
|
|
22
|
+
*/
|
|
23
|
+
function resolveStrategy(schema, table, field) {
|
|
24
|
+
const tableDef = schema?.tables[table];
|
|
25
|
+
if (tableDef?.merge) {
|
|
26
|
+
const m = tableDef.merge;
|
|
27
|
+
if (typeof m === 'object' && 'fields' in m) {
|
|
28
|
+
// TableMergeConfig
|
|
29
|
+
const config = m;
|
|
30
|
+
if (config.fields?.[field])
|
|
31
|
+
return config.fields[field];
|
|
32
|
+
if (config.strategy)
|
|
33
|
+
return config.strategy;
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
// bare MergeStrategy (string or function) on the table
|
|
37
|
+
return m;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
// No schema at all → LWW (backwards compat for raw applyOp callers).
|
|
41
|
+
// Schema present but no mergeStrategy → remote-wins (sensible default).
|
|
42
|
+
if (!schema)
|
|
43
|
+
return 'lww';
|
|
44
|
+
return schema.mergeStrategy ?? 'remote-wins';
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Decide which column entry wins given a strategy.
|
|
48
|
+
*
|
|
49
|
+
* `local` may be undefined (new column). In that case, remote always wins
|
|
50
|
+
* regardless of strategy — there's no conflict.
|
|
51
|
+
*/
|
|
52
|
+
function mergeColumn(local, remote, strategy, table, rowId, field) {
|
|
53
|
+
// No local value → accept remote unconditionally
|
|
54
|
+
if (!local || !local.hlc)
|
|
55
|
+
return remote;
|
|
56
|
+
if (typeof strategy === 'function') {
|
|
57
|
+
const result = strategy(local, remote, { table, rowId, field });
|
|
58
|
+
// Only count as changed if the result differs from local
|
|
59
|
+
return result.hlc !== local.hlc || result.value !== local.value ? result : null;
|
|
60
|
+
}
|
|
61
|
+
switch (strategy) {
|
|
62
|
+
case 'remote-wins':
|
|
63
|
+
return remote;
|
|
64
|
+
case 'local-wins':
|
|
65
|
+
// Only accept remote if it's strictly newer (no conflict — local
|
|
66
|
+
// hasn't written this column yet at this HLC).
|
|
67
|
+
// When both have values, local keeps its value.
|
|
68
|
+
return null;
|
|
69
|
+
default:
|
|
70
|
+
return hlcCompareStr(remote.hlc, local.hlc) > 0 ? remote : null;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Apply a single op to the in-memory state.
|
|
75
|
+
* Returns the affected row (mutated in place) or null if no change.
|
|
76
|
+
*/
|
|
77
|
+
export function applyOp(tables, op, schemaVersion, schema) {
|
|
78
|
+
if (!tables[op.table]) {
|
|
79
|
+
tables[op.table] = {};
|
|
80
|
+
}
|
|
81
|
+
const table = tables[op.table];
|
|
82
|
+
if (op.type === 'delete') {
|
|
83
|
+
const existing = table[op.rowId];
|
|
84
|
+
if (existing) {
|
|
85
|
+
// Only apply delete if its HLC is newer than all column HLCs
|
|
86
|
+
if (existing._deletedHlc && hlcCompareStr(op.hlc, existing._deletedHlc) <= 0) {
|
|
87
|
+
return null; // stale delete
|
|
88
|
+
}
|
|
89
|
+
// Check if any column has a newer HLC than this delete
|
|
90
|
+
const hasNewerColumn = Object.entries(existing).some(([key, val]) => {
|
|
91
|
+
if (META_KEYS.has(key))
|
|
92
|
+
return false;
|
|
93
|
+
const entry = val;
|
|
94
|
+
return entry?.hlc && hlcCompareStr(entry.hlc, op.hlc) > 0;
|
|
95
|
+
});
|
|
96
|
+
if (hasNewerColumn)
|
|
97
|
+
return null;
|
|
98
|
+
existing._deleted = true;
|
|
99
|
+
existing._deletedHlc = op.hlc;
|
|
100
|
+
return existing;
|
|
101
|
+
}
|
|
102
|
+
// Tombstone for a row we haven't seen — create it
|
|
103
|
+
const row = {
|
|
104
|
+
_table: op.table,
|
|
105
|
+
_rowId: op.rowId,
|
|
106
|
+
_deleted: true,
|
|
107
|
+
_deletedHlc: op.hlc,
|
|
108
|
+
_schemaVersion: schemaVersion,
|
|
109
|
+
};
|
|
110
|
+
table[op.rowId] = row;
|
|
111
|
+
return row;
|
|
112
|
+
}
|
|
113
|
+
// Upsert
|
|
114
|
+
let row = table[op.rowId];
|
|
115
|
+
let changed = false;
|
|
116
|
+
if (!row) {
|
|
117
|
+
row = {
|
|
118
|
+
_table: op.table,
|
|
119
|
+
_rowId: op.rowId,
|
|
120
|
+
_deleted: false,
|
|
121
|
+
_schemaVersion: schemaVersion,
|
|
122
|
+
};
|
|
123
|
+
table[op.rowId] = row;
|
|
124
|
+
changed = true;
|
|
125
|
+
}
|
|
126
|
+
for (const [col, entry] of Object.entries(op.columns)) {
|
|
127
|
+
const existing = row[col];
|
|
128
|
+
const strategy = resolveStrategy(schema, op.table, col);
|
|
129
|
+
const winner = mergeColumn(existing, entry, strategy, op.table, op.rowId, col);
|
|
130
|
+
if (winner) {
|
|
131
|
+
row[col] = winner;
|
|
132
|
+
changed = true;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
// An upsert that's newer than a delete revives the row
|
|
136
|
+
if (row._deleted && row._deletedHlc) {
|
|
137
|
+
const newestOpHlc = Object.values(op.columns).reduce((max, entry) => {
|
|
138
|
+
return !max || hlcCompareStr(entry.hlc, max) > 0 ? entry.hlc : max;
|
|
139
|
+
}, '');
|
|
140
|
+
if (newestOpHlc && hlcCompareStr(newestOpHlc, row._deletedHlc) > 0) {
|
|
141
|
+
row._deleted = false;
|
|
142
|
+
changed = true;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return changed ? row : null;
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Apply a full change entry (potentially multiple ops).
|
|
149
|
+
* Returns list of affected rows.
|
|
150
|
+
*/
|
|
151
|
+
export function applyChangeEntry(tables, entry, schemaVersion, schema) {
|
|
152
|
+
const affected = [];
|
|
153
|
+
for (const op of entry.ops) {
|
|
154
|
+
const row = applyOp(tables, op, schemaVersion, schema);
|
|
155
|
+
if (row)
|
|
156
|
+
affected.push(row);
|
|
157
|
+
}
|
|
158
|
+
return affected;
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Read a column value from a row, unwrapping the ColumnEntry.
|
|
162
|
+
*/
|
|
163
|
+
export function readColumn(row, column) {
|
|
164
|
+
const entry = row[column];
|
|
165
|
+
if (entry && typeof entry === 'object' && 'value' in entry && 'hlc' in entry) {
|
|
166
|
+
return entry.value;
|
|
167
|
+
}
|
|
168
|
+
return undefined;
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Build a plain object from a row (strip HLC metadata).
|
|
172
|
+
*/
|
|
173
|
+
export function rowToPlain(row) {
|
|
174
|
+
const result = {
|
|
175
|
+
_table: row._table,
|
|
176
|
+
_rowId: row._rowId,
|
|
177
|
+
_deleted: row._deleted,
|
|
178
|
+
};
|
|
179
|
+
for (const [key, val] of Object.entries(row)) {
|
|
180
|
+
if (META_KEYS.has(key))
|
|
181
|
+
continue;
|
|
182
|
+
if (val && typeof val === 'object' && 'value' in val && 'hlc' in val) {
|
|
183
|
+
result[key] = val.value;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
return result;
|
|
187
|
+
}
|
|
188
|
+
//# sourceMappingURL=crdt.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"crdt.js","sourceRoot":"","sources":["../../src/core/crdt.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Flush — push queued local outbox entries to remote cloud.
|
|
3
|
+
*
|
|
4
|
+
* Extracted from SyncEngine. Not part of the public API.
|
|
5
|
+
*/
|
|
6
|
+
import type { StorageAdapter, ChangeEntry } from './types.ts';
|
|
7
|
+
import type { CodecState } from './codec.ts';
|
|
8
|
+
export declare function flushToAdapter(adapter: StorageAdapter, remotePath: string, entries: ChangeEntry[], isPrimary: boolean, codecState: CodecState, deviceId: string): Promise<void>;
|
|
9
|
+
//# sourceMappingURL=flush.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"flush.d.ts","sourceRoot":"","sources":["../../src/core/flush.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EACV,cAAc,EACd,WAAW,EAEZ,MAAM,YAAY,CAAC;AAIpB,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAG7C,wBAAsB,cAAc,CAClC,OAAO,EAAE,cAAc,EACvB,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,WAAW,EAAE,EACtB,SAAS,EAAE,OAAO,EAClB,UAAU,EAAE,UAAU,EACtB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,IAAI,CAAC,CAsCf"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Flush — push queued local outbox entries to remote cloud.
|
|
3
|
+
*
|
|
4
|
+
* Extracted from SyncEngine. Not part of the public API.
|
|
5
|
+
*/
|
|
6
|
+
import { hlcCompareStr } from "./hlc.js";
|
|
7
|
+
import { paths, textEncoder, textDecoder } from "./internals.js";
|
|
8
|
+
import { encodeChangePayload } from "./codec.js";
|
|
9
|
+
import { upsertDeviceMetadata } from "./manifest.js";
|
|
10
|
+
export async function flushToAdapter(adapter, remotePath, entries, isPrimary, codecState, deviceId) {
|
|
11
|
+
const p = paths(remotePath);
|
|
12
|
+
await adapter.ensureFolder(p.changesFolder);
|
|
13
|
+
let lastWrittenHlc = '';
|
|
14
|
+
for (const entry of entries) {
|
|
15
|
+
const fileName = `${entry.hlc}-${entry.id}.json`;
|
|
16
|
+
const payload = await encodeChangePayload(codecState, entry);
|
|
17
|
+
await adapter.writeFile(p.changeFile(fileName), textEncoder.encode(payload));
|
|
18
|
+
if (!lastWrittenHlc || hlcCompareStr(entry.hlc, lastWrittenHlc) > 0) {
|
|
19
|
+
lastWrittenHlc = entry.hlc;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
// Update global head — monotonic HLC hint for fast poll skipping.
|
|
23
|
+
const readHeadIfExists = async () => {
|
|
24
|
+
try {
|
|
25
|
+
const data = await adapter.readFile(p.changesHead);
|
|
26
|
+
return JSON.parse(textDecoder.decode(data));
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
const priorHead = await readHeadIfExists();
|
|
33
|
+
const bestHlc = (priorHead?.latestHlc && hlcCompareStr(priorHead.latestHlc, lastWrittenHlc) > 0)
|
|
34
|
+
? priorHead.latestHlc
|
|
35
|
+
: lastWrittenHlc;
|
|
36
|
+
await adapter.writeFile(p.changesHead, textEncoder.encode(JSON.stringify({ latestHlc: bestHlc }, null, 2)));
|
|
37
|
+
if (isPrimary) {
|
|
38
|
+
await upsertDeviceMetadata(adapter, remotePath, deviceId);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=flush.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"flush.js","sourceRoot":"","sources":["../../src/core/flush.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAOH,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,KAAK,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AACjE,OAAO,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAEjD,OAAO,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC;AAErD,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,OAAuB,EACvB,UAAkB,EAClB,OAAsB,EACtB,SAAkB,EAClB,UAAsB,EACtB,QAAgB;IAEhB,MAAM,CAAC,GAAG,KAAK,CAAC,UAAU,CAAC,CAAC;IAC5B,MAAM,OAAO,CAAC,YAAY,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC;IAE5C,IAAI,cAAc,GAAG,EAAE,CAAC;IAExB,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAC5B,MAAM,QAAQ,GAAG,GAAG,KAAK,CAAC,GAAG,IAAI,KAAK,CAAC,EAAE,OAAO,CAAC;QACjD,MAAM,OAAO,GAAG,MAAM,mBAAmB,CAAC,UAAU,EAAE,KAAK,CAAC,CAAC;QAC7D,MAAM,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC;QAE7E,IAAI,CAAC,cAAc,IAAI,aAAa,CAAC,KAAK,CAAC,GAAG,EAAE,cAAc,CAAC,GAAG,CAAC,EAAE,CAAC;YACpE,cAAc,GAAG,KAAK,CAAC,GAAG,CAAC;QAC7B,CAAC;IACH,CAAC;IAED,kEAAkE;IAClE,MAAM,gBAAgB,GAAG,KAAK,IAAiC,EAAE;QAC/D,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,MAAM,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC;YACnD,OAAO,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,MAAM,CAAC,IAAI,CAAC,CAAgB,CAAC;QAC7D,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC,CAAC;IAEF,MAAM,SAAS,GAAG,MAAM,gBAAgB,EAAE,CAAC;IAC3C,MAAM,OAAO,GAAG,CAAC,SAAS,EAAE,SAAS,IAAI,aAAa,CAAC,SAAS,CAAC,SAAS,EAAE,cAAc,CAAC,GAAG,CAAC,CAAC;QAC9F,CAAC,CAAC,SAAS,CAAC,SAAS;QACrB,CAAC,CAAC,cAAc,CAAC;IACnB,MAAM,OAAO,CAAC,SAAS,CACrB,CAAC,CAAC,WAAW,EACb,WAAW,CAAC,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,SAAS,EAAE,OAAO,EAAwB,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAC1F,CAAC;IAEF,IAAI,SAAS,EAAE,CAAC;QACd,MAAM,oBAAoB,CAAC,OAAO,EAAE,UAAU,EAAE,QAAQ,CAAC,CAAC;IAC5D,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hybrid Logical Clock
|
|
3
|
+
*
|
|
4
|
+
* Combines wall-clock time with a logical counter to produce
|
|
5
|
+
* a total order across distributed devices without coordination.
|
|
6
|
+
*
|
|
7
|
+
* Format when serialized: "{ts}-{counter:04x}-{nodeId}"
|
|
8
|
+
* Example: "1711785600000-0000-dev_x1"
|
|
9
|
+
*/
|
|
10
|
+
import type { HLC } from './types.ts';
|
|
11
|
+
export declare const HLC_MAX_FUTURE_SKEW_MS: number;
|
|
12
|
+
export declare function hlcInit(nodeId: string): HLC;
|
|
13
|
+
/** Tick the local clock forward for a new local event. */
|
|
14
|
+
export declare function hlcNow(local: HLC): HLC;
|
|
15
|
+
/** Merge a remote HLC into the local clock (called on receive). */
|
|
16
|
+
export declare function hlcReceive(local: HLC, remote: HLC): HLC;
|
|
17
|
+
/** Total ordering: -1 if a < b, 0 if equal, 1 if a > b */
|
|
18
|
+
export declare function hlcCompare(a: HLC, b: HLC): number;
|
|
19
|
+
/** Compare two serialized HLC strings without parsing (fast path). */
|
|
20
|
+
export declare function hlcCompareStr(a: string, b: string): number;
|
|
21
|
+
/** Serialize HLC to a string that sorts lexicographically. */
|
|
22
|
+
export declare function hlcSerialize(hlc: HLC): string;
|
|
23
|
+
/** Parse a serialized HLC string back to an HLC object. */
|
|
24
|
+
export declare function hlcParse(s: string): HLC;
|
|
25
|
+
//# sourceMappingURL=hlc.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hlc.d.ts","sourceRoot":"","sources":["../../src/core/hlc.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,YAAY,CAAC;AAGtC,eAAO,MAAM,sBAAsB,QAAgB,CAAC;AAEpD,wBAAgB,OAAO,CAAC,MAAM,EAAE,MAAM,GAAG,GAAG,CAE3C;AAED,0DAA0D;AAC1D,wBAAgB,MAAM,CAAC,KAAK,EAAE,GAAG,GAAG,GAAG,CAMtC;AAED,mEAAmE;AACnE,wBAAgB,UAAU,CAAC,KAAK,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,GAAG,GAAG,CAiBvD;AAED,0DAA0D;AAC1D,wBAAgB,UAAU,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,GAAG,GAAG,MAAM,CAMjD;AAED,sEAAsE;AACtE,wBAAgB,aAAa,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAE1D;AAED,8DAA8D;AAC9D,wBAAgB,YAAY,CAAC,GAAG,EAAE,GAAG,GAAG,MAAM,CAK7C;AAED,2DAA2D;AAC3D,wBAAgB,QAAQ,CAAC,CAAC,EAAE,MAAM,GAAG,GAAG,CAQvC"}
|
package/dist/core/hlc.js
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hybrid Logical Clock
|
|
3
|
+
*
|
|
4
|
+
* Combines wall-clock time with a logical counter to produce
|
|
5
|
+
* a total order across distributed devices without coordination.
|
|
6
|
+
*
|
|
7
|
+
* Format when serialized: "{ts}-{counter:04x}-{nodeId}"
|
|
8
|
+
* Example: "1711785600000-0000-dev_x1"
|
|
9
|
+
*/
|
|
10
|
+
// Guard against poisoned/misconfigured peers that report far-future clocks.
|
|
11
|
+
export const HLC_MAX_FUTURE_SKEW_MS = 5 * 60 * 1000;
|
|
12
|
+
export function hlcInit(nodeId) {
|
|
13
|
+
return { ts: Date.now(), counter: 0, nodeId };
|
|
14
|
+
}
|
|
15
|
+
/** Tick the local clock forward for a new local event. */
|
|
16
|
+
export function hlcNow(local) {
|
|
17
|
+
const wall = Date.now();
|
|
18
|
+
if (wall > local.ts) {
|
|
19
|
+
return { ts: wall, counter: 0, nodeId: local.nodeId };
|
|
20
|
+
}
|
|
21
|
+
return { ts: local.ts, counter: local.counter + 1, nodeId: local.nodeId };
|
|
22
|
+
}
|
|
23
|
+
/** Merge a remote HLC into the local clock (called on receive). */
|
|
24
|
+
export function hlcReceive(local, remote) {
|
|
25
|
+
const wall = Date.now();
|
|
26
|
+
const safeRemoteTs = Math.min(remote.ts, wall + HLC_MAX_FUTURE_SKEW_MS);
|
|
27
|
+
const maxTs = Math.max(wall, local.ts, safeRemoteTs);
|
|
28
|
+
let counter;
|
|
29
|
+
if (maxTs === local.ts && maxTs === remote.ts) {
|
|
30
|
+
counter = Math.max(local.counter, remote.counter) + 1;
|
|
31
|
+
}
|
|
32
|
+
else if (maxTs === local.ts) {
|
|
33
|
+
counter = local.counter + 1;
|
|
34
|
+
}
|
|
35
|
+
else if (maxTs === safeRemoteTs) {
|
|
36
|
+
counter = remote.counter + 1;
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
counter = 0;
|
|
40
|
+
}
|
|
41
|
+
return { ts: maxTs, counter, nodeId: local.nodeId };
|
|
42
|
+
}
|
|
43
|
+
/** Total ordering: -1 if a < b, 0 if equal, 1 if a > b */
|
|
44
|
+
export function hlcCompare(a, b) {
|
|
45
|
+
if (a.ts !== b.ts)
|
|
46
|
+
return a.ts - b.ts;
|
|
47
|
+
if (a.counter !== b.counter)
|
|
48
|
+
return a.counter - b.counter;
|
|
49
|
+
if (a.nodeId < b.nodeId)
|
|
50
|
+
return -1;
|
|
51
|
+
if (a.nodeId > b.nodeId)
|
|
52
|
+
return 1;
|
|
53
|
+
return 0;
|
|
54
|
+
}
|
|
55
|
+
/** Compare two serialized HLC strings without parsing (fast path). */
|
|
56
|
+
export function hlcCompareStr(a, b) {
|
|
57
|
+
return hlcCompare(hlcParse(a), hlcParse(b));
|
|
58
|
+
}
|
|
59
|
+
/** Serialize HLC to a string that sorts lexicographically. */
|
|
60
|
+
export function hlcSerialize(hlc) {
|
|
61
|
+
// Zero-pad ts to 15 digits (covers until year 2286)
|
|
62
|
+
const ts = hlc.ts.toString().padStart(15, '0');
|
|
63
|
+
const counter = hlc.counter.toString(16).padStart(4, '0');
|
|
64
|
+
return `${ts}-${counter}-${hlc.nodeId}`;
|
|
65
|
+
}
|
|
66
|
+
/** Parse a serialized HLC string back to an HLC object. */
|
|
67
|
+
export function hlcParse(s) {
|
|
68
|
+
const firstDash = s.indexOf('-');
|
|
69
|
+
const secondDash = s.indexOf('-', firstDash + 1);
|
|
70
|
+
return {
|
|
71
|
+
ts: parseInt(s.slice(0, firstDash), 10),
|
|
72
|
+
counter: parseInt(s.slice(firstDash + 1, secondDash), 16),
|
|
73
|
+
nodeId: s.slice(secondDash + 1),
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
//# sourceMappingURL=hlc.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hlc.js","sourceRoot":"","sources":["../../src/core/hlc.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Internal helpers shared across sync-engine modules.
|
|
3
|
+
*
|
|
4
|
+
* Not part of the public API.
|
|
5
|
+
*/
|
|
6
|
+
export interface CloudPaths {
|
|
7
|
+
manifestPointer: string;
|
|
8
|
+
manifestFile: (generation: number) => string;
|
|
9
|
+
devicesFolder: string;
|
|
10
|
+
deviceFile: (deviceId: string) => string;
|
|
11
|
+
mainlineFolder: string;
|
|
12
|
+
changesFolder: string;
|
|
13
|
+
changesHead: string;
|
|
14
|
+
changeFile: (fileName: string) => string;
|
|
15
|
+
}
|
|
16
|
+
export declare function paths(root: string): CloudPaths;
|
|
17
|
+
export declare function log(level: 'debug' | 'info' | 'warn' | 'error', ...args: unknown[]): void;
|
|
18
|
+
export declare function generateId(prefix: string): string;
|
|
19
|
+
export declare function getDeviceId(override?: string): string;
|
|
20
|
+
export declare const textEncoder: TextEncoder;
|
|
21
|
+
export declare const textDecoder: TextDecoder;
|
|
22
|
+
export declare const ROW_META_KEYS: Set<string>;
|
|
23
|
+
export declare function hexFromBytes(bytes: Uint8Array): string;
|
|
24
|
+
export declare function computeContentHash(payload: unknown): Promise<string>;
|
|
25
|
+
//# sourceMappingURL=internals.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"internals.d.ts","sourceRoot":"","sources":["../../src/core/internals.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAIH,MAAM,WAAW,UAAU;IACzB,eAAe,EAAE,MAAM,CAAC;IACxB,YAAY,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,MAAM,CAAC;IAC7C,aAAa,EAAE,MAAM,CAAC;IACtB,UAAU,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,MAAM,CAAC;IACzC,cAAc,EAAE,MAAM,CAAC;IACvB,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,MAAM,CAAC;CAC1C;AAED,wBAAgB,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,CAY9C;AAMD,wBAAgB,GAAG,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,CAGxF;AAID,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAIjD;AAED,wBAAgB,WAAW,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CASrD;AAID,eAAO,MAAM,WAAW,aAAoB,CAAC;AAC7C,eAAO,MAAM,WAAW,aAAoB,CAAC;AAE7C,eAAO,MAAM,aAAa,aAA6E,CAAC;AAExG,wBAAgB,YAAY,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,CAEtD;AAED,wBAAsB,kBAAkB,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,CAI1E"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Internal helpers shared across sync-engine modules.
|
|
3
|
+
*
|
|
4
|
+
* Not part of the public API.
|
|
5
|
+
*/
|
|
6
|
+
export function paths(root) {
|
|
7
|
+
const changesFolder = `${root}/changes`;
|
|
8
|
+
return {
|
|
9
|
+
manifestPointer: `${root}/manifest.json`,
|
|
10
|
+
manifestFile: (generation) => `${root}/manifest-${generation}.json`,
|
|
11
|
+
devicesFolder: `${root}/devices`,
|
|
12
|
+
deviceFile: (deviceId) => `${root}/devices/${deviceId}.json`,
|
|
13
|
+
mainlineFolder: `${root}/mainline`,
|
|
14
|
+
changesFolder,
|
|
15
|
+
changesHead: `${changesFolder}/head.json`,
|
|
16
|
+
changeFile: (fileName) => `${changesFolder}/${fileName}`,
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
// ─── Logger ──────────────────────────────────────────────────────────
|
|
20
|
+
const LOG_PREFIX = '[interocitor]';
|
|
21
|
+
export function log(level, ...args) {
|
|
22
|
+
// eslint-disable-next-line no-console
|
|
23
|
+
console[level](LOG_PREFIX, ...args);
|
|
24
|
+
}
|
|
25
|
+
// ─── ID generation ───────────────────────────────────────────────────
|
|
26
|
+
export function generateId(prefix) {
|
|
27
|
+
const rand = crypto.getRandomValues(new Uint8Array(8));
|
|
28
|
+
const hex = Array.from(rand).map(b => b.toString(16).padStart(2, '0')).join('');
|
|
29
|
+
return `${prefix}_${hex}`;
|
|
30
|
+
}
|
|
31
|
+
export function getDeviceId(override) {
|
|
32
|
+
if (override)
|
|
33
|
+
return override;
|
|
34
|
+
const KEY = 'interocitor-device-id';
|
|
35
|
+
let id = localStorage.getItem(KEY);
|
|
36
|
+
if (!id) {
|
|
37
|
+
id = generateId('dev');
|
|
38
|
+
localStorage.setItem(KEY, id);
|
|
39
|
+
}
|
|
40
|
+
return id;
|
|
41
|
+
}
|
|
42
|
+
// ─── Encoding / Hashing ─────────────────────────────────────────────
|
|
43
|
+
export const textEncoder = new TextEncoder();
|
|
44
|
+
export const textDecoder = new TextDecoder();
|
|
45
|
+
export const ROW_META_KEYS = new Set(['_table', '_rowId', '_deleted', '_deletedHlc', '_schemaVersion']);
|
|
46
|
+
export function hexFromBytes(bytes) {
|
|
47
|
+
return Array.from(bytes).map(b => b.toString(16).padStart(2, '0')).join('');
|
|
48
|
+
}
|
|
49
|
+
export async function computeContentHash(payload) {
|
|
50
|
+
const json = JSON.stringify(payload);
|
|
51
|
+
const digest = await crypto.subtle.digest('SHA-256', textEncoder.encode(json));
|
|
52
|
+
return `sha256:${hexFromBytes(new Uint8Array(digest))}`;
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=internals.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"internals.js","sourceRoot":"","sources":["../../src/core/internals.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAeH,MAAM,UAAU,KAAK,CAAC,IAAY;IAChC,MAAM,aAAa,GAAG,GAAG,IAAI,UAAU,CAAC;IACxC,OAAO;QACL,eAAe,EAAE,GAAG,IAAI,gBAAgB;QACxC,YAAY,EAAE,CAAC,UAAkB,EAAE,EAAE,CAAC,GAAG,IAAI,aAAa,UAAU,OAAO;QAC3E,aAAa,EAAE,GAAG,IAAI,UAAU;QAChC,UAAU,EAAE,CAAC,QAAgB,EAAE,EAAE,CAAC,GAAG,IAAI,YAAY,QAAQ,OAAO;QACpE,cAAc,EAAE,GAAG,IAAI,WAAW;QAClC,aAAa;QACb,WAAW,EAAE,GAAG,aAAa,YAAY;QACzC,UAAU,EAAE,CAAC,QAAgB,EAAE,EAAE,CAAC,GAAG,aAAa,IAAI,QAAQ,EAAE;KACjE,CAAC;AACJ,CAAC;AAED,wEAAwE;AAExE,MAAM,UAAU,GAAG,eAAe,CAAC;AAEnC,MAAM,UAAU,GAAG,CAAC,KAA0C,EAAE,GAAG,IAAe;IAChF,sCAAsC;IACtC,OAAO,CAAC,KAAK,CAAC,CAAC,UAAU,EAAE,GAAG,IAAI,CAAC,CAAC;AACtC,CAAC;AAED,wEAAwE;AAExE,MAAM,UAAU,UAAU,CAAC,MAAc;IACvC,MAAM,IAAI,GAAG,MAAM,CAAC,eAAe,CAAC,IAAI,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC;IACvD,MAAM,GAAG,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAChF,OAAO,GAAG,MAAM,IAAI,GAAG,EAAE,CAAC;AAC5B,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,QAAiB;IAC3C,IAAI,QAAQ;QAAE,OAAO,QAAQ,CAAC;IAC9B,MAAM,GAAG,GAAG,uBAAuB,CAAC;IACpC,IAAI,EAAE,GAAG,YAAY,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IACnC,IAAI,CAAC,EAAE,EAAE,CAAC;QACR,EAAE,GAAG,UAAU,CAAC,KAAK,CAAC,CAAC;QACvB,YAAY,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;IAChC,CAAC;IACD,OAAO,EAAE,CAAC;AACZ,CAAC;AAED,uEAAuE;AAEvE,MAAM,CAAC,MAAM,WAAW,GAAG,IAAI,WAAW,EAAE,CAAC;AAC7C,MAAM,CAAC,MAAM,WAAW,GAAG,IAAI,WAAW,EAAE,CAAC;AAE7C,MAAM,CAAC,MAAM,aAAa,GAAG,IAAI,GAAG,CAAC,CAAC,QAAQ,EAAE,QAAQ,EAAE,UAAU,EAAE,aAAa,EAAE,gBAAgB,CAAC,CAAC,CAAC;AAExG,MAAM,UAAU,YAAY,CAAC,KAAiB;IAC5C,OAAO,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;AAC9E,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,kBAAkB,CAAC,OAAgB;IACvD,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;IACrC,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,SAAS,EAAE,WAAW,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC;IAC/E,OAAO,UAAU,YAAY,CAAC,IAAI,UAAU,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC;AAC1D,CAAC"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Manifest — reading, writing, creating, and validating cloud manifests.
|
|
3
|
+
*
|
|
4
|
+
* Extracted from SyncEngine. Not part of the public API.
|
|
5
|
+
*/
|
|
6
|
+
import type { StorageAdapter, Manifest, DatabaseSchemaDefinition, SyncEvent } from './types.ts';
|
|
7
|
+
import type { CodecState } from './codec.ts';
|
|
8
|
+
export interface ManifestContext {
|
|
9
|
+
adapter: StorageAdapter;
|
|
10
|
+
remotePath: string;
|
|
11
|
+
serverId: string;
|
|
12
|
+
serverManaged: boolean;
|
|
13
|
+
deviceId: string;
|
|
14
|
+
encrypted: boolean;
|
|
15
|
+
schema?: DatabaseSchemaDefinition;
|
|
16
|
+
emit: (event: SyncEvent) => void;
|
|
17
|
+
}
|
|
18
|
+
export declare function readJson<T>(adapter: StorageAdapter, path: string): Promise<T>;
|
|
19
|
+
export declare function readJsonIfExists<T>(adapter: StorageAdapter, path: string): Promise<T | null>;
|
|
20
|
+
export declare function assertServerAuth(manifest: {
|
|
21
|
+
writtenBy: string;
|
|
22
|
+
}, serverId: string): void;
|
|
23
|
+
export declare function validateManifestHash(manifest: {
|
|
24
|
+
contentHash: string;
|
|
25
|
+
[key: string]: unknown;
|
|
26
|
+
}): Promise<void>;
|
|
27
|
+
export declare function writeJson(adapter: StorageAdapter, path: string, value: unknown): Promise<void>;
|
|
28
|
+
export declare function createBootstrapManifest(ctx: ManifestContext): Promise<void>;
|
|
29
|
+
export declare function loadOrCreateManifest(ctx: ManifestContext, codecState: CodecState, local: import('./types.ts').LocalStoreAdapter, poisonRemote: (error: unknown, path?: string) => Promise<Error>): Promise<Manifest>;
|
|
30
|
+
export declare function upsertDeviceMetadata(adapter: StorageAdapter, remotePath: string, deviceId: string): Promise<void>;
|
|
31
|
+
//# sourceMappingURL=manifest.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../../src/core/manifest.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EACV,cAAc,EACd,QAAQ,EAGR,wBAAwB,EACxB,SAAS,EACV,MAAM,YAAY,CAAC;AAGpB,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAE7C,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,cAAc,CAAC;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,OAAO,CAAC;IACvB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,OAAO,CAAC;IACnB,MAAM,CAAC,EAAE,wBAAwB,CAAC;IAClC,IAAI,EAAE,CAAC,KAAK,EAAE,SAAS,KAAK,IAAI,CAAC;CAClC;AAED,wBAAsB,QAAQ,CAAC,CAAC,EAAE,OAAO,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC,CAGnF;AAED,wBAAsB,gBAAgB,CAAC,CAAC,EAAE,OAAO,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC,CAMlG;AAED,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE;IAAE,SAAS,EAAE,MAAM,CAAA;CAAE,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,CAIxF;AAED,wBAAsB,oBAAoB,CACxC,QAAQ,EAAE;IAAE,WAAW,EAAE,MAAM,CAAC;IAAC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;CAAE,GACxD,OAAO,CAAC,IAAI,CAAC,CAMf;AAED,wBAAsB,SAAS,CAAC,OAAO,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAEpG;AAED,wBAAsB,uBAAuB,CAAC,GAAG,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CAqCjF;AAED,wBAAsB,oBAAoB,CACxC,GAAG,EAAE,eAAe,EACpB,UAAU,EAAE,UAAU,EACtB,KAAK,EAAE,OAAO,YAAY,EAAE,iBAAiB,EAC7C,YAAY,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,KAAK,CAAC,GAC9D,OAAO,CAAC,QAAQ,CAAC,CA8BnB;AAED,wBAAsB,oBAAoB,CACxC,OAAO,EAAE,cAAc,EACvB,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,IAAI,CAAC,CAaf"}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Manifest — reading, writing, creating, and validating cloud manifests.
|
|
3
|
+
*
|
|
4
|
+
* Extracted from SyncEngine. Not part of the public API.
|
|
5
|
+
*/
|
|
6
|
+
import { paths, textEncoder, textDecoder, generateId, computeContentHash } from "./internals.js";
|
|
7
|
+
import { assertExpectedMeshId } from "./codec.js";
|
|
8
|
+
export async function readJson(adapter, path) {
|
|
9
|
+
const data = await adapter.readFile(path);
|
|
10
|
+
return JSON.parse(textDecoder.decode(data));
|
|
11
|
+
}
|
|
12
|
+
export async function readJsonIfExists(adapter, path) {
|
|
13
|
+
try {
|
|
14
|
+
return await readJson(adapter, path);
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
export function assertServerAuth(manifest, serverId) {
|
|
21
|
+
if (manifest.writtenBy !== serverId) {
|
|
22
|
+
throw new Error(`Unauthorized manifest writer: ${manifest.writtenBy}`);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
export async function validateManifestHash(manifest) {
|
|
26
|
+
const { contentHash, ...payload } = manifest;
|
|
27
|
+
const expected = await computeContentHash(payload);
|
|
28
|
+
if (contentHash !== expected) {
|
|
29
|
+
throw new Error('Manifest content hash mismatch');
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
export async function writeJson(adapter, path, value) {
|
|
33
|
+
await adapter.writeFile(path, textEncoder.encode(JSON.stringify(value, null, 2)));
|
|
34
|
+
}
|
|
35
|
+
export async function createBootstrapManifest(ctx) {
|
|
36
|
+
const p = paths(ctx.remotePath);
|
|
37
|
+
const now = new Date().toISOString();
|
|
38
|
+
const payload = {
|
|
39
|
+
generation: 1,
|
|
40
|
+
parentGeneration: 0,
|
|
41
|
+
writtenBy: ctx.serverId,
|
|
42
|
+
writtenAt: now,
|
|
43
|
+
version: 3,
|
|
44
|
+
meshId: generateId('mesh'),
|
|
45
|
+
schema: ctx.schema?.version ?? 1,
|
|
46
|
+
encrypted: ctx.encrypted,
|
|
47
|
+
server: {
|
|
48
|
+
managed: ctx.serverManaged,
|
|
49
|
+
relayUrl: null,
|
|
50
|
+
serverId: ctx.serverId,
|
|
51
|
+
},
|
|
52
|
+
createdAt: now,
|
|
53
|
+
epoch: 0,
|
|
54
|
+
watermarkHlc: '',
|
|
55
|
+
snapshotPath: null,
|
|
56
|
+
deltaPath: null,
|
|
57
|
+
};
|
|
58
|
+
const manifest = {
|
|
59
|
+
...payload,
|
|
60
|
+
contentHash: await computeContentHash(payload),
|
|
61
|
+
};
|
|
62
|
+
const manifestFile = `manifest-${manifest.generation}.json`;
|
|
63
|
+
await writeJson(ctx.adapter, p.manifestFile(manifest.generation), manifest);
|
|
64
|
+
await writeJson(ctx.adapter, p.manifestPointer, {
|
|
65
|
+
currentGeneration: manifest.generation,
|
|
66
|
+
file: manifestFile,
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
export async function loadOrCreateManifest(ctx, codecState, local, poisonRemote) {
|
|
70
|
+
const p = paths(ctx.remotePath);
|
|
71
|
+
const globalPointer = await readJsonIfExists(ctx.adapter, p.manifestPointer);
|
|
72
|
+
if (!globalPointer) {
|
|
73
|
+
await createBootstrapManifest(ctx);
|
|
74
|
+
}
|
|
75
|
+
const pointer = await readJson(ctx.adapter, p.manifestPointer);
|
|
76
|
+
const manifestPath = `${ctx.remotePath}/${pointer.file}`;
|
|
77
|
+
const manifest = await readJson(ctx.adapter, manifestPath);
|
|
78
|
+
await validateManifestHash(manifest);
|
|
79
|
+
try {
|
|
80
|
+
await assertExpectedMeshId(local, codecState.manifest, manifest.meshId);
|
|
81
|
+
}
|
|
82
|
+
catch (err) {
|
|
83
|
+
throw await poisonRemote(err, manifestPath);
|
|
84
|
+
}
|
|
85
|
+
if (manifest.version !== 3) {
|
|
86
|
+
throw new Error(`Unsupported manifest version ${manifest.version} (expected 3).`);
|
|
87
|
+
}
|
|
88
|
+
if (ctx.schema && manifest.schema !== ctx.schema.version) {
|
|
89
|
+
ctx.emit({ type: 'schema:mismatch', local: ctx.schema.version, remote: manifest.schema });
|
|
90
|
+
throw new Error(`Schema version mismatch: local=${ctx.schema.version}, remote=${manifest.schema}`);
|
|
91
|
+
}
|
|
92
|
+
if (manifest.server.managed) {
|
|
93
|
+
assertServerAuth(manifest, ctx.serverId);
|
|
94
|
+
}
|
|
95
|
+
return manifest;
|
|
96
|
+
}
|
|
97
|
+
export async function upsertDeviceMetadata(adapter, remotePath, deviceId) {
|
|
98
|
+
const p = paths(remotePath);
|
|
99
|
+
const now = new Date().toISOString();
|
|
100
|
+
const existing = await readJsonIfExists(adapter, p.deviceFile(deviceId));
|
|
101
|
+
const next = {
|
|
102
|
+
deviceId,
|
|
103
|
+
registeredAt: existing?.registeredAt ?? now,
|
|
104
|
+
lastSeenAt: now,
|
|
105
|
+
userId: existing?.userId,
|
|
106
|
+
name: existing?.name,
|
|
107
|
+
retired: existing?.retired,
|
|
108
|
+
};
|
|
109
|
+
await writeJson(adapter, p.deviceFile(deviceId), next);
|
|
110
|
+
}
|
|
111
|
+
//# sourceMappingURL=manifest.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"manifest.js","sourceRoot":"","sources":["../../src/core/manifest.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pull — download remote changes and merge into local state.
|
|
3
|
+
*
|
|
4
|
+
* Extracted from SyncEngine. Not part of the public API.
|
|
5
|
+
*/
|
|
6
|
+
import type { StorageAdapter, LocalStoreAdapter, Row, Op, SyncEvent, DatabaseSchemaDefinition } from './types.ts';
|
|
7
|
+
import type { HLC } from './types.ts';
|
|
8
|
+
import type { CodecState } from './codec.ts';
|
|
9
|
+
export interface PullContext {
|
|
10
|
+
adapter: StorageAdapter;
|
|
11
|
+
local: LocalStoreAdapter;
|
|
12
|
+
remotePath: string;
|
|
13
|
+
codecState: CodecState;
|
|
14
|
+
hlc: HLC;
|
|
15
|
+
deviceId: string;
|
|
16
|
+
tables: Record<string, Record<string, Row>>;
|
|
17
|
+
knownTables: Set<string>;
|
|
18
|
+
schema?: DatabaseSchemaDefinition;
|
|
19
|
+
emit: (event: SyncEvent) => void;
|
|
20
|
+
ensureRowsCached: (ops: Op[]) => Promise<void>;
|
|
21
|
+
poisonRemote: (error: unknown, path?: string) => Promise<Error>;
|
|
22
|
+
loadOrCreateManifest: () => Promise<void>;
|
|
23
|
+
}
|
|
24
|
+
/** Returns the updated HLC after pull. */
|
|
25
|
+
export declare function pull(ctx: PullContext): Promise<HLC>;
|
|
26
|
+
//# sourceMappingURL=pull.d.ts.map
|