@lix-js/sdk 0.9.0 → 0.11.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 +28 -12
- package/dist/binding-types.d.ts +7 -4
- package/dist/binding.browser.js +1 -1
- package/dist/binding.node.js +7 -4
- package/dist/bundled-plugins/plugin_csv.lixplugin +0 -0
- package/dist/bundled-plugins/plugin_markdown.lixplugin +0 -0
- package/dist/bundled-plugins.js +4 -4
- package/dist/client-state.d.ts +16 -3
- package/dist/client-state.js +137 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/lix.d.ts +7 -4
- package/dist/lix.js +16 -2
- package/dist/open-lix.d.ts +1 -5
- package/dist/open-lix.js +12 -33
- package/dist/remote/client.d.ts +1 -0
- package/dist/remote/client.js +156 -7
- package/dist/remote/protocol.d.ts +19 -1
- package/dist/remote/protocol.js +20 -3
- package/dist/result.d.ts +2 -1
- package/dist/result.js +12 -0
- package/dist/types.d.ts +22 -4
- package/dist/wasm/lix_js_sdk.d.ts +8 -2
- package/dist/wasm/lix_js_sdk.js +28 -7
- package/dist/wasm/lix_js_sdk_bg.wasm +0 -0
- package/dist/wasm/lix_js_sdk_bg.wasm.d.ts +5 -2
- package/dist/worker/client.js +42 -1
- package/dist/worker/factory.browser.d.ts +2 -0
- package/dist/worker/factory.browser.js +2 -0
- package/dist/worker/factory.node.d.ts +2 -0
- package/dist/worker/factory.node.js +5 -0
- package/dist/worker/host.js +6 -0
- package/dist/worker/protocol.d.ts +6 -0
- package/package.json +5 -5
- package/dist/bundled-plugins/plugin_csv_v2.lixplugin +0 -0
- package/dist/bundled-plugins/plugin_markdown_incremental_v2.lixplugin +0 -0
package/README.md
CHANGED
|
@@ -41,7 +41,7 @@ const files = lix.observe("SELECT path FROM lix_file ORDER BY path");
|
|
|
41
41
|
const initial = await files.next();
|
|
42
42
|
|
|
43
43
|
await lix.execute(
|
|
44
|
-
"INSERT INTO lix_file (path,
|
|
44
|
+
"INSERT INTO lix_file (path, content) VALUES ($1, $2)",
|
|
45
45
|
["/hello.txt", new TextEncoder().encode("hello")],
|
|
46
46
|
);
|
|
47
47
|
const update = await files.next();
|
|
@@ -88,7 +88,7 @@ server switch into a rejected operation. Explicit `lix.clientState.set()` and
|
|
|
88
88
|
Rust transaction has already committed, `get()` continues to expose that live
|
|
89
89
|
session value; a later successful snapshot save can make it durable.
|
|
90
90
|
|
|
91
|
-
Filesystem sync
|
|
91
|
+
Filesystem sync uses native Node.js dependencies:
|
|
92
92
|
|
|
93
93
|
```ts
|
|
94
94
|
import { LocalFilesystem, openLix } from "@lix-js/sdk";
|
|
@@ -101,20 +101,37 @@ const lix = await openLix({
|
|
|
101
101
|
});
|
|
102
102
|
|
|
103
103
|
await lix.execute(
|
|
104
|
-
"INSERT INTO lix_file (path,
|
|
104
|
+
"INSERT INTO lix_file (path, content) VALUES ($1, $2) ON CONFLICT (path) DO UPDATE SET content = excluded.content",
|
|
105
105
|
["/hello.txt", new TextEncoder().encode("world")],
|
|
106
106
|
);
|
|
107
107
|
|
|
108
|
-
const result = await lix.execute("SELECT
|
|
108
|
+
const result = await lix.execute("SELECT content FROM lix_file WHERE path = $1", [
|
|
109
109
|
"/hello.txt",
|
|
110
110
|
]);
|
|
111
|
-
const bytes = result.rows[0]?.value("
|
|
111
|
+
const bytes = result.rows[0]?.value("content").asBytes();
|
|
112
112
|
|
|
113
113
|
console.log(bytes && new TextDecoder().decode(bytes));
|
|
114
114
|
|
|
115
115
|
await lix.close();
|
|
116
116
|
```
|
|
117
117
|
|
|
118
|
+
## Discover the SQL contract
|
|
119
|
+
|
|
120
|
+
Lix extends the standard `information_schema.columns` relation with
|
|
121
|
+
`lix_value_kind` and `lix_insert_policy`. Inspect it before generating writes:
|
|
122
|
+
|
|
123
|
+
```sql
|
|
124
|
+
SELECT table_name, column_name, data_type, is_nullable, column_default,
|
|
125
|
+
lix_value_kind, lix_insert_policy
|
|
126
|
+
FROM information_schema.columns
|
|
127
|
+
WHERE table_name = 'lix_file'
|
|
128
|
+
ORDER BY ordinal_position;
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
`lix_insert_policy` distinguishes `REQUIRED`, `DEFAULT`, `CONDITIONAL`, and
|
|
132
|
+
`READ_ONLY` columns. For the complete table and history-function map, see
|
|
133
|
+
[SQL Surfaces](https://lix.dev/docs/surfaces).
|
|
134
|
+
|
|
118
135
|
## Branches
|
|
119
136
|
|
|
120
137
|
```ts
|
|
@@ -123,7 +140,7 @@ const draft = await lix.createBranch({ name: "Draft" });
|
|
|
123
140
|
|
|
124
141
|
await lix.switchBranch({ branchId: draft.id });
|
|
125
142
|
await lix.execute(
|
|
126
|
-
"INSERT INTO lix_file (path,
|
|
143
|
+
"INSERT INTO lix_file (path, content) VALUES ($1, $2) ON CONFLICT (path) DO UPDATE SET content = excluded.content",
|
|
127
144
|
["/status.txt", new TextEncoder().encode("draft")],
|
|
128
145
|
);
|
|
129
146
|
|
|
@@ -139,11 +156,11 @@ const tx = await lix.beginTransaction();
|
|
|
139
156
|
|
|
140
157
|
try {
|
|
141
158
|
await tx.execute(
|
|
142
|
-
"INSERT INTO lix_file (path,
|
|
159
|
+
"INSERT INTO lix_file (path, content) VALUES ($1, $2) ON CONFLICT (path) DO UPDATE SET content = excluded.content",
|
|
143
160
|
["/a.txt", new TextEncoder().encode("1")],
|
|
144
161
|
);
|
|
145
162
|
await tx.execute(
|
|
146
|
-
"INSERT INTO lix_file (path,
|
|
163
|
+
"INSERT INTO lix_file (path, content) VALUES ($1, $2) ON CONFLICT (path) DO UPDATE SET content = excluded.content",
|
|
147
164
|
["/b.txt", new TextEncoder().encode("2")],
|
|
148
165
|
);
|
|
149
166
|
await tx.commit();
|
|
@@ -158,11 +175,10 @@ try {
|
|
|
158
175
|
- `openLix()` opens a fresh in-memory Lix. Pass `new LocalFilesystem({ path, syncAllFiles: true })` for a filesystem workspace directory backed by `<path>/.lix/.internal/rocksdb`.
|
|
159
176
|
- Pass `new LocalFilesystem({ path, lixDir, syncAllFiles: true })` for filesystem sync with repository metadata in an external `.lix` directory and no workspace `.lix` directory.
|
|
160
177
|
- 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.
|
|
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.
|
|
162
178
|
- In browsers, local mode and remote mode with client storage load the Rust
|
|
163
179
|
engine as WebAssembly. Supplying a snapshot storage adapter persists that
|
|
164
180
|
local Lix; in remote mode, the local engine contains only client state.
|
|
165
|
-
- `LocalFilesystem`
|
|
181
|
+
- `LocalFilesystem` is Node.js-only. Constructing it is safe in
|
|
166
182
|
shared code, but passing one to `openLix()` in a browser throws an error.
|
|
167
183
|
- The package is ESM-only.
|
|
168
184
|
- The package uses conditional ESM imports internally: Node.js resolves the
|
|
@@ -170,10 +186,10 @@ try {
|
|
|
170
186
|
WebAssembly binding. Vite follows this split without consumer configuration.
|
|
171
187
|
- Every browser `openLix()` owns one dedicated worker, so database work does
|
|
172
188
|
not block the page's main thread. Node.js uses the native binding's actor.
|
|
173
|
-
- Node.js executes installed Component API
|
|
189
|
+
- Node.js executes installed Component API v1 plugins with the Rust SDK's
|
|
174
190
|
Wasmtime runtime. The browser and Workerd bindings currently open without a
|
|
175
191
|
component runtime: they can use ordinary Lix storage and SQL, but do not
|
|
176
|
-
execute installed plugins. A browser
|
|
192
|
+
execute installed plugins. A browser Component host is a separate follow-up.
|
|
177
193
|
- A page Content Security Policy only needs to permit the package's same-origin
|
|
178
194
|
worker. WebAssembly compilation happens inside that worker, so the required
|
|
179
195
|
permission can be scoped to the worker script's HTTP response instead of
|
package/dist/binding-types.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import type { CreateBranchOptions, CreateBranchReceipt, CreateCheckpointReceipt, ExecuteOptions, LixBatchOptions, MergeBranchOptions, MergeBranchPreview, MergeBranchReceipt, SwitchBranchOptions, SwitchBranchReceipt, LixTelemetrySpan, JsonValue } from "./types.js";
|
|
1
|
+
import type { CreateBranchOptions, CreateBranchReceipt, CreateCheckpointReceipt, UndoReceipt, RedoReceipt, 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
|
+
statementIndex?: number;
|
|
5
|
+
label?: string;
|
|
4
6
|
columns: string[];
|
|
5
7
|
rows: NativeLixValue[][];
|
|
6
8
|
rowsAffected: number;
|
|
@@ -19,6 +21,7 @@ export type BindingParam = NativeLixValue;
|
|
|
19
21
|
export type BindingBatchStatement = {
|
|
20
22
|
sql: string;
|
|
21
23
|
params: BindingParam[];
|
|
24
|
+
label?: string;
|
|
22
25
|
};
|
|
23
26
|
export type LixBinding = {
|
|
24
27
|
execute(sql: string, params: BindingParam[], options?: ExecuteOptions): Promise<BindingExecuteResult>;
|
|
@@ -26,6 +29,7 @@ export type LixBinding = {
|
|
|
26
29
|
observe(sql: string, params: BindingParam[]): Promise<ObserveEventsBinding>;
|
|
27
30
|
beginTransaction(): Promise<LixTransactionBinding>;
|
|
28
31
|
activeBranchId(): Promise<string>;
|
|
32
|
+
activeAccountId(): Promise<string>;
|
|
29
33
|
clientStateEntries?(): Promise<Array<{
|
|
30
34
|
key: string;
|
|
31
35
|
value: JsonValue;
|
|
@@ -35,6 +39,8 @@ export type LixBinding = {
|
|
|
35
39
|
clientStateDelete?(key: string): Promise<void>;
|
|
36
40
|
createBranch(options: CreateBranchOptions): Promise<CreateBranchReceipt>;
|
|
37
41
|
createCheckpoint(): Promise<CreateCheckpointReceipt>;
|
|
42
|
+
undo(): Promise<UndoReceipt>;
|
|
43
|
+
redo(): Promise<RedoReceipt>;
|
|
38
44
|
switchBranch(options: SwitchBranchOptions): Promise<SwitchBranchReceipt>;
|
|
39
45
|
importFilesystemPaths(paths: string[]): Promise<void>;
|
|
40
46
|
mergeBranchPreview(options: MergeBranchOptions): Promise<MergeBranchPreview>;
|
|
@@ -57,9 +63,6 @@ export type TelemetryDispatch = (span: LixTelemetrySpan) => void;
|
|
|
57
63
|
export type LixStorageConfig = {
|
|
58
64
|
kind: "memory";
|
|
59
65
|
snapshot?: Uint8Array;
|
|
60
|
-
} | {
|
|
61
|
-
kind: "sqlite";
|
|
62
|
-
path: string;
|
|
63
66
|
} | {
|
|
64
67
|
kind: "localFilesystem";
|
|
65
68
|
path: string;
|
package/dist/binding.browser.js
CHANGED
|
@@ -7,7 +7,7 @@ function initializeWasm() {
|
|
|
7
7
|
}
|
|
8
8
|
export async function openLixBinding(storage, telemetry) {
|
|
9
9
|
if (storage.kind !== "memory") {
|
|
10
|
-
throw new Error(
|
|
10
|
+
throw new Error("LocalFilesystem is only available in Node.js");
|
|
11
11
|
}
|
|
12
12
|
await initializeWasm();
|
|
13
13
|
return openMemoryFromSnapshot(telemetry, storage.snapshot);
|
package/dist/binding.node.js
CHANGED
|
@@ -48,10 +48,13 @@ export function openLixBinding(storage, telemetry) {
|
|
|
48
48
|
if (storage.snapshot !== undefined) {
|
|
49
49
|
throw new Error("Memory snapshots are only available in the browser binding");
|
|
50
50
|
}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
return addon.Lix.
|
|
51
|
+
if (nativeTelemetry)
|
|
52
|
+
return addon.Lix.openMemory(nativeTelemetry);
|
|
53
|
+
return addon.Lix.openMemory();
|
|
54
54
|
case "localFilesystem":
|
|
55
|
-
|
|
55
|
+
if (nativeTelemetry) {
|
|
56
|
+
return addon.Lix.openLocalFilesystem(storage.path, storage.lixDir, storage.syncAllFiles, nativeTelemetry);
|
|
57
|
+
}
|
|
58
|
+
return addon.Lix.openLocalFilesystem(storage.path, storage.lixDir, storage.syncAllFiles);
|
|
56
59
|
}
|
|
57
60
|
}
|
|
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",
|
|
4
|
+
fileName: "plugin_markdown.lixplugin",
|
|
5
5
|
},
|
|
6
6
|
{
|
|
7
|
-
key: "
|
|
8
|
-
fileName: "
|
|
7
|
+
key: "plugin_csv",
|
|
8
|
+
fileName: "plugin_csv.lixplugin",
|
|
9
9
|
},
|
|
10
10
|
];
|
|
11
11
|
export async function bundledPluginArchives() {
|
package/dist/client-state.d.ts
CHANGED
|
@@ -1,16 +1,20 @@
|
|
|
1
1
|
import type { LixBinding } from "./binding-types.js";
|
|
2
|
-
import type { JsonValue } from "./types.js";
|
|
2
|
+
import type { JsonValue, LixSnapshotStorage } from "./types.js";
|
|
3
3
|
export declare const ACTIVE_BRANCH_CLIENT_STATE_KEY = "lix_active_branch_id";
|
|
4
|
+
export declare const ACTIVE_ACCOUNT_CLIENT_STATE_KEY = "lix_active_account_id";
|
|
4
5
|
export type LixClientState = {
|
|
5
6
|
/** Returns the hydrated client-local value without a network round trip. */
|
|
6
7
|
get<T extends JsonValue = JsonValue>(key: string): T | undefined;
|
|
7
|
-
/**
|
|
8
|
+
/** Persists a client-local value in the configured client storage. */
|
|
8
9
|
set(key: string, value: JsonValue): Promise<void>;
|
|
9
|
-
/** Deletes
|
|
10
|
+
/** Deletes a client-local value from the configured client storage. */
|
|
10
11
|
delete(key: string): Promise<void>;
|
|
11
12
|
/** Subscribes to successful mutations made through this client-state handle. */
|
|
12
13
|
subscribe(listener: () => void): () => void;
|
|
13
14
|
};
|
|
15
|
+
export type ManagedClientState = LixClientState & {
|
|
16
|
+
close(): Promise<void>;
|
|
17
|
+
};
|
|
14
18
|
export declare function unavailableClientState(): LixClientState;
|
|
15
19
|
type ClientStateBinding = LixBinding & {
|
|
16
20
|
exportSnapshot?: () => Promise<Uint8Array>;
|
|
@@ -37,4 +41,13 @@ export declare class ManagedLixClientState implements LixClientState {
|
|
|
37
41
|
subscribe(listener: () => void): () => void;
|
|
38
42
|
close(): Promise<void>;
|
|
39
43
|
}
|
|
44
|
+
/**
|
|
45
|
+
* Opens client state directly over snapshot storage without starting a local
|
|
46
|
+
* Lix runtime. This is used by remote Lix connections, where the storage
|
|
47
|
+
* option persists client-local state rather than the remote workspace.
|
|
48
|
+
*/
|
|
49
|
+
export declare function openStoredClientState(options: {
|
|
50
|
+
readonly storage: LixSnapshotStorage;
|
|
51
|
+
readonly namespace: string;
|
|
52
|
+
}): Promise<ManagedClientState>;
|
|
40
53
|
export {};
|
package/dist/client-state.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { isSnapshotPersistenceAfterCommitError } from "./snapshot-persistence.js";
|
|
2
2
|
import { Value } from "./value.js";
|
|
3
3
|
export const ACTIVE_BRANCH_CLIENT_STATE_KEY = "lix_active_branch_id";
|
|
4
|
+
export const ACTIVE_ACCOUNT_CLIENT_STATE_KEY = "lix_active_account_id";
|
|
5
|
+
const STORED_CLIENT_STATE_HEADER = "lix-client-state-v1\n";
|
|
4
6
|
export function unavailableClientState() {
|
|
5
7
|
const unavailable = () => {
|
|
6
8
|
const error = new Error("Lix client state requires client storage; pass storage to openLix()");
|
|
@@ -158,6 +160,141 @@ export class ManagedLixClientState {
|
|
|
158
160
|
}
|
|
159
161
|
}
|
|
160
162
|
}
|
|
163
|
+
/**
|
|
164
|
+
* Opens client state directly over snapshot storage without starting a local
|
|
165
|
+
* Lix runtime. This is used by remote Lix connections, where the storage
|
|
166
|
+
* option persists client-local state rather than the remote workspace.
|
|
167
|
+
*/
|
|
168
|
+
export async function openStoredClientState(options) {
|
|
169
|
+
const snapshot = await options.storage.load(options.namespace);
|
|
170
|
+
if (snapshot !== undefined && !(snapshot instanceof Uint8Array)) {
|
|
171
|
+
throw new TypeError("Client-state storage load() must return a Uint8Array");
|
|
172
|
+
}
|
|
173
|
+
return new StoredClientState(options.storage, options.namespace, decodeStoredClientState(snapshot));
|
|
174
|
+
}
|
|
175
|
+
class StoredClientState {
|
|
176
|
+
#storage;
|
|
177
|
+
#namespace;
|
|
178
|
+
#values;
|
|
179
|
+
#listeners = new Set();
|
|
180
|
+
#operationQueue = Promise.resolve();
|
|
181
|
+
#closePromise;
|
|
182
|
+
#acceptingOperations = true;
|
|
183
|
+
#dirty = false;
|
|
184
|
+
constructor(storage, namespace, values) {
|
|
185
|
+
this.#storage = storage;
|
|
186
|
+
this.#namespace = namespace;
|
|
187
|
+
this.#values = values;
|
|
188
|
+
}
|
|
189
|
+
get(key) {
|
|
190
|
+
assertClientStateKey(key);
|
|
191
|
+
const value = this.#values.get(key);
|
|
192
|
+
return value === undefined ? undefined : cloneJsonValue(value);
|
|
193
|
+
}
|
|
194
|
+
set(key, value) {
|
|
195
|
+
assertClientStateKey(key);
|
|
196
|
+
assertJsonValue(value);
|
|
197
|
+
this.#assertOpen();
|
|
198
|
+
const nextValue = cloneJsonValue(value);
|
|
199
|
+
return this.#enqueue(async () => {
|
|
200
|
+
this.#values.set(key, nextValue);
|
|
201
|
+
this.#dirty = true;
|
|
202
|
+
this.#publish();
|
|
203
|
+
await this.#persist();
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
delete(key) {
|
|
207
|
+
assertClientStateKey(key);
|
|
208
|
+
this.#assertOpen();
|
|
209
|
+
return this.#enqueue(async () => {
|
|
210
|
+
if (this.#values.delete(key))
|
|
211
|
+
this.#publish();
|
|
212
|
+
this.#dirty = true;
|
|
213
|
+
await this.#persist();
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
subscribe(listener) {
|
|
217
|
+
if (typeof listener !== "function") {
|
|
218
|
+
throw new TypeError("clientState.subscribe() requires a function");
|
|
219
|
+
}
|
|
220
|
+
this.#assertOpen();
|
|
221
|
+
this.#listeners.add(listener);
|
|
222
|
+
return () => this.#listeners.delete(listener);
|
|
223
|
+
}
|
|
224
|
+
async close() {
|
|
225
|
+
if (this.#closePromise)
|
|
226
|
+
return this.#closePromise;
|
|
227
|
+
this.#acceptingOperations = false;
|
|
228
|
+
this.#closePromise = (async () => {
|
|
229
|
+
await this.#operationQueue;
|
|
230
|
+
if (this.#dirty)
|
|
231
|
+
await this.#persist();
|
|
232
|
+
this.#listeners.clear();
|
|
233
|
+
})();
|
|
234
|
+
return this.#closePromise;
|
|
235
|
+
}
|
|
236
|
+
#enqueue(operation) {
|
|
237
|
+
const result = this.#operationQueue.then(operation, operation);
|
|
238
|
+
this.#operationQueue = result.then(() => undefined, () => undefined);
|
|
239
|
+
return result;
|
|
240
|
+
}
|
|
241
|
+
async #persist() {
|
|
242
|
+
await this.#storage.save(this.#namespace, encodeStoredClientState(this.#values));
|
|
243
|
+
this.#dirty = false;
|
|
244
|
+
}
|
|
245
|
+
#publish() {
|
|
246
|
+
for (const listener of [...this.#listeners]) {
|
|
247
|
+
try {
|
|
248
|
+
listener();
|
|
249
|
+
}
|
|
250
|
+
catch {
|
|
251
|
+
// Subscribers do not participate in the completed mutation.
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
#assertOpen() {
|
|
256
|
+
if (!this.#acceptingOperations) {
|
|
257
|
+
throw new Error("Lix client state is closed");
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
function encodeStoredClientState(values) {
|
|
262
|
+
const entries = [...values].map(([key, value]) => [
|
|
263
|
+
key,
|
|
264
|
+
cloneJsonValue(value),
|
|
265
|
+
]);
|
|
266
|
+
return new TextEncoder().encode(`${STORED_CLIENT_STATE_HEADER}${JSON.stringify(entries)}`);
|
|
267
|
+
}
|
|
268
|
+
function decodeStoredClientState(snapshot) {
|
|
269
|
+
if (snapshot === undefined)
|
|
270
|
+
return new Map();
|
|
271
|
+
const header = new TextEncoder().encode(STORED_CLIENT_STATE_HEADER);
|
|
272
|
+
if (snapshot.length < header.length ||
|
|
273
|
+
header.some((byte, index) => snapshot[index] !== byte)) {
|
|
274
|
+
throw new Error("Stored Lix client state header is invalid");
|
|
275
|
+
}
|
|
276
|
+
let parsed;
|
|
277
|
+
try {
|
|
278
|
+
parsed = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(snapshot.subarray(header.length)));
|
|
279
|
+
}
|
|
280
|
+
catch (error) {
|
|
281
|
+
throw new Error("Stored Lix client state is invalid", { cause: error });
|
|
282
|
+
}
|
|
283
|
+
if (!Array.isArray(parsed)) {
|
|
284
|
+
throw new Error("Stored Lix client state entries must be an array");
|
|
285
|
+
}
|
|
286
|
+
const values = new Map();
|
|
287
|
+
for (const entry of parsed) {
|
|
288
|
+
if (!Array.isArray(entry) || entry.length !== 2) {
|
|
289
|
+
throw new Error("Stored Lix client state entry is invalid");
|
|
290
|
+
}
|
|
291
|
+
const [key, value] = entry;
|
|
292
|
+
assertClientStateKey(key);
|
|
293
|
+
assertJsonValue(value);
|
|
294
|
+
values.set(key, cloneJsonValue(value));
|
|
295
|
+
}
|
|
296
|
+
return values;
|
|
297
|
+
}
|
|
161
298
|
function assertClientStateKey(key) {
|
|
162
299
|
if (typeof key !== "string" || key.length === 0) {
|
|
163
300
|
throw new TypeError("clientState key must be a non-empty string");
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
export { LocalFilesystem, Lix, LixTransaction, ObserveEvents, openLix,
|
|
1
|
+
export { LocalFilesystem, Lix, LixTransaction, ObserveEvents, openLix, } from "./open-lix.js";
|
|
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
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,
|
|
6
|
+
export type { CreateBranchOptions, CreateBranchReceipt, CreateCheckpointReceipt, RedoReceipt, ExecuteOptions, ExecuteResult, LixBatchOptions, LixBatchStatement, LocalFilesystemOptions, JsonValue, LixValue, MergeBranchOptions, MergeBranchOutcome, MergeBranchPreview, MergeBranchReceipt, MergeChangeStats, MergeConflict, MergeConflictSide, ObserveEvent, OpenLixOptions, LixTelemetryOptions, LixTelemetrySpan, LixSnapshotStorage, RemoteLixFetch, RemoteLixServerOptions, UndoReceipt, SqlParam, SwitchBranchOptions, SwitchBranchReceipt, } from "./types.js";
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { LocalFilesystem, Lix, LixTransaction, ObserveEvents, openLix,
|
|
1
|
+
export { LocalFilesystem, Lix, LixTransaction, ObserveEvents, openLix, } from "./open-lix.js";
|
|
2
2
|
export { bundledPluginArchives, } from "./bundled-plugins.js";
|
|
3
3
|
export { Row } from "./result.js";
|
|
4
4
|
export { Value } from "./value.js";
|
package/dist/lix.d.ts
CHANGED
|
@@ -1,22 +1,25 @@
|
|
|
1
|
-
import { type LixClientState, type
|
|
1
|
+
import { type LixClientState, type ManagedClientState } from "./client-state.js";
|
|
2
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";
|
|
3
|
+
import type { CreateBranchOptions, CreateBranchReceipt, CreateCheckpointReceipt, RedoReceipt, ExecuteOptions, ExecuteResult, ExecuteBatchResult, LixBatchOptions, LixBatchStatement, MergeBranchOptions, MergeBranchPreview, MergeBranchReceipt, ObserveEvent, SqlParam, SwitchBranchOptions, SwitchBranchReceipt, UndoReceipt } from "./types.js";
|
|
4
4
|
export declare class Lix {
|
|
5
5
|
#private;
|
|
6
6
|
private readonly binding;
|
|
7
7
|
private readonly managedClientState?;
|
|
8
8
|
private closePromise;
|
|
9
9
|
readonly clientState: LixClientState;
|
|
10
|
-
constructor(binding: LixBinding, managedClientState?:
|
|
10
|
+
constructor(binding: LixBinding, managedClientState?: ManagedClientState | undefined);
|
|
11
11
|
execute(sql: string, params?: SqlParam[], options?: ExecuteOptions): Promise<ExecuteResult>;
|
|
12
|
-
executeBatch(statements: readonly LixBatchStatement[], options?: LixBatchOptions): Promise<readonly
|
|
12
|
+
executeBatch(statements: readonly LixBatchStatement[], options?: LixBatchOptions): Promise<readonly ExecuteBatchResult[]>;
|
|
13
13
|
observe(sql: string, params?: SqlParam[]): ObserveEvents;
|
|
14
14
|
beginTransaction(): Promise<LixTransaction>;
|
|
15
15
|
activeBranchId(): Promise<string>;
|
|
16
|
+
activeAccountId(): Promise<string>;
|
|
16
17
|
/** Subscribes to successful branch switches made through this Lix handle. */
|
|
17
18
|
subscribeActiveBranch(listener: () => void): () => void;
|
|
18
19
|
createBranch(options: CreateBranchOptions): Promise<CreateBranchReceipt>;
|
|
19
20
|
createCheckpoint(): Promise<CreateCheckpointReceipt>;
|
|
21
|
+
undo(): Promise<UndoReceipt>;
|
|
22
|
+
redo(): Promise<RedoReceipt>;
|
|
20
23
|
switchBranch(options: SwitchBranchOptions): Promise<SwitchBranchReceipt>;
|
|
21
24
|
mergeBranchPreview(options: MergeBranchOptions): Promise<MergeBranchPreview>;
|
|
22
25
|
mergeBranch(options: MergeBranchOptions): Promise<MergeBranchReceipt>;
|
package/dist/lix.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { invalidArgument } from "./errors.js";
|
|
2
2
|
import { ACTIVE_BRANCH_CLIENT_STATE_KEY, unavailableClientState, } from "./client-state.js";
|
|
3
|
-
import { normalizeOptionals, wrapExecuteResult } from "./result.js";
|
|
3
|
+
import { normalizeOptionals, wrapExecuteBatchResult, wrapExecuteResult, } from "./result.js";
|
|
4
4
|
import { isSnapshotPersistenceAfterCommitError } from "./snapshot-persistence.js";
|
|
5
5
|
import { normalizeParam, toNativeValue } from "./value.js";
|
|
6
6
|
const transactionFinalizer = new FinalizationRegistry(({ transaction, onFinish }) => {
|
|
@@ -50,7 +50,7 @@ export class Lix {
|
|
|
50
50
|
const normalizedStatements = normalizeBatchStatements(statements, options);
|
|
51
51
|
return this.#runOperation(async () => {
|
|
52
52
|
const results = await this.binding.executeBatch(normalizedStatements, options);
|
|
53
|
-
return results.map(
|
|
53
|
+
return results.map(wrapExecuteBatchResult);
|
|
54
54
|
});
|
|
55
55
|
}
|
|
56
56
|
observe(sql, params = []) {
|
|
@@ -83,6 +83,9 @@ export class Lix {
|
|
|
83
83
|
async activeBranchId() {
|
|
84
84
|
return this.#runOperation(() => this.binding.activeBranchId());
|
|
85
85
|
}
|
|
86
|
+
async activeAccountId() {
|
|
87
|
+
return this.#runOperation(() => this.binding.activeAccountId());
|
|
88
|
+
}
|
|
86
89
|
/** Subscribes to successful branch switches made through this Lix handle. */
|
|
87
90
|
subscribeActiveBranch(listener) {
|
|
88
91
|
if (typeof listener !== "function") {
|
|
@@ -98,6 +101,12 @@ export class Lix {
|
|
|
98
101
|
async createCheckpoint() {
|
|
99
102
|
return this.#runOperation(() => this.binding.createCheckpoint());
|
|
100
103
|
}
|
|
104
|
+
async undo() {
|
|
105
|
+
return this.#runOperation(() => this.binding.undo());
|
|
106
|
+
}
|
|
107
|
+
async redo() {
|
|
108
|
+
return this.#runOperation(() => this.binding.redo());
|
|
109
|
+
}
|
|
101
110
|
async switchBranch(options) {
|
|
102
111
|
return this.#runOperation(async () => {
|
|
103
112
|
const receipt = await this.binding.switchBranch(options);
|
|
@@ -327,9 +336,14 @@ function normalizeBatchStatements(statements, options) {
|
|
|
327
336
|
if (!Array.isArray(params)) {
|
|
328
337
|
throw invalidArgument("executeBatch", `statements[${statementIndex}].params`, "array", typeof params);
|
|
329
338
|
}
|
|
339
|
+
if (statement.label !== undefined &&
|
|
340
|
+
typeof statement.label !== "string") {
|
|
341
|
+
throw invalidArgument("executeBatch", `statements[${statementIndex}].label`, "string", typeof statement.label);
|
|
342
|
+
}
|
|
330
343
|
return {
|
|
331
344
|
sql: statement.sql,
|
|
332
345
|
params: params.map((param, parameterIndex) => toNativeValue(normalizeParam(param, parameterIndex))),
|
|
346
|
+
...(statement.label === undefined ? {} : { label: statement.label }),
|
|
333
347
|
};
|
|
334
348
|
}
|
|
335
349
|
catch (error) {
|
package/dist/open-lix.d.ts
CHANGED
|
@@ -1,10 +1,6 @@
|
|
|
1
1
|
import { Lix } from "./lix.js";
|
|
2
|
-
import type { LocalFilesystemOptions, OpenLixOptions
|
|
2
|
+
import type { LocalFilesystemOptions, OpenLixOptions } from "./types.js";
|
|
3
3
|
export { Lix, LixTransaction, ObserveEvents } from "./lix.js";
|
|
4
|
-
export declare class SQLite {
|
|
5
|
-
readonly path: string;
|
|
6
|
-
constructor(options: SQLiteOptions);
|
|
7
|
-
}
|
|
8
4
|
export declare class LocalFilesystem {
|
|
9
5
|
readonly path: string;
|
|
10
6
|
readonly lixDir: string | undefined;
|
package/dist/open-lix.js
CHANGED
|
@@ -1,18 +1,7 @@
|
|
|
1
1
|
import { localFilesystemAlreadyOpen, localFilesystemNotOpen, } from "./errors.js";
|
|
2
|
-
import { ACTIVE_BRANCH_CLIENT_STATE_KEY, openClientState, } from "./client-state.js";
|
|
2
|
+
import { ACTIVE_ACCOUNT_CLIENT_STATE_KEY, ACTIVE_BRANCH_CLIENT_STATE_KEY, openClientState, openStoredClientState, } from "./client-state.js";
|
|
3
3
|
import { Lix } from "./lix.js";
|
|
4
4
|
export { Lix, LixTransaction, ObserveEvents } from "./lix.js";
|
|
5
|
-
export class SQLite {
|
|
6
|
-
path;
|
|
7
|
-
constructor(options) {
|
|
8
|
-
if (!options ||
|
|
9
|
-
typeof options.path !== "string" ||
|
|
10
|
-
options.path.length === 0) {
|
|
11
|
-
throw new TypeError("SQLite requires a non-empty path");
|
|
12
|
-
}
|
|
13
|
-
this.path = options.path;
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
5
|
const openLocalFilesystems = new WeakMap();
|
|
17
6
|
export class LocalFilesystem {
|
|
18
7
|
path;
|
|
@@ -78,39 +67,35 @@ export async function openLix(options = {}) {
|
|
|
78
67
|
return new Lix(await openRemoteLixBinding(options.server));
|
|
79
68
|
}
|
|
80
69
|
assertSnapshotStorage(options.storage);
|
|
81
|
-
const
|
|
82
|
-
const clientBinding = await openPersistentLixWorkerBinding({
|
|
70
|
+
const clientState = await openStoredClientState({
|
|
83
71
|
storage: options.storage,
|
|
84
72
|
namespace: remoteClientStateNamespace(options.server.url),
|
|
85
73
|
});
|
|
86
|
-
let clientState;
|
|
87
|
-
try {
|
|
88
|
-
clientState = await openClientState({
|
|
89
|
-
binding: clientBinding,
|
|
90
|
-
closeBinding: true,
|
|
91
|
-
});
|
|
92
|
-
}
|
|
93
|
-
catch (error) {
|
|
94
|
-
await clientBinding.close().catch(() => undefined);
|
|
95
|
-
throw error;
|
|
96
|
-
}
|
|
97
74
|
const restoredBranchId = clientState.get(ACTIVE_BRANCH_CLIENT_STATE_KEY);
|
|
75
|
+
const restoredAccountId = clientState.get(ACTIVE_ACCOUNT_CLIENT_STATE_KEY);
|
|
98
76
|
let remoteBinding;
|
|
99
77
|
try {
|
|
100
78
|
try {
|
|
101
79
|
remoteBinding = await openRemoteLixBinding(options.server, {
|
|
102
80
|
initialActiveBranchId: restoredBranchId,
|
|
81
|
+
initialActiveAccountId: restoredAccountId,
|
|
103
82
|
});
|
|
104
83
|
}
|
|
105
84
|
catch (error) {
|
|
106
85
|
if (!restoredBranchId || !isBranchNotFoundError(error))
|
|
107
86
|
throw error;
|
|
108
|
-
remoteBinding = await openRemoteLixBinding(options.server
|
|
87
|
+
remoteBinding = await openRemoteLixBinding(options.server, {
|
|
88
|
+
initialActiveAccountId: restoredAccountId,
|
|
89
|
+
});
|
|
109
90
|
}
|
|
110
91
|
const activeBranchId = await remoteBinding.activeBranchId();
|
|
92
|
+
const activeAccountId = await remoteBinding.activeAccountId();
|
|
111
93
|
if (activeBranchId !== restoredBranchId) {
|
|
112
94
|
await clientState.set(ACTIVE_BRANCH_CLIENT_STATE_KEY, activeBranchId);
|
|
113
95
|
}
|
|
96
|
+
if (activeAccountId !== restoredAccountId) {
|
|
97
|
+
await clientState.set(ACTIVE_ACCOUNT_CLIENT_STATE_KEY, activeAccountId);
|
|
98
|
+
}
|
|
114
99
|
return new Lix(remoteBinding, clientState);
|
|
115
100
|
}
|
|
116
101
|
catch (error) {
|
|
@@ -123,12 +108,6 @@ export async function openLix(options = {}) {
|
|
|
123
108
|
if (options.storage === undefined) {
|
|
124
109
|
return new Lix(await openLixWorkerBinding({ kind: "memory" }, undefined, options.telemetry));
|
|
125
110
|
}
|
|
126
|
-
if (options.storage instanceof SQLite) {
|
|
127
|
-
return new Lix(await openLixWorkerBinding({
|
|
128
|
-
kind: "sqlite",
|
|
129
|
-
path: options.storage.path,
|
|
130
|
-
}, undefined, options.telemetry));
|
|
131
|
-
}
|
|
132
111
|
if (options.storage instanceof LocalFilesystem) {
|
|
133
112
|
const storage = options.storage;
|
|
134
113
|
if (openLocalFilesystems.has(storage)) {
|
|
@@ -166,7 +145,7 @@ export async function openLix(options = {}) {
|
|
|
166
145
|
throw error;
|
|
167
146
|
}
|
|
168
147
|
}
|
|
169
|
-
throw new TypeError("openLix() requires storage to be
|
|
148
|
+
throw new TypeError("openLix() requires storage to be LocalFilesystem or a Lix snapshot storage adapter");
|
|
170
149
|
}
|
|
171
150
|
function isSnapshotStorage(value) {
|
|
172
151
|
return (typeof value === "object" &&
|
package/dist/remote/client.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ import type { RemoteLixServerOptions } from "../types.js";
|
|
|
3
3
|
import { type RemoteHandshakeRequest } from "./protocol.js";
|
|
4
4
|
type RemoteLixClientOptions = {
|
|
5
5
|
initialActiveBranchId?: RemoteHandshakeRequest["activeBranchId"];
|
|
6
|
+
initialActiveAccountId?: RemoteHandshakeRequest["activeAccountId"];
|
|
6
7
|
};
|
|
7
8
|
export declare function openRemoteLixBinding(options: RemoteLixServerOptions, clientOptions?: RemoteLixClientOptions): Promise<LixBinding>;
|
|
8
9
|
export {};
|