@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.
Files changed (49) hide show
  1. package/README.md +105 -22
  2. package/dist/binding-types.d.ts +21 -2
  3. package/dist/binding.browser.d.ts +2 -2
  4. package/dist/binding.browser.js +3 -3
  5. package/dist/binding.node.d.ts +2 -2
  6. package/dist/binding.node.js +18 -4
  7. package/dist/bundled-plugins/plugin_csv.lixplugin +0 -0
  8. package/dist/bundled-plugins/plugin_markdown.lixplugin +0 -0
  9. package/dist/bundled-plugins.js +2 -2
  10. package/dist/client-state.d.ts +53 -0
  11. package/dist/client-state.js +318 -0
  12. package/dist/index.d.ts +2 -1
  13. package/dist/lix.d.ts +47 -0
  14. package/dist/lix.js +378 -0
  15. package/dist/local-storage-adapter.d.ts +26 -0
  16. package/dist/local-storage-adapter.js +117 -0
  17. package/dist/open-lix.d.ts +3 -34
  18. package/dist/open-lix.js +99 -170
  19. package/dist/remote/client.d.ts +9 -0
  20. package/dist/remote/client.js +1150 -0
  21. package/dist/remote/protocol.d.ts +178 -0
  22. package/dist/remote/protocol.js +367 -0
  23. package/dist/remote/sse.d.ts +12 -0
  24. package/dist/remote/sse.js +87 -0
  25. package/dist/snapshot-persistence.d.ts +7 -0
  26. package/dist/snapshot-persistence.js +26 -0
  27. package/dist/types.d.ts +68 -1
  28. package/dist/wasm/lix_js_sdk.d.ts +22 -4
  29. package/dist/wasm/lix_js_sdk.js +96 -17
  30. package/dist/wasm/lix_js_sdk_bg.wasm +0 -0
  31. package/dist/wasm/lix_js_sdk_bg.wasm.d.ts +11 -2
  32. package/dist/worker/client.d.ts +23 -4
  33. package/dist/worker/client.js +329 -11
  34. package/dist/worker/factory.browser.d.ts +2 -0
  35. package/dist/worker/factory.browser.js +2 -0
  36. package/dist/worker/factory.node.d.ts +2 -0
  37. package/dist/worker/factory.node.js +5 -0
  38. package/dist/worker/host.js +36 -2
  39. package/dist/worker/protocol.d.ts +32 -2
  40. package/dist/workerd.js +1 -3
  41. package/package.json +19 -16
  42. package/dist/bundled-plugins/plugin_md_v2.lixplugin +0 -0
  43. package/dist/jco/js-component-bindgen-component.core.wasm +0 -0
  44. package/dist/jco/js-component-bindgen-component.core2.wasm +0 -0
  45. package/dist/jco/js-component-bindgen-component.js +0 -13662
  46. package/dist/jco-transpile.browser.d.ts +0 -14
  47. package/dist/jco-transpile.browser.js +0 -22
  48. package/dist/plugin-runtime.d.ts +0 -45
  49. package/dist/plugin-runtime.js +0 -124
package/dist/open-lix.js CHANGED
@@ -1,16 +1,7 @@
1
- import { localFilesystemAlreadyOpen, localFilesystemNotOpen, invalidArgument, } from "./errors.js";
2
- import { normalizeOptionals, wrapExecuteResult } from "./result.js";
3
- import { normalizeParam, toNativeValue } from "./value.js";
4
- import { openLixWorker } from "./worker/client.js";
5
- const transactionFinalizer = new FinalizationRegistry(({ client, transactionId }) => {
6
- client.notify({ kind: "transaction.abandon", transactionId });
7
- });
8
- const observeFinalizer = new FinalizationRegistry(({ client, observeId }) => {
9
- void observeId.then((id) => {
10
- if (id !== undefined)
11
- client.notify({ kind: "observe.close", observeId: id });
12
- });
13
- });
1
+ import { localFilesystemAlreadyOpen, localFilesystemNotOpen, } from "./errors.js";
2
+ import { ACTIVE_ACCOUNT_CLIENT_STATE_KEY, ACTIVE_BRANCH_CLIENT_STATE_KEY, openClientState, openStoredClientState, } from "./client-state.js";
3
+ import { Lix } from "./lix.js";
4
+ export { Lix, LixTransaction, ObserveEvents } from "./lix.js";
14
5
  export class SQLite {
15
6
  path;
16
7
  constructor(options) {
@@ -53,16 +44,13 @@ export class LocalFilesystem {
53
44
  throw new TypeError("importPaths() paths must contain non-empty strings");
54
45
  }
55
46
  if (path.endsWith("/")) {
56
- throw new TypeError("importPaths() paths must contain file paths, not directory paths");
47
+ throw new TypeError("importPaths() paths must not end with a trailing slash");
57
48
  }
58
49
  }
59
- await this.client("importPaths").request({
60
- kind: "importFilesystemPaths",
61
- paths: [...paths],
62
- });
50
+ await this.client("importPaths").importFilesystemPaths([...paths]);
63
51
  }
