@hraness/oh 0.4.1 → 0.4.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.
Files changed (92) hide show
  1. package/LICENSE +3 -0
  2. package/README.md +4 -1
  3. package/dist/cli.d.ts +1 -1
  4. package/dist/cli.d.ts.map +1 -1
  5. package/dist/cli.js +15590 -1705
  6. package/dist/index.js +15209 -261
  7. package/dist/libsql-model.d.ts +75 -0
  8. package/dist/libsql-model.d.ts.map +1 -0
  9. package/dist/libsql-platform.d.ts +24 -0
  10. package/dist/libsql-platform.d.ts.map +1 -0
  11. package/dist/libsql-program.d.ts +64 -0
  12. package/dist/libsql-program.d.ts.map +1 -0
  13. package/dist/libsql-runtime.d.ts +13 -0
  14. package/dist/libsql-runtime.d.ts.map +1 -0
  15. package/dist/libsql.d.ts +4 -36
  16. package/dist/libsql.d.ts.map +1 -1
  17. package/dist/libsql.js +16583 -1426
  18. package/dist/memory-authority-platform.d.ts +210 -0
  19. package/dist/memory-authority-platform.d.ts.map +1 -0
  20. package/dist/memory-authority-program.d.ts +15 -0
  21. package/dist/memory-authority-program.d.ts.map +1 -0
  22. package/dist/memory-authority-runtime.d.ts +4 -0
  23. package/dist/memory-authority-runtime.d.ts.map +1 -0
  24. package/dist/memory-core.d.ts +522 -0
  25. package/dist/memory-core.d.ts.map +1 -0
  26. package/dist/memory.d.ts +5 -445
  27. package/dist/memory.d.ts.map +1 -1
  28. package/dist/memory.js +18232 -4143
  29. package/dist/sdk.js +15266 -318
  30. package/dist/semantic-cloud.js +1 -260
  31. package/dist/semantic-model.d.ts +75 -0
  32. package/dist/semantic-model.d.ts.map +1 -0
  33. package/dist/semantic-platform.d.ts +32 -0
  34. package/dist/semantic-platform.d.ts.map +1 -0
  35. package/dist/semantic-program.d.ts +18 -0
  36. package/dist/semantic-program.d.ts.map +1 -0
  37. package/dist/semantic-runtime.d.ts +6 -0
  38. package/dist/semantic-runtime.d.ts.map +1 -0
  39. package/dist/semantic.d.ts +6 -57
  40. package/dist/semantic.d.ts.map +1 -1
  41. package/dist/semantic.js +15541 -514
  42. package/dist/sqlite/index.js +1148 -1378
  43. package/dist/sqlite/port.d.ts +1 -1
  44. package/dist/sqlite/port.d.ts.map +1 -1
  45. package/dist/sync-libsql-program.d.ts +5 -0
  46. package/dist/sync-libsql-program.d.ts.map +1 -0
  47. package/dist/sync-model.d.ts +60 -0
  48. package/dist/sync-model.d.ts.map +1 -0
  49. package/dist/sync-platform.d.ts +41 -0
  50. package/dist/sync-platform.d.ts.map +1 -0
  51. package/dist/sync-program.d.ts +11 -0
  52. package/dist/sync-program.d.ts.map +1 -0
  53. package/dist/sync-runtime.d.ts +6 -0
  54. package/dist/sync-runtime.d.ts.map +1 -0
  55. package/dist/sync.d.ts +5 -55
  56. package/dist/sync.d.ts.map +1 -1
  57. package/dist/sync.js +15174 -226
  58. package/package.json +7 -5
  59. package/skills/oh/SKILL.md +2 -1
  60. package/spec/v1/store.md +14 -0
  61. package/src/cli.test.ts +35 -0
  62. package/src/cli.ts +4 -3
  63. package/src/cloudflare-embedding.ts +1 -1
  64. package/src/libsql-lifecycle.test.ts +76 -0
  65. package/src/libsql-model.ts +350 -0
  66. package/src/libsql-platform.ts +51 -0
  67. package/src/libsql-program.ts +1583 -0
  68. package/src/libsql-runtime.ts +89 -0
  69. package/src/libsql-semantic-v2.ts +1 -1
  70. package/src/libsql-semantic.ts +1 -1
  71. package/src/libsql.test.ts +245 -0
  72. package/src/libsql.ts +26 -1698
  73. package/src/memory-authority-platform.ts +133 -0
  74. package/src/memory-authority-program.ts +309 -0
  75. package/src/memory-authority-runtime.ts +40 -0
  76. package/src/memory-core.ts +2401 -0
  77. package/src/memory.test.ts +55 -0
  78. package/src/memory.ts +68 -2759
  79. package/src/semantic-model.ts +142 -0
  80. package/src/semantic-platform.ts +130 -0
  81. package/src/semantic-program.ts +113 -0
  82. package/src/semantic-runtime.ts +53 -0
  83. package/src/semantic.test.ts +115 -1
  84. package/src/semantic.ts +14 -324
  85. package/src/sqlite/port.ts +1 -1
  86. package/src/sync-libsql-program.ts +170 -0
  87. package/src/sync-lifecycle.test.ts +110 -0
  88. package/src/sync-model.ts +530 -0
  89. package/src/sync-platform.ts +87 -0
  90. package/src/sync-program.ts +117 -0
  91. package/src/sync-runtime.ts +33 -0
  92. package/src/sync.ts +10 -765
