@lix-js/sdk 0.11.0 → 0.12.1

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 (40) hide show
  1. package/README.md +48 -76
  2. package/dist/binding-types.d.ts +6 -15
  3. package/dist/binding.browser.js +18 -5
  4. package/dist/binding.node.js +5 -6
  5. package/dist/bundled-plugins/plugin_csv.lixplugin +0 -0
  6. package/dist/bundled-plugins/plugin_markdown.lixplugin +0 -0
  7. package/dist/errors.d.ts +0 -2
  8. package/dist/errors.js +0 -13
  9. package/dist/index.d.ts +3 -3
  10. package/dist/index.js +1 -1
  11. package/dist/indexeddb-backend.d.ts +19 -0
  12. package/dist/indexeddb-backend.js +121 -0
  13. package/dist/lix.d.ts +1 -4
  14. package/dist/lix.js +21 -52
  15. package/dist/open-lix.d.ts +4 -9
  16. package/dist/open-lix.js +45 -135
  17. package/dist/remote/client.d.ts +2 -3
  18. package/dist/remote/client.js +13 -21
  19. package/dist/remote/{protocol.d.ts → server-protocol.d.ts} +35 -33
  20. package/dist/remote/{protocol.js → server-protocol.js} +24 -17
  21. package/dist/storage-adapter.d.ts +30 -0
  22. package/dist/storage-adapter.js +12 -0
  23. package/dist/types.d.ts +14 -24
  24. package/dist/value.d.ts +2 -1
  25. package/dist/value.js +23 -10
  26. package/dist/wasm/lix_js_sdk.d.ts +5 -10
  27. package/dist/wasm/lix_js_sdk.js +33 -45
  28. package/dist/wasm/lix_js_sdk_bg.wasm +0 -0
  29. package/dist/wasm/lix_js_sdk_bg.wasm.d.ts +3 -6
  30. package/dist/worker/client.d.ts +1 -12
  31. package/dist/worker/client.js +0 -161
  32. package/dist/worker/host.js +0 -23
  33. package/dist/worker/protocol.d.ts +1 -15
  34. package/package.json +8 -12
  35. package/dist/client-state.d.ts +0 -53
  36. package/dist/client-state.js +0 -315
  37. package/dist/local-storage-adapter.d.ts +0 -26
  38. package/dist/local-storage-adapter.js +0 -117
  39. package/dist/snapshot-persistence.d.ts +0 -7
  40. package/dist/snapshot-persistence.js +0 -26
package/dist/open-lix.js CHANGED
@@ -1,52 +1,15 @@
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
1
  import { Lix } from "./lix.js";
2
+ import { isLixStorage } from "./storage-adapter.js";
4
3
  export { Lix, LixTransaction, ObserveEvents } from "./lix.js";
