@lix-js/sdk 0.9.0 → 0.10.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 +26 -9
- package/dist/binding-types.d.ts +4 -1
- package/dist/binding.node.js +11 -3
- 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 +140 -0
- package/dist/index.d.ts +1 -1
- package/dist/lix.d.ts +6 -3
- package/dist/lix.js +9 -0
- package/dist/open-lix.js +11 -15
- package/dist/remote/client.d.ts +1 -0
- package/dist/remote/client.js +118 -4
- package/dist/remote/protocol.d.ts +13 -1
- package/dist/remote/protocol.js +8 -3
- package/dist/types.d.ts +10 -0
- 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();
|
|
@@ -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();
|
|
@@ -170,10 +187,10 @@ try {
|
|
|
170
187
|
WebAssembly binding. Vite follows this split without consumer configuration.
|
|
171
188
|
- Every browser `openLix()` owns one dedicated worker, so database work does
|
|
172
189
|
not block the page's main thread. Node.js uses the native binding's actor.
|
|
173
|
-
- Node.js executes installed Component API
|
|
190
|
+
- Node.js executes installed Component API v1 plugins with the Rust SDK's
|
|
174
191
|
Wasmtime runtime. The browser and Workerd bindings currently open without a
|
|
175
192
|
component runtime: they can use ordinary Lix storage and SQL, but do not
|
|
176
|
-
execute installed plugins. A browser
|
|
193
|
+
execute installed plugins. A browser Component host is a separate follow-up.
|
|
177
194
|
- A page Content Security Policy only needs to permit the package's same-origin
|
|
178
195
|
worker. WebAssembly compilation happens inside that worker, so the required
|
|
179
196
|
permission can be scoped to the worker script's HTTP response instead of
|
package/dist/binding-types.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
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
4
|
columns: string[];
|
|
@@ -26,6 +26,7 @@ export type LixBinding = {
|
|
|
26
26
|
observe(sql: string, params: BindingParam[]): Promise<ObserveEventsBinding>;
|
|
27
27
|
beginTransaction(): Promise<LixTransactionBinding>;
|
|
28
28
|
activeBranchId(): Promise<string>;
|
|
29
|
+
activeAccountId(): Promise<string>;
|
|
29
30
|
clientStateEntries?(): Promise<Array<{
|
|
30
31
|
key: string;
|
|
31
32
|
value: JsonValue;
|
|
@@ -35,6 +36,8 @@ export type LixBinding = {
|
|
|
35
36
|
clientStateDelete?(key: string): Promise<void>;
|
|
36
37
|
createBranch(options: CreateBranchOptions): Promise<CreateBranchReceipt>;
|
|
37
38
|
createCheckpoint(): Promise<CreateCheckpointReceipt>;
|
|
39
|
+
undo(): Promise<UndoReceipt>;
|
|
40
|
+
redo(): Promise<RedoReceipt>;
|
|
38
41
|
switchBranch(options: SwitchBranchOptions): Promise<SwitchBranchReceipt>;
|
|
39
42
|
importFilesystemPaths(paths: string[]): Promise<void>;
|
|
40
43
|
mergeBranchPreview(options: MergeBranchOptions): Promise<MergeBranchPreview>;
|
package/dist/binding.node.js
CHANGED
|
@@ -48,10 +48,18 @@ 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
|
-
|
|
51
|
+
if (nativeTelemetry)
|
|
52
|
+
return addon.Lix.openMemory(nativeTelemetry);
|
|
53
|
+
return addon.Lix.openMemory();
|
|
52
54
|
case "sqlite":
|
|
53
|
-
|
|
55
|
+
if (nativeTelemetry) {
|
|
56
|
+
return addon.Lix.openSQLite(storage.path, nativeTelemetry);
|
|
57
|
+
}
|
|
58
|
+
return addon.Lix.openSQLite(storage.path);
|
|
54
59
|
case "localFilesystem":
|
|
55
|
-
|
|
60
|
+
if (nativeTelemetry) {
|
|
61
|
+
return addon.Lix.openLocalFilesystem(storage.path, storage.lixDir, storage.syncAllFiles, nativeTelemetry);
|
|
62
|
+
}
|
|
63
|
+
return addon.Lix.openLocalFilesystem(storage.path, storage.lixDir, storage.syncAllFiles);
|
|
56
64
|
}
|
|
57
65
|
}
|
|
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,144 @@ 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
|
+
// Remote storage previously contained a full Lix snapshot. Backward
|
|
275
|
+
// compatibility is intentionally not provided: start with empty state and
|
|
276
|
+
// replace it on the next write.
|
|
277
|
+
return new Map();
|
|
278
|
+
}
|
|
279
|
+
let parsed;
|
|
280
|
+
try {
|
|
281
|
+
parsed = JSON.parse(new TextDecoder().decode(snapshot.subarray(header.length)));
|
|
282
|
+
}
|
|
283
|
+
catch (error) {
|
|
284
|
+
throw new Error("Stored Lix client state is invalid", { cause: error });
|
|
285
|
+
}
|
|
286
|
+
if (!Array.isArray(parsed)) {
|
|
287
|
+
throw new Error("Stored Lix client state entries must be an array");
|
|
288
|
+
}
|
|
289
|
+
const values = new Map();
|
|
290
|
+
for (const entry of parsed) {
|
|
291
|
+
if (!Array.isArray(entry) || entry.length !== 2) {
|
|
292
|
+
throw new Error("Stored Lix client state entry is invalid");
|
|
293
|
+
}
|
|
294
|
+
const [key, value] = entry;
|
|
295
|
+
assertClientStateKey(key);
|
|
296
|
+
assertJsonValue(value);
|
|
297
|
+
values.set(key, cloneJsonValue(value));
|
|
298
|
+
}
|
|
299
|
+
return values;
|
|
300
|
+
}
|
|
161
301
|
function assertClientStateKey(key) {
|
|
162
302
|
if (typeof key !== "string" || key.length === 0) {
|
|
163
303
|
throw new TypeError("clientState key must be a non-empty string");
|
package/dist/index.d.ts
CHANGED
|
@@ -3,4 +3,4 @@ export { bundledPluginArchives, type BundledPluginArchive, } from "./bundled-plu
|
|
|
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, SqlParam, SQLiteOptions, SwitchBranchOptions, SwitchBranchReceipt, } from "./types.js";
|
|
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, SQLiteOptions, SwitchBranchOptions, SwitchBranchReceipt, } from "./types.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, 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
12
|
executeBatch(statements: readonly LixBatchStatement[], options?: LixBatchOptions): Promise<readonly ExecuteResult[]>;
|
|
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
|
@@ -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);
|
package/dist/open-lix.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
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
5
|
export class SQLite {
|
|
@@ -78,39 +78,35 @@ export async function openLix(options = {}) {
|
|
|
78
78
|
return new Lix(await openRemoteLixBinding(options.server));
|
|
79
79
|
}
|
|
80
80
|
assertSnapshotStorage(options.storage);
|
|
81
|
-
const
|
|
82
|
-
const clientBinding = await openPersistentLixWorkerBinding({
|
|
81
|
+
const clientState = await openStoredClientState({
|
|
83
82
|
storage: options.storage,
|
|
84
83
|
namespace: remoteClientStateNamespace(options.server.url),
|
|
85
84
|
});
|
|
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
85
|
const restoredBranchId = clientState.get(ACTIVE_BRANCH_CLIENT_STATE_KEY);
|
|
86
|
+
const restoredAccountId = clientState.get(ACTIVE_ACCOUNT_CLIENT_STATE_KEY);
|
|
98
87
|
let remoteBinding;
|
|
99
88
|
try {
|
|
100
89
|
try {
|
|
101
90
|
remoteBinding = await openRemoteLixBinding(options.server, {
|
|
102
91
|
initialActiveBranchId: restoredBranchId,
|
|
92
|
+
initialActiveAccountId: restoredAccountId,
|
|
103
93
|
});
|
|
104
94
|
}
|
|
105
95
|
catch (error) {
|
|
106
96
|
if (!restoredBranchId || !isBranchNotFoundError(error))
|
|
107
97
|
throw error;
|
|
108
|
-
remoteBinding = await openRemoteLixBinding(options.server
|
|
98
|
+
remoteBinding = await openRemoteLixBinding(options.server, {
|
|
99
|
+
initialActiveAccountId: restoredAccountId,
|
|
100
|
+
});
|
|
109
101
|
}
|
|
110
102
|
const activeBranchId = await remoteBinding.activeBranchId();
|
|
103
|
+
const activeAccountId = await remoteBinding.activeAccountId();
|
|
111
104
|
if (activeBranchId !== restoredBranchId) {
|
|
112
105
|
await clientState.set(ACTIVE_BRANCH_CLIENT_STATE_KEY, activeBranchId);
|
|
113
106
|
}
|
|
107
|
+
if (activeAccountId !== restoredAccountId) {
|
|
108
|
+
await clientState.set(ACTIVE_ACCOUNT_CLIENT_STATE_KEY, activeAccountId);
|
|
109
|
+
}
|
|
114
110
|
return new Lix(remoteBinding, clientState);
|
|
115
111
|
}
|
|
116
112
|
catch (error) {
|
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 {};
|
package/dist/remote/client.js
CHANGED
|
@@ -3,6 +3,7 @@ import { readSseEvents } from "./sse.js";
|
|
|
3
3
|
const OBSERVE_RETRY_BASE_MS = 100;
|
|
4
4
|
const OBSERVE_RETRY_MAX_MS = 5_000;
|
|
5
5
|
const REMOTE_SESSION_HEADER = "Lix-Session-Id";
|
|
6
|
+
const REMOTE_TRANSACTION_HEADER = "Lix-Transaction-Id";
|
|
6
7
|
const IDEMPOTENCY_KEY_HEADER = "Idempotency-Key";
|
|
7
8
|
const REQUEST_BLOB_DELTA_MIN_BYTES = 32 * 1024;
|
|
8
9
|
const REQUEST_BLOB_DELTA_MIN_WIRE_RATIO = 0.9;
|
|
@@ -31,10 +32,12 @@ class RemoteLixBinding {
|
|
|
31
32
|
#fetch;
|
|
32
33
|
#headers;
|
|
33
34
|
#initialActiveBranchId;
|
|
35
|
+
#initialActiveAccountId;
|
|
34
36
|
#observationHub;
|
|
35
37
|
#requestBlobBases = new Map();
|
|
36
38
|
#sessionId;
|
|
37
39
|
#activeBranchId;
|
|
40
|
+
#activeAccountId;
|
|
38
41
|
#requestBlobBaseBytes = 0;
|
|
39
42
|
#acceptingOperations = true;
|
|
40
43
|
#operationQueue = Promise.resolve();
|
|
@@ -63,17 +66,28 @@ class RemoteLixBinding {
|
|
|
63
66
|
throw new TypeError("initialActiveBranchId must be a non-empty string");
|
|
64
67
|
}
|
|
65
68
|
this.#initialActiveBranchId = clientOptions.initialActiveBranchId;
|
|
69
|
+
if (clientOptions.initialActiveAccountId !== undefined &&
|
|
70
|
+
clientOptions.initialActiveAccountId.length === 0) {
|
|
71
|
+
throw new TypeError("initialActiveAccountId must be a non-empty string");
|
|
72
|
+
}
|
|
73
|
+
this.#initialActiveAccountId = clientOptions.initialActiveAccountId;
|
|
66
74
|
this.#observationHub = new RemoteObservationHub({
|
|
67
75
|
openStream: (subscriptions, signal) => this.#requestObserveStream(subscriptions, signal),
|
|
68
76
|
});
|
|
69
77
|
}
|
|
70
78
|
async open() {
|
|
71
|
-
const
|
|
72
|
-
|
|
73
|
-
|
|
79
|
+
const query = new URLSearchParams();
|
|
80
|
+
if (this.#initialActiveBranchId !== undefined) {
|
|
81
|
+
query.set("activeBranchId", this.#initialActiveBranchId);
|
|
82
|
+
}
|
|
83
|
+
if (this.#initialActiveAccountId !== undefined) {
|
|
84
|
+
query.set("activeAccountId", this.#initialActiveAccountId);
|
|
85
|
+
}
|
|
86
|
+
const path = query.size === 0 ? "" : `?${query}`;
|
|
74
87
|
const handshake = decodeHandshake(await this.#requestJson(path, { method: "GET" }));
|
|
75
88
|
this.#sessionId = handshake.sessionId;
|
|
76
89
|
this.#activeBranchId = handshake.activeBranchId;
|
|
90
|
+
this.#activeAccountId = handshake.activeAccountId;
|
|
77
91
|
}
|
|
78
92
|
async execute(sql, params, options) {
|
|
79
93
|
this.#assertOpen();
|
|
@@ -142,7 +156,62 @@ class RemoteLixBinding {
|
|
|
142
156
|
}
|
|
143
157
|
async beginTransaction() {
|
|
144
158
|
this.#assertOpen();
|
|
145
|
-
|
|
159
|
+
return this.#enqueue(async () => {
|
|
160
|
+
const begun = record(await this.#requestJson("transaction/begin", { method: "POST" }), "begin transaction response");
|
|
161
|
+
if (typeof begun.transactionId !== "string") {
|
|
162
|
+
throw protocolError("begin transaction response.transactionId must be a string");
|
|
163
|
+
}
|
|
164
|
+
const transactionId = begun.transactionId;
|
|
165
|
+
let active = true;
|
|
166
|
+
const assertActive = () => {
|
|
167
|
+
if (!active) {
|
|
168
|
+
throw remoteError("LIX_INVALID_TRANSACTION_STATE", "Lix transaction is closed");
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
return {
|
|
172
|
+
execute: async (sql, params, options) => {
|
|
173
|
+
assertActive();
|
|
174
|
+
const snapshot = snapshotParams(params);
|
|
175
|
+
const requestOptions = remoteExecuteOptions(options);
|
|
176
|
+
return this.#enqueue(async () => {
|
|
177
|
+
const value = await this.#requestJson("transaction/execute", {
|
|
178
|
+
method: "POST",
|
|
179
|
+
headers: { [REMOTE_TRANSACTION_HEADER]: transactionId },
|
|
180
|
+
body: JSON.stringify({
|
|
181
|
+
sql,
|
|
182
|
+
params: snapshot.map(encodeWireValue),
|
|
183
|
+
...(requestOptions === undefined
|
|
184
|
+
? {}
|
|
185
|
+
: { options: requestOptions }),
|
|
186
|
+
}),
|
|
187
|
+
});
|
|
188
|
+
return decodeExecuteResult(value);
|
|
189
|
+
});
|
|
190
|
+
},
|
|
191
|
+
commit: async () => {
|
|
192
|
+
assertActive();
|
|
193
|
+
return this.#enqueue(async () => {
|
|
194
|
+
assertActive();
|
|
195
|
+
await this.#requestJson("transaction/commit", {
|
|
196
|
+
method: "POST",
|
|
197
|
+
headers: { [REMOTE_TRANSACTION_HEADER]: transactionId },
|
|
198
|
+
}, "empty");
|
|
199
|
+
active = false;
|
|
200
|
+
});
|
|
201
|
+
},
|
|
202
|
+
rollback: async () => {
|
|
203
|
+
assertActive();
|
|
204
|
+
return this.#enqueue(async () => {
|
|
205
|
+
assertActive();
|
|
206
|
+
await this.#requestJson("transaction/rollback", {
|
|
207
|
+
method: "POST",
|
|
208
|
+
headers: { [REMOTE_TRANSACTION_HEADER]: transactionId },
|
|
209
|
+
}, "empty");
|
|
210
|
+
active = false;
|
|
211
|
+
});
|
|
212
|
+
},
|
|
213
|
+
};
|
|
214
|
+
});
|
|
146
215
|
}
|
|
147
216
|
async activeBranchId() {
|
|
148
217
|
this.#assertOpen();
|
|
@@ -157,6 +226,19 @@ class RemoteLixBinding {
|
|
|
157
226
|
return this.#activeBranchId;
|
|
158
227
|
});
|
|
159
228
|
}
|
|
229
|
+
async activeAccountId() {
|
|
230
|
+
this.#assertOpen();
|
|
231
|
+
return this.#enqueue(async () => {
|
|
232
|
+
if (this.#activeAccountId === undefined) {
|
|
233
|
+
const handshake = decodeHandshake(await this.#requestJson("", { method: "GET" }));
|
|
234
|
+
if (handshake.sessionId !== this.#sessionId) {
|
|
235
|
+
throw protocolError("remote handshake changed sessionId");
|
|
236
|
+
}
|
|
237
|
+
this.#activeAccountId = handshake.activeAccountId;
|
|
238
|
+
}
|
|
239
|
+
return this.#activeAccountId;
|
|
240
|
+
});
|
|
241
|
+
}
|
|
160
242
|
async createBranch(options) {
|
|
161
243
|
this.#assertOpen();
|
|
162
244
|
return this.#enqueue(async () => {
|
|
@@ -189,6 +271,38 @@ class RemoteLixBinding {
|
|
|
189
271
|
return { commitId: value.commitId };
|
|
190
272
|
});
|
|
191
273
|
}
|
|
274
|
+
async undo() {
|
|
275
|
+
this.#assertOpen();
|
|
276
|
+
return this.#enqueue(async () => {
|
|
277
|
+
const value = record(await this.#requestJson("undo", { method: "POST" }), "undo response");
|
|
278
|
+
if (typeof value.branchId !== "string" ||
|
|
279
|
+
typeof value.targetCommitId !== "string" ||
|
|
280
|
+
typeof value.inverseCommitId !== "string") {
|
|
281
|
+
throw protocolError("undo response is invalid");
|
|
282
|
+
}
|
|
283
|
+
return {
|
|
284
|
+
branchId: value.branchId,
|
|
285
|
+
targetCommitId: value.targetCommitId,
|
|
286
|
+
inverseCommitId: value.inverseCommitId,
|
|
287
|
+
};
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
async redo() {
|
|
291
|
+
this.#assertOpen();
|
|
292
|
+
return this.#enqueue(async () => {
|
|
293
|
+
const value = record(await this.#requestJson("redo", { method: "POST" }), "redo response");
|
|
294
|
+
if (typeof value.branchId !== "string" ||
|
|
295
|
+
typeof value.targetCommitId !== "string" ||
|
|
296
|
+
typeof value.replayCommitId !== "string") {
|
|
297
|
+
throw protocolError("redo response is invalid");
|
|
298
|
+
}
|
|
299
|
+
return {
|
|
300
|
+
branchId: value.branchId,
|
|
301
|
+
targetCommitId: value.targetCommitId,
|
|
302
|
+
replayCommitId: value.replayCommitId,
|
|
303
|
+
};
|
|
304
|
+
});
|
|
305
|
+
}
|
|
192
306
|
async switchBranch(options) {
|
|
193
307
|
this.#assertOpen();
|
|
194
308
|
return this.#enqueue(async () => {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { BindingExecuteResult, BindingObserveEvent } from "../binding-types.js";
|
|
2
2
|
import type { NativeLixValue } from "../value.js";
|
|
3
|
-
export declare const REMOTE_PROTOCOL_VERSION =
|
|
3
|
+
export declare const REMOTE_PROTOCOL_VERSION = 2;
|
|
4
4
|
export declare const REMOTE_PROTOCOL_PATH = "/lix/v1/";
|
|
5
5
|
export type WireValue = {
|
|
6
6
|
kind: "null";
|
|
@@ -36,10 +36,12 @@ export type WireRequestValue = WireValue | WireRequestBlobSplice;
|
|
|
36
36
|
export type RemoteHandshake = {
|
|
37
37
|
protocolVersion: number;
|
|
38
38
|
activeBranchId: string;
|
|
39
|
+
activeAccountId: string;
|
|
39
40
|
sessionId: string;
|
|
40
41
|
};
|
|
41
42
|
export type RemoteHandshakeRequest = {
|
|
42
43
|
activeBranchId?: string;
|
|
44
|
+
activeAccountId?: string;
|
|
43
45
|
};
|
|
44
46
|
export type RemoteExecuteRequest = {
|
|
45
47
|
sql: string;
|
|
@@ -122,6 +124,16 @@ export type RemoteCreateBranchResponse = {
|
|
|
122
124
|
export type RemoteCreateCheckpointResponse = {
|
|
123
125
|
commitId: string;
|
|
124
126
|
};
|
|
127
|
+
export type RemoteUndoResponse = {
|
|
128
|
+
branchId: string;
|
|
129
|
+
targetCommitId: string;
|
|
130
|
+
inverseCommitId: string;
|
|
131
|
+
};
|
|
132
|
+
export type RemoteRedoResponse = {
|
|
133
|
+
branchId: string;
|
|
134
|
+
targetCommitId: string;
|
|
135
|
+
replayCommitId: string;
|
|
136
|
+
};
|
|
125
137
|
export type RemoteSwitchBranchRequest = {
|
|
126
138
|
branchId: string;
|
|
127
139
|
};
|
package/dist/remote/protocol.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export const REMOTE_PROTOCOL_VERSION =
|
|
1
|
+
export const REMOTE_PROTOCOL_VERSION = 2;
|
|
2
2
|
export const REMOTE_PROTOCOL_PATH = "/lix/v1/";
|
|
3
3
|
export function encodeWireValue(value) {
|
|
4
4
|
switch (value.kind) {
|
|
@@ -66,6 +66,10 @@ export function decodeHandshake(value) {
|
|
|
66
66
|
handshake.activeBranchId.length === 0) {
|
|
67
67
|
throw protocolError("remote handshake requires activeBranchId");
|
|
68
68
|
}
|
|
69
|
+
if (typeof handshake.activeAccountId !== "string" ||
|
|
70
|
+
handshake.activeAccountId.length === 0) {
|
|
71
|
+
throw protocolError("remote handshake requires activeAccountId");
|
|
72
|
+
}
|
|
69
73
|
if (typeof handshake.sessionId !== "string" ||
|
|
70
74
|
!/^[\x21-\x7e]{1,256}$/.test(handshake.sessionId)) {
|
|
71
75
|
throw protocolError("remote handshake requires a valid sessionId");
|
|
@@ -73,6 +77,7 @@ export function decodeHandshake(value) {
|
|
|
73
77
|
return {
|
|
74
78
|
protocolVersion: REMOTE_PROTOCOL_VERSION,
|
|
75
79
|
activeBranchId: handshake.activeBranchId,
|
|
80
|
+
activeAccountId: handshake.activeAccountId,
|
|
76
81
|
sessionId: handshake.sessionId,
|
|
77
82
|
};
|
|
78
83
|
}
|
|
@@ -127,7 +132,7 @@ function applyObserveBlobDelta(delta, sequence, base) {
|
|
|
127
132
|
}
|
|
128
133
|
const baseValue = base.rows.rows[0]?.[0];
|
|
129
134
|
if (base.rows.columns.length !== 1 ||
|
|
130
|
-
base.rows.columns[0] !== "
|
|
135
|
+
base.rows.columns[0] !== "content" ||
|
|
131
136
|
base.rows.rows.length !== 1 ||
|
|
132
137
|
base.rows.rows[0]?.length !== 1 ||
|
|
133
138
|
base.rows.rowsAffected !== 0 ||
|
|
@@ -154,7 +159,7 @@ function applyObserveBlobDelta(delta, sequence, base) {
|
|
|
154
159
|
blob.set(insert, prefixBytes);
|
|
155
160
|
blob.set(baseValue.blob.subarray(baseValue.blob.byteLength - suffixBytes), prefixBytes + insert.byteLength);
|
|
156
161
|
return {
|
|
157
|
-
columns: ["
|
|
162
|
+
columns: ["content"],
|
|
158
163
|
rows: [[{ kind: "blob", value: null, blob }]],
|
|
159
164
|
rowsAffected: 0,
|
|
160
165
|
notices: [],
|
package/dist/types.d.ts
CHANGED
|
@@ -132,6 +132,16 @@ export type CreateBranchReceipt = {
|
|
|
132
132
|
export type CreateCheckpointReceipt = {
|
|
133
133
|
commitId: string;
|
|
134
134
|
};
|
|
135
|
+
export type UndoReceipt = {
|
|
136
|
+
branchId: string;
|
|
137
|
+
targetCommitId: string;
|
|
138
|
+
inverseCommitId: string;
|
|
139
|
+
};
|
|
140
|
+
export type RedoReceipt = {
|
|
141
|
+
branchId: string;
|
|
142
|
+
targetCommitId: string;
|
|
143
|
+
replayCommitId: string;
|
|
144
|
+
};
|
|
135
145
|
export type SwitchBranchOptions = {
|
|
136
146
|
branchId: string;
|
|
137
147
|
};
|
|
@@ -5,6 +5,7 @@ export class WasmLix {
|
|
|
5
5
|
private constructor();
|
|
6
6
|
free(): void;
|
|
7
7
|
[Symbol.dispose](): void;
|
|
8
|
+
activeAccountId(): Promise<string>;
|
|
8
9
|
activeBranchId(): Promise<string>;
|
|
9
10
|
beginTransaction(): Promise<WasmLixTransaction>;
|
|
10
11
|
clientStateDelete(key: string): Promise<void>;
|
|
@@ -20,7 +21,9 @@ export class WasmLix {
|
|
|
20
21
|
mergeBranch(options: any): Promise<any>;
|
|
21
22
|
mergeBranchPreview(options: any): Promise<any>;
|
|
22
23
|
observe(sql: string, params: any): Promise<WasmObserveEvents>;
|
|
24
|
+
redo(): Promise<any>;
|
|
23
25
|
switchBranch(options: any): Promise<any>;
|
|
26
|
+
undo(): Promise<any>;
|
|
24
27
|
}
|
|
25
28
|
|
|
26
29
|
export class WasmLixTransaction {
|
|
@@ -56,6 +59,7 @@ export interface InitOutput {
|
|
|
56
59
|
readonly openMemory: (a: number) => number;
|
|
57
60
|
readonly openMemoryFromSnapshot: (a: number, b: number, c: number) => number;
|
|
58
61
|
readonly parseSqlScript: (a: number, b: number, c: number, d: number) => void;
|
|
62
|
+
readonly wasmlix_activeAccountId: (a: number) => number;
|
|
59
63
|
readonly wasmlix_activeBranchId: (a: number) => number;
|
|
60
64
|
readonly wasmlix_beginTransaction: (a: number) => number;
|
|
61
65
|
readonly wasmlix_clientStateDelete: (a: number, b: number, c: number) => number;
|
|
@@ -71,14 +75,16 @@ export interface InitOutput {
|
|
|
71
75
|
readonly wasmlix_mergeBranch: (a: number, b: number) => number;
|
|
72
76
|
readonly wasmlix_mergeBranchPreview: (a: number, b: number) => number;
|
|
73
77
|
readonly wasmlix_observe: (a: number, b: number, c: number, d: number) => number;
|
|
78
|
+
readonly wasmlix_redo: (a: number) => number;
|
|
74
79
|
readonly wasmlix_switchBranch: (a: number, b: number) => number;
|
|
80
|
+
readonly wasmlix_undo: (a: number) => number;
|
|
75
81
|
readonly wasmlixtransaction_commit: (a: number) => number;
|
|
76
82
|
readonly wasmlixtransaction_execute: (a: number, b: number, c: number, d: number, e: number) => number;
|
|
77
83
|
readonly wasmlixtransaction_rollback: (a: number) => number;
|
|
78
84
|
readonly wasmobserveevents_close: (a: number) => void;
|
|
79
85
|
readonly wasmobserveevents_next: (a: number) => number;
|
|
80
|
-
readonly
|
|
81
|
-
readonly
|
|
86
|
+
readonly __wasm_bindgen_func_elem_120695: (a: number, b: number, c: number, d: number) => void;
|
|
87
|
+
readonly __wasm_bindgen_func_elem_120697: (a: number, b: number, c: number, d: number) => void;
|
|
82
88
|
readonly __wbindgen_export: (a: number, b: number) => number;
|
|
83
89
|
readonly __wbindgen_export2: (a: number, b: number, c: number, d: number) => number;
|
|
84
90
|
readonly __wbindgen_export3: (a: number) => void;
|
package/dist/wasm/lix_js_sdk.js
CHANGED
|
@@ -17,6 +17,13 @@ export class WasmLix {
|
|
|
17
17
|
const ptr = this.__destroy_into_raw();
|
|
18
18
|
wasm.__wbg_wasmlix_free(ptr, 0);
|
|
19
19
|
}
|
|
20
|
+
/**
|
|
21
|
+
* @returns {Promise<string>}
|
|
22
|
+
*/
|
|
23
|
+
activeAccountId() {
|
|
24
|
+
const ret = wasm.wasmlix_activeAccountId(this.__wbg_ptr);
|
|
25
|
+
return takeObject(ret);
|
|
26
|
+
}
|
|
20
27
|
/**
|
|
21
28
|
* @returns {Promise<string>}
|
|
22
29
|
*/
|
|
@@ -146,6 +153,13 @@ export class WasmLix {
|
|
|
146
153
|
const ret = wasm.wasmlix_observe(this.__wbg_ptr, ptr0, len0, addHeapObject(params));
|
|
147
154
|
return takeObject(ret);
|
|
148
155
|
}
|
|
156
|
+
/**
|
|
157
|
+
* @returns {Promise<any>}
|
|
158
|
+
*/
|
|
159
|
+
redo() {
|
|
160
|
+
const ret = wasm.wasmlix_redo(this.__wbg_ptr);
|
|
161
|
+
return takeObject(ret);
|
|
162
|
+
}
|
|
149
163
|
/**
|
|
150
164
|
* @param {any} options
|
|
151
165
|
* @returns {Promise<any>}
|
|
@@ -154,6 +168,13 @@ export class WasmLix {
|
|
|
154
168
|
const ret = wasm.wasmlix_switchBranch(this.__wbg_ptr, addHeapObject(options));
|
|
155
169
|
return takeObject(ret);
|
|
156
170
|
}
|
|
171
|
+
/**
|
|
172
|
+
* @returns {Promise<any>}
|
|
173
|
+
*/
|
|
174
|
+
undo() {
|
|
175
|
+
const ret = wasm.wasmlix_undo(this.__wbg_ptr);
|
|
176
|
+
return takeObject(ret);
|
|
177
|
+
}
|
|
157
178
|
}
|
|
158
179
|
if (Symbol.dispose) WasmLix.prototype[Symbol.dispose] = WasmLix.prototype.free;
|
|
159
180
|
|
|
@@ -515,7 +536,7 @@ function __wbg_get_imports() {
|
|
|
515
536
|
const a = state0.a;
|
|
516
537
|
state0.a = 0;
|
|
517
538
|
try {
|
|
518
|
-
return
|
|
539
|
+
return __wasm_bindgen_func_elem_120697(a, state0.b, arg0, arg1);
|
|
519
540
|
} finally {
|
|
520
541
|
state0.a = a;
|
|
521
542
|
}
|
|
@@ -625,8 +646,8 @@ function __wbg_get_imports() {
|
|
|
625
646
|
return addHeapObject(ret);
|
|
626
647
|
},
|
|
627
648
|
__wbindgen_cast_0000000000000001: function(arg0, arg1) {
|
|
628
|
-
// Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [Externref], shim_idx:
|
|
629
|
-
const ret = makeMutClosure(arg0, arg1,
|
|
649
|
+
// Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [Externref], shim_idx: 30501, ret: Result(Unit), inner_ret: Some(Result(Unit)) }, mutable: true }) -> Externref`.
|
|
650
|
+
const ret = makeMutClosure(arg0, arg1, __wasm_bindgen_func_elem_120695);
|
|
630
651
|
return addHeapObject(ret);
|
|
631
652
|
},
|
|
632
653
|
__wbindgen_cast_0000000000000002: function(arg0) {
|
|
@@ -675,10 +696,10 @@ function __wbg_get_imports() {
|
|
|
675
696
|
};
|
|
676
697
|
}
|
|
677
698
|
|
|
678
|
-
function
|
|
699
|
+
function __wasm_bindgen_func_elem_120695(arg0, arg1, arg2) {
|
|
679
700
|
try {
|
|
680
701
|
const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
|
|
681
|
-
wasm.
|
|
702
|
+
wasm.__wasm_bindgen_func_elem_120695(retptr, arg0, arg1, addHeapObject(arg2));
|
|
682
703
|
var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true);
|
|
683
704
|
var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true);
|
|
684
705
|
if (r1) {
|
|
@@ -689,8 +710,8 @@ function __wasm_bindgen_func_elem_111270(arg0, arg1, arg2) {
|
|
|
689
710
|
}
|
|
690
711
|
}
|
|
691
712
|
|
|
692
|
-
function
|
|
693
|
-
wasm.
|
|
713
|
+
function __wasm_bindgen_func_elem_120697(arg0, arg1, arg2, arg3) {
|
|
714
|
+
wasm.__wasm_bindgen_func_elem_120697(arg0, arg1, addHeapObject(arg2), addHeapObject(arg3));
|
|
694
715
|
}
|
|
695
716
|
|
|
696
717
|
const WasmLixFinalization = (typeof FinalizationRegistry === 'undefined')
|
|
Binary file
|
|
@@ -7,6 +7,7 @@ export const __wbg_wasmobserveevents_free: (a: number, b: number) => void;
|
|
|
7
7
|
export const openMemory: (a: number) => number;
|
|
8
8
|
export const openMemoryFromSnapshot: (a: number, b: number, c: number) => number;
|
|
9
9
|
export const parseSqlScript: (a: number, b: number, c: number, d: number) => void;
|
|
10
|
+
export const wasmlix_activeAccountId: (a: number) => number;
|
|
10
11
|
export const wasmlix_activeBranchId: (a: number) => number;
|
|
11
12
|
export const wasmlix_beginTransaction: (a: number) => number;
|
|
12
13
|
export const wasmlix_clientStateDelete: (a: number, b: number, c: number) => number;
|
|
@@ -22,14 +23,16 @@ export const wasmlix_exportSnapshot: (a: number) => number;
|
|
|
22
23
|
export const wasmlix_mergeBranch: (a: number, b: number) => number;
|
|
23
24
|
export const wasmlix_mergeBranchPreview: (a: number, b: number) => number;
|
|
24
25
|
export const wasmlix_observe: (a: number, b: number, c: number, d: number) => number;
|
|
26
|
+
export const wasmlix_redo: (a: number) => number;
|
|
25
27
|
export const wasmlix_switchBranch: (a: number, b: number) => number;
|
|
28
|
+
export const wasmlix_undo: (a: number) => number;
|
|
26
29
|
export const wasmlixtransaction_commit: (a: number) => number;
|
|
27
30
|
export const wasmlixtransaction_execute: (a: number, b: number, c: number, d: number, e: number) => number;
|
|
28
31
|
export const wasmlixtransaction_rollback: (a: number) => number;
|
|
29
32
|
export const wasmobserveevents_close: (a: number) => void;
|
|
30
33
|
export const wasmobserveevents_next: (a: number) => number;
|
|
31
|
-
export const
|
|
32
|
-
export const
|
|
34
|
+
export const __wasm_bindgen_func_elem_120695: (a: number, b: number, c: number, d: number) => void;
|
|
35
|
+
export const __wasm_bindgen_func_elem_120697: (a: number, b: number, c: number, d: number) => void;
|
|
33
36
|
export const __wbindgen_export: (a: number, b: number) => number;
|
|
34
37
|
export const __wbindgen_export2: (a: number, b: number, c: number, d: number) => number;
|
|
35
38
|
export const __wbindgen_export3: (a: number) => void;
|
package/dist/worker/client.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createWorkerConnection } from "#worker-factory";
|
|
1
|
+
import { createWorkerConnection, openDirectLixBinding } from "#worker-factory";
|
|
2
2
|
import { snapshotPersistenceAfterCommitError } from "../snapshot-persistence.js";
|
|
3
3
|
import { deserializeWorkerError, } from "./protocol.js";
|
|
4
4
|
const MAX_IDLE_WORKERS = 1;
|
|
@@ -26,6 +26,41 @@ export async function openLixWorker(storage, onDisposed, telemetry) {
|
|
|
26
26
|
}
|
|
27
27
|
/** Opens the local worker transport behind the semantic Lix binding. */
|
|
28
28
|
export async function openLixWorkerBinding(storage, onDisposed, telemetry) {
|
|
29
|
+
if (openDirectLixBinding) {
|
|
30
|
+
const telemetryDispatch = telemetry
|
|
31
|
+
? (span) => {
|
|
32
|
+
try {
|
|
33
|
+
telemetry.onSpan(span);
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
// Telemetry is observational and must not fail engine commands.
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
: undefined;
|
|
40
|
+
const binding = await openDirectLixBinding(storage, telemetryDispatch);
|
|
41
|
+
if (!onDisposed)
|
|
42
|
+
return binding;
|
|
43
|
+
let disposed = false;
|
|
44
|
+
return new Proxy(binding, {
|
|
45
|
+
get(target, property, receiver) {
|
|
46
|
+
if (property === "close") {
|
|
47
|
+
return async () => {
|
|
48
|
+
try {
|
|
49
|
+
await target.close();
|
|
50
|
+
}
|
|
51
|
+
finally {
|
|
52
|
+
if (!disposed) {
|
|
53
|
+
disposed = true;
|
|
54
|
+
onDisposed();
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
const value = Reflect.get(target, property, receiver);
|
|
60
|
+
return typeof value === "function" ? value.bind(target) : value;
|
|
61
|
+
},
|
|
62
|
+
});
|
|
63
|
+
}
|
|
29
64
|
const client = await openLixWorker(storage, onDisposed, telemetry);
|
|
30
65
|
return workerBinding(client);
|
|
31
66
|
}
|
|
@@ -95,12 +130,15 @@ function workerBinding(client) {
|
|
|
95
130
|
return workerTransactionBinding(request, transactionId);
|
|
96
131
|
},
|
|
97
132
|
activeBranchId: () => request({ kind: "activeBranchId" }),
|
|
133
|
+
activeAccountId: () => request({ kind: "activeAccountId" }),
|
|
98
134
|
clientStateEntries: () => request({ kind: "clientState.entries" }),
|
|
99
135
|
clientStateGet: (key) => request({ kind: "clientState.get", key }),
|
|
100
136
|
clientStateSet: (key, value) => request({ kind: "clientState.set", key, value }),
|
|
101
137
|
clientStateDelete: (key) => request({ kind: "clientState.delete", key }),
|
|
102
138
|
createBranch: (options) => request({ kind: "createBranch", options }),
|
|
103
139
|
createCheckpoint: () => request({ kind: "createCheckpoint" }),
|
|
140
|
+
undo: () => request({ kind: "undo" }),
|
|
141
|
+
redo: () => request({ kind: "redo" }),
|
|
104
142
|
switchBranch: (options) => request({ kind: "switchBranch", options }),
|
|
105
143
|
importFilesystemPaths: (paths) => request({ kind: "importFilesystemPaths", paths }),
|
|
106
144
|
mergeBranchPreview: (options) => request({ kind: "mergeBranchPreview", options }),
|
|
@@ -158,6 +196,7 @@ function persistentSnapshotBinding(binding, storage, namespace) {
|
|
|
158
196
|
};
|
|
159
197
|
},
|
|
160
198
|
activeBranchId: () => binding.activeBranchId(),
|
|
199
|
+
activeAccountId: () => binding.activeAccountId(),
|
|
161
200
|
clientStateEntries: () => {
|
|
162
201
|
const method = binding.clientStateEntries;
|
|
163
202
|
if (!method)
|
|
@@ -184,6 +223,8 @@ function persistentSnapshotBinding(binding, storage, namespace) {
|
|
|
184
223
|
},
|
|
185
224
|
createBranch: (branchOptions) => afterMutation(binding.createBranch(branchOptions)),
|
|
186
225
|
createCheckpoint: () => afterMutation(binding.createCheckpoint()),
|
|
226
|
+
undo: () => afterMutation(binding.undo()),
|
|
227
|
+
redo: () => afterMutation(binding.redo()),
|
|
187
228
|
switchBranch: (branchOptions) => afterMutation(binding.switchBranch(branchOptions)),
|
|
188
229
|
importFilesystemPaths: (paths) => afterMutation(binding.importFilesystemPaths(paths)),
|
|
189
230
|
mergeBranchPreview: (branchOptions) => binding.mergeBranchPreview(branchOptions),
|
|
@@ -1,2 +1,4 @@
|
|
|
1
|
+
import type { LixBinding, LixStorageConfig, TelemetryDispatch } from "../binding-types.js";
|
|
1
2
|
import type { WorkerConnection } from "./protocol.js";
|
|
3
|
+
export declare const openDirectLixBinding: undefined | ((storage: LixStorageConfig, telemetry?: TelemetryDispatch) => Promise<LixBinding>);
|
|
2
4
|
export declare function createWorkerConnection(): WorkerConnection;
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
/// <reference lib="webworker" />
|
|
2
|
+
// Browser/Wasm execution stays off the main thread.
|
|
3
|
+
export const openDirectLixBinding = undefined;
|
|
2
4
|
export function createWorkerConnection() {
|
|
3
5
|
const worker = new Worker(new URL("./entry.browser.js", import.meta.url), {
|
|
4
6
|
type: "module",
|
|
@@ -1,2 +1,4 @@
|
|
|
1
|
+
import type { LixBinding, LixStorageConfig, TelemetryDispatch } from "../binding-types.js";
|
|
1
2
|
import type { WorkerConnection } from "./protocol.js";
|
|
2
3
|
export declare function createWorkerConnection(): WorkerConnection;
|
|
4
|
+
export declare const openDirectLixBinding: (storage: LixStorageConfig, telemetry?: TelemetryDispatch) => Promise<LixBinding>;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Worker } from "node:worker_threads";
|
|
2
|
+
import { openLixBinding } from "../binding.node.js";
|
|
2
3
|
export function createWorkerConnection() {
|
|
3
4
|
const worker = new Worker(new URL("./entry.node.js", import.meta.url), {
|
|
4
5
|
name: "lix",
|
|
@@ -33,3 +34,7 @@ export function createWorkerConnection() {
|
|
|
33
34
|
},
|
|
34
35
|
};
|
|
35
36
|
}
|
|
37
|
+
/// Native Lix already owns a dedicated serialized engine actor. Routing it
|
|
38
|
+
/// through a second JavaScript worker adds two message-port hops per query
|
|
39
|
+
/// without adding isolation or concurrency.
|
|
40
|
+
export const openDirectLixBinding = (storage, telemetry) => openLixBinding(storage, telemetry);
|
package/dist/worker/host.js
CHANGED
|
@@ -87,6 +87,8 @@ export function startWorkerHost(endpoint) {
|
|
|
87
87
|
}
|
|
88
88
|
case "activeBranchId":
|
|
89
89
|
return requiredLix().activeBranchId();
|
|
90
|
+
case "activeAccountId":
|
|
91
|
+
return requiredLix().activeAccountId();
|
|
90
92
|
case "clientState.entries":
|
|
91
93
|
return requiredClientStateMethod("clientStateEntries")();
|
|
92
94
|
case "clientState.get":
|
|
@@ -99,6 +101,10 @@ export function startWorkerHost(endpoint) {
|
|
|
99
101
|
return requiredLix().createBranch(operation.options);
|
|
100
102
|
case "createCheckpoint":
|
|
101
103
|
return requiredLix().createCheckpoint();
|
|
104
|
+
case "undo":
|
|
105
|
+
return requiredLix().undo();
|
|
106
|
+
case "redo":
|
|
107
|
+
return requiredLix().redo();
|
|
102
108
|
case "switchBranch":
|
|
103
109
|
return requiredLix().switchBranch(operation.options);
|
|
104
110
|
case "mergeBranchPreview":
|
|
@@ -33,6 +33,8 @@ export type WorkerOperation = {
|
|
|
33
33
|
transactionId: number;
|
|
34
34
|
} | {
|
|
35
35
|
kind: "activeBranchId";
|
|
36
|
+
} | {
|
|
37
|
+
kind: "activeAccountId";
|
|
36
38
|
} | {
|
|
37
39
|
kind: "clientState.entries";
|
|
38
40
|
} | {
|
|
@@ -50,6 +52,10 @@ export type WorkerOperation = {
|
|
|
50
52
|
options: CreateBranchOptions;
|
|
51
53
|
} | {
|
|
52
54
|
kind: "createCheckpoint";
|
|
55
|
+
} | {
|
|
56
|
+
kind: "undo";
|
|
57
|
+
} | {
|
|
58
|
+
kind: "redo";
|
|
53
59
|
} | {
|
|
54
60
|
kind: "switchBranch";
|
|
55
61
|
options: SwitchBranchOptions;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lix-js/sdk",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.10.0",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
@@ -57,10 +57,10 @@
|
|
|
57
57
|
"typecheck": "tsc -p tsconfig.test.json --noEmit"
|
|
58
58
|
},
|
|
59
59
|
"optionalDependencies": {
|
|
60
|
-
"@lix-js/sdk-darwin-arm64": "0.
|
|
61
|
-
"@lix-js/sdk-linux-arm64": "0.
|
|
62
|
-
"@lix-js/sdk-linux-x64": "0.
|
|
63
|
-
"@lix-js/sdk-win32-x64": "0.
|
|
60
|
+
"@lix-js/sdk-darwin-arm64": "0.10.0",
|
|
61
|
+
"@lix-js/sdk-linux-arm64": "0.10.0",
|
|
62
|
+
"@lix-js/sdk-linux-x64": "0.10.0",
|
|
63
|
+
"@lix-js/sdk-win32-x64": "0.10.0"
|
|
64
64
|
},
|
|
65
65
|
"devDependencies": {
|
|
66
66
|
"@vitest/browser-playwright": "4.1.10",
|
|
Binary file
|
|
Binary file
|