@hraness/oh 0.4.0 → 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.
- package/LICENSE +3 -0
- package/README.md +12 -9
- package/dist/cli.d.ts +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +15590 -1705
- package/dist/index.js +15209 -261
- package/dist/libsql-model.d.ts +75 -0
- package/dist/libsql-model.d.ts.map +1 -0
- package/dist/libsql-platform.d.ts +24 -0
- package/dist/libsql-platform.d.ts.map +1 -0
- package/dist/libsql-program.d.ts +64 -0
- package/dist/libsql-program.d.ts.map +1 -0
- package/dist/libsql-runtime.d.ts +13 -0
- package/dist/libsql-runtime.d.ts.map +1 -0
- package/dist/libsql.d.ts +4 -36
- package/dist/libsql.d.ts.map +1 -1
- package/dist/libsql.js +16583 -1426
- package/dist/memory-authority-platform.d.ts +210 -0
- package/dist/memory-authority-platform.d.ts.map +1 -0
- package/dist/memory-authority-program.d.ts +15 -0
- package/dist/memory-authority-program.d.ts.map +1 -0
- package/dist/memory-authority-runtime.d.ts +4 -0
- package/dist/memory-authority-runtime.d.ts.map +1 -0
- package/dist/memory-core.d.ts +522 -0
- package/dist/memory-core.d.ts.map +1 -0
- package/dist/memory.d.ts +5 -445
- package/dist/memory.d.ts.map +1 -1
- package/dist/memory.js +18232 -4143
- package/dist/sdk.js +15266 -318
- package/dist/semantic-cloud.js +1 -260
- package/dist/semantic-model.d.ts +75 -0
- package/dist/semantic-model.d.ts.map +1 -0
- package/dist/semantic-platform.d.ts +32 -0
- package/dist/semantic-platform.d.ts.map +1 -0
- package/dist/semantic-program.d.ts +18 -0
- package/dist/semantic-program.d.ts.map +1 -0
- package/dist/semantic-runtime.d.ts +6 -0
- package/dist/semantic-runtime.d.ts.map +1 -0
- package/dist/semantic.d.ts +6 -57
- package/dist/semantic.d.ts.map +1 -1
- package/dist/semantic.js +15541 -514
- package/dist/sqlite/index.js +1148 -1378
- package/dist/sqlite/port.d.ts +1 -1
- package/dist/sqlite/port.d.ts.map +1 -1
- package/dist/sync-libsql-program.d.ts +5 -0
- package/dist/sync-libsql-program.d.ts.map +1 -0
- package/dist/sync-model.d.ts +60 -0
- package/dist/sync-model.d.ts.map +1 -0
- package/dist/sync-platform.d.ts +41 -0
- package/dist/sync-platform.d.ts.map +1 -0
- package/dist/sync-program.d.ts +11 -0
- package/dist/sync-program.d.ts.map +1 -0
- package/dist/sync-runtime.d.ts +6 -0
- package/dist/sync-runtime.d.ts.map +1 -0
- package/dist/sync.d.ts +5 -55
- package/dist/sync.d.ts.map +1 -1
- package/dist/sync.js +15174 -226
- package/package.json +7 -5
- package/skills/oh/SKILL.md +3 -2
- package/spec/v1/store.md +14 -0
- package/src/cli.test.ts +35 -0
- package/src/cli.ts +4 -3
- package/src/cloudflare-embedding.ts +1 -1
- package/src/libsql-lifecycle.test.ts +76 -0
- package/src/libsql-model.ts +350 -0
- package/src/libsql-platform.ts +51 -0
- package/src/libsql-program.ts +1583 -0
- package/src/libsql-runtime.ts +89 -0
- package/src/libsql-semantic-v2.ts +1 -1
- package/src/libsql-semantic.ts +1 -1
- package/src/libsql.test.ts +245 -0
- package/src/libsql.ts +26 -1698
- package/src/memory-authority-platform.ts +133 -0
- package/src/memory-authority-program.ts +309 -0
- package/src/memory-authority-runtime.ts +40 -0
- package/src/memory-core.ts +2401 -0
- package/src/memory.test.ts +55 -0
- package/src/memory.ts +68 -2759
- package/src/semantic-model.ts +142 -0
- package/src/semantic-platform.ts +130 -0
- package/src/semantic-program.ts +113 -0
- package/src/semantic-runtime.ts +53 -0
- package/src/semantic.test.ts +115 -1
- package/src/semantic.ts +14 -324
- package/src/sqlite/port.ts +1 -1
- package/src/sync-libsql-program.ts +170 -0
- package/src/sync-lifecycle.test.ts +110 -0
- package/src/sync-model.ts +530 -0
- package/src/sync-platform.ts +87 -0
- package/src/sync-program.ts +117 -0
- package/src/sync-runtime.ts +33 -0
- package/src/sync.ts +10 -765
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { Cause, Effect, Exit, Option } from "effect";
|
|
2
|
+
import type { OhLibSqlClientV1, OhLibSqlStoreAuthorityOptionsV1 } from "./libsql-model";
|
|
3
|
+
import type { OhStoreAuthorityV1, OhStoreV1, OhStoreHostControlV1, OhSpacePurgeReceiptV1 } from "./store";
|
|
4
|
+
import { bootstrapAuthority, createAuthority, openExistingAuthority, purgeWorkingSpace,
|
|
5
|
+
makeLibSqlOwner, type LibSqlStoreProgram } from "./libsql-program";
|
|
6
|
+
import { LibSqlAuthorityClient, libSqlAuthorityClientLive, type LibSqlFailure } from "./libsql-platform";
|
|
7
|
+
|
|
8
|
+
function unwrap<A>(exit: Exit.Exit<A, LibSqlFailure>): A {
|
|
9
|
+
if (Exit.isSuccess(exit)) return exit.value;
|
|
10
|
+
const expected = Cause.failureOption(exit.cause);
|
|
11
|
+
if (Option.isSome(expected)) throw expected.value.cause;
|
|
12
|
+
throw Cause.squash(exit.cause);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function run<A>(program: Effect.Effect<A, LibSqlFailure, never>): Promise<A> {
|
|
16
|
+
return Effect.runPromiseExit(Effect.uninterruptible(program)).then(unwrap);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function bindAuthority(authority: LibSqlStoreProgram, closeClient: Effect.Effect<void, LibSqlFailure>): OhStoreAuthorityV1 {
|
|
20
|
+
const owner = Effect.runSync(makeLibSqlOwner);
|
|
21
|
+
let closing: Promise<void> | undefined;
|
|
22
|
+
const operation = <A>(program: Effect.Effect<A, LibSqlFailure>): Promise<A> => {
|
|
23
|
+
if (!Effect.runSync(owner.admit)) return Promise.reject(authority.closedFailure().cause);
|
|
24
|
+
return run(owner.complete(program));
|
|
25
|
+
};
|
|
26
|
+
const close = (): Promise<void> => {
|
|
27
|
+
if (closing !== undefined) return closing;
|
|
28
|
+
// No await before the fence. Already-admitted operations retain access until
|
|
29
|
+
// their whole workflow settles, including reconciliation after a batch error.
|
|
30
|
+
Effect.runSync(owner.beginClose);
|
|
31
|
+
// Install the shared Promise before native close can synchronously reenter.
|
|
32
|
+
closing = run(Effect.yieldNow().pipe(Effect.andThen(owner.drained), Effect.andThen(closeClient)));
|
|
33
|
+
return closing;
|
|
34
|
+
};
|
|
35
|
+
return Object.freeze({
|
|
36
|
+
store: Object.freeze<OhStoreV1>({
|
|
37
|
+
binding: authority.binding,
|
|
38
|
+
changesSince: (from, options) => operation(authority.changesSince(from, options)),
|
|
39
|
+
close,
|
|
40
|
+
commit: input => operation(authority.commit(input)),
|
|
41
|
+
exportDependencyClosure: input => operation(authority.exportDependencyClosure(input)),
|
|
42
|
+
head: () => operation(authority.head()),
|
|
43
|
+
snapshot: options => operation(authority.snapshot(options)),
|
|
44
|
+
verify: () => operation(authority.verify()),
|
|
45
|
+
}),
|
|
46
|
+
host: Object.freeze<OhStoreHostControlV1>({
|
|
47
|
+
binding: authority.binding,
|
|
48
|
+
purgeWorkingSpace: input => operation(authority.purgeFromHost(input)),
|
|
49
|
+
}),
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function bootstrapOhLibSqlAuthorityV1(client: OhLibSqlClientV1):
|
|
54
|
+
Promise<Readonly<{ schemaSha256: import("./canonical").Sha256Hex; schemaVersion: 1; v: 1 }>> {
|
|
55
|
+
return run(Effect.gen(function* () {
|
|
56
|
+
return yield* bootstrapAuthority(yield* LibSqlAuthorityClient);
|
|
57
|
+
}).pipe(Effect.provide(libSqlAuthorityClientLive(client))));
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function open(client: OhLibSqlClientV1, options: OhLibSqlStoreAuthorityOptionsV1, existing: boolean): Promise<OhStoreAuthorityV1> {
|
|
61
|
+
return run(Effect.gen(function* () {
|
|
62
|
+
const port = yield* LibSqlAuthorityClient;
|
|
63
|
+
const authority = yield* (existing ? openExistingAuthority(port, options) : createAuthority(port, options));
|
|
64
|
+
return bindAuthority(authority, options.closeClient ? port.close : Effect.void);
|
|
65
|
+
}).pipe(Effect.provide(libSqlAuthorityClientLive(client))));
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function createOhLibSqlStoreAuthorityV1(client: OhLibSqlClientV1,
|
|
69
|
+
options: OhLibSqlStoreAuthorityOptionsV1 = {}): Promise<OhStoreAuthorityV1> {
|
|
70
|
+
return open(client, options, false);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function openExistingOhLibSqlStoreAuthorityV1(client: OhLibSqlClientV1,
|
|
74
|
+
options: OhLibSqlStoreAuthorityOptionsV1 = {}): Promise<OhStoreAuthorityV1> {
|
|
75
|
+
return open(client, options, true);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function purgeOhLibSqlWorkingSpaceV1(client: OhLibSqlClientV1,
|
|
79
|
+
options: OhLibSqlStoreAuthorityOptionsV1 & Readonly<{ purgedAt?: string }> = {}): Promise<OhSpacePurgeReceiptV1> {
|
|
80
|
+
return run(Effect.gen(function* () {
|
|
81
|
+
const port = yield* LibSqlAuthorityClient;
|
|
82
|
+
const closeClient = options.closeClient ?? false;
|
|
83
|
+
const result = yield* Effect.exit(purgeWorkingSpace(port, options));
|
|
84
|
+
// As in the original try/finally facade, a native close failure wins over
|
|
85
|
+
// the operation's rejection; otherwise replay its exact success/failure/defect.
|
|
86
|
+
if (closeClient) yield* port.close;
|
|
87
|
+
return yield* result;
|
|
88
|
+
}).pipe(Effect.provide(libSqlAuthorityClientLive(client))));
|
|
89
|
+
}
|
|
@@ -21,7 +21,7 @@ import type {
|
|
|
21
21
|
OhLibSqlResultV1,
|
|
22
22
|
OhLibSqlStatementV1,
|
|
23
23
|
} from "./libsql";
|
|
24
|
-
import { normalizeOhEmbeddingV1 } from "./semantic";
|
|
24
|
+
import { normalizeOhEmbeddingV1 } from "./semantic-model";
|
|
25
25
|
|
|
26
26
|
export const OH_LIBSQL_SEMANTIC_LIMITS_V2 = Object.freeze({
|
|
27
27
|
chunksPerDocument: 64,
|
package/src/libsql-semantic.ts
CHANGED
|
@@ -21,7 +21,7 @@ import type {
|
|
|
21
21
|
OhLibSqlResultV1,
|
|
22
22
|
OhLibSqlStatementV1,
|
|
23
23
|
} from "./libsql";
|
|
24
|
-
import { normalizeOhEmbeddingV1 } from "./semantic";
|
|
24
|
+
import { normalizeOhEmbeddingV1 } from "./semantic-model";
|
|
25
25
|
|
|
26
26
|
export const OH_LIBSQL_SEMANTIC_LIMITS_V1 = Object.freeze({
|
|
27
27
|
chunksPerDocument: 64,
|
package/src/libsql.test.ts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { describe, expect, test } from "bun:test";
|
|
2
2
|
import { Database, type SQLQueryBindings } from "bun:sqlite";
|
|
3
|
+
import { Effect } from "effect";
|
|
4
|
+
import { createAuthority } from "./libsql-program";
|
|
5
|
+
import { LibSqlAuthorityClient, libSqlAuthorityClientLive } from "./libsql-platform";
|
|
3
6
|
|
|
4
7
|
import { canonicalJson } from "./canonical";
|
|
5
8
|
import { createKnowledgeGraphRecordV1 } from "./graph";
|
|
@@ -691,3 +694,245 @@ describe("direct libSQL Oh authority", () => {
|
|
|
691
694
|
await authority.store.close(); client.close();
|
|
692
695
|
});
|
|
693
696
|
});
|
|
697
|
+
|
|
698
|
+
function gate<A = void>() {
|
|
699
|
+
let resolve!: (value: A) => void;
|
|
700
|
+
let reject!: (cause: unknown) => void;
|
|
701
|
+
const promise = new Promise<A>((accept, refuse) => { resolve = accept; reject = refuse; });
|
|
702
|
+
return { promise, resolve, reject };
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
describe("libSQL authority lifetime", () => {
|
|
706
|
+
test("close fences new calls and drains a complete admitted transaction before native close", async () => {
|
|
707
|
+
const provider = await bootstrappedClient();
|
|
708
|
+
const entered = gate();
|
|
709
|
+
const release = gate();
|
|
710
|
+
const events: string[] = [];
|
|
711
|
+
let hold = false;
|
|
712
|
+
const client: OhLibSqlClientV1 = {
|
|
713
|
+
execute: statement => provider.execute(statement),
|
|
714
|
+
batch: async (statements, mode) => {
|
|
715
|
+
if (hold && mode === "write") {
|
|
716
|
+
entered.resolve();
|
|
717
|
+
await release.promise;
|
|
718
|
+
events.push("write-settled");
|
|
719
|
+
}
|
|
720
|
+
return provider.batch(statements, mode);
|
|
721
|
+
},
|
|
722
|
+
close: () => { events.push("close"); },
|
|
723
|
+
};
|
|
724
|
+
try {
|
|
725
|
+
const { store } = await createOhLibSqlStoreAuthorityV1(client, { closeClient: true });
|
|
726
|
+
const head = await store.head();
|
|
727
|
+
hold = true;
|
|
728
|
+
const commit = store.commit({ actorId: "actor", operationId: "op_drain", expectedHead: head,
|
|
729
|
+
changes: [{ kind: "put", v: 1, record: entity("entity:drain", "Drain") }] });
|
|
730
|
+
await entered.promise;
|
|
731
|
+
const close = store.close();
|
|
732
|
+
expect(store.close()).toBe(close);
|
|
733
|
+
await expect(store.head()).rejects.toThrow("closed");
|
|
734
|
+
await expect(store.commit({ actorId: "actor", operationId: "op_after_close", expectedHead: head,
|
|
735
|
+
changes: [{ kind: "put", v: 1, record: entity("entity:late", "Late") }] })).rejects.toThrow("closed");
|
|
736
|
+
expect(events).toEqual([]);
|
|
737
|
+
release.resolve();
|
|
738
|
+
const operation = await commit;
|
|
739
|
+
await close;
|
|
740
|
+
expect(operation.operationId).toBe("op_drain");
|
|
741
|
+
expect(events).toEqual(["write-settled", "close"]);
|
|
742
|
+
expect(provider.database.query<{ count: number }, []>("SELECT count(*) AS count FROM oh_authority_operations").get()?.count).toBe(1);
|
|
743
|
+
} finally { release.resolve(); provider.close(); }
|
|
744
|
+
});
|
|
745
|
+
|
|
746
|
+
test("failed writes finish reconciliation before close and retain independent operation/close errors", async () => {
|
|
747
|
+
const provider = await bootstrappedClient();
|
|
748
|
+
const entered = gate();
|
|
749
|
+
const release = gate();
|
|
750
|
+
const writeFailure = Object.freeze({ operation: "uncertain-write" });
|
|
751
|
+
const closeFailure = Object.freeze({ operation: "close" });
|
|
752
|
+
const events: string[] = [];
|
|
753
|
+
let failWrite = false;
|
|
754
|
+
let writeCount = 0;
|
|
755
|
+
const client: OhLibSqlClientV1 = {
|
|
756
|
+
execute: async statement => {
|
|
757
|
+
if (failWrite) events.push("reconcile");
|
|
758
|
+
return provider.execute(statement);
|
|
759
|
+
},
|
|
760
|
+
batch: async (statements, mode) => {
|
|
761
|
+
if (failWrite && mode === "write") {
|
|
762
|
+
writeCount += 1;
|
|
763
|
+
entered.resolve();
|
|
764
|
+
await release.promise;
|
|
765
|
+
events.push("write-failed");
|
|
766
|
+
throw writeFailure;
|
|
767
|
+
}
|
|
768
|
+
return provider.batch(statements, mode);
|
|
769
|
+
},
|
|
770
|
+
close: () => { events.push("close"); throw closeFailure; },
|
|
771
|
+
};
|
|
772
|
+
try {
|
|
773
|
+
const { store } = await createOhLibSqlStoreAuthorityV1(client, { closeClient: true });
|
|
774
|
+
const head = await store.head();
|
|
775
|
+
failWrite = true;
|
|
776
|
+
const committed = store.commit({ actorId: "actor", operationId: "op_failed_drain", expectedHead: head,
|
|
777
|
+
changes: [{ kind: "put", v: 1, record: entity("entity:drain", "Drain") }] }).catch(cause => cause);
|
|
778
|
+
await entered.promise;
|
|
779
|
+
const closed = store.close().catch(cause => cause);
|
|
780
|
+
expect(events).toEqual([]);
|
|
781
|
+
release.resolve();
|
|
782
|
+
expect(await committed).toBe(writeFailure);
|
|
783
|
+
expect(await closed).toBe(closeFailure);
|
|
784
|
+
expect(writeCount).toBe(1);
|
|
785
|
+
expect(events).toEqual(["write-failed", "reconcile", "reconcile", "close"]);
|
|
786
|
+
expect(await store.close().catch(cause => cause)).toBe(closeFailure);
|
|
787
|
+
expect(events.filter(event => event === "close")).toHaveLength(1);
|
|
788
|
+
} finally { release.resolve(); provider.close(); }
|
|
789
|
+
});
|
|
790
|
+
|
|
791
|
+
test("a lost write response reconciles exact replay once before close", async () => {
|
|
792
|
+
const provider = await bootstrappedClient();
|
|
793
|
+
const entered = gate();
|
|
794
|
+
const release = gate();
|
|
795
|
+
let loseResponse = false;
|
|
796
|
+
let writes = 0;
|
|
797
|
+
let closes = 0;
|
|
798
|
+
const client: OhLibSqlClientV1 = {
|
|
799
|
+
execute: statement => provider.execute(statement),
|
|
800
|
+
batch: async (statements, mode) => {
|
|
801
|
+
const result = await provider.batch(statements, mode);
|
|
802
|
+
if (loseResponse && mode === "write") {
|
|
803
|
+
writes += 1;
|
|
804
|
+
entered.resolve();
|
|
805
|
+
await release.promise;
|
|
806
|
+
throw "lost-response";
|
|
807
|
+
}
|
|
808
|
+
return result;
|
|
809
|
+
},
|
|
810
|
+
close: () => { closes += 1; },
|
|
811
|
+
};
|
|
812
|
+
try {
|
|
813
|
+
const { store } = await createOhLibSqlStoreAuthorityV1(client, { closeClient: true });
|
|
814
|
+
const head = await store.head();
|
|
815
|
+
loseResponse = true;
|
|
816
|
+
const committed = store.commit({ actorId: "actor", operationId: "op_lost_response", expectedHead: head,
|
|
817
|
+
changes: [{ kind: "put", v: 1, record: entity("entity:drain", "Drain") }] });
|
|
818
|
+
await entered.promise;
|
|
819
|
+
const closed = store.close();
|
|
820
|
+
expect(closes).toBe(0);
|
|
821
|
+
release.resolve();
|
|
822
|
+
expect((await committed).operationId).toBe("op_lost_response");
|
|
823
|
+
await closed;
|
|
824
|
+
expect(writes).toBe(1);
|
|
825
|
+
expect(closes).toBe(1);
|
|
826
|
+
} finally { release.resolve(); provider.close(); }
|
|
827
|
+
});
|
|
828
|
+
|
|
829
|
+
test("one authority admits concurrent commits and lets atomic SQL select the CAS winner", async () => {
|
|
830
|
+
const provider = await bootstrappedClient();
|
|
831
|
+
const bothEntered = gate();
|
|
832
|
+
const release = gate();
|
|
833
|
+
let hold = false;
|
|
834
|
+
let writes = 0;
|
|
835
|
+
const client: OhLibSqlClientV1 = {
|
|
836
|
+
execute: statement => provider.execute(statement),
|
|
837
|
+
batch: async (statements, mode) => {
|
|
838
|
+
if (hold && mode === "write") {
|
|
839
|
+
writes += 1;
|
|
840
|
+
if (writes === 2) bothEntered.resolve();
|
|
841
|
+
await release.promise;
|
|
842
|
+
}
|
|
843
|
+
return provider.batch(statements, mode);
|
|
844
|
+
},
|
|
845
|
+
};
|
|
846
|
+
try {
|
|
847
|
+
const { store } = await createOhLibSqlStoreAuthorityV1(client);
|
|
848
|
+
const head = await store.head();
|
|
849
|
+
hold = true;
|
|
850
|
+
const commits = ["first", "second"].map(id => store.commit({ actorId: "actor",
|
|
851
|
+
operationId: `op_${id}`, expectedHead: head,
|
|
852
|
+
changes: [{ kind: "put", v: 1, record: entity(`entity:${id}`, id) }] }));
|
|
853
|
+
const results = Promise.allSettled(commits);
|
|
854
|
+
await bothEntered.promise;
|
|
855
|
+
const closed = store.close();
|
|
856
|
+
release.resolve();
|
|
857
|
+
const settled = await results;
|
|
858
|
+
await closed;
|
|
859
|
+
expect(settled.filter(result => result.status === "fulfilled")).toHaveLength(1);
|
|
860
|
+
const rejected = settled.find(result => result.status === "rejected");
|
|
861
|
+
expect(rejected?.status === "rejected" ? rejected.reason : null).toBeInstanceOf(OhConflictError);
|
|
862
|
+
expect(writes).toBe(2);
|
|
863
|
+
expect(provider.database.query<{ count: number }, []>("SELECT count(*) AS count FROM oh_authority_operations").get()?.count).toBe(1);
|
|
864
|
+
} finally { release.resolve(); provider.close(); }
|
|
865
|
+
});
|
|
866
|
+
|
|
867
|
+
test("borrowed clients remain open while their authority still fences calls", async () => {
|
|
868
|
+
const provider = await bootstrappedClient();
|
|
869
|
+
try {
|
|
870
|
+
const { store } = await createOhLibSqlStoreAuthorityV1(provider);
|
|
871
|
+
await store.close();
|
|
872
|
+
await expect(store.head()).rejects.toThrow("closed");
|
|
873
|
+
expect((await provider.execute("SELECT 1 AS ready")).rows).toHaveLength(1);
|
|
874
|
+
} finally { provider.close(); }
|
|
875
|
+
});
|
|
876
|
+
|
|
877
|
+
test("a synchronously reentrant native close observes the same close without closing twice", async () => {
|
|
878
|
+
const provider = await bootstrappedClient();
|
|
879
|
+
let closeAuthority: (() => Promise<void>) | undefined;
|
|
880
|
+
let reentered: Promise<void> | undefined;
|
|
881
|
+
let closes = 0;
|
|
882
|
+
const client: OhLibSqlClientV1 = {
|
|
883
|
+
execute: statement => provider.execute(statement),
|
|
884
|
+
batch: (statements, mode) => provider.batch(statements, mode),
|
|
885
|
+
close: () => { closes += 1; reentered = closeAuthority?.(); },
|
|
886
|
+
};
|
|
887
|
+
try {
|
|
888
|
+
const { store } = await createOhLibSqlStoreAuthorityV1(client, { closeClient: true });
|
|
889
|
+
closeAuthority = () => store.close();
|
|
890
|
+
const closed = store.close();
|
|
891
|
+
await closed;
|
|
892
|
+
expect(reentered).toBe(closed);
|
|
893
|
+
expect(closes).toBe(1);
|
|
894
|
+
} finally { provider.close(); }
|
|
895
|
+
});
|
|
896
|
+
|
|
897
|
+
test("standalone purge preserves original rejection and finally-close precedence", async () => {
|
|
898
|
+
const original = Object.freeze({ kind: "provider-rejection" });
|
|
899
|
+
const closeFailure = Object.freeze({ kind: "close-rejection" });
|
|
900
|
+
const client: OhLibSqlClientV1 = {
|
|
901
|
+
execute: async () => { throw original; }, batch: async () => { throw original; },
|
|
902
|
+
};
|
|
903
|
+
expect(await bootstrapOhLibSqlAuthorityV1(client).catch(cause => cause)).toBe(original);
|
|
904
|
+
expect(await purgeOhLibSqlWorkingSpaceV1(client).catch(cause => cause)).toBe(original);
|
|
905
|
+
let closes = 0;
|
|
906
|
+
const closingClient = { ...client, close() { closes += 1; throw closeFailure; } };
|
|
907
|
+
expect(await purgeOhLibSqlWorkingSpaceV1(closingClient, { closeClient: true }).catch(cause => cause)).toBe(closeFailure);
|
|
908
|
+
expect(closes).toBe(1);
|
|
909
|
+
});
|
|
910
|
+
});
|
|
911
|
+
|
|
912
|
+
|
|
913
|
+
test("libSQL programs sample the injected clock only for a missing operation instant", async () => {
|
|
914
|
+
const provider = await bootstrappedClient();
|
|
915
|
+
let sampled = 0;
|
|
916
|
+
const defaultInstant = "2026-09-06T12:00:00.000Z";
|
|
917
|
+
const suppliedInstant = "2026-09-06T13:00:00.000Z";
|
|
918
|
+
try {
|
|
919
|
+
const program = await Effect.runPromise(Effect.gen(function* () {
|
|
920
|
+
const client = yield* LibSqlAuthorityClient;
|
|
921
|
+
return yield* createAuthority({ ...client, currentInstant: Effect.sync(() => {
|
|
922
|
+
sampled += 1;
|
|
923
|
+
return defaultInstant;
|
|
924
|
+
}) });
|
|
925
|
+
}).pipe(Effect.provide(libSqlAuthorityClientLive(provider))));
|
|
926
|
+
expect(sampled).toBe(1); // Space initialization also samples its declared clock.
|
|
927
|
+
const first = await Effect.runPromise(program.commit({ actorId: "actor", operationId: "op_clock_default",
|
|
928
|
+
expectedHead: await Effect.runPromise(program.head()),
|
|
929
|
+
changes: [{ kind: "put", v: 1, record: entity("entity:default", "Default") }] }));
|
|
930
|
+
expect(first.instant).toBe(defaultInstant);
|
|
931
|
+
expect(sampled).toBe(2);
|
|
932
|
+
const second = await Effect.runPromise(program.commit({ actorId: "actor", operationId: "op_clock_supplied",
|
|
933
|
+
expectedHead: await Effect.runPromise(program.head()), instant: suppliedInstant,
|
|
934
|
+
changes: [{ kind: "put", v: 1, record: entity("entity:supplied", "Supplied") }] }));
|
|
935
|
+
expect(second.instant).toBe(suppliedInstant);
|
|
936
|
+
expect(sampled).toBe(2);
|
|
937
|
+
} finally { provider.close(); }
|
|
938
|
+
});
|