64
52
  async syncDiskToLix() {
65
- return this.client("syncDiskToLix").request({ kind: "syncDiskToLix" });
53
+ return this.client("syncDiskToLix").syncDiskToLix();
66
54
  }
67
55
  client(operation) {
68
56
  const client = openLocalFilesystems.get(this);
@@ -79,11 +67,63 @@ export async function openLix(options = {}) {
79
67
  if ("backend" in options) {
80
68
  throw new TypeError("openLix() option 'backend' was removed; use 'storage' instead");
81
69
  }
70
+ if (options.telemetry !== undefined &&
71
+ (typeof options.telemetry !== "object" ||
72
+ typeof options.telemetry.onSpan !== "function")) {
73
+ throw new TypeError("openLix() telemetry requires an onSpan callback");
74
+ }
75
+ if (options.server !== undefined) {
76
+ const { openRemoteLixBinding } = await import("./remote/client.js");
77
+ if (options.storage === undefined) {
78
+ return new Lix(await openRemoteLixBinding(options.server));
79
+ }
80
+ assertSnapshotStorage(options.storage);
81
+ const clientState = await openStoredClientState({
82
+ storage: options.storage,
83
+ namespace: remoteClientStateNamespace(options.server.url),
84
+ });
85
+ const restoredBranchId = clientState.get(ACTIVE_BRANCH_CLIENT_STATE_KEY);
86
+ const restoredAccountId = clientState.get(ACTIVE_ACCOUNT_CLIENT_STATE_KEY);
87
+ let remoteBinding;
88
+ try {
89
+ try {
90
+ remoteBinding = await openRemoteLixBinding(options.server, {
91
+ initialActiveBranchId: restoredBranchId,
92
+ initialActiveAccountId: restoredAccountId,
93
+ });
94
+ }
95
+ catch (error) {
96
+ if (!restoredBranchId || !isBranchNotFoundError(error))
97
+ throw error;
98
+ remoteBinding = await openRemoteLixBinding(options.server, {
99
+ initialActiveAccountId: restoredAccountId,
100
+ });
101
+ }
102
+ const activeBranchId = await remoteBinding.activeBranchId();
103
+ const activeAccountId = await remoteBinding.activeAccountId();
104
+ if (activeBranchId !== restoredBranchId) {
105
+ await clientState.set(ACTIVE_BRANCH_CLIENT_STATE_KEY, activeBranchId);
106
+ }
107
+ if (activeAccountId !== restoredAccountId) {
108
+ await clientState.set(ACTIVE_ACCOUNT_CLIENT_STATE_KEY, activeAccountId);
109
+ }
110
+ return new Lix(remoteBinding, clientState);
111
+ }
112
+ catch (error) {
113
+ await remoteBinding?.close().catch(() => undefined);
114
+ await clientState.close().catch(() => undefined);
115
+ throw error;
116
+ }
117
+ }
118
+ const { openLixWorkerBinding } = await import("./worker/client.js");
82
119
  if (options.storage === undefined) {
83
- return new Lix(await openLixWorker({ kind: "memory" }));
120
+ return new Lix(await openLixWorkerBinding({ kind: "memory" }, undefined, options.telemetry));
84
121
  }
85
122
  if (options.storage instanceof SQLite) {
86
- return new Lix(await openLixWorker({ kind: "sqlite", path: options.storage.path }));
123
+ return new Lix(await openLixWorkerBinding({
124
+ kind: "sqlite",
125
+ path: options.storage.path,
126
+ }, undefined, options.telemetry));
87
127
  }
88
128
  if (options.storage instanceof LocalFilesystem) {
89
129
  const storage = options.storage;
@@ -92,175 +132,64 @@ export async function openLix(options = {}) {
92
132
  }
93
133
  openLocalFilesystems.set(storage, null);
94
134
  try {
95
- const client = await openLixWorker({
135
+ const binding = await openLixWorkerBinding({
96
136
  kind: "localFilesystem",
97
137
  path: storage.path,
98
138
  lixDir: storage.lixDir,
99
139
  syncAllFiles: storage.syncAllFiles,
100
- }, () => openLocalFilesystems.delete(storage));
101
- openLocalFilesystems.set(storage, client);
102
- return new Lix(client);
140
+ }, () => openLocalFilesystems.delete(storage), options.telemetry);
141
+ openLocalFilesystems.set(storage, binding);
142
+ return new Lix(binding);
103
143
  }
104
144
  catch (error) {
105
145
  openLocalFilesystems.delete(storage);
106
146
  throw error;
107
147
  }
108
148
  }
109
- throw new TypeError("openLix() requires storage to be SQLite or LocalFilesystem");
110
- }
111
- export class Lix {
112
- client;
113
- closePromise;
114
- constructor(client) {
115
- this.client = client;
116
- }
117
- async execute(sql, params = [], options) {
118
- assertExecuteArgs("lix", sql, params, options);
119
- return wrapExecuteResult(await this.client.request({
120
- kind: "execute",
121
- sql,
122
- params: params.map((param, index) => toNativeValue(normalizeParam(param, index))),
123
- options,
124
- }));
125
- }
126
- observe(sql, params = []) {
127
- assertSqlArgs("observe", "lix", sql, params);
128
- return new ObserveEvents(this.client, this.client.request({
129
- kind: "observe",
130
- sql,
131
- params: params.map((param, index) => toNativeValue(normalizeParam(param, index))),
132
- }));
133
- }
134
- async beginTransaction() {
135
- const transactionId = await this.client.request({
136
- kind: "beginTransaction",
149
+ if (isSnapshotStorage(options.storage)) {
150
+ const { openPersistentLixWorkerBinding } = await import("./worker/client.js");
151
+ const binding = await openPersistentLixWorkerBinding({
152
+ storage: options.storage,
153
+ namespace: "local",
154
+ telemetry: options.telemetry,
137
155
  });
138
- return new LixTransaction(this.client, transactionId);
139
- }
140
- async activeBranchId() {
141
- return this.client.request({ kind: "activeBranchId" });
142
- }
143
- async createBranch(options) {
144
- return this.client.request({ kind: "createBranch", options });
145
- }
146
- async switchBranch(options) {
147
- return this.client.request({ kind: "switchBranch", options });
148
- }
149
- async mergeBranchPreview(options) {
150
- return normalizeOptionals(await this.client.request({ kind: "mergeBranchPreview", options }));
151
- }
152
- async mergeBranch(options) {
153
- const receipt = normalizeOptionals(await this.client.request({ kind: "mergeBranch", options }));
154
- receipt.createdMergeCommitId ??= null;
155
- return receipt;
156
- }
157
- async close() {
158
- this.closePromise ??= (async () => {
159
- await this.client.request({ kind: "close" });
160
- await this.client.terminate();
161
- })();
162
156
  try {
163
- await this.closePromise;
157
+ const clientState = await openClientState({ binding });
158
+ return new Lix(binding, clientState);
164
159
  }
165
160
  catch (error) {
166
- this.closePromise = undefined;
161
+ await binding.close().catch(() => undefined);
167
162
  throw error;
168
163
  }
169
164
  }
165
+ throw new TypeError("openLix() requires storage to be SQLite, LocalFilesystem, or a Lix snapshot storage adapter");
170
166
  }
171
- export class ObserveEvents {
172
- client;
173
- setup = {};
174
- closed = false;
175
- observeId;
176
- constructor(client, observeId) {
177
- this.client = client;
178
- const setup = this.setup;
179
- this.observeId = observeId.catch((error) => {
180
- setup.error = error;
181
- return undefined;
182
- });
183
- observeFinalizer.register(this, { client, observeId: this.observeId }, this);
184
- }
185
- async next() {
186
- if (this.closed)
187
- return undefined;
188
- const observeId = await this.observeId;
189
- if (observeId === undefined) {
190
- throw this.setup.error;
191
- }
192
- const event = await this.client.request({
193
- kind: "observe.next",
194
- observeId,
195
- });
196
- if (event == null) {
197
- return undefined;
198
- }
199
- return {
200
- sequence: event.sequence,
201
- mutationSequence: event.mutationSequence,
202
- result: wrapExecuteResult(event.rows),
203
- };
204
- }
205
- close() {
206
- if (this.closed)
207
- return;
208
- this.closed = true;
209
- observeFinalizer.unregister(this);
210
- void this.observeId.then((observeId) => {
211
- if (observeId !== undefined) {
212
- this.client.notify({ kind: "observe.close", observeId });
213
- }
214
- });
215
- }
167
+ function isSnapshotStorage(value) {
168
+ return (typeof value === "object" &&
169
+ value !== null &&
170
+ typeof value.load === "function" &&
171
+ typeof value.save === "function");
216
172
  }
217
- export class LixTransaction {
218
- client;
219
- transactionId;
220
- constructor(client, transactionId) {
221
- this.client = client;
222
- this.transactionId = transactionId;
223
- transactionFinalizer.register(this, { client, transactionId }, this);
224
- }
225
- async execute(sql, params = [], options) {
226
- assertExecuteArgs("lixTransaction", sql, params, options);
227
- return wrapExecuteResult(await this.client.request({
228
- kind: "transaction.execute",
229
- transactionId: this.transactionId,
230
- sql,
231
- params: params.map((param, index) => toNativeValue(normalizeParam(param, index))),
232
- options,
233
- }));
234
- }
235
- async commit() {
236
- return this.finish("transaction.commit");
237
- }
238
- async rollback() {
239
- return this.finish("transaction.rollback");
240
- }
241
- async finish(kind) {
242
- transactionFinalizer.unregister(this);
243
- return this.client.request({ kind, transactionId: this.transactionId });
173
+ function assertSnapshotStorage(value) {
174
+ if (!isSnapshotStorage(value)) {
175
+ throw new TypeError("openLix() remote storage must implement load() and save()");
244
176
  }
245
177
  }
246
- function assertExecuteArgs(receiver, sql, params, options) {
247
- assertSqlArgs("execute", receiver, sql, params);
248
- if (options === undefined) {
249
- return;
250
- }
251
- if (!options || typeof options !== "object" || Array.isArray(options)) {
252
- throw invalidArgument("execute", "options", "object", typeof options, receiver);
253
- }
254
- if (options.originKey !== undefined &&
255
- typeof options.originKey !== "string") {
256
- throw invalidArgument("execute", "options.originKey", "string", typeof options.originKey, receiver);
257
- }
178
+ function remoteClientStateNamespace(value) {
179
+ let url;
180
+ try {
181
+ url = new URL(value);
182
+ }
183
+ catch {
184
+ throw new TypeError("openLix() remote server url must be an absolute URL");
185
+ }
186
+ url.pathname = url.pathname.replace(/\/$/, "");
187
+ url.search = "";
188
+ url.hash = "";
189
+ return `remote:${url.href}`;
258
190
  }
259
- function assertSqlArgs(operation, receiver, sql, params) {
260
- if (typeof sql !== "string") {
261
- throw invalidArgument(operation, "sql", "string", typeof sql, receiver);
262
- }
263
- if (!Array.isArray(params)) {
264
- throw invalidArgument(operation, "params", "array", typeof params, receiver);
265
- }
191
+ function isBranchNotFoundError(error) {
192
+ return (error instanceof Error &&
193
+ "code" in error &&
194
+ error.code === "LIX_BRANCH_NOT_FOUND");
266
195
  }
@@ -0,0 +1,9 @@
1
+ import type { LixBinding } from "../binding-types.js";
2
+ import type { RemoteLixServerOptions } from "../types.js";
3
+ import { type RemoteHandshakeRequest } from "./protocol.js";
4
+ type RemoteLixClientOptions = {
5
+ initialActiveBranchId?: RemoteHandshakeRequest["activeBranchId"];
6
+ initialActiveAccountId?: RemoteHandshakeRequest["activeAccountId"];
7
+ };
8
+ export declare function openRemoteLixBinding(options: RemoteLixServerOptions, clientOptions?: RemoteLixClientOptions): Promise<LixBinding>;
9
+ export {};