@lix-js/sdk 0.7.0 → 0.8.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 +90 -22
- 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.js +10 -2
- package/dist/errors.d.ts +2 -0
- package/dist/errors.js +13 -0
- package/dist/index.d.ts +1 -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 +21 -39
- package/dist/open-lix.js +172 -34
- package/dist/plugin-runtime.d.ts +45 -0
- package/dist/plugin-runtime.js +124 -0
- package/dist/types.d.ts +5 -0
- package/dist/wasm/lix_js_sdk.d.ts +95 -0
- package/dist/wasm/lix_js_sdk.js +951 -0
- package/dist/wasm/lix_js_sdk_bg.wasm +0 -0
- package/dist/wasm/lix_js_sdk_bg.wasm.d.ts +31 -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/dist/workerd.d.ts +15 -0
- package/dist/workerd.js +32 -0
- package/package.json +38 -8
- package/dist/native.d.ts +0 -1
package/README.md
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# @lix-js/sdk
|
|
2
2
|
|
|
3
|
-
JavaScript SDK for Lix
|
|
3
|
+
JavaScript SDK for Lix. It uses the native Rust addon in Node.js and the same
|
|
4
|
+
Rust SDK compiled to WebAssembly in browsers.
|
|
4
5
|
|
|
5
6
|
## Install
|
|
6
7
|
|
|
@@ -10,20 +11,36 @@ npm install @lix-js/sdk
|
|
|
10
11
|
|
|
11
12
|
## Usage
|
|
12
13
|
|
|
14
|
+
The default in-memory backend works in browsers and Node.js:
|
|
15
|
+
|
|
16
|
+
```ts
|
|
17
|
+
import { openLix } from "@lix-js/sdk";
|
|
18
|
+
|
|
19
|
+
const lix = await openLix();
|
|
20
|
+
const result = await lix.execute("SELECT $1 AS message", ["hello"]);
|
|
21
|
+
console.log(result.rows[0]?.get("message"));
|
|
22
|
+
await lix.close();
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Filesystem and SQLite backends use native Node.js dependencies:
|
|
26
|
+
|
|
13
27
|
```ts
|
|
14
|
-
import {
|
|
28
|
+
import { FsBackend, openLix } from "@lix-js/sdk";
|
|
15
29
|
|
|
16
30
|
const lix = await openLix({
|
|
17
|
-
|
|
31
|
+
backend: new FsBackend({
|
|
32
|
+
path: "./workspace",
|
|
33
|
+
syncAllFiles: true,
|
|
34
|
+
}),
|
|
18
35
|
});
|
|
19
36
|
|
|
20
37
|
await lix.execute(
|
|
21
|
-
|
|
22
|
-
|
|
38
|
+
"INSERT INTO lix_file (path, data) VALUES ($1, $2) ON CONFLICT (path) DO UPDATE SET data = excluded.data",
|
|
39
|
+
["/hello.txt", new TextEncoder().encode("world")],
|
|
23
40
|
);
|
|
24
41
|
|
|
25
42
|
const result = await lix.execute("SELECT data FROM lix_file WHERE path = $1", [
|
|
26
|
-
|
|
43
|
+
"/hello.txt",
|
|
27
44
|
]);
|
|
28
45
|
const bytes = result.rows[0]?.value("data").asBytes();
|
|
29
46
|
|
|
@@ -40,8 +57,8 @@ const draft = await lix.createBranch({ name: "Draft" });
|
|
|
40
57
|
|
|
41
58
|
await lix.switchBranch({ branchId: draft.id });
|
|
42
59
|
await lix.execute(
|
|
43
|
-
|
|
44
|
-
|
|
60
|
+
"INSERT INTO lix_file (path, data) VALUES ($1, $2) ON CONFLICT (path) DO UPDATE SET data = excluded.data",
|
|
61
|
+
["/status.txt", new TextEncoder().encode("draft")],
|
|
45
62
|
);
|
|
46
63
|
|
|
47
64
|
await lix.switchBranch({ branchId: main });
|
|
@@ -55,26 +72,77 @@ const merge = await lix.mergeBranch({ sourceBranchId: draft.id });
|
|
|
55
72
|
const tx = await lix.beginTransaction();
|
|
56
73
|
|
|
57
74
|
try {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
75
|
+
await tx.execute(
|
|
76
|
+
"INSERT INTO lix_file (path, data) VALUES ($1, $2) ON CONFLICT (path) DO UPDATE SET data = excluded.data",
|
|
77
|
+
["/a.txt", new TextEncoder().encode("1")],
|
|
78
|
+
);
|
|
79
|
+
await tx.execute(
|
|
80
|
+
"INSERT INTO lix_file (path, data) VALUES ($1, $2) ON CONFLICT (path) DO UPDATE SET data = excluded.data",
|
|
81
|
+
["/b.txt", new TextEncoder().encode("2")],
|
|
82
|
+
);
|
|
83
|
+
await tx.commit();
|
|
67
84
|
} catch (error) {
|
|
68
|
-
|
|
69
|
-
|
|
85
|
+
await tx.rollback();
|
|
86
|
+
throw error;
|
|
70
87
|
}
|
|
71
88
|
```
|
|
72
89
|
|
|
73
90
|
## Notes
|
|
74
91
|
|
|
75
|
-
- `openLix()` opens a fresh in-memory Lix. Pass `new
|
|
76
|
-
-
|
|
92
|
+
- `openLix()` opens a fresh in-memory Lix. Pass `new FsBackend({ path, syncAllFiles: true })` for a filesystem workspace directory backed by `<path>/.lix/.internal/rocksdb`.
|
|
93
|
+
- Pass `new FsBackend({ path, lixDir, syncAllFiles: true })` for filesystem sync with repository metadata in an external `.lix` directory and no workspace `.lix` directory.
|
|
94
|
+
- Pass `syncAllFiles: false` to start filesystem sync with no regular workspace files, then call `backend.importPaths(["notes/today.md"])` on the `FsBackend` instance to sync selected files. Imported paths are exact workspace-relative file paths, not directories or globs.
|
|
95
|
+
- Use `new SqliteBackend({ path })` when a single SQLite-backed `.lix` file is the application document itself, for example when defining a new file format and using Lix as the application's file format.
|
|
96
|
+
- In browsers, `openLix()` loads the Rust engine as WebAssembly and uses the
|
|
97
|
+
in-memory backend.
|
|
98
|
+
- `FsBackend` and `SqliteBackend` are Node.js-only. Constructing them is safe in
|
|
99
|
+
shared code, but passing one to `openLix()` in a browser throws an error.
|
|
77
100
|
- The package is ESM-only.
|
|
78
|
-
- The
|
|
101
|
+
- The package uses conditional ESM imports internally: Node.js resolves the
|
|
102
|
+
native N-API binding, while browsers and other runtimes resolve the portable
|
|
103
|
+
WebAssembly binding. Vite follows this split without consumer configuration.
|
|
104
|
+
- Every `openLix()` owns one dedicated worker. The engine, storage backend, and
|
|
105
|
+
installed WASM plugin components all run in that worker in both Node.js and
|
|
106
|
+
browsers, so database and plugin work does not block the page's main thread.
|
|
107
|
+
- Installed WASM plugin components are transpiled with JCO and executed by the
|
|
108
|
+
worker's WebAssembly runtime in both environments. Plugin execution does not
|
|
109
|
+
yet enforce the declared fuel, timeout, or memory limits, so only install
|
|
110
|
+
trusted plugins.
|
|
111
|
+
- A page Content Security Policy only needs to permit the package's same-origin
|
|
112
|
+
worker. WebAssembly compilation and JCO's generated `data:` module imports
|
|
113
|
+
happen inside that worker, so they can be scoped to the worker script's HTTP
|
|
114
|
+
response instead of being allowed by the document:
|
|
115
|
+
|
|
116
|
+
```http
|
|
117
|
+
# HTML document response
|
|
118
|
+
Content-Security-Policy: default-src 'self'; script-src 'self'; worker-src 'self'
|
|
119
|
+
|
|
120
|
+
# Lix worker response (Vite emits assets/entry.browser-<hash>.js)
|
|
121
|
+
Content-Security-Policy: default-src 'none'; script-src 'self' data: 'wasm-unsafe-eval'; connect-src 'self'
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
Hosts that apply one policy to every response can use
|
|
125
|
+
`script-src 'self' data: 'wasm-unsafe-eval'; worker-src 'self'` globally
|
|
126
|
+
instead. Worker-scoped headers keep those permissions out of the page.
|
|
79
127
|
- SQL parameters use normal JavaScript values: `string`, finite `number`, `boolean`, `Uint8Array`, `null`, JSON-compatible arrays, and JSON-compatible plain objects.
|
|
80
128
|
- Use `Value.integer(...)`, `Value.real(...)`, `Value.text(...)`, `Value.json(...)`, or `Value.blob(...)` only when you need to pass an explicit native Lix value.
|
|
129
|
+
|
|
130
|
+
## Browser development
|
|
131
|
+
|
|
132
|
+
The browser suite runs the published package shape in a real headless Chromium
|
|
133
|
+
page through Vite/Vitest Browser Mode:
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
rustup target add wasm32-unknown-unknown
|
|
137
|
+
cargo install wasm-bindgen-cli --version 0.2.122 --locked
|
|
138
|
+
npx playwright install chromium
|
|
139
|
+
npm run test:browser
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
`npm run test:browser:production` additionally packs the SDK, installs the
|
|
143
|
+
tarball into a minimal Vite app, makes a production build, and exercises SQL
|
|
144
|
+
plus both bundled plugins in Chromium. It runs with both worker-scoped and
|
|
145
|
+
global strict CSP headers.
|
|
146
|
+
|
|
147
|
+
Use `npm run build:wasm:dev` while iterating on the Rust bridge when release
|
|
148
|
+
optimization is unnecessary.
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { CreateBranchOptions, CreateBranchReceipt, ExecuteOptions, MergeBranchOptions, MergeBranchPreview, MergeBranchReceipt, SwitchBranchOptions, SwitchBranchReceipt } from "./types.js";
|
|
2
|
+
import type { NativeLixValue } from "./value.js";
|
|
3
|
+
export type BindingExecuteResult = {
|
|
4
|
+
columns: string[];
|
|
5
|
+
rows: NativeLixValue[][];
|
|
6
|
+
rowsAffected: number;
|
|
7
|
+
notices: Array<{
|
|
8
|
+
code: string;
|
|
9
|
+
message: string;
|
|
10
|
+
hint?: string;
|
|
11
|
+
}>;
|
|
12
|
+
};
|
|
13
|
+
export type BindingObserveEvent = {
|
|
14
|
+
sequence: number;
|
|
15
|
+
mutationSequence: number;
|
|
16
|
+
rows: BindingExecuteResult;
|
|
17
|
+
};
|
|
18
|
+
export type BindingParam = NativeLixValue;
|
|
19
|
+
export type LixBinding = {
|
|
20
|
+
execute(sql: string, params: BindingParam[], options?: ExecuteOptions): Promise<BindingExecuteResult>;
|
|
21
|
+
observe(sql: string, params: BindingParam[]): Promise<ObserveEventsBinding>;
|
|
22
|
+
beginTransaction(): Promise<LixTransactionBinding>;
|
|
23
|
+
activeBranchId(): Promise<string>;
|
|
24
|
+
createBranch(options: CreateBranchOptions): Promise<CreateBranchReceipt>;
|
|
25
|
+
switchBranch(options: SwitchBranchOptions): Promise<SwitchBranchReceipt>;
|
|
26
|
+
importFilesystemPaths(paths: string[]): Promise<void>;
|
|
27
|
+
mergeBranchPreview(options: MergeBranchOptions): Promise<MergeBranchPreview>;
|
|
28
|
+
mergeBranch(options: MergeBranchOptions): Promise<MergeBranchReceipt>;
|
|
29
|
+
syncDiskToLix(): Promise<void>;
|
|
30
|
+
close(): Promise<void>;
|
|
31
|
+
};
|
|
32
|
+
export type LixTransactionBinding = {
|
|
33
|
+
execute(sql: string, params: BindingParam[], options?: ExecuteOptions): Promise<BindingExecuteResult>;
|
|
34
|
+
commit(): Promise<void>;
|
|
35
|
+
rollback(): Promise<void>;
|
|
36
|
+
};
|
|
37
|
+
export type ObserveEventsBinding = {
|
|
38
|
+
next(): Promise<BindingObserveEvent | null | undefined>;
|
|
39
|
+
close(): void;
|
|
40
|
+
};
|
|
41
|
+
export type PluginRuntimeDispatch = (request: unknown) => Promise<unknown>;
|
|
42
|
+
export type LixBackendConfig = {
|
|
43
|
+
kind: "memory";
|
|
44
|
+
} | {
|
|
45
|
+
kind: "sqlite";
|
|
46
|
+
path: string;
|
|
47
|
+
} | {
|
|
48
|
+
kind: "fs";
|
|
49
|
+
path: string;
|
|
50
|
+
lixDir?: string;
|
|
51
|
+
syncAllFiles: boolean;
|
|
52
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// Generated before TypeScript compilation and emitted beside this module.
|
|
2
|
+
// @ts-expect-error Generated by build:wasm.
|
|
3
|
+
import initWasm, { openMemory } from "./wasm/lix_js_sdk.js";
|
|
4
|
+
let wasmInitialized;
|
|
5
|
+
function initializeWasm() {
|
|
6
|
+
return (wasmInitialized ??= initWasm());
|
|
7
|
+
}
|
|
8
|
+
export async function openLixBinding(backend, dispatch) {
|
|
9
|
+
if (backend.kind !== "memory") {
|
|
10
|
+
throw new Error(`${backend.kind === "fs" ? "FsBackend" : "SqliteBackend"} is only available in Node.js`);
|
|
11
|
+
}
|
|
12
|
+
await initializeWasm();
|
|
13
|
+
return openMemory(dispatch);
|
|
14
|
+
}
|
|
@@ -9,15 +9,11 @@ const nativePackages = {
|
|
|
9
9
|
"darwin-arm64": "@lix-js/sdk-darwin-arm64",
|
|
10
10
|
"win32-x64": "@lix-js/sdk-win32-x64",
|
|
11
11
|
};
|
|
12
|
-
function nativePackageName() {
|
|
13
|
-
const key = `${process.platform}-${process.arch}`;
|
|
14
|
-
return nativePackages[key];
|
|
15
|
-
}
|
|
16
12
|
function resolveNativePath() {
|
|
17
|
-
if (existsSync(localNativePath))
|
|
13
|
+
if (existsSync(localNativePath))
|
|
18
14
|
return localNativePath;
|
|
19
|
-
}
|
|
20
|
-
const packageName =
|
|
15
|
+
const key = `${process.platform}-${process.arch}`;
|
|
16
|
+
const packageName = nativePackages[key];
|
|
21
17
|
let packageResolutionError;
|
|
22
18
|
if (packageName) {
|
|
23
19
|
try {
|
|
@@ -32,10 +28,9 @@ function resolveNativePath() {
|
|
|
32
28
|
}
|
|
33
29
|
throw packageResolutionError;
|
|
34
30
|
}
|
|
35
|
-
|
|
31
|
+
let addon;
|
|
36
32
|
try {
|
|
37
|
-
|
|
38
|
-
process.dlopen(native, nativePath);
|
|
33
|
+
addon = require(resolveNativePath());
|
|
39
34
|
}
|
|
40
35
|
catch (cause) {
|
|
41
36
|
const error = new Error(`Failed to load @lix-js/sdk native addon for ${process.platform}-${process.arch}. ` +
|
|
@@ -44,4 +39,13 @@ catch (cause) {
|
|
|
44
39
|
error.cause = cause;
|
|
45
40
|
throw error;
|
|
46
41
|
}
|
|
47
|
-
export
|
|
42
|
+
export function openLixBinding(backend, dispatch) {
|
|
43
|
+
switch (backend.kind) {
|
|
44
|
+
case "memory":
|
|
45
|
+
return addon.Lix.openMemory(dispatch);
|
|
46
|
+
case "sqlite":
|
|
47
|
+
return addon.Lix.openSqlite(backend.path, dispatch);
|
|
48
|
+
case "fs":
|
|
49
|
+
return addon.Lix.openFs(backend.path, backend.lixDir, backend.syncAllFiles, dispatch);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
Binary file
|
|
Binary file
|
package/dist/bundled-plugins.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { readFile } from "node:fs/promises";
|
|
2
1
|
const BUNDLED_PLUGIN_MANIFEST = [
|
|
3
2
|
{
|
|
4
3
|
key: "plugin_md_v2",
|
|
@@ -23,11 +22,20 @@ async function readBundledArchive(fileName) {
|
|
|
23
22
|
];
|
|
24
23
|
for (const url of urls) {
|
|
25
24
|
try {
|
|
25
|
+
if (url.protocol !== "file:") {
|
|
26
|
+
const response = await fetch(url);
|
|
27
|
+
if (response.ok) {
|
|
28
|
+
return new Uint8Array(await response.arrayBuffer());
|
|
29
|
+
}
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
const moduleName = "node:fs/promises";
|
|
33
|
+
const { readFile } = await import(/* @vite-ignore */ moduleName);
|
|
26
34
|
return new Uint8Array(await readFile(url));
|
|
27
35
|
}
|
|
28
36
|
catch {
|
|
29
37
|
// Try the next build/source layout.
|
|
30
38
|
}
|
|
31
39
|
}
|
|
32
|
-
|
|
40
|
+
throw new Error(`Could not load bundled plugin archive ${fileName}`);
|
|
33
41
|
}
|
package/dist/errors.d.ts
CHANGED
|
@@ -5,3 +5,5 @@ export type LixJsError = Error & {
|
|
|
5
5
|
};
|
|
6
6
|
export declare function invalidArgument(operation: string, argument: string, expected: string, actual: string, receiver?: string): LixJsError;
|
|
7
7
|
export declare function invalidParam(index: number, message: string, actual: string): LixJsError;
|
|
8
|
+
export declare function fsBackendNotOpen(operation: string): LixJsError;
|
|
9
|
+
export declare function fsBackendAlreadyOpen(): LixJsError;
|
package/dist/errors.js
CHANGED
|
@@ -17,3 +17,16 @@ export function invalidParam(index, message, actual) {
|
|
|
17
17
|
};
|
|
18
18
|
return error;
|
|
19
19
|
}
|
|
20
|
+
export function fsBackendNotOpen(operation) {
|
|
21
|
+
const error = new Error(`FsBackend.${operation}() requires the backend to be opened with openLix() first`);
|
|
22
|
+
error.name = "LixError";
|
|
23
|
+
error.code = "LIX_FS_BACKEND_NOT_OPEN";
|
|
24
|
+
error.details = { operation };
|
|
25
|
+
return error;
|
|
26
|
+
}
|
|
27
|
+
export function fsBackendAlreadyOpen() {
|
|
28
|
+
const error = new Error("openLix() FsBackend is already open; close the existing Lix or create a new FsBackend");
|
|
29
|
+
error.name = "LixError";
|
|
30
|
+
error.code = "LIX_FS_BACKEND_IN_USE";
|
|
31
|
+
return error;
|
|
32
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -2,4 +2,4 @@ export { FsBackend, Lix, LixTransaction, ObserveEvents, openLix, SqliteBackend,
|
|
|
2
2
|
export { bundledPluginArchives, type BundledPluginArchive, } from "./bundled-plugins.js";
|
|
3
3
|
export { Row } from "./result.js";
|
|
4
4
|
export { Value } from "./value.js";
|
|
5
|
-
export type { CreateBranchOptions, CreateBranchReceipt, ExecuteResult, FsBackendOptions, JsonValue, LixValue, MergeBranchOptions, MergeBranchOutcome, MergeBranchPreview, MergeBranchReceipt, MergeChangeStats, MergeConflict, MergeConflictSide, ObserveEvent, OpenLixOptions, SqlParam, SqliteBackendOptions, SwitchBranchOptions, SwitchBranchReceipt, } from "./types.js";
|
|
5
|
+
export type { CreateBranchOptions, CreateBranchReceipt, ExecuteOptions, ExecuteResult, FsBackendOptions, JsonValue, LixValue, MergeBranchOptions, MergeBranchOutcome, MergeBranchPreview, MergeBranchReceipt, MergeChangeStats, MergeConflict, MergeConflictSide, ObserveEvent, OpenLixOptions, SqlParam, SqliteBackendOptions, SwitchBranchOptions, SwitchBranchReceipt, } from "./types.js";
|
|
Binary file
|
|
Binary file
|