@lix-js/sdk 0.8.4 → 0.9.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.
- package/README.md +82 -16
- package/dist/binding-types.d.ts +18 -2
- package/dist/binding.browser.d.ts +2 -2
- package/dist/binding.browser.js +3 -3
- package/dist/binding.node.d.ts +2 -2
- package/dist/binding.node.js +10 -4
- package/dist/bundled-plugins/plugin_csv_v2.lixplugin +0 -0
- package/dist/bundled-plugins/plugin_markdown_incremental_v2.lixplugin +0 -0
- package/dist/bundled-plugins.js +4 -4
- package/dist/client-state.d.ts +40 -0
- package/dist/client-state.js +178 -0
- package/dist/index.d.ts +2 -1
- package/dist/lix.d.ts +44 -0
- package/dist/lix.js +369 -0
- package/dist/local-storage-adapter.d.ts +26 -0
- package/dist/local-storage-adapter.js +117 -0
- package/dist/open-lix.d.ts +3 -34
- package/dist/open-lix.js +103 -170
- package/dist/remote/client.d.ts +8 -0
- package/dist/remote/client.js +1036 -0
- package/dist/remote/protocol.d.ts +166 -0
- package/dist/remote/protocol.js +362 -0
- package/dist/remote/sse.d.ts +12 -0
- package/dist/remote/sse.js +87 -0
- package/dist/snapshot-persistence.d.ts +7 -0
- package/dist/snapshot-persistence.js +26 -0
- package/dist/types.d.ts +58 -1
- package/dist/wasm/lix_js_sdk.d.ts +16 -4
- package/dist/wasm/lix_js_sdk.js +75 -17
- package/dist/wasm/lix_js_sdk_bg.wasm +0 -0
- package/dist/wasm/lix_js_sdk_bg.wasm.d.ts +8 -2
- package/dist/worker/client.d.ts +23 -4
- package/dist/worker/client.js +287 -10
- package/dist/worker/host.js +30 -2
- package/dist/worker/protocol.d.ts +26 -2
- package/dist/workerd.js +1 -3
- package/package.json +19 -16
- package/dist/bundled-plugins/plugin_csv.lixplugin +0 -0
- package/dist/bundled-plugins/plugin_md_v2.lixplugin +0 -0
- package/dist/jco/js-component-bindgen-component.core.wasm +0 -0
- package/dist/jco/js-component-bindgen-component.core2.wasm +0 -0
- package/dist/jco/js-component-bindgen-component.js +0 -13662
- package/dist/jco-transpile.browser.d.ts +0 -14
- package/dist/jco-transpile.browser.js +0 -22
- package/dist/plugin-runtime.d.ts +0 -45
- package/dist/plugin-runtime.js +0 -124
package/README.md
CHANGED
|
@@ -22,6 +22,72 @@ console.log(result.rows[0]?.get("message"));
|
|
|
22
22
|
await lix.close();
|
|
23
23
|
```
|
|
24
24
|
|
|
25
|
+
## Remote workspaces
|
|
26
|
+
|
|
27
|
+
Use the same Lix client as a thin client against a hosted workspace:
|
|
28
|
+
|
|
29
|
+
```ts
|
|
30
|
+
const lix = await openLix({
|
|
31
|
+
server: {
|
|
32
|
+
mode: "remote",
|
|
33
|
+
url: "https://lixray.com/@namespace/workspace",
|
|
34
|
+
headers: async () => ({
|
|
35
|
+
Authorization: `Bearer ${await accessToken()}`,
|
|
36
|
+
}),
|
|
37
|
+
},
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
const files = lix.observe("SELECT path FROM lix_file ORDER BY path");
|
|
41
|
+
const initial = await files.next();
|
|
42
|
+
|
|
43
|
+
await lix.execute(
|
|
44
|
+
"INSERT INTO lix_file (path, data) VALUES ($1, $2)",
|
|
45
|
+
["/hello.txt", new TextEncoder().encode("hello")],
|
|
46
|
+
);
|
|
47
|
+
const update = await files.next();
|
|
48
|
+
|
|
49
|
+
files.close();
|
|
50
|
+
await lix.close();
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Without `storage`, remote mode uses the server for all persistence and does not
|
|
54
|
+
open a local engine. Dynamic headers are resolved for every request and
|
|
55
|
+
observation reconnect. An injected `fetch` can route requests through a service
|
|
56
|
+
binding or another authorized server-side transport.
|
|
57
|
+
|
|
58
|
+
Browser clients can opt into private, durable client state with the local
|
|
59
|
+
storage adapter:
|
|
60
|
+
|
|
61
|
+
```ts
|
|
62
|
+
import { openLix } from "@lix-js/sdk";
|
|
63
|
+
import { LocalStorage } from "@lix-js/sdk/local-storage-adapter";
|
|
64
|
+
|
|
65
|
+
const lix = await openLix({
|
|
66
|
+
server: {
|
|
67
|
+
mode: "remote",
|
|
68
|
+
url: "https://lixray.com/@namespace/workspace",
|
|
69
|
+
},
|
|
70
|
+
storage: new LocalStorage(),
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
const previousUiState = lix.clientState.get("atelier-ui");
|
|
74
|
+
await lix.clientState.set("atelier-ui", { sidebar: "history" });
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
`lix.clientState` is hydrated before `openLix()` resolves, so reads are
|
|
78
|
+
synchronous. Its JSON values and the client's active branch are stored in a
|
|
79
|
+
private local Lix snapshot; workspace SQL continues to execute only on the
|
|
80
|
+
server. Reopening the same remote URL with the same storage restores both. Each
|
|
81
|
+
remote server session is branch-pinned, so switching one client does not switch
|
|
82
|
+
another client.
|
|
83
|
+
|
|
84
|
+
After a remote branch switch succeeds, saving that branch as the next-reopen
|
|
85
|
+
preference is best effort: a client-storage failure does not turn the completed
|
|
86
|
+
server switch into a rejected operation. Explicit `lix.clientState.set()` and
|
|
87
|
+
`.delete()` calls report durability failures to their caller. Because the local
|
|
88
|
+
Rust transaction has already committed, `get()` continues to expose that live
|
|
89
|
+
session value; a later successful snapshot save can make it durable.
|
|
90
|
+
|
|
25
91
|
Filesystem sync and SQLite persistence use native Node.js dependencies:
|
|
26
92
|
|
|
27
93
|
```ts
|
|
@@ -93,36 +159,36 @@ try {
|
|
|
93
159
|
- Pass `new LocalFilesystem({ path, lixDir, syncAllFiles: true })` for filesystem sync with repository metadata in an external `.lix` directory and no workspace `.lix` directory.
|
|
94
160
|
- Pass `syncAllFiles: false` to start filesystem sync with no regular workspace files, then call `storage.importPaths(["notes/today.md"])` on the `LocalFilesystem` instance to sync selected files. Imported paths are exact workspace-relative file paths, not directories or globs.
|
|
95
161
|
- Use `new SQLite({ path })` when a single SQLite-backed `.lix` file is the application document itself, for example when defining a new file format and using Lix as the application's file format.
|
|
96
|
-
- In browsers,
|
|
97
|
-
|
|
162
|
+
- In browsers, local mode and remote mode with client storage load the Rust
|
|
163
|
+
engine as WebAssembly. Supplying a snapshot storage adapter persists that
|
|
164
|
+
local Lix; in remote mode, the local engine contains only client state.
|
|
98
165
|
- `LocalFilesystem` and `SQLite` are Node.js-only. Constructing them is safe in
|
|
99
166
|
shared code, but passing one to `openLix()` in a browser throws an error.
|
|
100
167
|
- The package is ESM-only.
|
|
101
168
|
- The package uses conditional ESM imports internally: Node.js resolves the
|
|
102
169
|
native N-API binding, while browsers and other runtimes resolve the portable
|
|
103
170
|
WebAssembly binding. Vite follows this split without consumer configuration.
|
|
104
|
-
- Every `openLix()` owns one dedicated worker
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
trusted plugins.
|
|
171
|
+
- Every browser `openLix()` owns one dedicated worker, so database work does
|
|
172
|
+
not block the page's main thread. Node.js uses the native binding's actor.
|
|
173
|
+
- Node.js executes installed Component API v2 plugins with the Rust SDK's
|
|
174
|
+
Wasmtime runtime. The browser and Workerd bindings currently open without a
|
|
175
|
+
component runtime: they can use ordinary Lix storage and SQL, but do not
|
|
176
|
+
execute installed plugins. A browser V2 host is a separate follow-up.
|
|
111
177
|
- A page Content Security Policy only needs to permit the package's same-origin
|
|
112
|
-
worker. WebAssembly compilation
|
|
113
|
-
|
|
114
|
-
|
|
178
|
+
worker. WebAssembly compilation happens inside that worker, so the required
|
|
179
|
+
permission can be scoped to the worker script's HTTP response instead of
|
|
180
|
+
being allowed by the document:
|
|
115
181
|
|
|
116
182
|
```http
|
|
117
183
|
# HTML document response
|
|
118
184
|
Content-Security-Policy: default-src 'self'; script-src 'self'; worker-src 'self'
|
|
119
185
|
|
|
120
186
|
# Lix worker response (Vite emits assets/entry.browser-<hash>.js)
|
|
121
|
-
Content-Security-Policy: default-src 'none'; script-src 'self'
|
|
187
|
+
Content-Security-Policy: default-src 'none'; script-src 'self' 'wasm-unsafe-eval'; connect-src 'self'
|
|
122
188
|
```
|
|
123
189
|
|
|
124
190
|
Hosts that apply one policy to every response can use
|
|
125
|
-
`script-src 'self'
|
|
191
|
+
`script-src 'self' 'wasm-unsafe-eval'; worker-src 'self'` globally
|
|
126
192
|
instead. Worker-scoped headers keep those permissions out of the page.
|
|
127
193
|
- SQL parameters use normal JavaScript values: `string`, finite `number`, `boolean`, `Uint8Array`, `null`, JSON-compatible arrays, and JSON-compatible plain objects.
|
|
128
194
|
- Use `Value.integer(...)`, `Value.real(...)`, `Value.text(...)`, `Value.json(...)`, or `Value.blob(...)` only when you need to pass an explicit native Lix value.
|
|
@@ -141,8 +207,8 @@ npm run test:browser
|
|
|
141
207
|
|
|
142
208
|
`npm run test:browser:production` additionally packs the SDK, installs the
|
|
143
209
|
tarball into a minimal Vite app, makes a production build, and exercises SQL
|
|
144
|
-
plus
|
|
145
|
-
global strict CSP headers.
|
|
210
|
+
plus bundled-plugin archive loading in Chromium. It runs with both
|
|
211
|
+
worker-scoped and global strict CSP headers.
|
|
146
212
|
|
|
147
213
|
Use `npm run build:wasm:dev` while iterating on the Rust bridge when release
|
|
148
214
|
optimization is unnecessary.
|
package/dist/binding-types.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { CreateBranchOptions, CreateBranchReceipt, ExecuteOptions, MergeBranchOptions, MergeBranchPreview, MergeBranchReceipt, SwitchBranchOptions, SwitchBranchReceipt } from "./types.js";
|
|
1
|
+
import type { CreateBranchOptions, CreateBranchReceipt, CreateCheckpointReceipt, ExecuteOptions, LixBatchOptions, MergeBranchOptions, MergeBranchPreview, MergeBranchReceipt, SwitchBranchOptions, SwitchBranchReceipt, LixTelemetrySpan, JsonValue } from "./types.js";
|
|
2
2
|
import type { NativeLixValue } from "./value.js";
|
|
3
3
|
export type BindingExecuteResult = {
|
|
4
4
|
columns: string[];
|
|
@@ -16,17 +16,32 @@ export type BindingObserveEvent = {
|
|
|
16
16
|
rows: BindingExecuteResult;
|
|
17
17
|
};
|
|
18
18
|
export type BindingParam = NativeLixValue;
|
|
19
|
+
export type BindingBatchStatement = {
|
|
20
|
+
sql: string;
|
|
21
|
+
params: BindingParam[];
|
|
22
|
+
};
|
|
19
23
|
export type LixBinding = {
|
|
20
24
|
execute(sql: string, params: BindingParam[], options?: ExecuteOptions): Promise<BindingExecuteResult>;
|
|
25
|
+
executeBatch(statements: BindingBatchStatement[], options?: LixBatchOptions): Promise<BindingExecuteResult[]>;
|
|
21
26
|
observe(sql: string, params: BindingParam[]): Promise<ObserveEventsBinding>;
|
|
22
27
|
beginTransaction(): Promise<LixTransactionBinding>;
|
|
23
28
|
activeBranchId(): Promise<string>;
|
|
29
|
+
clientStateEntries?(): Promise<Array<{
|
|
30
|
+
key: string;
|
|
31
|
+
value: JsonValue;
|
|
32
|
+
}>>;
|
|
33
|
+
clientStateGet?(key: string): Promise<JsonValue | undefined>;
|
|
34
|
+
clientStateSet?(key: string, value: JsonValue): Promise<void>;
|
|
35
|
+
clientStateDelete?(key: string): Promise<void>;
|
|
24
36
|
createBranch(options: CreateBranchOptions): Promise<CreateBranchReceipt>;
|
|
37
|
+
createCheckpoint(): Promise<CreateCheckpointReceipt>;
|
|
25
38
|
switchBranch(options: SwitchBranchOptions): Promise<SwitchBranchReceipt>;
|
|
26
39
|
importFilesystemPaths(paths: string[]): Promise<void>;
|
|
27
40
|
mergeBranchPreview(options: MergeBranchOptions): Promise<MergeBranchPreview>;
|
|
28
41
|
mergeBranch(options: MergeBranchOptions): Promise<MergeBranchReceipt>;
|
|
29
42
|
syncDiskToLix(): Promise<void>;
|
|
43
|
+
/** Internal snapshot capability implemented by browser memory bindings. */
|
|
44
|
+
exportSnapshot?(): Promise<Uint8Array>;
|
|
30
45
|
close(): Promise<void>;
|
|
31
46
|
};
|
|
32
47
|
export type LixTransactionBinding = {
|
|
@@ -38,9 +53,10 @@ export type ObserveEventsBinding = {
|
|
|
38
53
|
next(): Promise<BindingObserveEvent | null | undefined>;
|
|
39
54
|
close(): void;
|
|
40
55
|
};
|
|
41
|
-
export type
|
|
56
|
+
export type TelemetryDispatch = (span: LixTelemetrySpan) => void;
|
|
42
57
|
export type LixStorageConfig = {
|
|
43
58
|
kind: "memory";
|
|
59
|
+
snapshot?: Uint8Array;
|
|
44
60
|
} | {
|
|
45
61
|
kind: "sqlite";
|
|
46
62
|
path: string;
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import type { LixStorageConfig, LixBinding,
|
|
2
|
-
export declare function openLixBinding(storage: LixStorageConfig,
|
|
1
|
+
import type { LixStorageConfig, LixBinding, TelemetryDispatch } from "./binding-types.js";
|
|
2
|
+
export declare function openLixBinding(storage: LixStorageConfig, telemetry?: TelemetryDispatch): Promise<LixBinding>;
|
package/dist/binding.browser.js
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
// Generated before TypeScript compilation and emitted beside this module.
|
|
2
2
|
// @ts-expect-error Generated by build:wasm.
|
|
3
|
-
import initWasm, {
|
|
3
|
+
import initWasm, { openMemoryFromSnapshot } from "./wasm/lix_js_sdk.js";
|
|
4
4
|
let wasmInitialized;
|
|
5
5
|
function initializeWasm() {
|
|
6
6
|
return (wasmInitialized ??= initWasm());
|
|
7
7
|
}
|
|
8
|
-
export async function openLixBinding(storage,
|
|
8
|
+
export async function openLixBinding(storage, telemetry) {
|
|
9
9
|
if (storage.kind !== "memory") {
|
|
10
10
|
throw new Error(`${storage.kind === "localFilesystem" ? "LocalFilesystem" : "SQLite"} is only available in Node.js`);
|
|
11
11
|
}
|
|
12
12
|
await initializeWasm();
|
|
13
|
-
return
|
|
13
|
+
return openMemoryFromSnapshot(telemetry, storage.snapshot);
|
|
14
14
|
}
|
package/dist/binding.node.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import type { LixStorageConfig, LixBinding,
|
|
2
|
-
export declare function openLixBinding(storage: LixStorageConfig,
|
|
1
|
+
import type { LixStorageConfig, LixBinding, TelemetryDispatch } from "./binding-types.js";
|
|
2
|
+
export declare function openLixBinding(storage: LixStorageConfig, telemetry?: TelemetryDispatch): Promise<LixBinding>;
|
package/dist/binding.node.js
CHANGED
|
@@ -39,13 +39,19 @@ catch (cause) {
|
|
|
39
39
|
error.cause = cause;
|
|
40
40
|
throw error;
|
|
41
41
|
}
|
|
42
|
-
export function openLixBinding(storage,
|
|
42
|
+
export function openLixBinding(storage, telemetry) {
|
|
43
|
+
const nativeTelemetry = telemetry
|
|
44
|
+
? (spanJson) => telemetry(JSON.parse(spanJson))
|
|
45
|
+
: undefined;
|
|
43
46
|
switch (storage.kind) {
|
|
44
47
|
case "memory":
|
|
45
|
-
|
|
48
|
+
if (storage.snapshot !== undefined) {
|
|
49
|
+
throw new Error("Memory snapshots are only available in the browser binding");
|
|
50
|
+
}
|
|
51
|
+
return addon.Lix.openMemory(nativeTelemetry);
|
|
46
52
|
case "sqlite":
|
|
47
|
-
return addon.Lix.openSQLite(storage.path,
|
|
53
|
+
return addon.Lix.openSQLite(storage.path, nativeTelemetry);
|
|
48
54
|
case "localFilesystem":
|
|
49
|
-
return addon.Lix.openLocalFilesystem(storage.path, storage.lixDir, storage.syncAllFiles,
|
|
55
|
+
return addon.Lix.openLocalFilesystem(storage.path, storage.lixDir, storage.syncAllFiles, nativeTelemetry);
|
|
50
56
|
}
|
|
51
57
|
}
|
|
Binary file
|
|
Binary file
|
package/dist/bundled-plugins.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
const BUNDLED_PLUGIN_MANIFEST = [
|
|
2
2
|
{
|
|
3
|
-
key: "
|
|
4
|
-
fileName: "
|
|
3
|
+
key: "plugin_markdown_incremental_v2",
|
|
4
|
+
fileName: "plugin_markdown_incremental_v2.lixplugin",
|
|
5
5
|
},
|
|
6
6
|
{
|
|
7
|
-
key: "
|
|
8
|
-
fileName: "
|
|
7
|
+
key: "plugin_csv_v2",
|
|
8
|
+
fileName: "plugin_csv_v2.lixplugin",
|
|
9
9
|
},
|
|
10
10
|
];
|
|
11
11
|
export async function bundledPluginArchives() {
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { LixBinding } from "./binding-types.js";
|
|
2
|
+
import type { JsonValue } from "./types.js";
|
|
3
|
+
export declare const ACTIVE_BRANCH_CLIENT_STATE_KEY = "lix_active_branch_id";
|
|
4
|
+
export type LixClientState = {
|
|
5
|
+
/** Returns the hydrated client-local value without a network round trip. */
|
|
6
|
+
get<T extends JsonValue = JsonValue>(key: string): T | undefined;
|
|
7
|
+
/** Commits the value through the local Rust Lix and then persists its snapshot. */
|
|
8
|
+
set(key: string, value: JsonValue): Promise<void>;
|
|
9
|
+
/** Deletes the value through the local Rust Lix and then persists its snapshot. */
|
|
10
|
+
delete(key: string): Promise<void>;
|
|
11
|
+
/** Subscribes to successful mutations made through this client-state handle. */
|
|
12
|
+
subscribe(listener: () => void): () => void;
|
|
13
|
+
};
|
|
14
|
+
export declare function unavailableClientState(): LixClientState;
|
|
15
|
+
type ClientStateBinding = LixBinding & {
|
|
16
|
+
exportSnapshot?: () => Promise<Uint8Array>;
|
|
17
|
+
};
|
|
18
|
+
export type OpenClientStateOptions = {
|
|
19
|
+
readonly binding: ClientStateBinding;
|
|
20
|
+
readonly saveSnapshot?: (snapshot: Uint8Array) => Promise<void>;
|
|
21
|
+
readonly closeBinding?: boolean;
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* Opens the typed client-state facade over a private local Rust Lix.
|
|
25
|
+
*
|
|
26
|
+
* Values are ordinary global, untracked `lix_key_value` rows. The physical
|
|
27
|
+
* prefix is intentionally private so built-in Lix key/value rows never leak
|
|
28
|
+
* through this small API.
|
|
29
|
+
*/
|
|
30
|
+
export declare function openClientState(options: OpenClientStateOptions): Promise<ManagedLixClientState>;
|
|
31
|
+
export declare class ManagedLixClientState implements LixClientState {
|
|
32
|
+
#private;
|
|
33
|
+
constructor(options: OpenClientStateOptions, initial: Map<string, JsonValue>);
|
|
34
|
+
get<T extends JsonValue = JsonValue>(key: string): T | undefined;
|
|
35
|
+
set(key: string, value: JsonValue): Promise<void>;
|
|
36
|
+
delete(key: string): Promise<void>;
|
|
37
|
+
subscribe(listener: () => void): () => void;
|
|
38
|
+
close(): Promise<void>;
|
|
39
|
+
}
|
|
40
|
+
export {};
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import { isSnapshotPersistenceAfterCommitError } from "./snapshot-persistence.js";
|
|
2
|
+
import { Value } from "./value.js";
|
|
3
|
+
export const ACTIVE_BRANCH_CLIENT_STATE_KEY = "lix_active_branch_id";
|
|
4
|
+
export function unavailableClientState() {
|
|
5
|
+
const unavailable = () => {
|
|
6
|
+
const error = new Error("Lix client state requires client storage; pass storage to openLix()");
|
|
7
|
+
error.name = "LixError";
|
|
8
|
+
error.code = "LIX_CLIENT_STORAGE_REQUIRED";
|
|
9
|
+
return error;
|
|
10
|
+
};
|
|
11
|
+
return {
|
|
12
|
+
get: () => undefined,
|
|
13
|
+
set: async () => {
|
|
14
|
+
throw unavailable();
|
|
15
|
+
},
|
|
16
|
+
delete: async () => {
|
|
17
|
+
throw unavailable();
|
|
18
|
+
},
|
|
19
|
+
subscribe: () => () => undefined,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Opens the typed client-state facade over a private local Rust Lix.
|
|
24
|
+
*
|
|
25
|
+
* Values are ordinary global, untracked `lix_key_value` rows. The physical
|
|
26
|
+
* prefix is intentionally private so built-in Lix key/value rows never leak
|
|
27
|
+
* through this small API.
|
|
28
|
+
*/
|
|
29
|
+
export async function openClientState(options) {
|
|
30
|
+
const entries = options.binding.clientStateEntries;
|
|
31
|
+
if (!entries) {
|
|
32
|
+
throw new Error("The selected Lix binding does not support typed client state");
|
|
33
|
+
}
|
|
34
|
+
const initial = new Map();
|
|
35
|
+
for (const entry of await entries.call(options.binding)) {
|
|
36
|
+
assertClientStateKey(entry.key);
|
|
37
|
+
assertJsonValue(entry.value);
|
|
38
|
+
initial.set(entry.key, cloneJsonValue(entry.value));
|
|
39
|
+
}
|
|
40
|
+
return new ManagedLixClientState(options, initial);
|
|
41
|
+
}
|
|
42
|
+
export class ManagedLixClientState {
|
|
43
|
+
#binding;
|
|
44
|
+
#saveSnapshot;
|
|
45
|
+
#closeBinding;
|
|
46
|
+
#values;
|
|
47
|
+
#listeners = new Set();
|
|
48
|
+
#operationQueue = Promise.resolve();
|
|
49
|
+
#closePromise;
|
|
50
|
+
#acceptingOperations = true;
|
|
51
|
+
constructor(options, initial) {
|
|
52
|
+
this.#binding = options.binding;
|
|
53
|
+
this.#saveSnapshot = options.saveSnapshot;
|
|
54
|
+
this.#closeBinding = options.closeBinding ?? false;
|
|
55
|
+
this.#values = initial;
|
|
56
|
+
}
|
|
57
|
+
get(key) {
|
|
58
|
+
assertClientStateKey(key);
|
|
59
|
+
const value = this.#values.get(key);
|
|
60
|
+
return value === undefined ? undefined : cloneJsonValue(value);
|
|
61
|
+
}
|
|
62
|
+
set(key, value) {
|
|
63
|
+
assertClientStateKey(key);
|
|
64
|
+
assertJsonValue(value);
|
|
65
|
+
this.#assertOpen();
|
|
66
|
+
const nextValue = cloneJsonValue(value);
|
|
67
|
+
return this.#enqueue(async () => {
|
|
68
|
+
const set = this.#binding.clientStateSet;
|
|
69
|
+
if (!set)
|
|
70
|
+
throw new Error("Typed Lix client state is unavailable");
|
|
71
|
+
try {
|
|
72
|
+
await set.call(this.#binding, key, nextValue);
|
|
73
|
+
}
|
|
74
|
+
catch (error) {
|
|
75
|
+
if (!isSnapshotPersistenceAfterCommitError(error))
|
|
76
|
+
throw error;
|
|
77
|
+
this.#commitSet(key, nextValue);
|
|
78
|
+
throw error;
|
|
79
|
+
}
|
|
80
|
+
this.#commitSet(key, nextValue);
|
|
81
|
+
await this.#persist();
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
delete(key) {
|
|
85
|
+
assertClientStateKey(key);
|
|
86
|
+
this.#assertOpen();
|
|
87
|
+
return this.#enqueue(async () => {
|
|
88
|
+
const deleteValue = this.#binding.clientStateDelete;
|
|
89
|
+
if (!deleteValue)
|
|
90
|
+
throw new Error("Typed Lix client state is unavailable");
|
|
91
|
+
try {
|
|
92
|
+
await deleteValue.call(this.#binding, key);
|
|
93
|
+
}
|
|
94
|
+
catch (error) {
|
|
95
|
+
if (!isSnapshotPersistenceAfterCommitError(error))
|
|
96
|
+
throw error;
|
|
97
|
+
this.#commitDelete(key);
|
|
98
|
+
throw error;
|
|
99
|
+
}
|
|
100
|
+
this.#commitDelete(key);
|
|
101
|
+
await this.#persist();
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
subscribe(listener) {
|
|
105
|
+
if (typeof listener !== "function") {
|
|
106
|
+
throw new TypeError("clientState.subscribe() requires a function");
|
|
107
|
+
}
|
|
108
|
+
this.#assertOpen();
|
|
109
|
+
this.#listeners.add(listener);
|
|
110
|
+
return () => this.#listeners.delete(listener);
|
|
111
|
+
}
|
|
112
|
+
async close() {
|
|
113
|
+
if (this.#closePromise)
|
|
114
|
+
return this.#closePromise;
|
|
115
|
+
this.#acceptingOperations = false;
|
|
116
|
+
this.#closePromise = (async () => {
|
|
117
|
+
await this.#operationQueue;
|
|
118
|
+
this.#listeners.clear();
|
|
119
|
+
if (this.#closeBinding)
|
|
120
|
+
await this.#binding.close();
|
|
121
|
+
})();
|
|
122
|
+
return this.#closePromise;
|
|
123
|
+
}
|
|
124
|
+
#enqueue(operation) {
|
|
125
|
+
const result = this.#operationQueue.then(operation, operation);
|
|
126
|
+
this.#operationQueue = result.then(() => undefined, () => undefined);
|
|
127
|
+
return result;
|
|
128
|
+
}
|
|
129
|
+
async #persist() {
|
|
130
|
+
if (!this.#saveSnapshot)
|
|
131
|
+
return;
|
|
132
|
+
if (!this.#binding.exportSnapshot) {
|
|
133
|
+
throw new Error("The selected Lix binding cannot export storage snapshots");
|
|
134
|
+
}
|
|
135
|
+
await this.#saveSnapshot(await this.#binding.exportSnapshot());
|
|
136
|
+
}
|
|
137
|
+
#commitSet(key, value) {
|
|
138
|
+
this.#values.set(key, value);
|
|
139
|
+
this.#publish();
|
|
140
|
+
}
|
|
141
|
+
#commitDelete(key) {
|
|
142
|
+
if (this.#values.delete(key))
|
|
143
|
+
this.#publish();
|
|
144
|
+
}
|
|
145
|
+
#publish() {
|
|
146
|
+
for (const listener of [...this.#listeners]) {
|
|
147
|
+
try {
|
|
148
|
+
listener();
|
|
149
|
+
}
|
|
150
|
+
catch {
|
|
151
|
+
// Subscribers do not participate in the completed local transaction.
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
#assertOpen() {
|
|
156
|
+
if (!this.#acceptingOperations) {
|
|
157
|
+
throw new Error("Lix client state is closed");
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
function assertClientStateKey(key) {
|
|
162
|
+
if (typeof key !== "string" || key.length === 0) {
|
|
163
|
+
throw new TypeError("clientState key must be a non-empty string");
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
function assertJsonValue(value) {
|
|
167
|
+
// Value.json owns the SDK's full JSON validation, including finite numbers,
|
|
168
|
+
// well-formed strings, plain objects, and cycle detection.
|
|
169
|
+
Value.json(value);
|
|
170
|
+
}
|
|
171
|
+
function cloneJsonValue(value) {
|
|
172
|
+
if (Array.isArray(value))
|
|
173
|
+
return value.map(cloneJsonValue);
|
|
174
|
+
if (value && typeof value === "object") {
|
|
175
|
+
return Object.fromEntries(Object.entries(value).map(([key, entry]) => [key, cloneJsonValue(entry)]));
|
|
176
|
+
}
|
|
177
|
+
return value;
|
|
178
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -2,4 +2,5 @@ export { LocalFilesystem, Lix, LixTransaction, ObserveEvents, openLix, SQLite, }
|
|
|
2
2
|
export { bundledPluginArchives, type BundledPluginArchive, } from "./bundled-plugins.js";
|
|
3
3
|
export { Row } from "./result.js";
|
|
4
4
|
export { Value } from "./value.js";
|
|
5
|
-
export type {
|
|
5
|
+
export type { LixClientState } from "./client-state.js";
|
|
6
|
+
export type { CreateBranchOptions, CreateBranchReceipt, CreateCheckpointReceipt, ExecuteOptions, ExecuteResult, LixBatchOptions, LixBatchStatement, LocalFilesystemOptions, JsonValue, LixValue, MergeBranchOptions, MergeBranchOutcome, MergeBranchPreview, MergeBranchReceipt, MergeChangeStats, MergeConflict, MergeConflictSide, ObserveEvent, OpenLixOptions, LixTelemetryOptions, LixTelemetrySpan, LixSnapshotStorage, RemoteLixFetch, RemoteLixServerOptions, SqlParam, SQLiteOptions, SwitchBranchOptions, SwitchBranchReceipt, } from "./types.js";
|
package/dist/lix.d.ts
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { type LixClientState, type ManagedLixClientState } from "./client-state.js";
|
|
2
|
+
import type { LixBinding, LixTransactionBinding, ObserveEventsBinding } from "./binding-types.js";
|
|
3
|
+
import type { CreateBranchOptions, CreateBranchReceipt, CreateCheckpointReceipt, ExecuteOptions, ExecuteResult, LixBatchOptions, LixBatchStatement, MergeBranchOptions, MergeBranchPreview, MergeBranchReceipt, ObserveEvent, SqlParam, SwitchBranchOptions, SwitchBranchReceipt } from "./types.js";
|
|
4
|
+
export declare class Lix {
|
|
5
|
+
#private;
|
|
6
|
+
private readonly binding;
|
|
7
|
+
private readonly managedClientState?;
|
|
8
|
+
private closePromise;
|
|
9
|
+
readonly clientState: LixClientState;
|
|
10
|
+
constructor(binding: LixBinding, managedClientState?: ManagedLixClientState | undefined);
|
|
11
|
+
execute(sql: string, params?: SqlParam[], options?: ExecuteOptions): Promise<ExecuteResult>;
|
|
12
|
+
executeBatch(statements: readonly LixBatchStatement[], options?: LixBatchOptions): Promise<readonly ExecuteResult[]>;
|
|
13
|
+
observe(sql: string, params?: SqlParam[]): ObserveEvents;
|
|
14
|
+
beginTransaction(): Promise<LixTransaction>;
|
|
15
|
+
activeBranchId(): Promise<string>;
|
|
16
|
+
/** Subscribes to successful branch switches made through this Lix handle. */
|
|
17
|
+
subscribeActiveBranch(listener: () => void): () => void;
|
|
18
|
+
createBranch(options: CreateBranchOptions): Promise<CreateBranchReceipt>;
|
|
19
|
+
createCheckpoint(): Promise<CreateCheckpointReceipt>;
|
|
20
|
+
switchBranch(options: SwitchBranchOptions): Promise<SwitchBranchReceipt>;
|
|
21
|
+
mergeBranchPreview(options: MergeBranchOptions): Promise<MergeBranchPreview>;
|
|
22
|
+
mergeBranch(options: MergeBranchOptions): Promise<MergeBranchReceipt>;
|
|
23
|
+
close(): Promise<void>;
|
|
24
|
+
}
|
|
25
|
+
export declare class ObserveEvents {
|
|
26
|
+
private readonly onClose;
|
|
27
|
+
private readonly setup;
|
|
28
|
+
private closed;
|
|
29
|
+
private readonly observeBinding;
|
|
30
|
+
constructor(observeBinding: Promise<ObserveEventsBinding>, onClose?: () => void);
|
|
31
|
+
next(): Promise<ObserveEvent | undefined>;
|
|
32
|
+
close(): void;
|
|
33
|
+
}
|
|
34
|
+
export declare class LixTransaction {
|
|
35
|
+
private readonly binding;
|
|
36
|
+
private readonly onFinish;
|
|
37
|
+
private finishPromise;
|
|
38
|
+
private finished;
|
|
39
|
+
constructor(binding: LixTransactionBinding, onFinish?: () => void);
|
|
40
|
+
execute(sql: string, params?: SqlParam[], options?: ExecuteOptions): Promise<ExecuteResult>;
|
|
41
|
+
commit(): Promise<void>;
|
|
42
|
+
rollback(): Promise<void>;
|
|
43
|
+
private finish;
|
|
44
|
+
}
|