@lix-js/sdk 0.6.2 → 0.8.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 +97 -14
- package/dist/binding-types.d.ts +52 -0
- package/dist/binding-types.js +1 -0
- package/dist/binding.browser.d.ts +2 -0
- package/dist/binding.browser.js +14 -0
- package/dist/binding.node.d.ts +2 -0
- package/dist/{native.js → binding.node.js} +15 -11
- package/dist/bundled-plugins/plugin_csv.lixplugin +0 -0
- package/dist/bundled-plugins/plugin_md_v2.lixplugin +0 -0
- package/dist/bundled-plugins.d.ts +6 -0
- package/dist/bundled-plugins.js +41 -0
- package/dist/errors.d.ts +2 -0
- package/dist/errors.js +13 -0
- package/dist/index.d.ts +3 -2
- package/dist/index.js +2 -1
- 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 +13662 -0
- package/dist/jco-transpile.browser.d.ts +14 -0
- package/dist/jco-transpile.browser.js +22 -0
- package/dist/open-lix.d.ts +30 -27
- package/dist/open-lix.js +212 -31
- package/dist/plugin-runtime.d.ts +45 -0
- package/dist/plugin-runtime.js +124 -0
- package/dist/types.d.ts +14 -1
- package/dist/wasm/lix_js_sdk.d.ts +90 -0
- package/dist/wasm/lix_js_sdk.js +918 -0
- package/dist/wasm/lix_js_sdk_bg.wasm +0 -0
- package/dist/wasm/lix_js_sdk_bg.wasm.d.ts +29 -0
- package/dist/worker/client.d.ts +17 -0
- package/dist/worker/client.js +104 -0
- package/dist/worker/entry.browser.d.ts +1 -0
- package/dist/worker/entry.browser.js +11 -0
- package/dist/worker/entry.node.d.ts +1 -0
- package/dist/worker/entry.node.js +13 -0
- package/dist/worker/factory.browser.d.ts +2 -0
- package/dist/worker/factory.browser.js +23 -0
- package/dist/worker/factory.node.d.ts +2 -0
- package/dist/worker/factory.node.js +35 -0
- package/dist/worker/host.d.ts +2 -0
- package/dist/worker/host.js +145 -0
- package/dist/worker/protocol.d.ts +96 -0
- package/dist/worker/protocol.js +23 -0
- package/package.json +37 -8
- package/dist/native.d.ts +0 -1
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
type TranspileOptions = {
|
|
2
|
+
name?: string;
|
|
3
|
+
emitTypescriptDeclarations?: boolean;
|
|
4
|
+
instantiation?: "async" | "sync";
|
|
5
|
+
nodejsCompat?: boolean;
|
|
6
|
+
base64Cutoff?: number;
|
|
7
|
+
};
|
|
8
|
+
type TranspileResult = {
|
|
9
|
+
files: Record<string, Uint8Array>;
|
|
10
|
+
imports: string[];
|
|
11
|
+
exports: [string, "function" | "instance"][];
|
|
12
|
+
};
|
|
13
|
+
export declare function transpileBytes(component: Uint8Array, options?: TranspileOptions): Promise<TranspileResult>;
|
|
14
|
+
export {};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// This module is copied from the pinned JCO package by build:jco-browser. Using
|
|
2
|
+
// its browser-native generator avoids pulling JCO's CLI-only Node imports and
|
|
3
|
+
// optional minifier into application bundles.
|
|
4
|
+
// @ts-expect-error The generated module is copied into dist after tsc runs.
|
|
5
|
+
import { $init, generate } from "./jco/js-component-bindgen-component.js";
|
|
6
|
+
export async function transpileBytes(component, options = {}) {
|
|
7
|
+
await $init;
|
|
8
|
+
const generated = generate(component, {
|
|
9
|
+
name: options.name ?? "component",
|
|
10
|
+
noTypescript: options.emitTypescriptDeclarations === false,
|
|
11
|
+
instantiation: options.instantiation
|
|
12
|
+
? { tag: options.instantiation }
|
|
13
|
+
: undefined,
|
|
14
|
+
noNodejsCompat: options.nodejsCompat === false,
|
|
15
|
+
base64Cutoff: options.base64Cutoff ?? 5000,
|
|
16
|
+
});
|
|
17
|
+
return {
|
|
18
|
+
files: Object.fromEntries(generated.files),
|
|
19
|
+
imports: generated.imports,
|
|
20
|
+
exports: generated.exports,
|
|
21
|
+
};
|
|
22
|
+
}
|
package/dist/open-lix.d.ts
CHANGED
|
@@ -1,32 +1,25 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import type { CreateBranchOptions, CreateBranchReceipt, ExecuteResult, MergeBranchOptions, MergeBranchPreview, MergeBranchReceipt, OpenLixOptions, SqlParam, SqliteBackendOptions, SwitchBranchOptions, SwitchBranchReceipt } from "./types.js";
|
|
4
|
-
type NativeExecuteResult = Parameters<typeof wrapExecuteResult>[0];
|
|
5
|
-
type NativeParam = ReturnType<typeof toNativeValue>;
|
|
6
|
-
type NativeLix = {
|
|
7
|
-
execute(sql: string, params: NativeParam[]): NativeExecuteResult;
|
|
8
|
-
beginTransaction(): NativeLixTransaction;
|
|
9
|
-
activeBranchId(): string;
|
|
10
|
-
createBranch(options: CreateBranchOptions): CreateBranchReceipt;
|
|
11
|
-
switchBranch(options: SwitchBranchOptions): SwitchBranchReceipt;
|
|
12
|
-
mergeBranchPreview(options: MergeBranchOptions): MergeBranchPreview;
|
|
13
|
-
mergeBranch(options: MergeBranchOptions): MergeBranchReceipt;
|
|
14
|
-
close(): void;
|
|
15
|
-
};
|
|
16
|
-
type NativeLixTransaction = {
|
|
17
|
-
execute(sql: string, params: NativeParam[]): NativeExecuteResult;
|
|
18
|
-
commit(): void;
|
|
19
|
-
rollback(): void;
|
|
20
|
-
};
|
|
1
|
+
import { LixWorkerClient } from "./worker/client.js";
|
|
2
|
+
import type { CreateBranchOptions, CreateBranchReceipt, ExecuteOptions, ExecuteResult, FsBackendOptions, MergeBranchOptions, MergeBranchPreview, MergeBranchReceipt, ObserveEvent, OpenLixOptions, SqlParam, SqliteBackendOptions, SwitchBranchOptions, SwitchBranchReceipt } from "./types.js";
|
|
21
3
|
export declare class SqliteBackend {
|
|
22
4
|
readonly path: string;
|
|
23
5
|
constructor(options: SqliteBackendOptions);
|
|
24
6
|
}
|
|
7
|
+
export declare class FsBackend {
|
|
8
|
+
readonly path: string;
|
|
9
|
+
readonly lixDir: string | undefined;
|
|
10
|
+
readonly syncAllFiles: boolean;
|
|
11
|
+
constructor(options: FsBackendOptions);
|
|
12
|
+
importPaths(paths: readonly string[]): Promise<void>;
|
|
13
|
+
syncDiskToLix(): Promise<void>;
|
|
14
|
+
private client;
|
|
15
|
+
}
|
|
25
16
|
export declare function openLix(options?: OpenLixOptions): Promise<Lix>;
|
|
26
17
|
export declare class Lix {
|
|
27
|
-
private readonly
|
|
28
|
-
|
|
29
|
-
|
|
18
|
+
private readonly client;
|
|
19
|
+
private closePromise;
|
|
20
|
+
constructor(client: LixWorkerClient);
|
|
21
|
+
execute(sql: string, params?: SqlParam[], options?: ExecuteOptions): Promise<ExecuteResult>;
|
|
22
|
+
observe(sql: string, params?: SqlParam[]): ObserveEvents;
|
|
30
23
|
beginTransaction(): Promise<LixTransaction>;
|
|
31
24
|
activeBranchId(): Promise<string>;
|
|
32
25
|
createBranch(options: CreateBranchOptions): Promise<CreateBranchReceipt>;
|
|
@@ -35,11 +28,21 @@ export declare class Lix {
|
|
|
35
28
|
mergeBranch(options: MergeBranchOptions): Promise<MergeBranchReceipt>;
|
|
36
29
|
close(): Promise<void>;
|
|
37
30
|
}
|
|
31
|
+
export declare class ObserveEvents {
|
|
32
|
+
private readonly client;
|
|
33
|
+
private readonly setup;
|
|
34
|
+
private closed;
|
|
35
|
+
private readonly observeId;
|
|
36
|
+
constructor(client: LixWorkerClient, observeId: Promise<number>);
|
|
37
|
+
next(): Promise<ObserveEvent | undefined>;
|
|
38
|
+
close(): void;
|
|
39
|
+
}
|
|
38
40
|
export declare class LixTransaction {
|
|
39
|
-
private readonly
|
|
40
|
-
|
|
41
|
-
|
|
41
|
+
private readonly client;
|
|
42
|
+
private readonly transactionId;
|
|
43
|
+
constructor(client: LixWorkerClient, transactionId: number);
|
|
44
|
+
execute(sql: string, params?: SqlParam[], options?: ExecuteOptions): Promise<ExecuteResult>;
|
|
42
45
|
commit(): Promise<void>;
|
|
43
46
|
rollback(): Promise<void>;
|
|
47
|
+
private finish;
|
|
44
48
|
}
|
|
45
|
-
export {};
|
package/dist/open-lix.js
CHANGED
|
@@ -1,82 +1,263 @@
|
|
|
1
|
-
import { invalidArgument } from "./errors.js";
|
|
2
|
-
import { addon } from "./native.js";
|
|
1
|
+
import { fsBackendAlreadyOpen, fsBackendNotOpen, invalidArgument, } from "./errors.js";
|
|
3
2
|
import { normalizeOptionals, wrapExecuteResult } from "./result.js";
|
|
4
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
|
+
});
|
|
5
14
|
export class SqliteBackend {
|
|
6
15
|
path;
|
|
7
16
|
constructor(options) {
|
|
8
|
-
if (!options ||
|
|
17
|
+
if (!options ||
|
|
18
|
+
typeof options.path !== "string" ||
|
|
19
|
+
options.path.length === 0) {
|
|
9
20
|
throw new TypeError("SqliteBackend requires a non-empty path");
|
|
10
21
|
}
|
|
11
22
|
this.path = options.path;
|
|
12
23
|
}
|
|
13
24
|
}
|
|
25
|
+
const openFsBackends = new WeakMap();
|
|
26
|
+
export class FsBackend {
|
|
27
|
+
path;
|
|
28
|
+
lixDir;
|
|
29
|
+
syncAllFiles;
|
|
30
|
+
constructor(options) {
|
|
31
|
+
if (!options ||
|
|
32
|
+
typeof options.path !== "string" ||
|
|
33
|
+
options.path.length === 0) {
|
|
34
|
+
throw new TypeError("FsBackend requires a non-empty path");
|
|
35
|
+
}
|
|
36
|
+
if (options.lixDir !== undefined &&
|
|
37
|
+
(typeof options.lixDir !== "string" || options.lixDir.length === 0)) {
|
|
38
|
+
throw new TypeError("FsBackend lixDir must be a non-empty string");
|
|
39
|
+
}
|
|
40
|
+
if (typeof options.syncAllFiles !== "boolean") {
|
|
41
|
+
throw new TypeError("FsBackend syncAllFiles must be a boolean");
|
|
42
|
+
}
|
|
43
|
+
this.path = options.path;
|
|
44
|
+
this.lixDir = options.lixDir;
|
|
45
|
+
this.syncAllFiles = options.syncAllFiles;
|
|
46
|
+
}
|
|
47
|
+
async importPaths(paths) {
|
|
48
|
+
if (!Array.isArray(paths)) {
|
|
49
|
+
throw new TypeError("importPaths() paths must be an array");
|
|
50
|
+
}
|
|
51
|
+
for (const path of paths) {
|
|
52
|
+
if (typeof path !== "string" || path.length === 0) {
|
|
53
|
+
throw new TypeError("importPaths() paths must contain non-empty strings");
|
|
54
|
+
}
|
|
55
|
+
if (path.endsWith("/")) {
|
|
56
|
+
throw new TypeError("importPaths() paths must contain file paths, not directory paths");
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
await this.client("importPaths").request({
|
|
60
|
+
kind: "importFilesystemPaths",
|
|
61
|
+
paths: [...paths],
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
async syncDiskToLix() {
|
|
65
|
+
return this.client("syncDiskToLix").request({ kind: "syncDiskToLix" });
|
|
66
|
+
}
|
|
67
|
+
client(operation) {
|
|
68
|
+
const client = openFsBackends.get(this);
|
|
69
|
+
if (!client) {
|
|
70
|
+
throw fsBackendNotOpen(operation);
|
|
71
|
+
}
|
|
72
|
+
return client;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
14
75
|
export async function openLix(options = {}) {
|
|
15
76
|
if (!options || typeof options !== "object") {
|
|
16
77
|
throw new TypeError("openLix() options must be an object");
|
|
17
78
|
}
|
|
18
79
|
if (options.backend === undefined) {
|
|
19
|
-
return new Lix(
|
|
80
|
+
return new Lix(await openLixWorker({ kind: "memory" }));
|
|
20
81
|
}
|
|
21
|
-
if (
|
|
22
|
-
|
|
82
|
+
if (options.backend instanceof SqliteBackend) {
|
|
83
|
+
return new Lix(await openLixWorker({ kind: "sqlite", path: options.backend.path }));
|
|
84
|
+
}
|
|
85
|
+
if (options.backend instanceof FsBackend) {
|
|
86
|
+
const backend = options.backend;
|
|
87
|
+
if (openFsBackends.has(backend)) {
|
|
88
|
+
throw fsBackendAlreadyOpen();
|
|
89
|
+
}
|
|
90
|
+
openFsBackends.set(backend, null);
|
|
91
|
+
try {
|
|
92
|
+
const client = await openLixWorker({
|
|
93
|
+
kind: "fs",
|
|
94
|
+
path: backend.path,
|
|
95
|
+
lixDir: backend.lixDir,
|
|
96
|
+
syncAllFiles: backend.syncAllFiles,
|
|
97
|
+
}, () => openFsBackends.delete(backend));
|
|
98
|
+
openFsBackends.set(backend, client);
|
|
99
|
+
return new Lix(client);
|
|
100
|
+
}
|
|
101
|
+
catch (error) {
|
|
102
|
+
openFsBackends.delete(backend);
|
|
103
|
+
throw error;
|
|
104
|
+
}
|
|
23
105
|
}
|
|
24
|
-
|
|
106
|
+
throw new TypeError("openLix() requires backend to be SqliteBackend or FsBackend");
|
|
25
107
|
}
|
|
26
108
|
export class Lix {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
109
|
+
client;
|
|
110
|
+
closePromise;
|
|
111
|
+
constructor(client) {
|
|
112
|
+
this.client = client;
|
|
30
113
|
}
|
|
31
|
-
async execute(sql, params = []) {
|
|
32
|
-
assertExecuteArgs("lix", sql, params);
|
|
33
|
-
return wrapExecuteResult(this.
|
|
114
|
+
async execute(sql, params = [], options) {
|
|
115
|
+
assertExecuteArgs("lix", sql, params, options);
|
|
116
|
+
return wrapExecuteResult(await this.client.request({
|
|
117
|
+
kind: "execute",
|
|
118
|
+
sql,
|
|
119
|
+
params: params.map((param, index) => toNativeValue(normalizeParam(param, index))),
|
|
120
|
+
options,
|
|
121
|
+
}));
|
|
122
|
+
}
|
|
123
|
+
observe(sql, params = []) {
|
|
124
|
+
assertSqlArgs("observe", "lix", sql, params);
|
|
125
|
+
return new ObserveEvents(this.client, this.client.request({
|
|
126
|
+
kind: "observe",
|
|
127
|
+
sql,
|
|
128
|
+
params: params.map((param, index) => toNativeValue(normalizeParam(param, index))),
|
|
129
|
+
}));
|
|
34
130
|
}
|
|
35
131
|
async beginTransaction() {
|
|
36
|
-
|
|
132
|
+
const transactionId = await this.client.request({
|
|
133
|
+
kind: "beginTransaction",
|
|
134
|
+
});
|
|
135
|
+
return new LixTransaction(this.client, transactionId);
|
|
37
136
|
}
|
|
38
137
|
async activeBranchId() {
|
|
39
|
-
return this.
|
|
138
|
+
return this.client.request({ kind: "activeBranchId" });
|
|
40
139
|
}
|
|
41
140
|
async createBranch(options) {
|
|
42
|
-
return this.
|
|
141
|
+
return this.client.request({ kind: "createBranch", options });
|
|
43
142
|
}
|
|
44
143
|
async switchBranch(options) {
|
|
45
|
-
return this.
|
|
144
|
+
return this.client.request({ kind: "switchBranch", options });
|
|
46
145
|
}
|
|
47
146
|
async mergeBranchPreview(options) {
|
|
48
|
-
return normalizeOptionals(this.
|
|
147
|
+
return normalizeOptionals(await this.client.request({ kind: "mergeBranchPreview", options }));
|
|
49
148
|
}
|
|
50
149
|
async mergeBranch(options) {
|
|
51
|
-
const receipt = normalizeOptionals(this.
|
|
150
|
+
const receipt = normalizeOptionals(await this.client.request({ kind: "mergeBranch", options }));
|
|
52
151
|
receipt.createdMergeCommitId ??= null;
|
|
53
152
|
return receipt;
|
|
54
153
|
}
|
|
55
154
|
async close() {
|
|
56
|
-
|
|
155
|
+
this.closePromise ??= (async () => {
|
|
156
|
+
await this.client.request({ kind: "close" });
|
|
157
|
+
await this.client.terminate();
|
|
158
|
+
})();
|
|
159
|
+
try {
|
|
160
|
+
await this.closePromise;
|
|
161
|
+
}
|
|
162
|
+
catch (error) {
|
|
163
|
+
this.closePromise = undefined;
|
|
164
|
+
throw error;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
export class ObserveEvents {
|
|
169
|
+
client;
|
|
170
|
+
setup = {};
|
|
171
|
+
closed = false;
|
|
172
|
+
observeId;
|
|
173
|
+
constructor(client, observeId) {
|
|
174
|
+
this.client = client;
|
|
175
|
+
const setup = this.setup;
|
|
176
|
+
this.observeId = observeId.catch((error) => {
|
|
177
|
+
setup.error = error;
|
|
178
|
+
return undefined;
|
|
179
|
+
});
|
|
180
|
+
observeFinalizer.register(this, { client, observeId: this.observeId }, this);
|
|
181
|
+
}
|
|
182
|
+
async next() {
|
|
183
|
+
if (this.closed)
|
|
184
|
+
return undefined;
|
|
185
|
+
const observeId = await this.observeId;
|
|
186
|
+
if (observeId === undefined) {
|
|
187
|
+
throw this.setup.error;
|
|
188
|
+
}
|
|
189
|
+
const event = await this.client.request({
|
|
190
|
+
kind: "observe.next",
|
|
191
|
+
observeId,
|
|
192
|
+
});
|
|
193
|
+
if (event == null) {
|
|
194
|
+
return undefined;
|
|
195
|
+
}
|
|
196
|
+
return {
|
|
197
|
+
sequence: event.sequence,
|
|
198
|
+
mutationSequence: event.mutationSequence,
|
|
199
|
+
result: wrapExecuteResult(event.rows),
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
close() {
|
|
203
|
+
if (this.closed)
|
|
204
|
+
return;
|
|
205
|
+
this.closed = true;
|
|
206
|
+
observeFinalizer.unregister(this);
|
|
207
|
+
void this.observeId.then((observeId) => {
|
|
208
|
+
if (observeId !== undefined) {
|
|
209
|
+
this.client.notify({ kind: "observe.close", observeId });
|
|
210
|
+
}
|
|
211
|
+
});
|
|
57
212
|
}
|
|
58
213
|
}
|
|
59
214
|
export class LixTransaction {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
215
|
+
client;
|
|
216
|
+
transactionId;
|
|
217
|
+
constructor(client, transactionId) {
|
|
218
|
+
this.client = client;
|
|
219
|
+
this.transactionId = transactionId;
|
|
220
|
+
transactionFinalizer.register(this, { client, transactionId }, this);
|
|
63
221
|
}
|
|
64
|
-
async execute(sql, params = []) {
|
|
65
|
-
assertExecuteArgs("lixTransaction", sql, params);
|
|
66
|
-
return wrapExecuteResult(this.
|
|
222
|
+
async execute(sql, params = [], options) {
|
|
223
|
+
assertExecuteArgs("lixTransaction", sql, params, options);
|
|
224
|
+
return wrapExecuteResult(await this.client.request({
|
|
225
|
+
kind: "transaction.execute",
|
|
226
|
+
transactionId: this.transactionId,
|
|
227
|
+
sql,
|
|
228
|
+
params: params.map((param, index) => toNativeValue(normalizeParam(param, index))),
|
|
229
|
+
options,
|
|
230
|
+
}));
|
|
67
231
|
}
|
|
68
232
|
async commit() {
|
|
69
|
-
return this.
|
|
233
|
+
return this.finish("transaction.commit");
|
|
70
234
|
}
|
|
71
235
|
async rollback() {
|
|
72
|
-
return this.
|
|
236
|
+
return this.finish("transaction.rollback");
|
|
237
|
+
}
|
|
238
|
+
async finish(kind) {
|
|
239
|
+
transactionFinalizer.unregister(this);
|
|
240
|
+
return this.client.request({ kind, transactionId: this.transactionId });
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
function assertExecuteArgs(receiver, sql, params, options) {
|
|
244
|
+
assertSqlArgs("execute", receiver, sql, params);
|
|
245
|
+
if (options === undefined) {
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
if (!options || typeof options !== "object" || Array.isArray(options)) {
|
|
249
|
+
throw invalidArgument("execute", "options", "object", typeof options, receiver);
|
|
250
|
+
}
|
|
251
|
+
if (options.originKey !== undefined &&
|
|
252
|
+
typeof options.originKey !== "string") {
|
|
253
|
+
throw invalidArgument("execute", "options.originKey", "string", typeof options.originKey, receiver);
|
|
73
254
|
}
|
|
74
255
|
}
|
|
75
|
-
function
|
|
256
|
+
function assertSqlArgs(operation, receiver, sql, params) {
|
|
76
257
|
if (typeof sql !== "string") {
|
|
77
|
-
throw invalidArgument(
|
|
258
|
+
throw invalidArgument(operation, "sql", "string", typeof sql, receiver);
|
|
78
259
|
}
|
|
79
260
|
if (!Array.isArray(params)) {
|
|
80
|
-
throw invalidArgument(
|
|
261
|
+
throw invalidArgument(operation, "params", "array", typeof params, receiver);
|
|
81
262
|
}
|
|
82
263
|
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
type PluginRuntimeOperation = "initComponent" | "detectChanges" | "render" | "closeComponent";
|
|
2
|
+
export type PluginRuntimeRequest = {
|
|
3
|
+
operation: PluginRuntimeOperation;
|
|
4
|
+
componentId?: number;
|
|
5
|
+
componentBytes?: Uint8Array;
|
|
6
|
+
maxMemoryBytes?: string;
|
|
7
|
+
maxFuel?: string;
|
|
8
|
+
timeoutMs?: string;
|
|
9
|
+
state?: PluginEntityState[];
|
|
10
|
+
file?: PluginFile;
|
|
11
|
+
};
|
|
12
|
+
export type PluginRuntimeResponse = {
|
|
13
|
+
componentId?: number;
|
|
14
|
+
changes?: PluginDetectedChange[];
|
|
15
|
+
bytes?: Uint8Array;
|
|
16
|
+
errorMessage?: string;
|
|
17
|
+
};
|
|
18
|
+
type PluginFile = {
|
|
19
|
+
filename?: string;
|
|
20
|
+
data: Uint8Array;
|
|
21
|
+
};
|
|
22
|
+
type PluginEntityState = {
|
|
23
|
+
entityPk: string[];
|
|
24
|
+
schemaKey: string;
|
|
25
|
+
snapshotContent: string;
|
|
26
|
+
metadata?: string;
|
|
27
|
+
};
|
|
28
|
+
type PluginDetectedChange = {
|
|
29
|
+
entityPk: string[];
|
|
30
|
+
schemaKey: string;
|
|
31
|
+
snapshotContent?: string;
|
|
32
|
+
metadata?: string;
|
|
33
|
+
};
|
|
34
|
+
declare class WasmPluginRuntime {
|
|
35
|
+
private nextComponentId;
|
|
36
|
+
private readonly components;
|
|
37
|
+
readonly dispatch: (request: PluginRuntimeRequest) => Promise<PluginRuntimeResponse>;
|
|
38
|
+
private initComponent;
|
|
39
|
+
private detectChanges;
|
|
40
|
+
private render;
|
|
41
|
+
private closeComponent;
|
|
42
|
+
private requiredComponent;
|
|
43
|
+
}
|
|
44
|
+
export declare function createPluginRuntimeDispatch(): WasmPluginRuntime["dispatch"];
|
|
45
|
+
export {};
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { WASIShim } from "@bytecodealliance/preview2-shim/instantiation";
|
|
2
|
+
import { transpileBytes } from "#jco-transpile";
|
|
3
|
+
const webAssembly = globalThis.WebAssembly;
|
|
4
|
+
class WasmPluginRuntime {
|
|
5
|
+
nextComponentId = 1;
|
|
6
|
+
components = new Map();
|
|
7
|
+
dispatch = async (request) => {
|
|
8
|
+
try {
|
|
9
|
+
switch (request.operation) {
|
|
10
|
+
case "initComponent":
|
|
11
|
+
return await this.initComponent(request);
|
|
12
|
+
case "detectChanges":
|
|
13
|
+
return this.detectChanges(request);
|
|
14
|
+
case "render":
|
|
15
|
+
return this.render(request);
|
|
16
|
+
case "closeComponent":
|
|
17
|
+
return this.closeComponent(request);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
catch (cause) {
|
|
21
|
+
return {
|
|
22
|
+
errorMessage: `Lix plugin runtime ${request.operation} failed: ${errorMessage(cause)}`,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
async initComponent(request) {
|
|
27
|
+
if (!request.componentBytes) {
|
|
28
|
+
throw new TypeError("initComponent requires componentBytes");
|
|
29
|
+
}
|
|
30
|
+
const componentId = this.nextComponentId;
|
|
31
|
+
this.nextComponentId += 1;
|
|
32
|
+
const name = "lix_plugin";
|
|
33
|
+
const transpiled = await transpileBytes(request.componentBytes, {
|
|
34
|
+
name,
|
|
35
|
+
emitTypescriptDeclarations: false,
|
|
36
|
+
instantiation: "async",
|
|
37
|
+
nodejsCompat: false,
|
|
38
|
+
});
|
|
39
|
+
const files = new Map(Object.entries(transpiled.files));
|
|
40
|
+
const moduleSource = requiredFile(files, `${name}.js`);
|
|
41
|
+
const moduleUrl = `data:text/javascript;base64,${bytesToBase64(moduleSource)}`;
|
|
42
|
+
const generatedModule = (await import(
|
|
43
|
+
/* @vite-ignore */ moduleUrl));
|
|
44
|
+
const wasi = new WASIShim({
|
|
45
|
+
sandbox: {
|
|
46
|
+
preopens: {},
|
|
47
|
+
env: {},
|
|
48
|
+
args: ["lix-plugin"],
|
|
49
|
+
enableNetwork: false,
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
const instance = await generatedModule.instantiate(async (path) => await webAssembly.compile(copyArrayBuffer(requiredFile(files, path))), wasi.getImportObject());
|
|
53
|
+
if (!instance.api) {
|
|
54
|
+
throw new Error("component does not export lix:plugin/api");
|
|
55
|
+
}
|
|
56
|
+
this.components.set(componentId, instance.api);
|
|
57
|
+
return { componentId };
|
|
58
|
+
}
|
|
59
|
+
detectChanges(request) {
|
|
60
|
+
const component = this.requiredComponent(request.componentId);
|
|
61
|
+
if (!request.file) {
|
|
62
|
+
throw new TypeError("detectChanges requires file");
|
|
63
|
+
}
|
|
64
|
+
return {
|
|
65
|
+
changes: component.detectChanges(request.state ?? [], request.file),
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
render(request) {
|
|
69
|
+
const component = this.requiredComponent(request.componentId);
|
|
70
|
+
return { bytes: component.render(request.state ?? []) };
|
|
71
|
+
}
|
|
72
|
+
closeComponent(request) {
|
|
73
|
+
if (request.componentId !== undefined) {
|
|
74
|
+
this.components.delete(request.componentId);
|
|
75
|
+
}
|
|
76
|
+
return {};
|
|
77
|
+
}
|
|
78
|
+
requiredComponent(componentId) {
|
|
79
|
+
if (componentId === undefined) {
|
|
80
|
+
throw new TypeError("plugin runtime operation requires componentId");
|
|
81
|
+
}
|
|
82
|
+
const component = this.components.get(componentId);
|
|
83
|
+
if (!component) {
|
|
84
|
+
throw new Error(`unknown plugin component ${componentId}`);
|
|
85
|
+
}
|
|
86
|
+
return component;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
export function createPluginRuntimeDispatch() {
|
|
90
|
+
return new WasmPluginRuntime().dispatch;
|
|
91
|
+
}
|
|
92
|
+
function bytesToBase64(bytes) {
|
|
93
|
+
let binary = "";
|
|
94
|
+
const chunkSize = 0x8000;
|
|
95
|
+
for (let index = 0; index < bytes.length; index += chunkSize) {
|
|
96
|
+
binary += String.fromCharCode(...bytes.subarray(index, index + chunkSize));
|
|
97
|
+
}
|
|
98
|
+
return btoa(binary);
|
|
99
|
+
}
|
|
100
|
+
function requiredFile(files, path) {
|
|
101
|
+
const file = files.get(path);
|
|
102
|
+
if (!file) {
|
|
103
|
+
throw new Error(`JCO output is missing ${path}`);
|
|
104
|
+
}
|
|
105
|
+
return file;
|
|
106
|
+
}
|
|
107
|
+
function copyArrayBuffer(bytes) {
|
|
108
|
+
const copy = new Uint8Array(bytes.byteLength);
|
|
109
|
+
copy.set(bytes);
|
|
110
|
+
return copy.buffer;
|
|
111
|
+
}
|
|
112
|
+
function errorMessage(cause) {
|
|
113
|
+
if (cause instanceof Error) {
|
|
114
|
+
return cause.message;
|
|
115
|
+
}
|
|
116
|
+
if (cause && typeof cause === "object") {
|
|
117
|
+
const tag = "tag" in cause ? String(cause.tag) : undefined;
|
|
118
|
+
const value = "val" in cause ? String(cause.val) : undefined;
|
|
119
|
+
if (tag || value) {
|
|
120
|
+
return [tag, value].filter(Boolean).join(": ");
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return String(cause);
|
|
124
|
+
}
|
package/dist/types.d.ts
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
export type SqliteBackendOptions = {
|
|
2
2
|
path: string;
|
|
3
3
|
};
|
|
4
|
+
export type FsBackendOptions = {
|
|
5
|
+
path: string;
|
|
6
|
+
lixDir?: string;
|
|
7
|
+
syncAllFiles: boolean;
|
|
8
|
+
};
|
|
4
9
|
export type OpenLixOptions = {
|
|
5
|
-
backend?: import("./open-lix.js").SqliteBackend;
|
|
10
|
+
backend?: import("./open-lix.js").SqliteBackend | import("./open-lix.js").FsBackend;
|
|
6
11
|
};
|
|
7
12
|
export type LixValue = {
|
|
8
13
|
kind: "null";
|
|
@@ -30,6 +35,9 @@ export type JsonValue = null | boolean | number | string | readonly JsonValue[]
|
|
|
30
35
|
readonly [key: string]: JsonValue;
|
|
31
36
|
};
|
|
32
37
|
export type SqlParam = JsonValue | Uint8Array | import("./value.js").Value;
|
|
38
|
+
export type ExecuteOptions = {
|
|
39
|
+
originKey?: string;
|
|
40
|
+
};
|
|
33
41
|
export type ExecuteResult = {
|
|
34
42
|
columns: string[];
|
|
35
43
|
rows: RowLike[];
|
|
@@ -40,6 +48,11 @@ export type ExecuteResult = {
|
|
|
40
48
|
hint?: string;
|
|
41
49
|
}>;
|
|
42
50
|
};
|
|
51
|
+
export type ObserveEvent = {
|
|
52
|
+
sequence: number;
|
|
53
|
+
mutationSequence: number;
|
|
54
|
+
result: ExecuteResult;
|
|
55
|
+
};
|
|
43
56
|
export type RowLike = {
|
|
44
57
|
get(column: string): unknown;
|
|
45
58
|
value(column: string): ValueLike;
|