@lix-js/sdk 0.8.4 → 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 +105 -22
- package/dist/binding-types.d.ts +21 -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 +18 -4
- package/dist/bundled-plugins/plugin_csv.lixplugin +0 -0
- package/dist/bundled-plugins/plugin_markdown.lixplugin +0 -0
- package/dist/bundled-plugins.js +2 -2
- package/dist/client-state.d.ts +53 -0
- package/dist/client-state.js +318 -0
- package/dist/index.d.ts +2 -1
- package/dist/lix.d.ts +47 -0
- package/dist/lix.js +378 -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 +99 -170
- package/dist/remote/client.d.ts +9 -0
- package/dist/remote/client.js +1150 -0
- package/dist/remote/protocol.d.ts +178 -0
- package/dist/remote/protocol.js +367 -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 +68 -1
- package/dist/wasm/lix_js_sdk.d.ts +22 -4
- package/dist/wasm/lix_js_sdk.js +96 -17
- package/dist/wasm/lix_js_sdk_bg.wasm +0 -0
- package/dist/wasm/lix_js_sdk_bg.wasm.d.ts +11 -2
- package/dist/worker/client.d.ts +23 -4
- package/dist/worker/client.js +329 -11
- 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 +36 -2
- package/dist/worker/protocol.d.ts +32 -2
- package/dist/workerd.js +1 -3
- package/package.json +19 -16
- 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
|
@@ -0,0 +1,318 @@
|
|
|
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 const ACTIVE_ACCOUNT_CLIENT_STATE_KEY = "lix_active_account_id";
|
|
5
|
+
const STORED_CLIENT_STATE_HEADER = "lix-client-state-v1\n";
|
|
6
|
+
export function unavailableClientState() {
|
|
7
|
+
const unavailable = () => {
|
|
8
|
+
const error = new Error("Lix client state requires client storage; pass storage to openLix()");
|
|
9
|
+
error.name = "LixError";
|
|
10
|
+
error.code = "LIX_CLIENT_STORAGE_REQUIRED";
|
|
11
|
+
return error;
|
|
12
|
+
};
|
|
13
|
+
return {
|
|
14
|
+
get: () => undefined,
|
|
15
|
+
set: async () => {
|
|
16
|
+
throw unavailable();
|
|
17
|
+
},
|
|
18
|
+
delete: async () => {
|
|
19
|
+
throw unavailable();
|
|
20
|
+
},
|
|
21
|
+
subscribe: () => () => undefined,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Opens the typed client-state facade over a private local Rust Lix.
|
|
26
|
+
*
|
|
27
|
+
* Values are ordinary global, untracked `lix_key_value` rows. The physical
|
|
28
|
+
* prefix is intentionally private so built-in Lix key/value rows never leak
|
|
29
|
+
* through this small API.
|
|
30
|
+
*/
|
|
31
|
+
export async function openClientState(options) {
|
|
32
|
+
const entries = options.binding.clientStateEntries;
|
|
33
|
+
if (!entries) {
|
|
34
|
+
throw new Error("The selected Lix binding does not support typed client state");
|
|
35
|
+
}
|
|
36
|
+
const initial = new Map();
|
|
37
|
+
for (const entry of await entries.call(options.binding)) {
|
|
38
|
+
assertClientStateKey(entry.key);
|
|
39
|
+
assertJsonValue(entry.value);
|
|
40
|
+
initial.set(entry.key, cloneJsonValue(entry.value));
|
|
41
|
+
}
|
|
42
|
+
return new ManagedLixClientState(options, initial);
|
|
43
|
+
}
|
|
44
|
+
export class ManagedLixClientState {
|
|
45
|
+
#binding;
|
|
46
|
+
#saveSnapshot;
|
|
47
|
+
#closeBinding;
|
|
48
|
+
#values;
|
|
49
|
+
#listeners = new Set();
|
|
50
|
+
#operationQueue = Promise.resolve();
|
|
51
|
+
#closePromise;
|
|
52
|
+
#acceptingOperations = true;
|
|
53
|
+
constructor(options, initial) {
|
|
54
|
+
this.#binding = options.binding;
|
|
55
|
+
this.#saveSnapshot = options.saveSnapshot;
|
|
56
|
+
this.#closeBinding = options.closeBinding ?? false;
|
|
57
|
+
this.#values = initial;
|
|
58
|
+
}
|
|
59
|
+
get(key) {
|
|
60
|
+
assertClientStateKey(key);
|
|
61
|
+
const value = this.#values.get(key);
|
|
62
|
+
return value === undefined ? undefined : cloneJsonValue(value);
|
|
63
|
+
}
|
|
64
|
+
set(key, value) {
|
|
65
|
+
assertClientStateKey(key);
|
|
66
|
+
assertJsonValue(value);
|
|
67
|
+
this.#assertOpen();
|
|
68
|
+
const nextValue = cloneJsonValue(value);
|
|
69
|
+
return this.#enqueue(async () => {
|
|
70
|
+
const set = this.#binding.clientStateSet;
|
|
71
|
+
if (!set)
|
|
72
|
+
throw new Error("Typed Lix client state is unavailable");
|
|
73
|
+
try {
|
|
74
|
+
await set.call(this.#binding, key, nextValue);
|
|
75
|
+
}
|
|
76
|
+
catch (error) {
|
|
77
|
+
if (!isSnapshotPersistenceAfterCommitError(error))
|
|
78
|
+
throw error;
|
|
79
|
+
this.#commitSet(key, nextValue);
|
|
80
|
+
throw error;
|
|
81
|
+
}
|
|
82
|
+
this.#commitSet(key, nextValue);
|
|
83
|
+
await this.#persist();
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
delete(key) {
|
|
87
|
+
assertClientStateKey(key);
|
|
88
|
+
this.#assertOpen();
|
|
89
|
+
return this.#enqueue(async () => {
|
|
90
|
+
const deleteValue = this.#binding.clientStateDelete;
|
|
91
|
+
if (!deleteValue)
|
|
92
|
+
throw new Error("Typed Lix client state is unavailable");
|
|
93
|
+
try {
|
|
94
|
+
await deleteValue.call(this.#binding, key);
|
|
95
|
+
}
|
|
96
|
+
catch (error) {
|
|
97
|
+
if (!isSnapshotPersistenceAfterCommitError(error))
|
|
98
|
+
throw error;
|
|
99
|
+
this.#commitDelete(key);
|
|
100
|
+
throw error;
|
|
101
|
+
}
|
|
102
|
+
this.#commitDelete(key);
|
|
103
|
+
await this.#persist();
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
subscribe(listener) {
|
|
107
|
+
if (typeof listener !== "function") {
|
|
108
|
+
throw new TypeError("clientState.subscribe() requires a function");
|
|
109
|
+
}
|
|
110
|
+
this.#assertOpen();
|
|
111
|
+
this.#listeners.add(listener);
|
|
112
|
+
return () => this.#listeners.delete(listener);
|
|
113
|
+
}
|
|
114
|
+
async close() {
|
|
115
|
+
if (this.#closePromise)
|
|
116
|
+
return this.#closePromise;
|
|
117
|
+
this.#acceptingOperations = false;
|
|
118
|
+
this.#closePromise = (async () => {
|
|
119
|
+
await this.#operationQueue;
|
|
120
|
+
this.#listeners.clear();
|
|
121
|
+
if (this.#closeBinding)
|
|
122
|
+
await this.#binding.close();
|
|
123
|
+
})();
|
|
124
|
+
return this.#closePromise;
|
|
125
|
+
}
|
|
126
|
+
#enqueue(operation) {
|
|
127
|
+
const result = this.#operationQueue.then(operation, operation);
|
|
128
|
+
this.#operationQueue = result.then(() => undefined, () => undefined);
|
|
129
|
+
return result;
|
|
130
|
+
}
|
|
131
|
+
async #persist() {
|
|
132
|
+
if (!this.#saveSnapshot)
|
|
133
|
+
return;
|
|
134
|
+
if (!this.#binding.exportSnapshot) {
|
|
135
|
+
throw new Error("The selected Lix binding cannot export storage snapshots");
|
|
136
|
+
}
|
|
137
|
+
await this.#saveSnapshot(await this.#binding.exportSnapshot());
|
|
138
|
+
}
|
|
139
|
+
#commitSet(key, value) {
|
|
140
|
+
this.#values.set(key, value);
|
|
141
|
+
this.#publish();
|
|
142
|
+
}
|
|
143
|
+
#commitDelete(key) {
|
|
144
|
+
if (this.#values.delete(key))
|
|
145
|
+
this.#publish();
|
|
146
|
+
}
|
|
147
|
+
#publish() {
|
|
148
|
+
for (const listener of [...this.#listeners]) {
|
|
149
|
+
try {
|
|
150
|
+
listener();
|
|
151
|
+
}
|
|
152
|
+
catch {
|
|
153
|
+
// Subscribers do not participate in the completed local transaction.
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
#assertOpen() {
|
|
158
|
+
if (!this.#acceptingOperations) {
|
|
159
|
+
throw new Error("Lix client state is closed");
|
|
160
|
+
}
|
|
161
|
+
}
|
|
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
|
+
}
|
|
301
|
+
function assertClientStateKey(key) {
|
|
302
|
+
if (typeof key !== "string" || key.length === 0) {
|
|
303
|
+
throw new TypeError("clientState key must be a non-empty string");
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
function assertJsonValue(value) {
|
|
307
|
+
// Value.json owns the SDK's full JSON validation, including finite numbers,
|
|
308
|
+
// well-formed strings, plain objects, and cycle detection.
|
|
309
|
+
Value.json(value);
|
|
310
|
+
}
|
|
311
|
+
function cloneJsonValue(value) {
|
|
312
|
+
if (Array.isArray(value))
|
|
313
|
+
return value.map(cloneJsonValue);
|
|
314
|
+
if (value && typeof value === "object") {
|
|
315
|
+
return Object.fromEntries(Object.entries(value).map(([key, entry]) => [key, cloneJsonValue(entry)]));
|
|
316
|
+
}
|
|
317
|
+
return value;
|
|
318
|
+
}
|
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, 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
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { type LixClientState, type ManagedClientState } from "./client-state.js";
|
|
2
|
+
import type { LixBinding, LixTransactionBinding, ObserveEventsBinding } from "./binding-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
|
+
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?: ManagedClientState | 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
|
+
activeAccountId(): Promise<string>;
|
|
17
|
+
/** Subscribes to successful branch switches made through this Lix handle. */
|
|
18
|
+
subscribeActiveBranch(listener: () => void): () => void;
|
|
19
|
+
createBranch(options: CreateBranchOptions): Promise<CreateBranchReceipt>;
|
|
20
|
+
createCheckpoint(): Promise<CreateCheckpointReceipt>;
|
|
21
|
+
undo(): Promise<UndoReceipt>;
|
|
22
|
+
redo(): Promise<RedoReceipt>;
|
|
23
|
+
switchBranch(options: SwitchBranchOptions): Promise<SwitchBranchReceipt>;
|
|
24
|
+
mergeBranchPreview(options: MergeBranchOptions): Promise<MergeBranchPreview>;
|
|
25
|
+
mergeBranch(options: MergeBranchOptions): Promise<MergeBranchReceipt>;
|
|
26
|
+
close(): Promise<void>;
|
|
27
|
+
}
|
|
28
|
+
export declare class ObserveEvents {
|
|
29
|
+
private readonly onClose;
|
|
30
|
+
private readonly setup;
|
|
31
|
+
private closed;
|
|
32
|
+
private readonly observeBinding;
|
|
33
|
+
constructor(observeBinding: Promise<ObserveEventsBinding>, onClose?: () => void);
|
|
34
|
+
next(): Promise<ObserveEvent | undefined>;
|
|
35
|
+
close(): void;
|
|
36
|
+
}
|
|
37
|
+
export declare class LixTransaction {
|
|
38
|
+
private readonly binding;
|
|
39
|
+
private readonly onFinish;
|
|
40
|
+
private finishPromise;
|
|
41
|
+
private finished;
|
|
42
|
+
constructor(binding: LixTransactionBinding, onFinish?: () => void);
|
|
43
|
+
execute(sql: string, params?: SqlParam[], options?: ExecuteOptions): Promise<ExecuteResult>;
|
|
44
|
+
commit(): Promise<void>;
|
|
45
|
+
rollback(): Promise<void>;
|
|
46
|
+
private finish;
|
|
47
|
+
}
|