@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.
- package/README.md +48 -76
- package/dist/binding-types.d.ts +6 -15
- package/dist/binding.browser.js +18 -5
- package/dist/binding.node.js +5 -6
- package/dist/bundled-plugins/plugin_csv.lixplugin +0 -0
- package/dist/bundled-plugins/plugin_markdown.lixplugin +0 -0
- package/dist/errors.d.ts +0 -2
- package/dist/errors.js +0 -13
- package/dist/index.d.ts +3 -3
- package/dist/index.js +1 -1
- package/dist/indexeddb-backend.d.ts +19 -0
- package/dist/indexeddb-backend.js +121 -0
- package/dist/lix.d.ts +1 -4
- package/dist/lix.js +21 -52
- package/dist/open-lix.d.ts +4 -9
- package/dist/open-lix.js +45 -135
- package/dist/remote/client.d.ts +2 -3
- package/dist/remote/client.js +13 -21
- package/dist/remote/{protocol.d.ts → server-protocol.d.ts} +35 -33
- package/dist/remote/{protocol.js → server-protocol.js} +24 -17
- package/dist/storage-adapter.d.ts +30 -0
- package/dist/storage-adapter.js +12 -0
- package/dist/types.d.ts +14 -24
- package/dist/value.d.ts +2 -1
- package/dist/value.js +23 -10
- package/dist/wasm/lix_js_sdk.d.ts +5 -10
- package/dist/wasm/lix_js_sdk.js +33 -45
- package/dist/wasm/lix_js_sdk_bg.wasm +0 -0
- package/dist/wasm/lix_js_sdk_bg.wasm.d.ts +3 -6
- package/dist/worker/client.d.ts +1 -12
- package/dist/worker/client.js +0 -161
- package/dist/worker/host.js +0 -23
- package/dist/worker/protocol.d.ts +1 -15
- package/package.json +8 -12
- package/dist/client-state.d.ts +0 -53
- package/dist/client-state.js +0 -315
- package/dist/local-storage-adapter.d.ts +0 -26
- package/dist/local-storage-adapter.js +0 -117
- package/dist/snapshot-persistence.d.ts +0 -7
- 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
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
67
|
-
|
|
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
|
|
38
|
+
if (isLixStorage(options.storage)) {
|
|
112
39
|
const storage = options.storage;
|
|
113
|
-
if (
|
|
114
|
-
throw
|
|
40
|
+
if (openStorages.has(storage)) {
|
|
41
|
+
throw storageAlreadyOpen();
|
|
115
42
|
}
|
|
116
|
-
|
|
43
|
+
openStorages.add(storage);
|
|
44
|
+
let binding;
|
|
45
|
+
const disconnect = () => {
|
|
46
|
+
storage.lixStorage.connect(undefined);
|
|
47
|
+
openStorages.delete(storage);
|
|
48
|
+
};
|
|
117
49
|
try {
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
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
|
-
|
|
58
|
+
disconnect();
|
|
59
|
+
await binding?.close().catch(() => undefined);
|
|
129
60
|
throw error;
|
|
130
61
|
}
|
|
131
62
|
}
|
|
132
|
-
if (
|
|
133
|
-
const
|
|
134
|
-
const
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
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
|
-
|
|
141
|
-
return new Lix(binding
|
|
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
|
-
|
|
76
|
+
openIndexedDbStorageNames.delete(databaseName);
|
|
77
|
+
await binding?.close().catch(() => undefined);
|
|
145
78
|
throw error;
|
|
146
79
|
}
|
|
147
80
|
}
|
|
148
|
-
throw new TypeError("openLix() requires
|
|
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
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
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
|
}
|
package/dist/remote/client.d.ts
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import type { LixBinding } from "../binding-types.js";
|
|
2
2
|
import type { RemoteLixServerOptions } from "../types.js";
|
|
3
|
-
import { type
|
|
3
|
+
import { type ServerProtocolHandshakeRequest } from "./server-protocol.js";
|
|
4
4
|
type RemoteLixClientOptions = {
|
|
5
|
-
initialActiveBranchId?:
|
|
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 {};
|
package/dist/remote/client.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { decodeExecuteBatchResult, decodeExecuteResult, decodeHandshake, decodeObserveEvent, encodeWireValue, errorFromResponseBody, protocolError, record,
|
|
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("
|
|
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("
|
|
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 === "
|
|
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 "
|
|
1123
|
-
return right.kind === "
|
|
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
|
|
1146
|
+
let repositoryUrl;
|
|
1156
1147
|
try {
|
|
1157
|
-
|
|
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 (
|
|
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 (
|
|
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
|
-
|
|
1169
|
-
return
|
|
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
|
|
4
|
-
export declare const
|
|
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: "
|
|
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
|
|
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
|
|
45
|
+
export type ServerProtocolHandshakeRequest = {
|
|
43
46
|
activeBranchId?: string;
|
|
44
|
-
activeAccountId?: string;
|
|
45
47
|
};
|
|
46
|
-
export type
|
|
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
|
|
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
|
|
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
|
|
77
|
+
export type ServerProtocolExecuteBatchResponse = ServerProtocolExecuteResponse & {
|
|
76
78
|
statementIndex: number;
|
|
77
79
|
label?: string;
|
|
78
80
|
};
|
|
79
|
-
export type
|
|
81
|
+
export type ServerProtocolObserveRequest = {
|
|
80
82
|
sql: string;
|
|
81
83
|
params: WireValue[];
|
|
82
84
|
};
|
|
83
|
-
export type
|
|
85
|
+
export type ServerProtocolObserveSubscription = ServerProtocolObserveRequest & {
|
|
84
86
|
id: string;
|
|
85
87
|
};
|
|
86
|
-
export type
|
|
87
|
-
subscriptions:
|
|
88
|
+
export type ServerProtocolMultiplexObserveRequest = {
|
|
89
|
+
subscriptions: ServerProtocolObserveSubscription[];
|
|
88
90
|
};
|
|
89
|
-
type
|
|
91
|
+
type ServerProtocolObserveEventBase = {
|
|
90
92
|
sequence: number;
|
|
91
93
|
mutationSequence: number;
|
|
92
94
|
};
|
|
93
|
-
export type
|
|
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
|
|
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
|
|
108
|
-
export type
|
|
109
|
-
result:
|
|
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:
|
|
115
|
+
delta: ServerProtocolObserveDelta;
|
|
114
116
|
});
|
|
115
|
-
export type
|
|
117
|
+
export type ServerProtocolMultiplexObserveEvent = ServerProtocolObserveEvent & {
|
|
116
118
|
subscriptionId: string;
|
|
117
119
|
};
|
|
118
|
-
export type
|
|
120
|
+
export type ServerProtocolCreateBranchRequest = {
|
|
119
121
|
id?: string;
|
|
120
122
|
name: string;
|
|
121
123
|
fromCommitId?: string;
|
|
122
124
|
};
|
|
123
|
-
export type
|
|
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
|
|
131
|
+
export type ServerProtocolCreateCheckpointResponse = {
|
|
130
132
|
commitId: string;
|
|
131
133
|
};
|
|
132
|
-
export type
|
|
134
|
+
export type ServerProtocolUndoResponse = {
|
|
133
135
|
branchId: string;
|
|
134
136
|
targetCommitId: string;
|
|
135
137
|
inverseCommitId: string;
|
|
136
138
|
};
|
|
137
|
-
export type
|
|
139
|
+
export type ServerProtocolRedoResponse = {
|
|
138
140
|
branchId: string;
|
|
139
141
|
targetCommitId: string;
|
|
140
142
|
replayCommitId: string;
|
|
141
143
|
};
|
|
142
|
-
export type
|
|
144
|
+
export type ServerProtocolSwitchBranchRequest = {
|
|
143
145
|
branchId: string;
|
|
144
146
|
};
|
|
145
|
-
export type
|
|
147
|
+
export type ServerProtocolSwitchBranchResponse = {
|
|
146
148
|
branchId: string;
|
|
147
149
|
};
|
|
148
|
-
export type
|
|
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
|
|
158
|
+
export type ServerProtocolObserveErrorEvent = ServerProtocolErrorBody & {
|
|
157
159
|
retryable?: boolean;
|
|
158
160
|
};
|
|
159
|
-
export type
|
|
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):
|
|
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
|
|
2
|
-
export const
|
|
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 "
|
|
16
|
-
return { kind: "
|
|
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, "
|
|
74
|
-
if (handshake.protocolVersion !==
|
|
75
|
-
throw protocolError(`unsupported
|
|
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("
|
|
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("
|
|
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("
|
|
89
|
+
throw protocolError("Lix Server Protocol handshake requires a valid sessionId");
|
|
88
90
|
}
|
|
89
91
|
return {
|
|
90
|
-
protocolVersion:
|
|
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("
|
|
245
|
+
return remoteError("LIX_SERVER_PROTOCOL_ERROR", message);
|
|
244
246
|
}
|
|
245
247
|
export function errorFromResponseBody(value, status) {
|
|
246
|
-
const body = record(value, "
|
|
247
|
-
const rawError = record(body.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 "
|
|
297
|
-
assertJsonValue(wire.value, "
|
|
298
|
-
return { kind: "
|
|
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
|
+
}
|