5
- const openLocalFilesystems = new WeakMap();
6
- export class LocalFilesystem {
7
- path;
8
- lixDir;
9
- syncAllFiles;
4
+ const openStorages = new WeakSet();
5
+ const openIndexedDbStorageNames = new Set();
6
+ export class IndexedDbStorage {
7
+ name;
10
8
  constructor(options) {
11
- if (!options ||
12
- typeof options.path !== "string" ||
13
- options.path.length === 0) {
14
- throw new TypeError("LocalFilesystem requires a non-empty path");
9
+ if (!options || typeof options.name !== "string" || options.name.length === 0) {
10
+ throw new TypeError("IndexedDbStorage requires a non-empty name");
15
11
  }
16
- if (options.lixDir !== undefined &&
17
- (typeof options.lixDir !== "string" || options.lixDir.length === 0)) {
18
- throw new TypeError("LocalFilesystem lixDir must be a non-empty string");
19
- }
20
- if (typeof options.syncAllFiles !== "boolean") {
21
- throw new TypeError("LocalFilesystem syncAllFiles must be a boolean");
22
- }
23
- this.path = options.path;
24
- this.lixDir = options.lixDir;
25
- this.syncAllFiles = options.syncAllFiles;
26
- }
27
- async importPaths(paths) {
28
- if (!Array.isArray(paths)) {
29
- throw new TypeError("importPaths() paths must be an array");
30
- }
31
- for (const path of paths) {
32
- if (typeof path !== "string" || path.length === 0) {
33
- throw new TypeError("importPaths() paths must contain non-empty strings");
34
- }
35
- if (path.endsWith("/")) {
36
- throw new TypeError("importPaths() paths must not end with a trailing slash");
37
- }
38
- }
39
- await this.client("importPaths").importFilesystemPaths([...paths]);
40
- }
41
- async syncDiskToLix() {
42
- return this.client("syncDiskToLix").syncDiskToLix();
43
- }
44
- client(operation) {
45
- const client = openLocalFilesystems.get(this);
46
- if (!client) {
47
- throw localFilesystemNotOpen(operation);
48
- }
49
- return client;
12
+ this.name = options.name;
50
13
  }
51
14
  }
52
15
  export async function openLix(options = {}) {
@@ -63,116 +26,63 @@ export async function openLix(options = {}) {
63
26
  }
64
27
  if (options.server !== undefined) {
65
28
  const { openRemoteLixBinding } = await import("./remote/client.js");
66
- if (options.storage === undefined) {
67
- return new Lix(await openRemoteLixBinding(options.server));
68
- }
69
- assertSnapshotStorage(options.storage);
70
- const clientState = await openStoredClientState({
71
- storage: options.storage,
72
- namespace: remoteClientStateNamespace(options.server.url),
73
- });
74
- const restoredBranchId = clientState.get(ACTIVE_BRANCH_CLIENT_STATE_KEY);
75
- const restoredAccountId = clientState.get(ACTIVE_ACCOUNT_CLIENT_STATE_KEY);
76
- let remoteBinding;
77
- try {
78
- try {
79
- remoteBinding = await openRemoteLixBinding(options.server, {
80
- initialActiveBranchId: restoredBranchId,
81
- initialActiveAccountId: restoredAccountId,
82
- });
83
- }
84
- catch (error) {
85
- if (!restoredBranchId || !isBranchNotFoundError(error))
86
- throw error;
87
- remoteBinding = await openRemoteLixBinding(options.server, {
88
- initialActiveAccountId: restoredAccountId,
89
- });
90
- }
91
- const activeBranchId = await remoteBinding.activeBranchId();
92
- const activeAccountId = await remoteBinding.activeAccountId();
93
- if (activeBranchId !== restoredBranchId) {
94
- await clientState.set(ACTIVE_BRANCH_CLIENT_STATE_KEY, activeBranchId);
95
- }
96
- if (activeAccountId !== restoredAccountId) {
97
- await clientState.set(ACTIVE_ACCOUNT_CLIENT_STATE_KEY, activeAccountId);
98
- }
99
- return new Lix(remoteBinding, clientState);
100
- }
101
- catch (error) {
102
- await remoteBinding?.close().catch(() => undefined);
103
- await clientState.close().catch(() => undefined);
104
- throw error;
29
+ if ("storage" in options && options.storage !== undefined) {
30
+ throw new TypeError("openLix() remote mode does not accept storage");
105
31
  }
32
+ return new Lix(await openRemoteLixBinding(options.server));
106
33
  }
107
34
  const { openLixWorkerBinding } = await import("./worker/client.js");
108
35
  if (options.storage === undefined) {
109
36
  return new Lix(await openLixWorkerBinding({ kind: "memory" }, undefined, options.telemetry));
110
37
  }
111
- if (options.storage instanceof LocalFilesystem) {
38
+ if (isLixStorage(options.storage)) {
112
39
  const storage = options.storage;
113
- if (openLocalFilesystems.has(storage)) {
114
- throw localFilesystemAlreadyOpen();
40
+ if (openStorages.has(storage)) {
41
+ throw storageAlreadyOpen();
115
42
  }
116
- openLocalFilesystems.set(storage, null);
43
+ openStorages.add(storage);
44
+ let binding;
45
+ const disconnect = () => {
46
+ storage.lixStorage.connect(undefined);
47
+ openStorages.delete(storage);
48
+ };
117
49
  try {
118
- const binding = await openLixWorkerBinding({
119
- kind: "localFilesystem",
120
- path: storage.path,
121
- lixDir: storage.lixDir,
122
- syncAllFiles: storage.syncAllFiles,
123
- }, () => openLocalFilesystems.delete(storage), options.telemetry);
124
- openLocalFilesystems.set(storage, binding);
50
+ binding = await openLixWorkerBinding(storage.lixStorage.config, disconnect, options.telemetry);
51
+ storage.lixStorage.connect({
52
+ importFilesystemPaths: (paths) => binding.importFilesystemPaths(paths),
53
+ syncDiskToLix: () => binding.syncDiskToLix(),
54
+ });
125
55
  return new Lix(binding);
126
56
  }
127
57
  catch (error) {
128
- openLocalFilesystems.delete(storage);
58
+ disconnect();
59
+ await binding?.close().catch(() => undefined);
129
60
  throw error;
130
61
  }
131
62
  }
132
- if (isSnapshotStorage(options.storage)) {
133
- const { openPersistentLixWorkerBinding } = await import("./worker/client.js");
134
- const binding = await openPersistentLixWorkerBinding({
135
- storage: options.storage,
136
- namespace: "local",
137
- telemetry: options.telemetry,
138
- });
63
+ if (options.storage instanceof IndexedDbStorage) {
64
+ const storage = options.storage;
65
+ const databaseName = storage.name;
66
+ if (openIndexedDbStorageNames.has(databaseName)) {
67
+ throw new Error("IndexedDbStorage is already open");
68
+ }
69
+ openIndexedDbStorageNames.add(databaseName);
70
+ let binding;
139
71
  try {
140
- const clientState = await openClientState({ binding });
141
- return new Lix(binding, clientState);
72
+ binding = await openLixWorkerBinding({ kind: "indexedDb", name: databaseName }, () => openIndexedDbStorageNames.delete(databaseName), options.telemetry);
73
+ return new Lix(binding);
142
74
  }
143
75
  catch (error) {
144
- await binding.close().catch(() => undefined);
76
+ openIndexedDbStorageNames.delete(databaseName);
77
+ await binding?.close().catch(() => undefined);
145
78
  throw error;
146
79
  }
147
80
  }
148
- throw new TypeError("openLix() requires storage to be LocalFilesystem or a Lix snapshot storage adapter");
149
- }
150
- function isSnapshotStorage(value) {
151
- return (typeof value === "object" &&
152
- value !== null &&
153
- typeof value.load === "function" &&
154
- typeof value.save === "function");
155
- }
156
- function assertSnapshotStorage(value) {
157
- if (!isSnapshotStorage(value)) {
158
- throw new TypeError("openLix() remote storage must implement load() and save()");
159
- }
160
- }
161
- function remoteClientStateNamespace(value) {
162
- let url;
163
- try {
164
- url = new URL(value);
165
- }
166
- catch {
167
- throw new TypeError("openLix() remote server url must be an absolute URL");
168
- }
169
- url.pathname = url.pathname.replace(/\/$/, "");
170
- url.search = "";
171
- url.hash = "";
172
- return `remote:${url.href}`;
81
+ throw new TypeError("openLix() requires a Lix storage adapter or IndexedDbStorage");
173
82
  }
174
- function isBranchNotFoundError(error) {
175
- return (error instanceof Error &&
176
- "code" in error &&
177
- error.code === "LIX_BRANCH_NOT_FOUND");
83
+ function storageAlreadyOpen() {
84
+ const error = new Error("openLix() storage is already open; close the existing Lix or create a new storage adapter");
85
+ error.name = "LixError";
86
+ error.code = "LIX_STORAGE_IN_USE";
87
+ return error;
178
88
  }
@@ -1,9 +1,8 @@
1
1
  import type { LixBinding } from "../binding-types.js";
2
2
  import type { RemoteLixServerOptions } from "../types.js";
3
- import { type RemoteHandshakeRequest } from "./protocol.js";
3
+ import { type ServerProtocolHandshakeRequest } from "./server-protocol.js";
4
4
  type RemoteLixClientOptions = {
5
- initialActiveBranchId?: RemoteHandshakeRequest["activeBranchId"];
6
- initialActiveAccountId?: RemoteHandshakeRequest["activeAccountId"];
5
+ initialActiveBranchId?: ServerProtocolHandshakeRequest["activeBranchId"];
7
6
  };
8
7
  export declare function openRemoteLixBinding(options: RemoteLixServerOptions, clientOptions?: RemoteLixClientOptions): Promise<LixBinding>;
9
8
  export {};
@@ -1,4 +1,4 @@
1
- import { decodeExecuteBatchResult, decodeExecuteResult, decodeHandshake, decodeObserveEvent, encodeWireValue, errorFromResponseBody, protocolError, record, REMOTE_PROTOCOL_PATH, remoteError, } from "./protocol.js";
1
+ import { decodeExecuteBatchResult, decodeExecuteResult, decodeHandshake, decodeObserveEvent, encodeWireValue, errorFromResponseBody, protocolError, record, SERVER_PROTOCOL_PATH, remoteError, } from "./server-protocol.js";
2
2
  import { readSseEvents } from "./sse.js";
3
3
  const OBSERVE_RETRY_BASE_MS = 100;
4
4
  const OBSERVE_RETRY_MAX_MS = 5_000;
@@ -32,7 +32,6 @@ class RemoteLixBinding {
32
32
  #fetch;
33
33
  #headers;
34
34
  #initialActiveBranchId;
35
- #initialActiveAccountId;
36
35
  #observationHub;
37
36
  #requestBlobBases = new Map();
38
37
  #sessionId;
@@ -66,11 +65,6 @@ class RemoteLixBinding {
66
65
  throw new TypeError("initialActiveBranchId must be a non-empty string");
67
66
  }
68
67
  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;
74
68
  this.#observationHub = new RemoteObservationHub({
75
69
  openStream: (subscriptions, signal) => this.#requestObserveStream(subscriptions, signal),
76
70
  refreshObservation: (subscription) => this.#refreshObservation(subscription),
@@ -81,9 +75,6 @@ class RemoteLixBinding {
81
75
  if (this.#initialActiveBranchId !== undefined) {
82
76
  query.set("activeBranchId", this.#initialActiveBranchId);
83
77
  }
84
- if (this.#initialActiveAccountId !== undefined) {
85
- query.set("activeAccountId", this.#initialActiveAccountId);
86
- }
87
78
  const path = query.size === 0 ? "" : `?${query}`;
88
79
  const handshake = decodeHandshake(await this.#requestJson(path, { method: "GET" }));
89
80
  this.#sessionId = handshake.sessionId;
@@ -225,7 +216,7 @@ class RemoteLixBinding {
225
216
  if (this.#activeBranchId === undefined) {
226
217
  const handshake = decodeHandshake(await this.#requestJson("", { method: "GET" }));
227
218
  if (handshake.sessionId !== this.#sessionId) {
228
- throw protocolError("remote handshake changed sessionId");
219
+ throw protocolError("Lix Server Protocol handshake changed sessionId");
229
220
  }
230
221
  this.#activeBranchId = handshake.activeBranchId;
231
222
  }
@@ -238,7 +229,7 @@ class RemoteLixBinding {
238
229
  if (this.#activeAccountId === undefined) {
239
230
  const handshake = decodeHandshake(await this.#requestJson("", { method: "GET" }));
240
231
  if (handshake.sessionId !== this.#sessionId) {
241
- throw protocolError("remote handshake changed sessionId");
232
+ throw protocolError("Lix Server Protocol handshake changed sessionId");
242
233
  }
243
234
  this.#activeAccountId = handshake.activeAccountId;
244
235
  }
@@ -1092,7 +1083,7 @@ function isRetryableObserveError(error) {
1092
1083
  function asObserveProtocolError(error, event) {
1093
1084
  if (error instanceof Error &&
1094
1085
  "code" in error &&
1095
- error.code === "LIX_REMOTE_PROTOCOL_ERROR") {
1086
+ error.code === "LIX_SERVER_PROTOCOL_ERROR") {
1096
1087
  return error;
1097
1088
  }
1098
1089
  return protocolError(`remote observe ${event} event contains invalid data: ${errorMessage(error)}`);
@@ -1119,8 +1110,8 @@ function nativeValuesEqual(left, right) {
1119
1110
  return (right.kind === "blob" &&
1120
1111
  left.blob.length === right.blob.length &&
1121
1112
  left.blob.every((byte, index) => byte === right.blob[index]));
1122
- case "json":
1123
- return right.kind === "json" && jsonValuesEqual(left.value, right.value);
1113
+ case "jsonb":
1114
+ return right.kind === "jsonb" && jsonValuesEqual(left.value, right.value);
1124
1115
  default:
1125
1116
  return left.value === right.value;
1126
1117
  }
@@ -1152,21 +1143,22 @@ function stringArraysEqual(left, right) {
1152
1143
  left.every((value, index) => value === right[index]));
1153
1144
  }
1154
1145
  function protocolBaseUrl(value) {
1155
- let workspaceUrl;
1146
+ let repositoryUrl;
1156
1147
  try {
1157
- workspaceUrl = new URL(value);
1148
+ repositoryUrl = new URL(value);
1158
1149
  }
1159
1150
  catch {
1160
1151
  throw new TypeError("openLix() remote server url must be an absolute URL");
1161
1152
  }
1162
- if (workspaceUrl.protocol !== "http:" && workspaceUrl.protocol !== "https:") {
1153
+ if (repositoryUrl.protocol !== "http:" &&
1154
+ repositoryUrl.protocol !== "https:") {
1163
1155
  throw new TypeError("openLix() remote server url must use http or https");
1164
1156
  }
1165
- if (workspaceUrl.search || workspaceUrl.hash) {
1157
+ if (repositoryUrl.search || repositoryUrl.hash) {
1166
1158
  throw new TypeError("openLix() remote server url must not contain a query or fragment");
1167
1159
  }
1168
- workspaceUrl.pathname = `${workspaceUrl.pathname.replace(/\/$/, "")}${REMOTE_PROTOCOL_PATH}`;
1169
- return workspaceUrl;
1160
+ repositoryUrl.pathname = `${repositoryUrl.pathname.replace(/\/$/, "")}${SERVER_PROTOCOL_PATH}`;
1161
+ return repositoryUrl;
1170
1162
  }
1171
1163
  function unsupportedRemoteOperation(operation) {
1172
1164
  return remoteError("LIX_UNSUPPORTED_REMOTE_OPERATION", `${operation} is not supported in remote mode`, { details: { operation } });
@@ -1,7 +1,7 @@
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 = 2;
4
- export declare const REMOTE_PROTOCOL_PATH = "/lix/v1/";
3
+ export declare const SERVER_PROTOCOL_VERSION = 2;
4
+ export declare const SERVER_PROTOCOL_PATH = "/lix/v1/";
5
5
  export type WireValue = {
6
6
  kind: "null";
7
7
  value: null;
@@ -18,8 +18,11 @@ export type WireValue = {
18
18
  kind: "text";
19
19
  value: string;
20
20
  } | {
21
- kind: "json";
21
+ kind: "jsonb";
22
22
  value: unknown;
23
+ } | {
24
+ kind: "timestamptz";
25
+ value: string;
23
26
  } | {
24
27
  kind: "blob";
25
28
  base64: string;
@@ -33,17 +36,16 @@ export type WireRequestBlobSplice = {
33
36
  insertBase64: string;
34
37
  };
35
38
  export type WireRequestValue = WireValue | WireRequestBlobSplice;
36
- export type RemoteHandshake = {
39
+ export type ServerProtocolHandshake = {
37
40
  protocolVersion: number;
38
41
  activeBranchId: string;
39
42
  activeAccountId: string;
40
43
  sessionId: string;
41
44
  };
42
- export type RemoteHandshakeRequest = {
45
+ export type ServerProtocolHandshakeRequest = {
43
46
  activeBranchId?: string;
44
- activeAccountId?: string;
45
47
  };
46
- export type RemoteExecuteRequest = {
48
+ export type ServerProtocolExecuteRequest = {
47
49
  sql: string;
48
50
  params: WireRequestValue[];
49
51
  options?: {
@@ -51,7 +53,7 @@ export type RemoteExecuteRequest = {
51
53
  };
52
54
  cacheBlobs?: true;
53
55
  };
54
- export type RemoteExecuteBatchRequest = {
56
+ export type ServerProtocolExecuteBatchRequest = {
55
57
  statements: Array<{
56
58
  sql: string;
57
59
  params: WireRequestValue[];
@@ -62,7 +64,7 @@ export type RemoteExecuteBatchRequest = {
62
64
  };
63
65
  cacheBlobs?: true;
64
66
  };
65
- export type RemoteExecuteResponse = {
67
+ export type ServerProtocolExecuteResponse = {
66
68
  columns: string[];
67
69
  rows: WireValue[][];
68
70
  rowsAffected: number;
@@ -72,80 +74,80 @@ export type RemoteExecuteResponse = {
72
74
  hint?: string;
73
75
  }>;
74
76
  };
75
- export type RemoteExecuteBatchResponse = RemoteExecuteResponse & {
77
+ export type ServerProtocolExecuteBatchResponse = ServerProtocolExecuteResponse & {
76
78
  statementIndex: number;
77
79
  label?: string;
78
80
  };
79
- export type RemoteObserveRequest = {
81
+ export type ServerProtocolObserveRequest = {
80
82
  sql: string;
81
83
  params: WireValue[];
82
84
  };
83
- export type RemoteObserveSubscription = RemoteObserveRequest & {
85
+ export type ServerProtocolObserveSubscription = ServerProtocolObserveRequest & {
84
86
  id: string;
85
87
  };
86
- export type RemoteMultiplexObserveRequest = {
87
- subscriptions: RemoteObserveSubscription[];
88
+ export type ServerProtocolMultiplexObserveRequest = {
89
+ subscriptions: ServerProtocolObserveSubscription[];
88
90
  };
89
- type RemoteObserveEventBase = {
91
+ type ServerProtocolObserveEventBase = {
90
92
  sequence: number;
91
93
  mutationSequence: number;
92
94
  };
93
- export type RemoteObserveBlobDelta = {
95
+ export type ServerProtocolObserveBlobDelta = {
94
96
  kind: "single-blob-splice";
95
97
  baseSequence: number;
96
98
  prefixBytes: number;
97
99
  suffixBytes: number;
98
100
  insertBase64: string;
99
101
  };
100
- type RemoteObserveRowSplice = {
102
+ type ServerProtocolObserveRowSplice = {
101
103
  kind: "row-splice";
102
104
  baseSequence: number;
103
105
  prefixRows: number;
104
106
  deleteRows: number;
105
107
  insertRows: WireValue[][];
106
108
  };
107
- type RemoteObserveDelta = RemoteObserveBlobDelta | RemoteObserveRowSplice;
108
- export type RemoteObserveEvent = RemoteObserveEventBase & ({
109
- result: RemoteExecuteResponse;
109
+ type ServerProtocolObserveDelta = ServerProtocolObserveBlobDelta | ServerProtocolObserveRowSplice;
110
+ export type ServerProtocolObserveEvent = ServerProtocolObserveEventBase & ({
111
+ result: ServerProtocolExecuteResponse;
110
112
  delta?: never;
111
113
  } | {
112
114
  result?: never;
113
- delta: RemoteObserveDelta;
115
+ delta: ServerProtocolObserveDelta;
114
116
  });
115
- export type RemoteMultiplexObserveEvent = RemoteObserveEvent & {
117
+ export type ServerProtocolMultiplexObserveEvent = ServerProtocolObserveEvent & {
116
118
  subscriptionId: string;
117
119
  };
118
- export type RemoteCreateBranchRequest = {
120
+ export type ServerProtocolCreateBranchRequest = {
119
121
  id?: string;
120
122
  name: string;
121
123
  fromCommitId?: string;
122
124
  };
123
- export type RemoteCreateBranchResponse = {
125
+ export type ServerProtocolCreateBranchResponse = {
124
126
  id: string;
125
127
  name: string;
126
128
  hidden: boolean;
127
129
  commitId: string;
128
130
  };
129
- export type RemoteCreateCheckpointResponse = {
131
+ export type ServerProtocolCreateCheckpointResponse = {
130
132
  commitId: string;
131
133
  };
132
- export type RemoteUndoResponse = {
134
+ export type ServerProtocolUndoResponse = {
133
135
  branchId: string;
134
136
  targetCommitId: string;
135
137
  inverseCommitId: string;
136
138
  };
137
- export type RemoteRedoResponse = {
139
+ export type ServerProtocolRedoResponse = {
138
140
  branchId: string;
139
141
  targetCommitId: string;
140
142
  replayCommitId: string;
141
143
  };
142
- export type RemoteSwitchBranchRequest = {
144
+ export type ServerProtocolSwitchBranchRequest = {
143
145
  branchId: string;
144
146
  };
145
- export type RemoteSwitchBranchResponse = {
147
+ export type ServerProtocolSwitchBranchResponse = {
146
148
  branchId: string;
147
149
  };
148
- export type RemoteErrorBody = {
150
+ export type ServerProtocolErrorBody = {
149
151
  error: {
150
152
  code?: string;
151
153
  message?: string;
@@ -153,16 +155,16 @@ export type RemoteErrorBody = {
153
155
  details?: unknown;
154
156
  };
155
157
  };
156
- export type RemoteObserveErrorEvent = RemoteErrorBody & {
158
+ export type ServerProtocolObserveErrorEvent = ServerProtocolErrorBody & {
157
159
  retryable?: boolean;
158
160
  };
159
- export type RemoteMultiplexObserveErrorEvent = RemoteObserveErrorEvent & {
161
+ export type ServerProtocolMultiplexObserveErrorEvent = ServerProtocolObserveErrorEvent & {
160
162
  subscriptionId?: string;
161
163
  };
162
164
  export declare function encodeWireValue(value: NativeLixValue): WireValue;
163
165
  export declare function decodeExecuteResult(value: unknown): BindingExecuteResult;
164
166
  export declare function decodeExecuteBatchResult(value: unknown): BindingExecuteResult;
165
- export declare function decodeHandshake(value: unknown): RemoteHandshake;
167
+ export declare function decodeHandshake(value: unknown): ServerProtocolHandshake;
166
168
  export declare function decodeObserveEvent(value: unknown, base?: BindingObserveEvent): BindingObserveEvent;
167
169
  export declare function remoteError(code: string, message: string, options?: {
168
170
  hint?: string;
@@ -1,5 +1,5 @@
1
- export const REMOTE_PROTOCOL_VERSION = 2;
2
- export const REMOTE_PROTOCOL_PATH = "/lix/v1/";
1
+ export const SERVER_PROTOCOL_VERSION = 2;
2
+ export const SERVER_PROTOCOL_PATH = "/lix/v1/";
3
3
  export function encodeWireValue(value) {
4
4
  switch (value.kind) {
5
5
  case "null":
@@ -12,8 +12,10 @@ export function encodeWireValue(value) {
12
12
  return { kind: "float", value: value.value };
13
13
  case "text":
14
14
  return { kind: "text", value: value.value };
15
- case "json":
16
- return { kind: "json", value: value.value };
15
+ case "jsonb":
16
+ return { kind: "jsonb", value: value.value };
17
+ case "timestamptz":
18
+ return { kind: "timestamptz", value: value.value };
17
19
  case "blob":
18
20
  return { kind: "blob", base64: bytesToBase64(value.blob) };
19
21
  }
@@ -70,24 +72,24 @@ export function decodeExecuteBatchResult(value) {
70
72
  };
71
73
  }
72
74
  export function decodeHandshake(value) {
73
- const handshake = record(value, "remote handshake");
74
- if (handshake.protocolVersion !== REMOTE_PROTOCOL_VERSION) {
75
- throw protocolError(`unsupported remote protocol version: ${String(handshake.protocolVersion)}`);
75
+ const handshake = record(value, "Lix Server Protocol handshake");
76
+ if (handshake.protocolVersion !== SERVER_PROTOCOL_VERSION) {
77
+ throw protocolError(`unsupported Lix Server Protocol version: ${String(handshake.protocolVersion)}`);
76
78
  }
77
79
  if (typeof handshake.activeBranchId !== "string" ||
78
80
  handshake.activeBranchId.length === 0) {
79
- throw protocolError("remote handshake requires activeBranchId");
81
+ throw protocolError("Lix Server Protocol handshake requires activeBranchId");
80
82
  }
81
83
  if (typeof handshake.activeAccountId !== "string" ||
82
84
  handshake.activeAccountId.length === 0) {
83
- throw protocolError("remote handshake requires activeAccountId");
85
+ throw protocolError("Lix Server Protocol handshake requires activeAccountId");
84
86
  }
85
87
  if (typeof handshake.sessionId !== "string" ||
86
88
  !/^[\x21-\x7e]{1,256}$/.test(handshake.sessionId)) {
87
- throw protocolError("remote handshake requires a valid sessionId");
89
+ throw protocolError("Lix Server Protocol handshake requires a valid sessionId");
88
90
  }
89
91
  return {
90
- protocolVersion: REMOTE_PROTOCOL_VERSION,
92
+ protocolVersion: SERVER_PROTOCOL_VERSION,
91
93
  activeBranchId: handshake.activeBranchId,
92
94
  activeAccountId: handshake.activeAccountId,
93
95
  sessionId: handshake.sessionId,
@@ -240,11 +242,11 @@ export function remoteError(code, message, options = {}) {
240
242
  return error;
241
243
  }
242
244
  export function protocolError(message) {
243
- return remoteError("LIX_REMOTE_PROTOCOL_ERROR", message);
245
+ return remoteError("LIX_SERVER_PROTOCOL_ERROR", message);
244
246
  }
245
247
  export function errorFromResponseBody(value, status) {
246
- const body = record(value, "remote error response");
247
- const rawError = record(body.error, "remote error response error");
248
+ const body = record(value, "Lix Server Protocol error response");
249
+ const rawError = record(body.error, "Lix Server Protocol error response error");
248
250
  return remoteError(typeof rawError.code === "string"
249
251
  ? rawError.code
250
252
  : "LIX_REMOTE_REQUEST_FAILED", typeof rawError.message === "string"
@@ -293,9 +295,14 @@ function decodeWireValue(value) {
293
295
  throw protocolError("text wire value is invalid");
294
296
  }
295
297
  return { kind: "text", value: wire.value };
296
- case "json":
297
- assertJsonValue(wire.value, "json wire value");
298
- return { kind: "json", value: wire.value };
298
+ case "jsonb":
299
+ assertJsonValue(wire.value, "jsonb wire value");
300
+ return { kind: "jsonb", value: wire.value };
301
+ case "timestamptz":
302
+ if (typeof wire.value !== "string") {
303
+ throw protocolError("timestamptz wire value is invalid");
304
+ }
305
+ return { kind: "timestamptz", value: wire.value };
299
306
  case "blob":
300
307
  if (typeof wire.base64 !== "string") {
301
308
  throw protocolError("blob wire value is invalid");
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Operations exposed to a storage adapter while its Lix is open.
3
+ *
4
+ * This is intentionally narrower than the engine binding. Storage packages
5
+ * receive only the controls required to manage their own persistence layer.
6
+ */
7
+ export type LixStorageConnection = {
8
+ importFilesystemPaths(paths: string[]): Promise<void>;
9
+ syncDiskToLix(): Promise<void>;
10
+ };
11
+ /** Engine configuration currently supported by external JavaScript storage packages. */
12
+ export type LixStorageAdapterConfig = {
13
+ kind: "filesystem";
14
+ path: string;
15
+ syncAllFiles: boolean;
16
+ };
17
+ /**
18
+ * Protocol implemented by JavaScript storage packages accepted by `openLix()`.
19
+ *
20
+ * Applications normally use a concrete package such as
21
+ * `@lix-js/storage-filesystem` rather than implementing this directly.
22
+ */
23
+ export type LixStorage = {
24
+ readonly lixStorage: {
25
+ readonly version: 1;
26
+ readonly config: LixStorageAdapterConfig;
27
+ connect(connection: LixStorageConnection | undefined): void;
28
+ };
29
+ };
30
+ export declare function isLixStorage(value: unknown): value is LixStorage;
@@ -0,0 +1,12 @@
1
+ export function isLixStorage(value) {
2
+ if (!value || typeof value !== "object" || !("lixStorage" in value)) {
3
+ return false;
4
+ }
5
+ const adapter = value.lixStorage;
6
+ return Boolean(adapter &&
7
+ typeof adapter === "object" &&
8
+ adapter.version === 1 &&
9
+ typeof adapter.connect === "function" &&
10
+ adapter.config?.kind ===
11
+ "filesystem");
12
+ }