@hraness/oh 0.3.2 → 0.4.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 (57) hide show
  1. package/README.md +164 -114
  2. package/dist/cli.d.ts +1 -1
  3. package/dist/cli.d.ts.map +1 -1
  4. package/dist/cli.js +811 -97
  5. package/dist/errors.d.ts +39 -0
  6. package/dist/errors.d.ts.map +1 -0
  7. package/dist/graph.d.ts.map +1 -1
  8. package/dist/index.js +676 -61
  9. package/dist/libsql.d.ts.map +1 -1
  10. package/dist/libsql.js +162 -35
  11. package/dist/memory-page.js +2 -2
  12. package/dist/memory.d.ts +87 -6
  13. package/dist/memory.d.ts.map +1 -1
  14. package/dist/memory.js +1106 -148
  15. package/dist/operation.d.ts +3 -1
  16. package/dist/operation.d.ts.map +1 -1
  17. package/dist/projection-public.js +2 -2
  18. package/dist/projection-suss.js +2 -2
  19. package/dist/sdk.js +780 -88
  20. package/dist/semantic-cloud.js +2 -2
  21. package/dist/semantic.js +2 -2
  22. package/dist/sqlite/index.js +1251 -306
  23. package/dist/sqlite/port.d.ts +31 -3
  24. package/dist/sqlite/port.d.ts.map +1 -1
  25. package/dist/sqlite/store.d.ts +18 -2
  26. package/dist/sqlite/store.d.ts.map +1 -1
  27. package/dist/store.d.ts +3 -12
  28. package/dist/store.d.ts.map +1 -1
  29. package/dist/store.js +154 -32
  30. package/dist/sync.d.ts +7 -1
  31. package/dist/sync.d.ts.map +1 -1
  32. package/dist/sync.js +668 -35
  33. package/package.json +5 -1
  34. package/skills/oh/SKILL.md +42 -16
  35. package/spec/README.md +2 -2
  36. package/spec/v1/memory.md +134 -16
  37. package/spec/v1/storage.md +8 -5
  38. package/spec/v1/store.md +20 -0
  39. package/spec/v1/sync.md +77 -8
  40. package/src/cli.test.ts +53 -1
  41. package/src/cli.ts +34 -8
  42. package/src/errors.test.ts +87 -0
  43. package/src/errors.ts +185 -0
  44. package/src/graph.ts +2 -2
  45. package/src/libsql.test.ts +36 -0
  46. package/src/libsql.ts +26 -5
  47. package/src/memory.test.ts +1488 -18
  48. package/src/memory.ts +1199 -122
  49. package/src/operation.ts +13 -3
  50. package/src/sqlite/port.test.ts +209 -0
  51. package/src/sqlite/port.ts +118 -4
  52. package/src/sqlite/store.test.ts +106 -1
  53. package/src/sqlite/store.ts +168 -30
  54. package/src/store.test.ts +12 -0
  55. package/src/store.ts +30 -20
  56. package/src/sync.test.ts +570 -2
  57. package/src/sync.ts +586 -36
@@ -1,78 +1,7 @@
1
1
  // @bun
2
- // src/sqlite/driver.ts
3
- import { existsSync } from "fs";
4
- import { Database } from "bun:sqlite";
5
-
6
- // src/sqlite/runtime.ts
7
- var MACOS_SQLITE_LIBRARY_CANDIDATES = Object.freeze([
8
- "/opt/homebrew/opt/sqlite/lib/libsqlite3.dylib",
9
- "/usr/local/opt/sqlite/lib/libsqlite3.dylib"
10
- ]);
11
- function macosSqliteLibraryCandidates() {
12
- return MACOS_SQLITE_LIBRARY_CANDIDATES;
13
- }
14
- function createOhSqliteRuntime(dependencies) {
15
- let customLibrary = null;
16
- if (dependencies.platform === "darwin") {
17
- for (const candidate of macosSqliteLibraryCandidates()) {
18
- if (!dependencies.exists(candidate))
19
- continue;
20
- try {
21
- if (!dependencies.setCustomSQLite(candidate))
22
- continue;
23
- customLibrary = candidate;
24
- break;
25
- } catch {}
26
- }
27
- }
28
- return Object.freeze({
29
- customLibrary,
30
- open: (path) => dependencies.open(path, { create: true, strict: true })
31
- });
32
- }
2
+ // src/sync.ts
3
+ import { isProxy } from "util/types";
33
4
 
34
- // src/sqlite/driver.ts
35
- var SQLITE_RUNTIME = createOhSqliteRuntime({
36
- exists: existsSync,
37
- open: (path, options) => new Database(path, options),
38
- platform: process.platform,
39
- setCustomSQLite: (path) => Database.setCustomSQLite(path)
40
- });
41
- function openOhSqliteDatabase(path) {
42
- const database = SQLITE_RUNTIME.open(path);
43
- database.exec("PRAGMA foreign_keys = ON");
44
- database.exec("PRAGMA journal_mode = WAL");
45
- database.exec("PRAGMA synchronous = NORMAL");
46
- database.exec("PRAGMA busy_timeout = 5000");
47
- database.exec("PRAGMA trusted_schema = OFF");
48
- return database;
49
- }
50
- function withImmediateTransaction(database, work) {
51
- database.exec("BEGIN IMMEDIATE");
52
- try {
53
- const result = work();
54
- database.exec("COMMIT");
55
- return result;
56
- } catch (error) {
57
- try {
58
- database.exec("ROLLBACK");
59
- } catch {}
60
- throw error;
61
- }
62
- }
63
- function withReadTransaction(database, work) {
64
- database.exec("BEGIN");
65
- try {
66
- const result = work();
67
- database.exec("COMMIT");
68
- return result;
69
- } catch (error) {
70
- try {
71
- database.exec("ROLLBACK");
72
- } catch {}
73
- throw error;
74
- }
75
- }
76
5
  // src/canonical.ts
77
6
  import { createHash, randomBytes } from "crypto";
78
7
 
@@ -263,176 +192,6 @@ function sortUnique(values, key) {
263
192
  return sorted;
264
193
  }
265
194
 
266
- // src/sqlite/migrations.ts
267
- var OH_SQLITE_SCHEMA_VERSION = 2;
268
- var OH_SQLITE_MIGRATIONS = Object.freeze([
269
- Object.freeze({
270
- name: "0001_oh_core",
271
- version: 1,
272
- sql: `
273
- CREATE TABLE oh_contracts (
274
- contract_id TEXT PRIMARY KEY,
275
- contract_sha256 TEXT NOT NULL CHECK(length(contract_sha256) = 64),
276
- manifest_json TEXT NOT NULL CHECK(json_valid(manifest_json)),
277
- created_at TEXT NOT NULL
278
- ) STRICT;
279
-
280
- CREATE TABLE oh_spaces (
281
- space_id TEXT PRIMARY KEY,
282
- contract_id TEXT NOT NULL REFERENCES oh_contracts(contract_id),
283
- generation INTEGER NOT NULL CHECK(generation >= 0),
284
- head_operation_sha256 TEXT CHECK(head_operation_sha256 IS NULL OR length(head_operation_sha256) = 64),
285
- graph_revision_sha256 TEXT CHECK(graph_revision_sha256 IS NULL OR length(graph_revision_sha256) = 64),
286
- records_sha256 TEXT NOT NULL CHECK(length(records_sha256) = 64),
287
- sequence INTEGER NOT NULL CHECK(sequence >= 0),
288
- created_at TEXT NOT NULL,
289
- updated_at TEXT NOT NULL,
290
- CHECK(generation = sequence),
291
- CHECK((sequence = 0) = (head_operation_sha256 IS NULL)),
292
- CHECK((sequence = 0) = (graph_revision_sha256 IS NULL))
293
- ) STRICT;
294
-
295
- CREATE TABLE oh_operations (
296
- operation_sha256 TEXT PRIMARY KEY CHECK(length(operation_sha256) = 64),
297
- space_id TEXT NOT NULL REFERENCES oh_spaces(space_id),
298
- sequence INTEGER NOT NULL CHECK(sequence > 0),
299
- operation_id TEXT NOT NULL,
300
- parent_operation_sha256 TEXT CHECK(parent_operation_sha256 IS NULL OR length(parent_operation_sha256) = 64),
301
- graph_revision_sha256 TEXT NOT NULL CHECK(length(graph_revision_sha256) = 64),
302
- records_sha256 TEXT NOT NULL CHECK(length(records_sha256) = 64),
303
- operation_json TEXT NOT NULL CHECK(json_valid(operation_json)),
304
- instant TEXT NOT NULL,
305
- UNIQUE(space_id, sequence),
306
- UNIQUE(space_id, operation_id)
307
- ) STRICT;
308
-
309
- CREATE TABLE oh_operation_records (
310
- operation_sha256 TEXT NOT NULL REFERENCES oh_operations(operation_sha256),
311
- ordinal INTEGER NOT NULL CHECK(ordinal >= 0),
312
- record_key TEXT NOT NULL,
313
- change_kind TEXT NOT NULL CHECK(change_kind IN ('put', 'tombstone')),
314
- record_sha256 TEXT CHECK(record_sha256 IS NULL OR length(record_sha256) = 64),
315
- PRIMARY KEY(operation_sha256, ordinal),
316
- UNIQUE(operation_sha256, record_key)
317
- ) STRICT;
318
-
319
- CREATE TABLE oh_records (
320
- space_id TEXT NOT NULL REFERENCES oh_spaces(space_id),
321
- record_key TEXT NOT NULL,
322
- kind TEXT NOT NULL,
323
- record_sha256 TEXT NOT NULL CHECK(length(record_sha256) = 64),
324
- record_json TEXT NOT NULL CHECK(json_valid(record_json)),
325
- operation_sha256 TEXT NOT NULL REFERENCES oh_operations(operation_sha256),
326
- sequence INTEGER NOT NULL CHECK(sequence > 0),
327
- PRIMARY KEY(space_id, record_key)
328
- ) STRICT;
329
-
330
- CREATE TABLE oh_dependencies (
331
- space_id TEXT NOT NULL,
332
- record_key TEXT NOT NULL,
333
- dependency_key TEXT NOT NULL,
334
- PRIMARY KEY(space_id, record_key, dependency_key),
335
- FOREIGN KEY(space_id, record_key) REFERENCES oh_records(space_id, record_key) ON DELETE CASCADE,
336
- FOREIGN KEY(space_id, dependency_key) REFERENCES oh_records(space_id, record_key)
337
- ) STRICT;
338
-
339
- CREATE TABLE oh_sync_outbox (
340
- space_id TEXT NOT NULL REFERENCES oh_spaces(space_id),
341
- sequence INTEGER NOT NULL,
342
- operation_sha256 TEXT NOT NULL REFERENCES oh_operations(operation_sha256),
343
- PRIMARY KEY(space_id, sequence),
344
- UNIQUE(operation_sha256)
345
- ) STRICT;
346
-
347
- CREATE TABLE oh_sync_state (
348
- remote_id TEXT NOT NULL,
349
- space_id TEXT NOT NULL REFERENCES oh_spaces(space_id),
350
- pulled_sequence INTEGER NOT NULL CHECK(pulled_sequence >= 0),
351
- pushed_sequence INTEGER NOT NULL CHECK(pushed_sequence >= 0),
352
- remote_head_sha256 TEXT CHECK(remote_head_sha256 IS NULL OR length(remote_head_sha256) = 64),
353
- updated_at TEXT NOT NULL,
354
- PRIMARY KEY(remote_id, space_id)
355
- ) STRICT;
356
-
357
- CREATE TABLE oh_search_documents (
358
- space_id TEXT NOT NULL,
359
- record_key TEXT NOT NULL,
360
- record_sha256 TEXT NOT NULL CHECK(length(record_sha256) = 64),
361
- text TEXT NOT NULL,
362
- PRIMARY KEY(space_id, record_key),
363
- FOREIGN KEY(space_id, record_key) REFERENCES oh_records(space_id, record_key) ON DELETE CASCADE
364
- ) STRICT;
365
-
366
- CREATE VIRTUAL TABLE oh_search_fts USING fts5(
367
- space_id UNINDEXED,
368
- record_key UNINDEXED,
369
- text,
370
- tokenize='unicode61 remove_diacritics 2'
371
- );
372
-
373
- CREATE INDEX oh_operations_space_sequence ON oh_operations(space_id, sequence);
374
- CREATE INDEX oh_records_space_kind ON oh_records(space_id, kind, record_key);
375
- CREATE INDEX oh_dependencies_dependency ON oh_dependencies(space_id, dependency_key);
376
- `
377
- }),
378
- Object.freeze({
379
- name: "0002_store_realms",
380
- version: 2,
381
- sql: `
382
- CREATE TABLE oh_space_bindings (
383
- space_id TEXT PRIMARY KEY REFERENCES oh_spaces(space_id),
384
- realm_id TEXT NOT NULL,
385
- profile_id TEXT NOT NULL,
386
- profile_kind TEXT NOT NULL CHECK(profile_kind IN ('canonical', 'working')),
387
- profile_sha256 TEXT NOT NULL CHECK(length(profile_sha256) = 64),
388
- binding_sha256 TEXT NOT NULL UNIQUE CHECK(length(binding_sha256) = 64),
389
- binding_json TEXT NOT NULL CHECK(json_valid(binding_json)),
390
- created_at TEXT NOT NULL
391
- ) STRICT;
392
-
393
- CREATE TABLE oh_space_purges (
394
- space_id TEXT PRIMARY KEY,
395
- binding_sha256 TEXT NOT NULL CHECK(length(binding_sha256) = 64),
396
- prior_operation_sha256 TEXT CHECK(prior_operation_sha256 IS NULL OR length(prior_operation_sha256) = 64),
397
- prior_sequence INTEGER NOT NULL CHECK(prior_sequence >= 0),
398
- purged_at TEXT NOT NULL,
399
- receipt_sha256 TEXT NOT NULL UNIQUE CHECK(length(receipt_sha256) = 64),
400
- receipt_json TEXT NOT NULL CHECK(json_valid(receipt_json))
401
- ) STRICT;
402
- `
403
- })
404
- ]);
405
- function applyOhSqliteMigrations(database) {
406
- database.exec(`CREATE TABLE IF NOT EXISTS oh_migrations (
407
- version INTEGER PRIMARY KEY,
408
- name TEXT NOT NULL UNIQUE,
409
- migration_sha256 TEXT NOT NULL CHECK(length(migration_sha256) = 64),
410
- applied_at TEXT NOT NULL
411
- ) STRICT`);
412
- const select = database.query("SELECT name, migration_sha256 FROM oh_migrations WHERE version = ?");
413
- const insert = database.query("INSERT INTO oh_migrations(version, name, migration_sha256, applied_at) VALUES (?, ?, ?, ?)");
414
- for (const migration of OH_SQLITE_MIGRATIONS) {
415
- const digest = sha256Hex(migration.sql);
416
- database.exec("BEGIN IMMEDIATE");
417
- try {
418
- const existing = select.get(migration.version);
419
- if (existing !== null) {
420
- if (existing.name !== migration.name || existing.migration_sha256 !== digest) {
421
- throw new Error(`SQLite migration ${migration.version} does not match the applied migration.`);
422
- }
423
- } else {
424
- database.exec(migration.sql);
425
- insert.run(migration.version, migration.name, digest, canonicalNow());
426
- }
427
- database.exec("COMMIT");
428
- } catch (error) {
429
- try {
430
- database.exec("ROLLBACK");
431
- } catch {}
432
- throw error;
433
- }
434
- }
435
- }
436
195
  // src/graph.ts