@@ -0,0 +1,117 @@
1
+ import { Effect } from "effect";
2
+ import { safeCode } from "./canonical";
3
+ import { OH_CONTRACT_MANIFEST_V1 } from "./contract";
4
+ import { createOhSyncBundleV1, parseOhSyncBundleV1, parseOhSyncHeadV1,
5
+ type OhSyncHeadV1, type OhSyncResultV1 } from "./sync-model";
6
+ import { SyncStore, SyncTransport, type SyncFailure } from "./sync-platform";
7
+
8
+ export type SyncOptions = Readonly<{ batchSize?: number; maximumRounds?: number; remoteId?: string }>;
9
+
10
+ /** Only the store adapter can enter the synchronous SQLite transaction. */
11
+ export function synchronize(options: SyncOptions = {}):
12
+ Effect.Effect<OhSyncResultV1, SyncFailure, SyncStore | SyncTransport> {
13
+ return Effect.gen(function* () {
14
+ const store = yield* SyncStore;
15
+ const transport = yield* SyncTransport;
16
+ const batchSize = options.batchSize ?? 100;
17
+ const maximumRounds = options.maximumRounds ?? 100;
18
+ const remoteId = safeCode(options.remoteId ?? "default");
19
+ if (!Number.isSafeInteger(batchSize) || batchSize < 1 || batchSize > 1000
20
+ || !Number.isSafeInteger(maximumRounds) || maximumRounds < 1 || maximumRounds > 10_000
21
+ || remoteId === null) return yield* Effect.fail({ _tag: "ValidationFailure", cause: new TypeError("Invalid sync options.") } satisfies SyncFailure);
22
+ yield* transport.handshake(OH_CONTRACT_MANIFEST_V1);
23
+ let pulled = 0;
24
+ let pushed = 0;
25
+ const settled = (head: OhSyncHeadV1, rounds: number): Effect.Effect<OhSyncResultV1, SyncFailure> => Effect.gen(function* () {
26
+ yield* store.updateSyncState(remoteId, { pulledSequence: head.sequence,
27
+ pushedSequence: head.sequence, remoteHeadSha256: head.operationSha256 });
28
+ return { head, pulled, pushed, rounds, v: 1 };
29
+ });
30
+ for (let round = 1; round <= maximumRounds; round += 1) {
31
+ const remote = parseOhSyncHeadV1(yield* transport.head(store.spaceId));
32
+ if (remote === null) return yield* Effect.fail({ _tag: "ValidationFailure", cause: new Error("The sync transport returned an invalid head.") } satisfies SyncFailure);
33
+ const local = (yield* store.head);
34
+ if (local.sequence === remote.sequence) {
35
+ if (local.operationSha256 !== remote.operationSha256) {
36
+ return yield* Effect.fail({ _tag: "SyncConflict", cause: new Error("Sync conflict: equal sequence numbers have different heads.") } satisfies SyncFailure);
37
+ }
38
+ return yield* settled(remote, round);
39
+ }
40
+ if (local.sequence < remote.sequence) {
41
+ const bundle = parseOhSyncBundleV1(yield* transport.pull(store.spaceId, local.sequence, batchSize));
42
+ const terminal = bundle?.operations.at(-1);
43
+ if (bundle === null || bundle.operations.length === 0
44
+ || bundle.operations.length > batchSize
45
+ || bundle.spaceId !== store.spaceId
46
+ || bundle.operations[0]?.sequence !== local.sequence + 1
47
+ || bundle.operations[0]?.parentOperationSha256 !== local.operationSha256
48
+ || terminal === undefined || terminal.sequence > remote.sequence
49
+ || (terminal.sequence === remote.sequence
50
+ && terminal.operationSha256 !== remote.operationSha256)) {
51
+ return yield* Effect.fail({ _tag: "SyncConflict", cause: new Error("Sync conflict: remote history does not extend the local head.") } satisfies SyncFailure);
52
+ }
53
+ yield* store.importOperations({
54
+ expectedHead: {
55
+ operationSha256: local.operationSha256,
56
+ sequence: local.sequence,
57
+ },
58
+ operations: bundle.operations,
59
+ });
60
+ pulled += bundle.operations.length;
61
+ const afterPull = (yield* store.head);
62
+ if (afterPull.sequence === remote.sequence
63
+ && afterPull.operationSha256 === remote.operationSha256) {
64
+ const confirmed = parseOhSyncHeadV1(yield* transport.head(store.spaceId));
65
+ if (confirmed === null) return yield* Effect.fail({ _tag: "ValidationFailure", cause: new Error("The sync transport returned an invalid head.") } satisfies SyncFailure);
66
+ const confirmedLocal = (yield* store.head);
67
+ if (confirmed.sequence === remote.sequence
68
+ && confirmed.operationSha256 === remote.operationSha256
69
+ && confirmedLocal.sequence === confirmed.sequence
70
+ && confirmedLocal.operationSha256 === confirmed.operationSha256) {
71
+ return yield* settled(confirmed, round);
72
+ }
73
+ }
74
+ } else {
75
+ const candidates = (yield* store.changesSince({
76
+ operationSha256: remote.operationSha256,
77
+ sequence: remote.sequence,
78
+ }, {
79
+ limit: batchSize,
80
+ through: {
81
+ operationSha256: local.operationSha256,
82
+ sequence: local.sequence,
83
+ },
84
+ })).operations;
85
+ if (candidates.length === 0 || candidates[0]?.sequence !== remote.sequence + 1
86
+ || candidates[0]?.parentOperationSha256 !== remote.operationSha256) {
87
+ return yield* Effect.fail({ _tag: "SyncConflict", cause: new Error("Sync conflict: local history does not extend the remote head.") } satisfies SyncFailure);
88
+ }
89
+ const bundle = createOhSyncBundleV1(store.spaceId, candidates, {
90
+ largestFittingPrefix: true,
91
+ });
92
+ const operations = bundle.operations;
93
+ const head = parseOhSyncHeadV1(yield* transport.push(bundle));
94
+ if (head === null) return yield* Effect.fail({ _tag: "ValidationFailure", cause: new Error("The sync transport returned an invalid push head.") } satisfies SyncFailure);
95
+ if (head.sequence !== operations.at(-1)?.sequence
96
+ || head.operationSha256 !== operations.at(-1)?.operationSha256) {
97
+ return yield* Effect.fail({ _tag: "ValidationFailure", cause: new Error("The sync transport acknowledged a different head.") } satisfies SyncFailure);
98
+ }
99
+ pushed += operations.length;
100
+ const afterPush = (yield* store.head);
101
+ if (afterPush.sequence === head.sequence
102
+ && afterPush.operationSha256 === head.operationSha256) {
103
+ const confirmed = parseOhSyncHeadV1(yield* transport.head(store.spaceId));
104
+ if (confirmed === null) return yield* Effect.fail({ _tag: "ValidationFailure", cause: new Error("The sync transport returned an invalid head.") } satisfies SyncFailure);
105
+ const confirmedLocal = (yield* store.head);
106
+ if (confirmed.sequence === head.sequence
107
+ && confirmed.operationSha256 === head.operationSha256
108
+ && confirmedLocal.sequence === confirmed.sequence
109
+ && confirmedLocal.operationSha256 === confirmed.operationSha256) {
110
+ return yield* settled(confirmed, round);
111
+ }
112
+ }
113
+ }
114
+ }
115
+ return yield* Effect.fail({ _tag: "RoundLimit", cause: new Error("Sync did not settle within maximumRounds.") } satisfies SyncFailure);
116
+ });
117
+ }
@@ -0,0 +1,33 @@
1
+ import { Cause, Effect, Exit, Layer, Option } from "effect";
2
+ import type { OhSqliteStore } from "./sqlite/store";
3
+ import type { LibSqlClientV1, OhOperationSyncTransportV1, OhSyncResultV1 } from "./sync-model";
4
+ import { makeLibSqlSyncTransport } from "./sync-libsql-program";
5
+ import { libSqlSyncClientLive, syncStoreLive, syncTransportLive, type SyncFailure } from "./sync-platform";
6
+ import { synchronize, type SyncOptions } from "./sync-program";
7
+
8
+ /** Closed runners retain the public facade's exact foreign rejection value. */
9
+ async function runClosed<A>(program: Effect.Effect<Exit.Exit<A, SyncFailure>, never, never>): Promise<A> {
10
+ const result = await Effect.runPromise(program);
11
+ if (Exit.isSuccess(result)) return result.value;
12
+ const expected = Cause.failureOption(result.cause);
13
+ if (Option.isSome(expected)) throw expected.value.cause;
14
+ throw Cause.squash(result.cause);
15
+ }
16
+
17
+ export function runSyncBoundary(store: OhSqliteStore, transport: OhOperationSyncTransportV1,
18
+ options: SyncOptions): Promise<OhSyncResultV1> {
19
+ return runClosed(synchronize(options).pipe(
20
+ Effect.provide(Layer.merge(syncStoreLive(store), syncTransportLive(transport))),
21
+ Effect.exit,
22
+ ));
23
+ }
24
+
25
+ export function makeLibSqlSyncBoundary(client: LibSqlClientV1): OhOperationSyncTransportV1 {
26
+ const transport = Effect.runSync(makeLibSqlSyncTransport.pipe(Effect.provide(libSqlSyncClientLive(client))));
27
+ return {
28
+ handshake: (manifest) => runClosed(Effect.exit(transport.handshake(manifest))),
29
+ head: (spaceId) => runClosed(Effect.exit(transport.head(spaceId))),
30
+ pull: (spaceId, afterSequence, limit) => runClosed(Effect.exit(transport.pull(spaceId, afterSequence, limit))),
31
+ push: (bundle) => runClosed(Effect.exit(transport.push(bundle))),
32
+ };
33
+ }