437
196
  var OH_GRAPH_FORMAT_VERSION_V1 = 1;
438
197
  var OH_GRAPH_LIMITS_V1 = Object.freeze({
@@ -441,7 +200,7 @@ var OH_GRAPH_LIMITS_V1 = Object.freeze({
441
200
  recordBytes: 1024 * 1024,
442
201
  recordsPerSnapshot: 65536
443
202
  });
444
- var OH_KNOWLEDGE_GRAPH_RECORD_KINDS_V1 = [
203
+ var OH_KNOWLEDGE_GRAPH_RECORD_KINDS_V1 = Object.freeze([
445
204
  "activity",
446
205
  "assertion",
447
206
  "context",
@@ -460,7 +219,7 @@ var OH_KNOWLEDGE_GRAPH_RECORD_KINDS_V1 = [
460
219
  "type-membership",
461
220
  "view",
462
221
  "vocabulary"
463
- ];
222
+ ]);
464
223
  var KNOWLEDGE_GRAPH_RECORD_KEYS_V1 = [
465
224
  "dependencies",
466
225
  "key",
@@ -1386,17 +1145,153 @@ class OhRecordCodecRegistry {
1386
1145
  }
1387
1146
  }
1388
1147
 
1389
- // src/operation.ts
1390
- var OH_OPERATION_MAX_BYTES_V1 = 64 * 1024 * 1024;
1391
- function parsePayload(value) {
1392
- if (!isPlainRecord(value) || !hasExactKeys(value, [
1393
- "actorId",
1394
- "changes",
1395
- "contractId",
1396
- "graphRevisionSha256",
1397
- "instant",
1398
- "operationId",
1399
- "parentOperationSha256",
1148
+ // src/errors.ts
1149
+ var OH_OPERATION_SIZE_ERROR_CODE_V1 = "oh.operation-size.v1";
1150
+ var OH_CONFLICT_ERROR_BRAND_V1 = Symbol.for("@hraness/oh/OhConflictError/v1");
1151
+ var OH_DEPENDENCY_ERROR_BRAND_V1 = Symbol.for("@hraness/oh/OhDependencyError/v1");
1152
+ var OH_INTEGRITY_ERROR_BRAND_V1 = Symbol.for("@hraness/oh/OhIntegrityError/v1");
1153
+ var OH_OPERATION_SIZE_ERROR_BRAND_V1 = Symbol.for("@hraness/oh/OhOperationSizeError/v1");
1154
+ var OH_PROFILE_ERROR_BRAND_V1 = Symbol.for("@hraness/oh/OhProfileError/v1");
1155
+ function immutableOwnValue(value, key) {
1156
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
1157
+ return descriptor !== undefined && descriptor.get === undefined && descriptor.set === undefined && descriptor.configurable === false && descriptor.writable === false ? descriptor.value : undefined;
1158
+ }
1159
+ function brandNativeError(value, brand) {
1160
+ Object.defineProperty(value, brand, {
1161
+ configurable: false,
1162
+ enumerable: false,
1163
+ value: true,
1164
+ writable: false
1165
+ });
1166
+ }
1167
+ function hasNativeErrorBrand(value, brand) {
1168
+ try {
1169
+ return Error.isError(value) && immutableOwnValue(value, brand) === true;
1170
+ } catch {
1171
+ return false;
1172
+ }
1173
+ }
1174
+ function hasNativeSubclassInstance(constructor, value) {
1175
+ return Function.prototype[Symbol.hasInstance].call(constructor, value);
1176
+ }
1177
+ function isOhConflictError(value) {
1178
+ return hasNativeErrorBrand(value, OH_CONFLICT_ERROR_BRAND_V1);
1179
+ }
1180
+
1181
+ class OhConflictError extends Error {
1182
+ static [Symbol.hasInstance](value) {
1183
+ return this === OhConflictError ? isOhConflictError(value) : hasNativeSubclassInstance(this, value);
1184
+ }
1185
+ constructor(message) {
1186
+ super(message);
1187
+ this.name = "OhConflictError";
1188
+ brandNativeError(this, OH_CONFLICT_ERROR_BRAND_V1);
1189
+ }
1190
+ }
1191
+ function isOhIntegrityError(value) {
1192
+ return hasNativeErrorBrand(value, OH_INTEGRITY_ERROR_BRAND_V1);
1193
+ }
1194
+
1195
+ class OhIntegrityError extends Error {
1196
+ static [Symbol.hasInstance](value) {
1197
+ return this === OhIntegrityError ? isOhIntegrityError(value) : hasNativeSubclassInstance(this, value);
1198
+ }
1199
+ constructor(message) {
1200
+ super(message);
1201
+ this.name = "OhIntegrityError";
1202
+ brandNativeError(this, OH_INTEGRITY_ERROR_BRAND_V1);
1203
+ }
1204
+ }
1205
+ function isOhDependencyError(value) {
1206
+ return hasNativeErrorBrand(value, OH_DEPENDENCY_ERROR_BRAND_V1);
1207
+ }
1208
+
1209
+ class OhDependencyError extends Error {
1210
+ static [Symbol.hasInstance](value) {
1211
+ return this === OhDependencyError ? isOhDependencyError(value) : hasNativeSubclassInstance(this, value);
1212
+ }
1213
+ constructor(message) {
1214
+ super(message);
1215
+ this.name = "OhDependencyError";
1216
+ brandNativeError(this, OH_DEPENDENCY_ERROR_BRAND_V1);
1217
+ }
1218
+ }
1219
+ function isOhProfileError(value) {
1220
+ return hasNativeErrorBrand(value, OH_PROFILE_ERROR_BRAND_V1);
1221
+ }
1222
+
1223
+ class OhProfileError extends Error {
1224
+ static [Symbol.hasInstance](value) {
1225
+ return this === OhProfileError ? isOhProfileError(value) : hasNativeSubclassInstance(this, value);
1226
+ }
1227
+ constructor(message) {
1228
+ super(message);
1229
+ this.name = "OhProfileError";
1230
+ brandNativeError(this, OH_PROFILE_ERROR_BRAND_V1);
1231
+ }
1232
+ }
1233
+ function isOhOperationSizeError(value) {
1234
+ try {
1235
+ if (!Error.isError(value) || !(value instanceof RangeError))
1236
+ return false;
1237
+ const operationBytes = immutableOwnValue(value, "operationBytes");
1238
+ const maximumOperationBytes = immutableOwnValue(value, "maximumOperationBytes");
1239
+ return immutableOwnValue(value, OH_OPERATION_SIZE_ERROR_BRAND_V1) === true && immutableOwnValue(value, "code") === OH_OPERATION_SIZE_ERROR_CODE_V1 && Number.isSafeInteger(operationBytes) && operationBytes > 0 && Number.isSafeInteger(maximumOperationBytes) && maximumOperationBytes > 0 && operationBytes > maximumOperationBytes;
1240
+ } catch {
1241
+ return false;
1242
+ }
1243
+ }
1244
+
1245
+ class OhOperationSizeError extends RangeError {
1246
+ static [Symbol.hasInstance](value) {
1247
+ return this === OhOperationSizeError ? isOhOperationSizeError(value) : hasNativeSubclassInstance(this, value);
1248
+ }
1249
+ constructor(operationBytes, maximumOperationBytes) {
1250
+ if (!Number.isSafeInteger(operationBytes) || operationBytes < 1 || !Number.isSafeInteger(maximumOperationBytes) || maximumOperationBytes < 1 || operationBytes <= maximumOperationBytes) {
1251
+ throw new TypeError("Invalid Oh operation size refusal.");
1252
+ }
1253
+ super(`The ${operationBytes}-byte operation exceeds the host-declared ${maximumOperationBytes}-byte canonical bound.`);
1254
+ this.name = "OhOperationSizeError";
1255
+ Object.defineProperties(this, {
1256
+ [OH_OPERATION_SIZE_ERROR_BRAND_V1]: {
1257
+ configurable: false,
1258
+ enumerable: false,
1259
+ value: true,
1260
+ writable: false
1261
+ },
1262
+ code: {
1263
+ configurable: false,
1264
+ enumerable: true,
1265
+ value: OH_OPERATION_SIZE_ERROR_CODE_V1,
1266
+ writable: false
1267
+ },
1268
+ maximumOperationBytes: {
1269
+ configurable: false,
1270
+ enumerable: true,
1271
+ value: maximumOperationBytes,
1272
+ writable: false
1273
+ },
1274
+ operationBytes: {
1275
+ configurable: false,
1276
+ enumerable: true,
1277
+ value: operationBytes,
1278
+ writable: false
1279
+ }
1280
+ });
1281
+ }
1282
+ }
1283
+
1284
+ // src/operation.ts
1285
+ var OH_OPERATION_MAX_BYTES_V1 = 64 * 1024 * 1024;
1286
+ function parsePayload(value) {
1287
+ if (!isPlainRecord(value) || !hasExactKeys(value, [
1288
+ "actorId",
1289
+ "changes",
1290
+ "contractId",
1291
+ "graphRevisionSha256",
1292
+ "instant",
1293
+ "operationId",
1294
+ "parentOperationSha256",
1400
1295
  "recordsSha256",
1401
1296
  "sequence",
1402
1297
  "spaceId",
@@ -1433,13 +1328,18 @@ function parsePayload(value) {
1433
1328
  v: 1
1434
1329
  } : null;
1435
1330
  }
1436
- function createOhOperationV1(input) {
1331
+ function createOhOperationV1(input, options = {}) {
1332
+ const maximumOperationBytes = options.maximumOperationBytes ?? OH_OPERATION_MAX_BYTES_V1;
1333
+ if (!Number.isSafeInteger(maximumOperationBytes) || maximumOperationBytes < 1 || maximumOperationBytes > OH_OPERATION_MAX_BYTES_V1) {
1334
+ throw new TypeError("Invalid Oh operation byte bound.");
1335
+ }
1437
1336
  const payload = parsePayload(input);
1438
1337
  if (payload === null)
1439
1338
  throw new TypeError("Invalid Oh operation payload.");
1440
1339
  const operation = { ...payload, operationSha256: canonicalSha256(payload) };
1441
- if (Buffer.byteLength(canonicalJson(operation), "utf8") > OH_OPERATION_MAX_BYTES_V1) {
1442
- throw new RangeError("Oh operation exceeds its canonical byte limit.");
1340
+ const operationBytes = Buffer.byteLength(canonicalJson(operation), "utf8");
1341
+ if (operationBytes > maximumOperationBytes) {
1342
+ throw new OhOperationSizeError(operationBytes, maximumOperationBytes);
1443
1343
  }
1444
1344
  return operation;
1445
1345
  }
@@ -1452,34 +1352,923 @@ function parseOhOperationV1(value) {
1452
1352
  return operationSha256 !== null && payload !== null && Buffer.byteLength(canonicalJson({ ...payload, operationSha256 }), "utf8") <= OH_OPERATION_MAX_BYTES_V1 && canonicalSha256(payload) === operationSha256 ? { ...payload, operationSha256 } : null;
1453
1353
  }
1454
1354
 
1455
- // src/store.ts
1456
- class OhConflictError extends Error {
1457
- constructor(message) {
1458
- super(message);
1459
- this.name = "OhConflictError";
1355
+ // src/sync.ts
1356
+ var OH_SYNC_PROTOCOL_V1 = "oh.sync.v1";
1357
+ var OH_SYNC_BUNDLE_MAX_OPERATIONS_V1 = 1000;
1358
+ var OH_SYNC_BUNDLE_KEYS_V1 = [
1359
+ "bundleSha256",
1360
+ "contractSha256",
1361
+ "operations",
1362
+ "protocol",
1363
+ "spaceId",
1364
+ "v"
1365
+ ];
1366
+ var OH_SYNC_HEAD_KEYS_V1 = ["operationSha256", "sequence", "v"];
1367
+ var OH_SYNC_HEAD_REF_KEYS_V1 = ["operationSha256", "sequence"];
1368
+ var OH_OPERATION_KEYS_V1 = [
1369
+ "actorId",
1370
+ "changes",
1371
+ "contractId",
1372
+ "graphRevisionSha256",
1373
+ "instant",
1374
+ "operationId",
1375
+ "operationSha256",
1376
+ "parentOperationSha256",
1377
+ "recordsSha256",
1378
+ "sequence",
1379
+ "spaceId",
1380
+ "v"
1381
+ ];
1382
+ var OH_PUT_CHANGE_KEYS_V1 = ["kind", "record", "v"];
1383
+ var OH_TOMBSTONE_CHANGE_KEYS_V1 = ["key", "kind", "priorSha256", "v"];
1384
+ var OH_RECORD_KEYS_V1 = ["dependencies", "key", "kind", "recordSha256", "v", "value"];
1385
+ var OH_SYNC_INGRESS_VALUE_DEPTH_V1 = 128;
1386
+ var OH_SYNC_INGRESS_VALUE_NODES_V1 = OH_GRAPH_LIMITS_V1.recordBytes;
1387
+ var OH_SYNC_INGRESS_OPERATION_DEPTH_V1 = OH_SYNC_INGRESS_VALUE_DEPTH_V1 + 4;
1388
+ var OH_SYNC_INGRESS_OPERATION_NODES_V1 = OH_OPERATION_MAX_BYTES_V1;
1389
+ var OH_SYNC_BUNDLE_MAX_BYTES_V1 = OH_OPERATION_MAX_BYTES_V1 + 4 * 1024;
1390
+ var OH_SYNC_INGRESS_BUNDLE_NODES_V1 = OH_SYNC_INGRESS_OPERATION_NODES_V1 + 4 * 1024;
1391
+ function exactDataRecordV1(value, expectedKeys) {
1392
+ try {
1393
+ if (typeof value !== "object" || value === null || Array.isArray(value) || isProxy(value))
1394
+ return null;
1395
+ const prototype = Object.getPrototypeOf(value);
1396
+ const keys = Reflect.ownKeys(value);
1397
+ if (prototype !== Object.prototype && prototype !== null || keys.length !== expectedKeys.length || keys.some((key) => typeof key !== "string") || expectedKeys.some((key) => !keys.includes(key)))
1398
+ return null;
1399
+ const detached = Object.create(null);
1400
+ for (const key of expectedKeys) {
1401
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
1402
+ if (descriptor === undefined || !descriptor.enumerable || descriptor.get !== undefined || descriptor.set !== undefined)
1403
+ return null;
1404
+ Object.defineProperty(detached, key, {
1405
+ configurable: false,
1406
+ enumerable: true,
1407
+ value: descriptor.value,
1408
+ writable: false
1409
+ });
1410
+ }
1411
+ return detached;
1412
+ } catch {
1413
+ return null;
1414
+ }
1415
+ }
1416
+ function exactDataArrayV1(value, maximumLength, clone = true) {
1417
+ try {
1418
+ if (typeof value !== "object" || value === null || isProxy(value) || !Array.isArray(value))
1419
+ return null;
1420
+ const lengthDescriptor = Object.getOwnPropertyDescriptor(value, "length");
1421
+ const length = lengthDescriptor?.value;
1422
+ if (typeof length !== "number" || !Number.isSafeInteger(length) || length < 0 || length > maximumLength)
1423
+ return null;
1424
+ const keys = Reflect.ownKeys(value);
1425
+ if (keys.length !== length + 1 || !keys.includes("length") || keys.some((key) => key !== "length" && (typeof key !== "string" || !/^(?:0|[1-9][0-9]*)$/u.test(key) || Number(key) >= length)))
1426
+ return null;
1427
+ const detached = clone ? [] : null;
1428
+ for (let index = 0;index < length; index += 1) {
1429
+ const descriptor = Object.getOwnPropertyDescriptor(value, String(index));
1430
+ if (descriptor === undefined || !descriptor.enumerable || descriptor.get !== undefined || descriptor.set !== undefined)
1431
+ return null;
1432
+ detached?.push(descriptor.value);
1433
+ }
1434
+ return detached ?? value;
1435
+ } catch {
1436
+ return null;
1437
+ }
1438
+ }
1439
+ function stagedSyncOperationV1(value, bundleBudget) {
1440
+ const operation = exactDataRecordV1(value, OH_OPERATION_KEYS_V1);
1441
+ if (operation === null || operation.v !== 1 || operation.contractId !== OH_CONTRACT_ID_V1 || safeCode(operation.actorId) === null || safeCode(operation.operationId) === null || safeCode(operation.spaceId) === null || parseCanonicalInstantV1(operation.instant) === null || parseSha256Hex(operation.operationSha256) === null || parseSha256Hex(operation.graphRevisionSha256) === null || parseSha256Hex(operation.recordsSha256) === null)
1442
+ return null;
1443
+ const parentOperationSha256 = operation.parentOperationSha256 === null ? null : parseSha256Hex(operation.parentOperationSha256);
1444
+ const sequence = Number.isSafeInteger(operation.sequence) && operation.sequence > 0 ? operation.sequence : null;
1445
+ if (sequence === null || operation.parentOperationSha256 !== null && parentOperationSha256 === null || sequence === 1 !== (parentOperationSha256 === null))
1446
+ return null;
1447
+ const changes = exactDataArrayV1(operation.changes, OH_GRAPH_LIMITS_V1.changesPerOperation, false);
1448
+ if (changes === null || changes.length === 0)
1449
+ return null;
1450
+ const aggregateDataBudget = {
1451
+ bytes: 0,
1452
+ maximumBytes: OH_OPERATION_MAX_BYTES_V1,
1453
+ maximumNodes: OH_SYNC_INGRESS_OPERATION_NODES_V1,
1454
+ nodes: 0
1455
+ };
1456
+ for (const change of changes) {
1457
+ const put = exactDataRecordV1(change, OH_PUT_CHANGE_KEYS_V1);
1458
+ if (put !== null && put.kind === "put" && put.v === 1) {
1459
+ const record = exactDataRecordV1(put.record, OH_RECORD_KEYS_V1);
1460
+ if (record === null)
1461
+ return null;
1462
+ const valuePreflight = preflightSyncIngressValueV1(record.value, aggregateDataBudget);
1463
+ const dependenciesPreflight = preflightSyncIngressDependenciesV1(record.dependencies, aggregateDataBudget);
1464
+ if (valuePreflight === null || dependenciesPreflight === null)
1465
+ return null;
1466
+ continue;
1467
+ }
1468
+ const tombstone = exactDataRecordV1(change, OH_TOMBSTONE_CHANGE_KEYS_V1);
1469
+ if (tombstone === null || tombstone.kind !== "tombstone" || tombstone.v !== 1)
1470
+ return null;
1471
+ }
1472
+ const detached = boundedSyncIngressV1(operation, {
1473
+ maximumBytes: OH_OPERATION_MAX_BYTES_V1,
1474
+ maximumDepth: OH_SYNC_INGRESS_OPERATION_DEPTH_V1,
1475
+ maximumNodes: OH_SYNC_INGRESS_OPERATION_NODES_V1
1476
+ }, bundleBudget, true);
1477
+ return detached === null ? null : detached.value;
1478
+ }
1479
+ function canonicalStringBytesV1(value, maximumBytes) {
1480
+ if (value.length + 2 > maximumBytes)
1481
+ throw new RangeError("String exceeds its canonical byte budget.");
1482
+ let bytes = 2;
1483
+ for (let index = 0;index < value.length; index += 1) {
1484
+ const code = value.charCodeAt(index);
1485
+ if (code >= 55296 && code <= 56319) {
1486
+ const next = value.charCodeAt(index + 1);
1487
+ if (!(next >= 56320 && next <= 57343))
1488
+ throw new TypeError("Invalid Unicode string.");
1489
+ bytes += 4;
1490
+ index += 1;
1491
+ } else if (code >= 56320 && code <= 57343) {
1492
+ throw new TypeError("Invalid Unicode string.");
1493
+ } else if (code === 34 || code === 92 || code === 8 || code === 9 || code === 10 || code === 12 || code === 13) {
1494
+ bytes += 2;
1495
+ } else if (code <= 31) {
1496
+ bytes += 6;
1497
+ } else if (code <= 127) {
1498
+ bytes += 1;
1499
+ } else if (code <= 2047) {
1500
+ bytes += 2;
1501
+ } else {
1502
+ bytes += 3;
1503
+ }
1504
+ if (bytes > maximumBytes)
1505
+ throw new RangeError("String exceeds its canonical byte budget.");
1506
+ }
1507
+ return bytes;
1508
+ }
1509
+ function boundedSyncIngressV1(value, limits, aggregate, clone) {
1510
+ const ancestors = new Set;
1511
+ const budget = {
1512
+ bytes: 0,
1513
+ maximumBytes: limits.maximumBytes,
1514
+ maximumNodes: limits.maximumNodes,
1515
+ nodes: 0
1516
+ };
1517
+ const canSpendBytes = (count) => budget.bytes + count <= budget.maximumBytes && (aggregate === null || aggregate.bytes + count <= aggregate.maximumBytes);
1518
+ const spendBytes = (count) => {
1519
+ if (!canSpendBytes(count))
1520
+ throw new RangeError("Sync ingress exceeds its canonical byte budget.");
1521
+ budget.bytes += count;
1522
+ if (aggregate !== null)
1523
+ aggregate.bytes += count;
1524
+ };
1525
+ const canSpendNodes = (count) => budget.nodes + count <= budget.maximumNodes && (aggregate === null || aggregate.nodes + count <= aggregate.maximumNodes);
1526
+ const spendNode = () => {
1527
+ if (!canSpendNodes(1))
1528
+ throw new RangeError("Sync ingress exceeds its node budget.");
1529
+ budget.nodes += 1;
1530
+ if (aggregate !== null)
1531
+ aggregate.nodes += 1;
1532
+ };
1533
+ const detach = (candidate, depth) => {
1534
+ if (depth > limits.maximumDepth)
1535
+ throw new RangeError("Sync ingress exceeds its depth budget.");
1536
+ spendNode();
1537
+ if (candidate === null) {
1538
+ spendBytes(4);
1539
+ return candidate;
1540
+ }
1541
+ if (typeof candidate === "boolean") {
1542
+ spendBytes(candidate ? 4 : 5);
1543
+ return candidate;
1544
+ }
1545
+ if (typeof candidate === "string") {
1546
+ spendBytes(canonicalStringBytesV1(candidate, Math.min(budget.maximumBytes - budget.bytes, aggregate === null ? Number.MAX_SAFE_INTEGER : aggregate.maximumBytes - aggregate.bytes)));
1547
+ return candidate;
1548
+ }
1549
+ if (typeof candidate === "number") {
1550
+ if (!Number.isFinite(candidate) || Object.is(candidate, -0))
1551
+ throw new TypeError("Invalid number.");
1552
+ spendBytes(utf8ByteLength(canonicalJson(candidate)));
1553
+ return candidate;
1554
+ }
1555
+ if (typeof candidate !== "object" || isProxy(candidate))
1556
+ throw new TypeError("Invalid data value.");
1557
+ if (ancestors.has(candidate))
1558
+ throw new TypeError("Cyclic data value.");
1559
+ ancestors.add(candidate);
1560
+ try {
1561
+ if (Array.isArray(candidate)) {
1562
+ const lengthDescriptor = Object.getOwnPropertyDescriptor(candidate, "length");
1563
+ const length = lengthDescriptor?.value;
1564
+ if (typeof length !== "number" || !Number.isSafeInteger(length) || length < 0 || !canSpendNodes(length) || !canSpendBytes(2 + Math.max(0, length - 1) + length)) {
1565
+ throw new TypeError("Invalid or over-budget data array.");
1566
+ }
1567
+ const keys2 = Reflect.ownKeys(candidate);
1568
+ if (keys2.length !== length + 1 || !keys2.includes("length") || keys2.some((key) => key !== "length" && (typeof key !== "string" || !/^(?:0|[1-9][0-9]*)$/u.test(key) || Number(key) >= length))) {
1569
+ throw new TypeError("Invalid data array.");
1570
+ }
1571
+ spendBytes(2 + Math.max(0, length - 1));
1572
+ const detached2 = clone ? [] : null;
1573
+ for (let index = 0;index < length; index += 1) {
1574
+ const descriptor = Object.getOwnPropertyDescriptor(candidate, String(index));
1575
+ if (descriptor === undefined || !descriptor.enumerable || descriptor.get !== undefined || descriptor.set !== undefined) {
1576
+ throw new TypeError("Invalid data array entry.");
1577
+ }
1578
+ const item = detach(descriptor.value, depth + 1);
1579
+ detached2?.push(item);
1580
+ }
1581
+ return detached2 === null ? candidate : Object.freeze(detached2);
1582
+ }
1583
+ const prototype = Object.getPrototypeOf(candidate);
1584
+ if (prototype !== Object.prototype && prototype !== null) {
1585
+ throw new TypeError("Invalid data object.");
1586
+ }
1587
+ let containerBytes = 2;
1588
+ let properties = 0;
1589
+ for (const key in candidate) {
1590
+ if (!Object.hasOwn(candidate, key))
1591
+ continue;
1592
+ properties += 1;
1593
+ if (!canSpendNodes(properties))
1594
+ throw new RangeError("Sync ingress exceeds its node budget.");
1595
+ const remainingBytes = Math.min(budget.maximumBytes - budget.bytes - containerBytes - properties, aggregate === null ? Number.MAX_SAFE_INTEGER : aggregate.maximumBytes - aggregate.bytes - containerBytes - properties);
1596
+ containerBytes += (properties === 1 ? 0 : 1) + canonicalStringBytesV1(key, remainingBytes) + 1;
1597
+ if (!canSpendBytes(containerBytes + properties)) {
1598
+ throw new RangeError("Sync ingress exceeds its canonical byte budget.");
1599
+ }
1600
+ }
1601
+ const keys = Reflect.ownKeys(candidate);
1602
+ if (keys.length !== properties || keys.some((key) => typeof key !== "string")) {
1603
+ throw new TypeError("Invalid data object.");
1604
+ }
1605
+ spendBytes(containerBytes);
1606
+ const detached = clone ? Object.create(null) : null;
1607
+ for (const key of keys) {
1608
+ const descriptor = Object.getOwnPropertyDescriptor(candidate, key);
1609
+ if (descriptor === undefined || !descriptor.enumerable || descriptor.get !== undefined || descriptor.set !== undefined) {
1610
+ throw new TypeError("Invalid data property.");
1611
+ }
1612
+ const item = detach(descriptor.value, depth + 1);
1613
+ if (detached !== null) {
1614
+ Object.defineProperty(detached, key, {
1615
+ configurable: false,
1616
+ enumerable: true,
1617
+ value: item,
1618
+ writable: false
1619
+ });
1620
+ }
1621
+ }
1622
+ return detached === null ? candidate : Object.freeze(detached);
1623
+ } finally {
1624
+ ancestors.delete(candidate);
1625
+ }
1626
+ };
1627
+ try {
1628
+ return Object.freeze({ value: detach(value, 0) });
1629
+ } catch {
1630
+ return null;
1631
+ }
1632
+ }
1633
+ function preflightSyncIngressValueV1(value, aggregate) {
1634
+ return boundedSyncIngressV1(value, {
1635
+ maximumBytes: OH_GRAPH_LIMITS_V1.recordBytes,
1636
+ maximumDepth: OH_SYNC_INGRESS_VALUE_DEPTH_V1,
1637
+ maximumNodes: OH_SYNC_INGRESS_VALUE_NODES_V1
1638
+ }, aggregate, false);
1639
+ }
1640
+ function preflightSyncIngressDependenciesV1(value, aggregate) {
1641
+ try {
1642
+ if (typeof value !== "object" || value === null || isProxy(value) || !Array.isArray(value)) {
1643
+ return null;
1644
+ }
1645
+ const length = Object.getOwnPropertyDescriptor(value, "length")?.value;
1646
+ if (typeof length !== "number" || !Number.isSafeInteger(length) || length < 0 || length > OH_GRAPH_LIMITS_V1.dependenciesPerRecord)
1647
+ return null;
1648
+ const maximumBytes = length === 0 ? 2 : 1 + length * 515;
1649
+ return boundedSyncIngressV1(value, {
1650
+ maximumBytes,
1651
+ maximumDepth: 1,
1652
+ maximumNodes: length + 1
1653
+ }, aggregate, false);
1654
+ } catch {
1655
+ return null;
1656
+ }
1657
+ }
1658
+ function syncIngressBundleBudgetV1(spaceId) {
1659
+ const emptyBundle = {
1660
+ bundleSha256: "0".repeat(64),
1661
+ contractSha256: OH_CONTRACT_MANIFEST_V1.contractSha256,
1662
+ operations: [],
1663
+ protocol: OH_SYNC_PROTOCOL_V1,
1664
+ spaceId,
1665
+ v: 1
1666
+ };
1667
+ return {
1668
+ bytes: utf8ByteLength(canonicalJson(emptyBundle)),
1669
+ maximumBytes: OH_SYNC_BUNDLE_MAX_BYTES_V1,
1670
+ maximumNodes: OH_SYNC_INGRESS_BUNDLE_NODES_V1,
1671
+ nodes: 7
1672
+ };
1673
+ }
1674
+ function spendSyncIngressBudgetV1(budget, bytes, nodes = 0) {
1675
+ if (budget.bytes + bytes > budget.maximumBytes || budget.nodes + nodes > budget.maximumNodes)
1676
+ return false;
1677
+ budget.bytes += bytes;
1678
+ budget.nodes += nodes;
1679
+ return true;
1680
+ }
1681
+ function measureSyncOperationV1(operation) {
1682
+ const measurement = {
1683
+ bytes: 0,
1684
+ maximumBytes: OH_OPERATION_MAX_BYTES_V1,
1685
+ maximumNodes: OH_SYNC_INGRESS_OPERATION_NODES_V1,
1686
+ nodes: 0
1687
+ };
1688
+ return boundedSyncIngressV1(operation, {
1689
+ maximumBytes: OH_OPERATION_MAX_BYTES_V1,
1690
+ maximumDepth: OH_SYNC_INGRESS_OPERATION_DEPTH_V1,
1691
+ maximumNodes: OH_SYNC_INGRESS_OPERATION_NODES_V1
1692
+ }, measurement, false) === null ? null : measurement;
1693
+ }
1694
+ function buildOhSyncBundleV1(parsedSpaceId, operations, largestFittingPrefix) {
1695
+ let priorSequence = null;
1696
+ let priorSha256 = null;
1697
+ const parsed = [];
1698
+ const bundleBudget = syncIngressBundleBudgetV1(parsedSpaceId);
1699
+ for (const candidate of operations) {
1700
+ const operation = parseOhOperationV1(candidate);
1701
+ if (operation === null || operation.spaceId !== parsedSpaceId || priorSequence !== null && operation.sequence !== priorSequence + 1 || priorSequence !== null && operation.parentOperationSha256 !== priorSha256) {
1702
+ throw new TypeError("Sync operations must form one ordered chain.");
1703
+ }
1704
+ const measurement = measureSyncOperationV1(operation);
1705
+ if (measurement === null) {
1706
+ throw new RangeError("Sync operation exceeds its canonical byte, node, or depth limit.");
1707
+ }
1708
+ if (!spendSyncIngressBudgetV1(bundleBudget, measurement.bytes + (parsed.length === 0 ? 0 : 1), measurement.nodes)) {
1709
+ if (largestFittingPrefix && parsed.length > 0)
1710
+ break;
1711
+ throw new RangeError("Sync bundle exceeds its canonical byte or node limit.");
1712
+ }
1713
+ parsed.push(operation);
1714
+ priorSequence = operation.sequence;
1715
+ priorSha256 = operation.operationSha256;
1716
+ }
1717
+ const payload = {
1718
+ contractSha256: OH_CONTRACT_MANIFEST_V1.contractSha256,
1719
+ operations: parsed,
1720
+ protocol: OH_SYNC_PROTOCOL_V1,
1721
+ spaceId: parsedSpaceId,
1722
+ v: 1
1723
+ };
1724
+ return { ...payload, bundleSha256: canonicalSha256(payload) };
1725
+ }
1726
+ function parseSyncHeadRefEnvelopeV1(value) {
1727
+ const operationSha256 = value.operationSha256 === null ? null : parseSha256Hex(value.operationSha256);
1728
+ const sequence = Number.isSafeInteger(value.sequence) && value.sequence >= 0 && !Object.is(value.sequence, -0) ? value.sequence : null;
1729
+ return sequence !== null && (value.operationSha256 === null || operationSha256 !== null) && sequence === 0 === (operationSha256 === null) ? { operationSha256, sequence } : null;
1730
+ }
1731
+ function parseOhSyncHeadRefV1(value) {
1732
+ const reference = exactDataRecordV1(value, OH_SYNC_HEAD_REF_KEYS_V1);
1733
+ return reference === null ? null : parseSyncHeadRefEnvelopeV1(reference);
1734
+ }
1735
+ function parseOhSyncHeadV1(value) {
1736
+ const envelope = exactDataRecordV1(value, OH_SYNC_HEAD_KEYS_V1);
1737
+ if (envelope === null || envelope.v !== 1)
1738
+ return null;
1739
+ const reference = parseSyncHeadRefEnvelopeV1(envelope);
1740
+ return reference === null ? null : { ...reference, v: 1 };
1741
+ }
1742
+ function createOhSyncBundleV1(spaceId, operations, options = {}) {
1743
+ const parsedSpaceId = safeCode(spaceId);
1744
+ const largestFittingPrefix = options.largestFittingPrefix ?? false;
1745
+ if (parsedSpaceId === null || operations.length > OH_SYNC_BUNDLE_MAX_OPERATIONS_V1 || typeof largestFittingPrefix !== "boolean") {
1746
+ throw new TypeError("Invalid sync bundle.");
1747
+ }
1748
+ return buildOhSyncBundleV1(parsedSpaceId, operations, largestFittingPrefix);
1749
+ }
1750
+ function parseOhSyncBundleV1(value) {
1751
+ const envelope = exactDataRecordV1(value, OH_SYNC_BUNDLE_KEYS_V1);
1752
+ if (envelope === null || envelope.protocol !== OH_SYNC_PROTOCOL_V1 || envelope.v !== 1)
1753
+ return null;
1754
+ const bundleSha256 = parseSha256Hex(envelope.bundleSha256);
1755
+ const contractSha256 = parseSha256Hex(envelope.contractSha256);
1756
+ const spaceId = safeCode(envelope.spaceId);
1757
+ if (bundleSha256 === null || contractSha256 !== OH_CONTRACT_MANIFEST_V1.contractSha256 || spaceId === null)
1758
+ return null;
1759
+ const operations = exactDataArrayV1(envelope.operations, OH_SYNC_BUNDLE_MAX_OPERATIONS_V1);
1760
+ if (operations === null)
1761
+ return null;
1762
+ const detachedOperations = [];
1763
+ const bundleBudget = syncIngressBundleBudgetV1(spaceId);
1764
+ for (const operation of operations) {
1765
+ if (detachedOperations.length > 0 && !spendSyncIngressBudgetV1(bundleBudget, 1))
1766
+ return null;
1767
+ const detached = stagedSyncOperationV1(operation, bundleBudget);
1768
+ if (detached === null)
1769
+ return null;
1770
+ detachedOperations.push(detached);
1771
+ }
1772
+ try {
1773
+ const created = createOhSyncBundleV1(spaceId, detachedOperations);
1774
+ if (detachedOperations.some((operation, index) => canonicalJson(operation) !== canonicalJson(created.operations[index])))
1775
+ return null;
1776
+ return created.bundleSha256 === bundleSha256 ? { ...created, bundleSha256 } : null;
1777
+ } catch {
1778
+ return null;
1779
+ }
1780
+ }
1781
+ async function synchronizeOhStoreV1(store, transport, options = {}) {
1782
+ const batchSize = options.batchSize ?? 100;
1783
+ const maximumRounds = options.maximumRounds ?? 100;
1784
+ const remoteId = safeCode(options.remoteId ?? "default");
1785
+ if (!Number.isSafeInteger(batchSize) || batchSize < 1 || batchSize > 1000 || !Number.isSafeInteger(maximumRounds) || maximumRounds < 1 || maximumRounds > 1e4 || remoteId === null)
1786
+ throw new TypeError("Invalid sync options.");
1787
+ await transport.handshake(OH_CONTRACT_MANIFEST_V1);
1788
+ let pulled = 0;
1789
+ let pushed = 0;
1790
+ const settled = (head, rounds) => {
1791
+ store.updateSyncState(remoteId, {
1792
+ pulledSequence: head.sequence,
1793
+ pushedSequence: head.sequence,
1794
+ remoteHeadSha256: head.operationSha256
1795
+ });
1796
+ return { head, pulled, pushed, rounds, v: 1 };
1797
+ };
1798
+ for (let round = 1;round <= maximumRounds; round += 1) {
1799
+ const remote = parseOhSyncHeadV1(await transport.head(store.spaceId));
1800
+ if (remote === null)
1801
+ throw new Error("The sync transport returned an invalid head.");
1802
+ const local = store.head();
1803
+ if (local.sequence === remote.sequence) {
1804
+ if (local.operationSha256 !== remote.operationSha256) {
1805
+ throw new Error("Sync conflict: equal sequence numbers have different heads.");
1806
+ }
1807
+ return settled(remote, round);
1808
+ }
1809
+ if (local.sequence < remote.sequence) {
1810
+ const bundle = parseOhSyncBundleV1(await transport.pull(store.spaceId, local.sequence, batchSize));
1811
+ const terminal = bundle?.operations.at(-1);
1812
+ if (bundle === null || bundle.operations.length === 0 || bundle.operations.length > batchSize || bundle.spaceId !== store.spaceId || bundle.operations[0]?.sequence !== local.sequence + 1 || bundle.operations[0]?.parentOperationSha256 !== local.operationSha256 || terminal === undefined || terminal.sequence > remote.sequence || terminal.sequence === remote.sequence && terminal.operationSha256 !== remote.operationSha256) {
1813
+ throw new Error("Sync conflict: remote history does not extend the local head.");
1814
+ }
1815
+ store.importOperations({
1816
+ expectedHead: {
1817
+ operationSha256: local.operationSha256,
1818
+ sequence: local.sequence
1819
+ },
1820
+ operations: bundle.operations
1821
+ });
1822
+ pulled += bundle.operations.length;
1823
+ const afterPull = store.head();
1824
+ if (afterPull.sequence === remote.sequence && afterPull.operationSha256 === remote.operationSha256) {
1825
+ const confirmed = parseOhSyncHeadV1(await transport.head(store.spaceId));
1826
+ if (confirmed === null)
1827
+ throw new Error("The sync transport returned an invalid head.");
1828
+ const confirmedLocal = store.head();
1829
+ if (confirmed.sequence === remote.sequence && confirmed.operationSha256 === remote.operationSha256 && confirmedLocal.sequence === confirmed.sequence && confirmedLocal.operationSha256 === confirmed.operationSha256) {
1830
+ return settled(confirmed, round);
1831
+ }
1832
+ }
1833
+ } else {
1834
+ const candidates = store.changesSince({
1835
+ operationSha256: remote.operationSha256,
1836
+ sequence: remote.sequence
1837
+ }, {
1838
+ limit: batchSize,
1839
+ through: {
1840
+ operationSha256: local.operationSha256,
1841
+ sequence: local.sequence
1842
+ }
1843
+ }).operations;
1844
+ if (candidates.length === 0 || candidates[0]?.sequence !== remote.sequence + 1 || candidates[0]?.parentOperationSha256 !== remote.operationSha256) {
1845
+ throw new Error("Sync conflict: local history does not extend the remote head.");
1846
+ }
1847
+ const bundle = createOhSyncBundleV1(store.spaceId, candidates, {
1848
+ largestFittingPrefix: true
1849
+ });
1850
+ const operations = bundle.operations;
1851
+ const head = parseOhSyncHeadV1(await transport.push(bundle));
1852
+ if (head === null)
1853
+ throw new Error("The sync transport returned an invalid push head.");
1854
+ if (head.sequence !== operations.at(-1)?.sequence || head.operationSha256 !== operations.at(-1)?.operationSha256) {
1855
+ throw new Error("The sync transport acknowledged a different head.");
1856
+ }
1857
+ pushed += operations.length;
1858
+ const afterPush = store.head();
1859
+ if (afterPush.sequence === head.sequence && afterPush.operationSha256 === head.operationSha256) {
1860
+ const confirmed = parseOhSyncHeadV1(await transport.head(store.spaceId));
1861
+ if (confirmed === null)
1862
+ throw new Error("The sync transport returned an invalid head.");
1863
+ const confirmedLocal = store.head();
1864
+ if (confirmed.sequence === head.sequence && confirmed.operationSha256 === head.operationSha256 && confirmedLocal.sequence === confirmed.sequence && confirmedLocal.operationSha256 === confirmed.operationSha256) {
1865
+ return settled(confirmed, round);
1866
+ }
1867
+ }
1868
+ }
1460
1869
  }
1870
+ throw new Error("Sync did not settle within maximumRounds.");
1871
+ }
1872
+ function rowValue(row, key, index) {
1873
+ return Array.isArray(row) ? row[index] : row[key];
1874
+ }
1875
+ function createLibSqlOperationSyncTransportV1(client) {
1876
+ let ready = null;
1877
+ const setup = async (manifest) => {
1878
+ if (parseOhContractManifestV1(manifest) === null)
1879
+ throw new Error("Unsupported contract manifest.");
1880
+ await client.batch([
1881
+ { sql: `CREATE TABLE IF NOT EXISTS oh_sync_contracts (
1882
+ contract_id TEXT PRIMARY KEY, contract_sha256 TEXT NOT NULL, manifest_json TEXT NOT NULL
1883
+ ) STRICT` },
1884
+ { sql: `CREATE TABLE IF NOT EXISTS oh_sync_operations (
1885
+ space_id TEXT NOT NULL, sequence INTEGER NOT NULL, operation_sha256 TEXT NOT NULL UNIQUE,
1886
+ operation_json TEXT NOT NULL, PRIMARY KEY(space_id, sequence)
1887
+ ) STRICT` },
1888
+ {
1889
+ sql: "INSERT INTO oh_sync_contracts(contract_id, contract_sha256, manifest_json) VALUES (?, ?, ?) ON CONFLICT(contract_id) DO NOTHING",
1890
+ args: [manifest.contractId, manifest.contractSha256, canonicalJson(manifest)]
1891
+ }
1892
+ ], "write");
1893
+ const result = await client.execute({
1894
+ sql: "SELECT contract_sha256, manifest_json FROM oh_sync_contracts WHERE contract_id = ?",
1895
+ args: [manifest.contractId]
1896
+ });
1897
+ const row = result.rows[0];
1898
+ if (row === undefined || rowValue(row, "contract_sha256", 0) !== manifest.contractSha256 || rowValue(row, "manifest_json", 1) !== canonicalJson(manifest)) {
1899
+ throw new Error("Remote contract manifest mismatch.");
1900
+ }
1901
+ };
1902
+ const ensure = (manifest = OH_CONTRACT_MANIFEST_V1) => {
1903
+ ready ??= setup(manifest).catch((error) => {
1904
+ ready = null;
1905
+ throw error;
1906
+ });
1907
+ return ready;
1908
+ };
1909
+ const head = async (spaceId) => {
1910
+ await ensure();
1911
+ const result = await client.execute({ sql: `SELECT sequence, operation_sha256 FROM oh_sync_operations
1912
+ WHERE space_id = ? ORDER BY sequence DESC LIMIT 1`, args: [spaceId] });
1913
+ const row = result.rows[0];
1914
+ if (row === undefined)
1915
+ return { operationSha256: null, sequence: 0, v: 1 };
1916
+ const sequence = Number(rowValue(row, "sequence", 0));
1917
+ const operationSha256 = parseSha256Hex(rowValue(row, "operation_sha256", 1));
1918
+ if (!Number.isSafeInteger(sequence) || sequence < 1 || operationSha256 === null)
1919
+ throw new Error("Invalid remote head.");
1920
+ return { operationSha256, sequence, v: 1 };
1921
+ };
1922
+ return {
1923
+ handshake: async (manifest) => {
1924
+ const parsed = parseOhContractManifestV1(manifest);
1925
+ if (parsed === null)
1926
+ throw new Error("Unsupported contract manifest.");
1927
+ await ensure(parsed);
1928
+ },
1929
+ head,
1930
+ pull: async (spaceId, afterSequence, limit) => {
1931
+ const parsedSpaceId = safeCode(spaceId);
1932
+ if (parsedSpaceId === null || !Number.isSafeInteger(afterSequence) || afterSequence < 0 || Object.is(afterSequence, -0) || !Number.isSafeInteger(limit) || limit < 1 || limit > OH_SYNC_BUNDLE_MAX_OPERATIONS_V1) {
1933
+ throw new TypeError("Invalid sync pull request.");
1934
+ }
1935
+ await ensure();
1936
+ const bundleBudget = syncIngressBundleBudgetV1(parsedSpaceId);
1937
+ const result = await client.execute({
1938
+ sql: `SELECT operation_json FROM (
1939
+ SELECT sequence, operation_json,
1940
+ row_number() OVER (ORDER BY sequence) AS ordinal,
1941
+ sum(length(CAST(operation_json AS BLOB))) OVER (
1942
+ ORDER BY sequence ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
1943
+ ) AS cumulative_bytes
1944
+ FROM (
1945
+ SELECT sequence, operation_json FROM oh_sync_operations
1946
+ WHERE space_id = ? AND sequence > ? ORDER BY sequence LIMIT ?
1947
+ )
1948
+ ) WHERE ordinal = 1 OR cumulative_bytes + ordinal - 1 <= ? ORDER BY sequence`,
1949
+ args: [
1950
+ parsedSpaceId,
1951
+ afterSequence,
1952
+ limit,
1953
+ bundleBudget.maximumBytes - bundleBudget.bytes
1954
+ ]
1955
+ });
1956
+ const rows = result.rows;
1957
+ if (!Array.isArray(rows) || rows.length > limit) {
1958
+ throw new Error("Remote sync pull exceeded its requested row limit.");
1959
+ }
1960
+ const operations = rows.map((row) => {
1961
+ const json = rowValue(row, "operation_json", 0);
1962
+ if (typeof json !== "string")
1963
+ throw new Error("Invalid remote operation JSON.");
1964
+ if (utf8ByteLength(json) > OH_OPERATION_MAX_BYTES_V1) {
1965
+ throw new Error("Remote operation JSON exceeds the canonical operation byte limit.");
1966
+ }
1967
+ const operation = parseOhOperationV1(JSON.parse(json));
1968
+ if (operation === null || canonicalJson(operation) !== json)
1969
+ throw new Error("Invalid remote operation.");
1970
+ return operation;
1971
+ });
1972
+ return createOhSyncBundleV1(parsedSpaceId, operations);
1973
+ },
1974
+ push: async (value) => {
1975
+ await ensure();
1976
+ const bundle = parseOhSyncBundleV1(value);
1977
+ if (bundle === null)
1978
+ throw new Error("Invalid outgoing sync bundle.");
1979
+ if (bundle.operations.length === 0)
1980
+ return head(bundle.spaceId);
1981
+ const remote = await head(bundle.spaceId);
1982
+ const first = bundle.operations[0];
1983
+ const last = bundle.operations.at(-1);
1984
+ if (remote.sequence >= last.sequence) {
1985
+ const result = await client.execute({ sql: `SELECT sequence, operation_sha256, operation_json
1986
+ FROM oh_sync_operations WHERE space_id = ? AND sequence >= ? AND sequence <= ?
1987
+ ORDER BY sequence LIMIT ?`, args: [
1988
+ bundle.spaceId,
1989
+ first.sequence,
1990
+ last.sequence,
1991
+ bundle.operations.length
1992
+ ] });
1993
+ const rows = result.rows;
1994
+ if (!Array.isArray(rows) || rows.length !== bundle.operations.length) {
1995
+ throw new Error("Sync conflict: remote history does not contain the exact pushed operations.");
1996
+ }
1997
+ for (let index = 0;index < bundle.operations.length; index += 1) {
1998
+ const operation = bundle.operations[index];
1999
+ const row = rows[index];
2000
+ if (row === undefined) {
2001
+ throw new Error("Sync conflict: remote history does not contain the exact pushed operations.");
2002
+ }
2003
+ const sequence = Number(rowValue(row, "sequence", 0));
2004
+ const operationSha256 = parseSha256Hex(rowValue(row, "operation_sha256", 1));
2005
+ const operationJson = rowValue(row, "operation_json", 2);
2006
+ const expectedJson = canonicalJson(operation);
2007
+ if (sequence !== operation.sequence || operationSha256 !== operation.operationSha256 || typeof operationJson !== "string" || utf8ByteLength(operationJson) > OH_OPERATION_MAX_BYTES_V1 || operationJson !== expectedJson) {
2008
+ throw new Error("Sync conflict: remote history differs from the pushed operations.");
2009
+ }
2010
+ }
2011
+ return { operationSha256: last.operationSha256, sequence: last.sequence, v: 1 };
2012
+ }
2013
+ if (first.sequence !== remote.sequence + 1 || first.parentOperationSha256 !== remote.operationSha256) {
2014
+ throw new Error("Sync conflict: pushed history does not extend the remote head.");
2015
+ }
2016
+ await client.batch(bundle.operations.map((operation) => ({
2017
+ sql: "INSERT INTO oh_sync_operations(space_id, sequence, operation_sha256, operation_json) VALUES (?, ?, ?, ?)",
2018
+ args: [bundle.spaceId, operation.sequence, operation.operationSha256, canonicalJson(operation)]
2019
+ })), "write");
2020
+ return { operationSha256: last.operationSha256, sequence: last.sequence, v: 1 };
2021
+ }
2022
+ };
2023
+ }
2024
+
2025
+ // src/sqlite/driver.ts
2026
+ import { existsSync } from "fs";
2027
+ import { Database } from "bun:sqlite";
2028
+
2029
+ // src/sqlite/runtime.ts
2030
+ var MACOS_SQLITE_LIBRARY_CANDIDATES = Object.freeze([
2031
+ "/opt/homebrew/opt/sqlite/lib/libsqlite3.dylib",
2032
+ "/usr/local/opt/sqlite/lib/libsqlite3.dylib"
2033
+ ]);
2034
+ function macosSqliteLibraryCandidates() {
2035
+ return MACOS_SQLITE_LIBRARY_CANDIDATES;
2036
+ }
2037
+ function createOhSqliteRuntime(dependencies) {
2038
+ let customLibrary = null;
2039
+ if (dependencies.platform === "darwin") {
2040
+ for (const candidate of macosSqliteLibraryCandidates()) {
2041
+ if (!dependencies.exists(candidate))
2042
+ continue;
2043
+ try {
2044
+ if (!dependencies.setCustomSQLite(candidate))
2045
+ continue;
2046
+ customLibrary = candidate;
2047
+ break;
2048
+ } catch {}
2049
+ }
2050
+ }
2051
+ return Object.freeze({
2052
+ customLibrary,
2053
+ open: (path) => dependencies.open(path, { create: true, strict: true })
2054
+ });
2055
+ }
2056
+
2057
+ // src/sqlite/driver.ts
2058
+ var SQLITE_RUNTIME = createOhSqliteRuntime({
2059
+ exists: existsSync,
2060
+ open: (path, options) => new Database(path, options),
2061
+ platform: process.platform,
2062
+ setCustomSQLite: (path) => Database.setCustomSQLite(path)
2063
+ });
2064
+ function openOhSqliteDatabase(path) {
2065
+ const database = SQLITE_RUNTIME.open(path);
2066
+ database.exec("PRAGMA foreign_keys = ON");
2067
+ database.exec("PRAGMA journal_mode = WAL");
2068
+ database.exec("PRAGMA synchronous = NORMAL");
2069
+ database.exec("PRAGMA busy_timeout = 5000");
2070
+ database.exec("PRAGMA trusted_schema = OFF");
2071
+ return database;
1461
2072
  }
1462
-
1463
- class OhIntegrityError extends Error {
1464
- constructor(message) {
1465
- super(message);
1466
- this.name = "OhIntegrityError";
2073
+ function withImmediateTransaction(database, work) {
2074
+ database.exec("BEGIN IMMEDIATE");
2075
+ try {
2076
+ const result = work();
2077
+ database.exec("COMMIT");
2078
+ return result;
2079
+ } catch (error) {
2080
+ try {
2081
+ database.exec("ROLLBACK");
2082
+ } catch {}
2083
+ throw error;
1467
2084
  }
1468
2085
  }
1469
-
1470
- class OhDependencyError extends Error {
1471
- constructor(message) {
1472
- super(message);
1473
- this.name = "OhDependencyError";
2086
+ function withReadTransaction(database, work) {
2087
+ database.exec("BEGIN");
2088
+ try {
2089
+ const result = work();
2090
+ database.exec("COMMIT");
2091
+ return result;
2092
+ } catch (error) {
2093
+ try {
2094
+ database.exec("ROLLBACK");
2095
+ } catch {}
2096
+ throw error;
1474
2097
  }
1475
2098
  }
2099
+ // src/sqlite/migrations.ts
2100
+ var OH_SQLITE_SCHEMA_VERSION = 2;
2101
+ var OH_SQLITE_MIGRATIONS = Object.freeze([
2102
+ Object.freeze({
2103
+ name: "0001_oh_core",
2104
+ version: 1,
2105
+ sql: `
2106
+ CREATE TABLE oh_contracts (
2107
+ contract_id TEXT PRIMARY KEY,
2108
+ contract_sha256 TEXT NOT NULL CHECK(length(contract_sha256) = 64),
2109
+ manifest_json TEXT NOT NULL CHECK(json_valid(manifest_json)),
2110
+ created_at TEXT NOT NULL
2111
+ ) STRICT;
1476
2112
 
1477
- class OhProfileError extends Error {
1478
- constructor(message) {
1479
- super(message);
1480
- this.name = "OhProfileError";
2113
+ CREATE TABLE oh_spaces (
2114
+ space_id TEXT PRIMARY KEY,
2115
+ contract_id TEXT NOT NULL REFERENCES oh_contracts(contract_id),
2116
+ generation INTEGER NOT NULL CHECK(generation >= 0),
2117
+ head_operation_sha256 TEXT CHECK(head_operation_sha256 IS NULL OR length(head_operation_sha256) = 64),
2118
+ graph_revision_sha256 TEXT CHECK(graph_revision_sha256 IS NULL OR length(graph_revision_sha256) = 64),
2119
+ records_sha256 TEXT NOT NULL CHECK(length(records_sha256) = 64),
2120
+ sequence INTEGER NOT NULL CHECK(sequence >= 0),
2121
+ created_at TEXT NOT NULL,
2122
+ updated_at TEXT NOT NULL,
2123
+ CHECK(generation = sequence),
2124
+ CHECK((sequence = 0) = (head_operation_sha256 IS NULL)),
2125
+ CHECK((sequence = 0) = (graph_revision_sha256 IS NULL))
2126
+ ) STRICT;
2127
+
2128
+ CREATE TABLE oh_operations (
2129
+ operation_sha256 TEXT PRIMARY KEY CHECK(length(operation_sha256) = 64),
2130
+ space_id TEXT NOT NULL REFERENCES oh_spaces(space_id),
2131
+ sequence INTEGER NOT NULL CHECK(sequence > 0),
2132
+ operation_id TEXT NOT NULL,
2133
+ parent_operation_sha256 TEXT CHECK(parent_operation_sha256 IS NULL OR length(parent_operation_sha256) = 64),
2134
+ graph_revision_sha256 TEXT NOT NULL CHECK(length(graph_revision_sha256) = 64),
2135
+ records_sha256 TEXT NOT NULL CHECK(length(records_sha256) = 64),
2136
+ operation_json TEXT NOT NULL CHECK(json_valid(operation_json)),
2137
+ instant TEXT NOT NULL,
2138
+ UNIQUE(space_id, sequence),
2139
+ UNIQUE(space_id, operation_id)
2140
+ ) STRICT;
2141
+
2142
+ CREATE TABLE oh_operation_records (
2143
+ operation_sha256 TEXT NOT NULL REFERENCES oh_operations(operation_sha256),
2144
+ ordinal INTEGER NOT NULL CHECK(ordinal >= 0),
2145
+ record_key TEXT NOT NULL,
2146
+ change_kind TEXT NOT NULL CHECK(change_kind IN ('put', 'tombstone')),
2147
+ record_sha256 TEXT CHECK(record_sha256 IS NULL OR length(record_sha256) = 64),
2148
+ PRIMARY KEY(operation_sha256, ordinal),
2149
+ UNIQUE(operation_sha256, record_key)
2150
+ ) STRICT;
2151
+
2152
+ CREATE TABLE oh_records (
2153
+ space_id TEXT NOT NULL REFERENCES oh_spaces(space_id),
2154
+ record_key TEXT NOT NULL,
2155
+ kind TEXT NOT NULL,
2156
+ record_sha256 TEXT NOT NULL CHECK(length(record_sha256) = 64),
2157
+ record_json TEXT NOT NULL CHECK(json_valid(record_json)),
2158
+ operation_sha256 TEXT NOT NULL REFERENCES oh_operations(operation_sha256),
2159
+ sequence INTEGER NOT NULL CHECK(sequence > 0),
2160
+ PRIMARY KEY(space_id, record_key)
2161
+ ) STRICT;
2162
+
2163
+ CREATE TABLE oh_dependencies (
2164
+ space_id TEXT NOT NULL,
2165
+ record_key TEXT NOT NULL,
2166
+ dependency_key TEXT NOT NULL,
2167
+ PRIMARY KEY(space_id, record_key, dependency_key),
2168
+ FOREIGN KEY(space_id, record_key) REFERENCES oh_records(space_id, record_key) ON DELETE CASCADE,
2169
+ FOREIGN KEY(space_id, dependency_key) REFERENCES oh_records(space_id, record_key)
2170
+ ) STRICT;
2171
+
2172
+ CREATE TABLE oh_sync_outbox (
2173
+ space_id TEXT NOT NULL REFERENCES oh_spaces(space_id),
2174
+ sequence INTEGER NOT NULL,
2175
+ operation_sha256 TEXT NOT NULL REFERENCES oh_operations(operation_sha256),
2176
+ PRIMARY KEY(space_id, sequence),
2177
+ UNIQUE(operation_sha256)
2178
+ ) STRICT;
2179
+
2180
+ CREATE TABLE oh_sync_state (
2181
+ remote_id TEXT NOT NULL,
2182
+ space_id TEXT NOT NULL REFERENCES oh_spaces(space_id),
2183
+ pulled_sequence INTEGER NOT NULL CHECK(pulled_sequence >= 0),
2184
+ pushed_sequence INTEGER NOT NULL CHECK(pushed_sequence >= 0),
2185
+ remote_head_sha256 TEXT CHECK(remote_head_sha256 IS NULL OR length(remote_head_sha256) = 64),
2186
+ updated_at TEXT NOT NULL,
2187
+ PRIMARY KEY(remote_id, space_id)
2188
+ ) STRICT;
2189
+
2190
+ CREATE TABLE oh_search_documents (
2191
+ space_id TEXT NOT NULL,
2192
+ record_key TEXT NOT NULL,
2193
+ record_sha256 TEXT NOT NULL CHECK(length(record_sha256) = 64),
2194
+ text TEXT NOT NULL,
2195
+ PRIMARY KEY(space_id, record_key),
2196
+ FOREIGN KEY(space_id, record_key) REFERENCES oh_records(space_id, record_key) ON DELETE CASCADE
2197
+ ) STRICT;
2198
+
2199
+ CREATE VIRTUAL TABLE oh_search_fts USING fts5(
2200
+ space_id UNINDEXED,
2201
+ record_key UNINDEXED,
2202
+ text,
2203
+ tokenize='unicode61 remove_diacritics 2'
2204
+ );
2205
+
2206
+ CREATE INDEX oh_operations_space_sequence ON oh_operations(space_id, sequence);
2207
+ CREATE INDEX oh_records_space_kind ON oh_records(space_id, kind, record_key);
2208
+ CREATE INDEX oh_dependencies_dependency ON oh_dependencies(space_id, dependency_key);
2209
+ `
2210
+ }),
2211
+ Object.freeze({
2212
+ name: "0002_store_realms",
2213
+ version: 2,
2214
+ sql: `
2215
+ CREATE TABLE oh_space_bindings (
2216
+ space_id TEXT PRIMARY KEY REFERENCES oh_spaces(space_id),
2217
+ realm_id TEXT NOT NULL,
2218
+ profile_id TEXT NOT NULL,
2219
+ profile_kind TEXT NOT NULL CHECK(profile_kind IN ('canonical', 'working')),
2220
+ profile_sha256 TEXT NOT NULL CHECK(length(profile_sha256) = 64),
2221
+ binding_sha256 TEXT NOT NULL UNIQUE CHECK(length(binding_sha256) = 64),
2222
+ binding_json TEXT NOT NULL CHECK(json_valid(binding_json)),
2223
+ created_at TEXT NOT NULL
2224
+ ) STRICT;
2225
+
2226
+ CREATE TABLE oh_space_purges (
2227
+ space_id TEXT PRIMARY KEY,
2228
+ binding_sha256 TEXT NOT NULL CHECK(length(binding_sha256) = 64),
2229
+ prior_operation_sha256 TEXT CHECK(prior_operation_sha256 IS NULL OR length(prior_operation_sha256) = 64),
2230
+ prior_sequence INTEGER NOT NULL CHECK(prior_sequence >= 0),
2231
+ purged_at TEXT NOT NULL,
2232
+ receipt_sha256 TEXT NOT NULL UNIQUE CHECK(length(receipt_sha256) = 64),
2233
+ receipt_json TEXT NOT NULL CHECK(json_valid(receipt_json))
2234
+ ) STRICT;
2235
+ `
2236
+ })
2237
+ ]);
2238
+ function applyOhSqliteMigrations(database) {
2239
+ database.exec(`CREATE TABLE IF NOT EXISTS oh_migrations (
2240
+ version INTEGER PRIMARY KEY,
2241
+ name TEXT NOT NULL UNIQUE,
2242
+ migration_sha256 TEXT NOT NULL CHECK(length(migration_sha256) = 64),
2243
+ applied_at TEXT NOT NULL
2244
+ ) STRICT`);
2245
+ const select = database.query("SELECT name, migration_sha256 FROM oh_migrations WHERE version = ?");
2246
+ const insert = database.query("INSERT INTO oh_migrations(version, name, migration_sha256, applied_at) VALUES (?, ?, ?, ?)");
2247
+ for (const migration of OH_SQLITE_MIGRATIONS) {
2248
+ const digest = sha256Hex(migration.sql);
2249
+ database.exec("BEGIN IMMEDIATE");
2250
+ try {
2251
+ const existing = select.get(migration.version);
2252
+ if (existing !== null) {
2253
+ if (existing.name !== migration.name || existing.migration_sha256 !== digest) {
2254
+ throw new Error(`SQLite migration ${migration.version} does not match the applied migration.`);
2255
+ }
2256
+ } else {
2257
+ database.exec(migration.sql);
2258
+ insert.run(migration.version, migration.name, digest, canonicalNow());
2259
+ }
2260
+ database.exec("COMMIT");
2261
+ } catch (error) {
2262
+ try {
2263
+ database.exec("ROLLBACK");
2264
+ } catch {}
2265
+ throw error;
2266
+ }
1481
2267
  }
1482
2268
  }
2269
+ // src/sqlite/port.ts
2270
+ import { isProxy as isProxy2 } from "util/types";
2271
+ // src/store.ts
1483
2272
  var OH_CANONICAL_STORE_PROFILE_V1 = createOhStoreProfileV1({
1484
2273
  applicationProfileSha256: null,
1485
2274
  capabilities: {
@@ -1792,6 +2581,8 @@ function transitionOhSnapshotV1(input) {
1792
2581
  sequence: head.sequence + 1,
1793
2582
  spaceId,
1794
2583
  v: 1
2584
+ }, input.maximumOperationBytes === undefined ? {} : {
2585
+ maximumOperationBytes: input.maximumOperationBytes
1795
2586
  });
1796
2587
  const nextHead = {
1797
2588
  generation: operation.sequence,
@@ -2123,6 +2914,9 @@ function normalizeLimit(value, fallback = 50, maximum = 1000) {
2123
2914
  }
2124
2915
  return value;
2125
2916
  }
2917
+ function exactHeadRef(left, right) {
2918
+ return left.sequence === right.sequence && left.operationSha256 === right.operationSha256;
2919
+ }
2126
2920
  function ftsQuery(value) {
2127
2921
  const normalized = boundedText(value.normalize("NFC"), 4096);
2128
2922
  if (normalized === null)
@@ -2239,8 +3033,7 @@ class OhSqliteStore {
2239
3033
  }
2240
3034
  return records;
2241
3035
  }
2242
- #transition(head, changes, operationId) {
2243
- const records = this.#loadRecords();
3036
+ #transition(head, changes, operationId, records = this.#loadRecords()) {
2244
3037
  for (const change of changes) {
2245
3038
  if (change.kind === "put") {
2246
3039
  records.set(change.record.key, change.record);
@@ -2297,7 +3090,7 @@ class OhSqliteStore {
2297
3090
  if (operation.sequence < 1 || operation.sequence > head.sequence) {
2298
3091
  throw new OhIntegrityError("A stored idempotent operation is not reachable from the current head.");
2299
3092
  }
2300
- const rows = this.database.query(`SELECT operation_sha256, parent_operation_sha256, sequence
3093
+ const rows = this.database.query(`SELECT ${OPERATION_COLUMNS}
2301
3094
  FROM oh_operations WHERE space_id = ? AND sequence >= ? AND sequence <= ? ORDER BY sequence`).all(this.spaceId, operation.sequence, head.sequence);
2302
3095
  if (rows.length !== head.sequence - operation.sequence + 1) {
2303
3096
  throw new OhIntegrityError("A stored idempotent operation has an incomplete path to the current head.");
@@ -2305,10 +3098,14 @@ class OhSqliteStore {
2305
3098
  let priorSha256 = operation.parentOperationSha256;
2306
3099
  for (let index = 0;index < rows.length; index += 1) {
2307
3100
  const row = rows[index];
2308
- if (row === undefined || row.sequence !== operation.sequence + index || row.parent_operation_sha256 !== priorSha256 || index === 0 && row.operation_sha256 !== operation.operationSha256) {
3101
+ if (row === undefined) {
3102
+ throw new OhIntegrityError("A stored idempotent operation is not on the current authority chain.");
3103
+ }
3104
+ const reachable = parseStoredOperationRow(row, { spaceId: this.spaceId });
3105
+ if (reachable.sequence !== operation.sequence + index || reachable.parentOperationSha256 !== priorSha256 || index === 0 && reachable.operationSha256 !== operation.operationSha256) {
2309
3106
  throw new OhIntegrityError("A stored idempotent operation is not on the current authority chain.");
2310
3107
  }
2311
- priorSha256 = row.operation_sha256;
3108
+ priorSha256 = reachable.operationSha256;
2312
3109
  }
2313
3110
  if (priorSha256 !== head.operationSha256) {
2314
3111
  throw new OhIntegrityError("A stored idempotent operation does not reach the current head digest.");
@@ -2369,8 +3166,10 @@ class OhSqliteStore {
2369
3166
  this.#assertOpen();
2370
3167
  const actorId = safeCode(input.actorId);
2371
3168
  const operationId = safeCode(input.operationId);
2372
- if (actorId === null || operationId === null)
2373
- throw new TypeError("Invalid actor or operation ID.");
3169
+ const maximumOperationBytes = input.maximumOperationBytes ?? OH_OPERATION_MAX_BYTES_V1;
3170
+ if (actorId === null || operationId === null || !Number.isSafeInteger(maximumOperationBytes) || maximumOperationBytes < 1 || maximumOperationBytes > OH_OPERATION_MAX_BYTES_V1) {
3171
+ throw new TypeError("Invalid actor, operation ID, or operation byte bound.");
3172
+ }
2374
3173
  const changes = canonicalKnowledgeGraphChangesV1(input.changes);
2375
3174
  if (changes.length === 0 || changes.length > 8192)
2376
3175
  throw new TypeError("A commit needs 1 through 8192 changes.");
@@ -2384,6 +3183,10 @@ class OhSqliteStore {
2384
3183
  if (existing.actorId !== actorId || canonicalJson(existing.changes) !== canonicalJson(changes)) {
2385
3184
  throw new OhConflictError("The operation ID is already bound to different content.");
2386
3185
  }
3186
+ const operationBytes = Buffer.byteLength(canonicalJson(existing), "utf8");
3187
+ if (operationBytes > maximumOperationBytes) {
3188
+ throw new OhOperationSizeError(operationBytes, maximumOperationBytes);
3189
+ }
2387
3190
  return existing;
2388
3191
  }
2389
3192
  if (head.generation !== input.expectedHead.generation || head.operationSha256 !== input.expectedHead.operationSha256) {
@@ -2402,6 +3205,8 @@ class OhSqliteStore {
2402
3205
  sequence: head.sequence + 1,
2403
3206
  spaceId: this.spaceId,
2404
3207
  v: 1
3208
+ }, {
3209
+ maximumOperationBytes
2405
3210
  });
2406
3211
  this.#persist(operation);
2407
3212
  return operation;
@@ -2413,30 +3218,84 @@ class OhSqliteStore {
2413
3218
  const operation = parseOhOperationV1(value);
2414
3219
  if (operation === null || operation.spaceId !== this.spaceId)
2415
3220
  throw new OhIntegrityError("Invalid imported operation.");
3221
+ const result = this.importOperations({
3222
+ expectedHead: {
3223
+ operationSha256: operation.parentOperationSha256,
3224
+ sequence: operation.sequence - 1
3225
+ },
3226
+ operations: [operation]
3227
+ });
3228
+ return { imported: result.imported === 1, operation };
3229
+ }
3230
+ importOperations(input) {
3231
+ this.#assertOpen();
3232
+ this.#assertOperationReplication();
3233
+ const expectedHead = parseOhHeadRefV1(input.expectedHead);
3234
+ if (expectedHead === null || !Array.isArray(input.operations) || input.operations.length > 1000) {
3235
+ throw new TypeError("Invalid operation import interval.");
3236
+ }
3237
+ const operations = input.operations.map((value) => {
3238
+ const operation = parseOhOperationV1(value);
3239
+ if (operation === null || operation.spaceId !== this.spaceId) {
3240
+ throw new OhIntegrityError("Invalid imported operation.");
3241
+ }
3242
+ return operation;
3243
+ });
3244
+ let prior = expectedHead;
3245
+ for (const operation of operations) {
3246
+ if (operation.sequence !== prior.sequence + 1 || operation.parentOperationSha256 !== prior.operationSha256) {
3247
+ throw new OhConflictError("Imported operations do not extend the expected head.");
3248
+ }
3249
+ prior = { operationSha256: operation.operationSha256, sequence: operation.sequence };
3250
+ }
2416
3251
  return withImmediateTransaction(this.database, () => {
2417
- const head = this.head();
2418
- this.#assertCurrentHeadAuthority(head);
2419
- const duplicate = this.database.query(`SELECT ${OPERATION_COLUMNS} FROM oh_operations WHERE operation_sha256 = ?`).get(operation.operationSha256);
2420
- if (duplicate !== null) {
2421
- const existing = parseStoredOperationRow(duplicate, {
2422
- operationSha256: operation.operationSha256,
2423
- spaceId: this.spaceId
2424
- });
2425
- this.#assertOperationReachable(existing, head);
2426
- if (canonicalJson(existing) !== canonicalJson(operation)) {
2427
- throw new OhIntegrityError("An operation digest is bound to different bytes.");
3252
+ const current = this.head();
3253
+ this.#assertCurrentHeadAuthority(current);
3254
+ this.#headAt(expectedHead);
3255
+ if (!exactHeadRef(current, expectedHead)) {
3256
+ if (operations.length === 0 || current.sequence < prior.sequence) {
3257
+ throw new OhConflictError("The imported operation interval does not extend the local head.");
3258
+ }
3259
+ for (const operation of operations) {
3260
+ const row = this.database.query(`SELECT ${OPERATION_COLUMNS} FROM oh_operations WHERE space_id = ? AND sequence = ?`).get(this.spaceId, operation.sequence);
3261
+ if (row === null)
3262
+ throw new OhIntegrityError("An imported replay is missing from the authority chain.");
3263
+ const existing = parseStoredOperationRow(row, { spaceId: this.spaceId });
3264
+ if (existing.operationSha256 !== operation.operationSha256) {
3265
+ throw new OhConflictError("The imported operation interval diverges from the local authority chain.");
3266
+ }
3267
+ if (canonicalJson(existing) !== canonicalJson(operation)) {
3268
+ throw new OhIntegrityError("An operation digest is bound to different bytes.");
3269
+ }
2428
3270
  }
2429
- return { imported: false, operation };
3271
+ this.#assertOperationReachable(operations[operations.length - 1], current);
3272
+ return { head: current, imported: 0, status: "already-present", v: 1 };
2430
3273
  }
2431
- if (operation.sequence !== head.sequence + 1 || operation.parentOperationSha256 !== head.operationSha256) {
2432
- throw new OhConflictError("The imported operation does not extend the local head.");
3274
+ if (operations.length === 0) {
3275
+ return { head: current, imported: 0, status: "already-present", v: 1 };
2433
3276
  }
2434
- const transition = this.#transition(head, operation.changes, operation.operationId);
2435
- if (transition.recordsSha256 !== operation.recordsSha256 || transition.graphRevisionSha256 !== operation.graphRevisionSha256) {
2436
- throw new OhIntegrityError("The imported operation does not reproduce its declared graph head.");
3277
+ const records = this.#loadRecords();
3278
+ let head = current;
3279
+ for (const operation of operations) {
3280
+ const duplicate = this.database.query(`SELECT ${OPERATION_COLUMNS} FROM oh_operations WHERE space_id = ? AND operation_id = ?`).get(this.spaceId, operation.operationId);
3281
+ if (duplicate !== null) {
3282
+ throw new OhConflictError("An imported operation ID is already bound on the local authority chain.");
3283
+ }
3284
+ const transition = this.#transition(head, operation.changes, operation.operationId, records);
3285
+ if (transition.recordsSha256 !== operation.recordsSha256 || transition.graphRevisionSha256 !== operation.graphRevisionSha256) {
3286
+ throw new OhIntegrityError("An imported operation does not reproduce its declared graph head.");
3287
+ }
3288
+ this.#persist(operation);
3289
+ head = {
3290
+ generation: operation.sequence,
3291
+ graphRevisionSha256: operation.graphRevisionSha256,
3292
+ operationSha256: operation.operationSha256,
3293
+ recordsSha256: operation.recordsSha256,
3294
+ sequence: operation.sequence,
3295
+ v: 1
3296
+ };
2437
3297
  }
2438
- this.#persist(operation);
2439
- return { imported: true, operation };
3298
+ return { head, imported: operations.length, status: "imported", v: 1 };
2440
3299
  });
2441
3300
  }
2442
3301
  exportOperations(afterSequence = 0, limit = 1000) {
@@ -2654,6 +3513,10 @@ class OhSqliteStore {
2654
3513
  const integrity = this.database.query("PRAGMA integrity_check").get();
2655
3514
  if (integrity?.integrity_check !== "ok")
2656
3515
  throw new OhIntegrityError("SQLite integrity_check failed.");
3516
+ const foreignKeyViolations = this.database.query("PRAGMA foreign_key_check").all();
3517
+ if (foreignKeyViolations.length !== 0) {
3518
+ throw new OhIntegrityError("SQLite foreign_key_check failed.");
3519
+ }
2657
3520
  const storedCount = this.database.query("SELECT count(*) AS count FROM oh_operations WHERE space_id = ?").get(this.spaceId)?.count ?? 0;
2658
3521
  const operations = this.database.query(`SELECT ${OPERATION_COLUMNS} FROM oh_operations WHERE space_id = ? ORDER BY sequence`).all(this.spaceId).map((row) => parseStoredOperationRow(row, { spaceId: this.spaceId }));
2659
3522
  if (operations.length !== storedCount)
@@ -2730,6 +3593,22 @@ class OhSqliteStore {
2730
3593
  if (canonicalJson(storedDependencies) !== canonicalJson(expectedDependencies)) {
2731
3594
  throw new OhIntegrityError("Materialized dependencies do not match operation replay.");
2732
3595
  }
3596
+ const expectedSearchDocuments = [...records.values()].sort((left, right) => left.key < right.key ? -1 : left.key > right.key ? 1 : 0).map((record) => ({
3597
+ record_key: record.key,
3598
+ record_sha256: record.recordSha256,
3599
+ text: `${record.key} ${record.kind} ${extractSearchText(record.value)}`
3600
+ }));
3601
+ const storedSearchDocuments = this.database.query(`SELECT record_key, record_sha256, text FROM oh_search_documents
3602
+ WHERE space_id = ? ORDER BY record_key`).all(this.spaceId);
3603
+ if (canonicalJson(storedSearchDocuments) !== canonicalJson(expectedSearchDocuments)) {
3604
+ throw new OhIntegrityError("Materialized search documents do not match operation replay.");
3605
+ }
3606
+ const expectedSearchFts = expectedSearchDocuments.map(({ record_key, text }) => ({ record_key, text }));
3607
+ const storedSearchFts = this.database.query(`SELECT record_key, text FROM oh_search_fts
3608
+ WHERE space_id = ? ORDER BY record_key, text, rowid`).all(this.spaceId);
3609
+ if (canonicalJson(storedSearchFts) !== canonicalJson(expectedSearchFts)) {
3610
+ throw new OhIntegrityError("Materialized full-text search rows do not match operation replay.");
3611
+ }
2733
3612
  const storedOperationRecords = this.database.query(`SELECT materialized.operation_sha256, materialized.ordinal, materialized.record_key,
2734
3613
  materialized.change_kind, materialized.record_sha256
2735
3614
  FROM oh_operation_records AS materialized
@@ -2821,6 +3700,24 @@ class OhSqliteStore {
2821
3700
  }
2822
3701
 
2823
3702
  // src/sqlite/port.ts
3703
+ function exactReplicationImportInputV1(value) {
3704
+ try {
3705
+ if (typeof value !== "object" || value === null || Array.isArray(value) || isProxy2(value))
3706
+ return null;
3707
+ const prototype = Object.getPrototypeOf(value);
3708
+ const keys = Reflect.ownKeys(value);
3709
+ if (prototype !== Object.prototype && prototype !== null || keys.length !== 2 || !keys.includes("bundle") || !keys.includes("expectedHead") || keys.some((key) => typeof key !== "string"))
3710
+ return null;
3711
+ const bundle = Object.getOwnPropertyDescriptor(value, "bundle");
3712
+ const expectedHead = Object.getOwnPropertyDescriptor(value, "expectedHead");
3713
+ if (bundle === undefined || expectedHead === undefined || !bundle.enumerable || !expectedHead.enumerable || bundle.get !== undefined || bundle.set !== undefined || expectedHead.get !== undefined || expectedHead.set !== undefined)
3714
+ return null;
3715
+ return { bundle: bundle.value, expectedHead: expectedHead.value };
3716
+ } catch {
3717
+ return null;
3718
+ }
3719
+ }
3720
+
2824
3721
  class OhSqliteStorePortV1 {
2825
3722
  binding;
2826
3723
  #authority;
@@ -2879,6 +3776,46 @@ function createOhSqliteStoreAuthorityV1(options = {}) {
2879
3776
  });
2880
3777
  const store = new OhSqliteStorePortV1(authority, binding);
2881
3778
  let purge = null;
3779
+ const replication = profile.capabilities.operationReplication ? Object.freeze({
3780
+ binding,
3781
+ exportBundle: async (input) => {
3782
+ const page = authority.changesSince(input.after, {
3783
+ ...input.limit === undefined ? {} : { limit: input.limit },
3784
+ through: input.through
3785
+ });
3786
+ const bundle = createOhSyncBundleV1(binding.spaceId, page.operations, {
3787
+ largestFittingPrefix: true
3788
+ });
3789
+ const last = bundle.operations.at(-1);
3790
+ return Object.freeze({
3791
+ bundle,
3792
+ from: page.from,
3793
+ hasMore: page.hasMore || bundle.operations.length < page.operations.length,
3794
+ through: page.through,
3795
+ to: last === undefined ? page.from : {
3796
+ operationSha256: last.operationSha256,
3797
+ sequence: last.sequence
3798
+ },
3799
+ v: 1
3800
+ });
3801
+ },
3802
+ head: async () => authority.head(),
3803
+ importBundle: async (input) => {
3804
+ const request = exactReplicationImportInputV1(input);
3805
+ const expectedHead = request === null ? null : parseOhSyncHeadRefV1(request.expectedHead);
3806
+ if (request === null || expectedHead === null) {
3807
+ throw new TypeError("Invalid canonical replication request.");
3808
+ }
3809
+ const bundle = parseOhSyncBundleV1(request.bundle);
3810
+ if (bundle === null || bundle.spaceId !== binding.spaceId) {
3811
+ throw new TypeError("Invalid canonical replication bundle.");
3812
+ }
3813
+ return authority.importOperations({
3814
+ expectedHead,
3815
+ operations: bundle.operations
3816
+ });
3817
+ }
3818
+ }) : null;
2882
3819
  const host = Object.freeze({
2883
3820
  binding,
2884
3821
  purgeWorkingSpace: async (input) => {
@@ -2890,7 +3827,8 @@ function createOhSqliteStoreAuthorityV1(options = {}) {
2890
3827
  purge = authority.purgeWorkingSpace(binding, input.purgedAt);
2891
3828
  authority.close();
2892
3829
  return purge;
2893
- }
3830
+ },
3831
+ replication
2894
3832
  });
2895
3833
  return Object.freeze({ host, store });
2896
3834
  }
@@ -2898,15 +3836,22 @@ export {
2898
3836
  withReadTransaction,
2899
3837
  withImmediateTransaction,
2900
3838
  openOhSqliteDatabase,
3839
+ isOhProfileError,
3840
+ isOhOperationSizeError,
3841
+ isOhIntegrityError,
3842
+ isOhDependencyError,
3843
+ isOhConflictError,
2901
3844
  createOhSqliteStoreAuthorityV1,
2902
3845
  applyOhSqliteMigrations,
2903
3846
  OhSqliteStorePortV1,
2904
3847
  OhSqliteStore,
2905
3848
  OhPurgedSpaceError,
2906
3849
  OhProfileError,
3850
+ OhOperationSizeError,
2907
3851
  OhIntegrityError,
2908
3852
  OhDependencyError,
2909
3853
  OhConflictError,
2910
3854
  OH_SQLITE_SCHEMA_VERSION,
2911
- OH_SQLITE_MIGRATIONS
3855
+ OH_SQLITE_MIGRATIONS,
3856
+ OH_OPERATION_SIZE_ERROR_CODE_V1
2912
3857
  };