@lix-js/sdk 0.6.2 → 0.7.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 CHANGED
@@ -17,9 +17,15 @@ const lix = await openLix({
17
17
  backend: new SqliteBackend({ path: "app.lix" }),
18
18
  });
19
19
 
20
- await lix.fs.writeFile("/hello.txt", new TextEncoder().encode("world"));
20
+ await lix.execute(
21
+ "INSERT INTO lix_file (path, data) VALUES ($1, $2) ON CONFLICT (path) DO UPDATE SET data = excluded.data",
22
+ ["/hello.txt", new TextEncoder().encode("world")],
23
+ );
21
24
 
22
- const bytes = await lix.fs.readFile("/hello.txt");
25
+ const result = await lix.execute("SELECT data FROM lix_file WHERE path = $1", [
26
+ "/hello.txt",
27
+ ]);
28
+ const bytes = result.rows[0]?.value("data").asBytes();
23
29
 
24
30
  console.log(bytes && new TextDecoder().decode(bytes));
25
31
 
@@ -33,7 +39,10 @@ const main = await lix.activeBranchId();
33
39
  const draft = await lix.createBranch({ name: "Draft" });
34
40
 
35
41
  await lix.switchBranch({ branchId: draft.id });
36
- await lix.fs.writeFile("/status.txt", new TextEncoder().encode("draft"));
42
+ await lix.execute(
43
+ "INSERT INTO lix_file (path, data) VALUES ($1, $2) ON CONFLICT (path) DO UPDATE SET data = excluded.data",
44
+ ["/status.txt", new TextEncoder().encode("draft")],
45
+ );
37
46
 
38
47
  await lix.switchBranch({ branchId: main });
39
48
  const preview = await lix.mergeBranchPreview({ sourceBranchId: draft.id });
@@ -46,8 +55,14 @@ const merge = await lix.mergeBranch({ sourceBranchId: draft.id });
46
55
  const tx = await lix.beginTransaction();
47
56
 
48
57
  try {
49
- await tx.fs.writeFile("/a.txt", new TextEncoder().encode("1"));
50
- await tx.fs.writeFile("/b.txt", new TextEncoder().encode("2"));
58
+ await tx.execute(
59
+ "INSERT INTO lix_file (path, data) VALUES ($1, $2) ON CONFLICT (path) DO UPDATE SET data = excluded.data",
60
+ ["/a.txt", new TextEncoder().encode("1")],
61
+ );
62
+ await tx.execute(
63
+ "INSERT INTO lix_file (path, data) VALUES ($1, $2) ON CONFLICT (path) DO UPDATE SET data = excluded.data",
64
+ ["/b.txt", new TextEncoder().encode("2")],
65
+ );
51
66
  await tx.commit();
52
67
  } catch (error) {
53
68
  await tx.rollback();
@@ -57,7 +72,7 @@ try {
57
72
 
58
73
  ## Notes
59
74
 
60
- - `openLix()` opens a fresh in-memory Lix. Pass `new SqliteBackend({ path })` to persist to disk.
75
+ - `openLix()` opens a fresh in-memory Lix. Pass `new SqliteBackend({ path })` for a raw SQLite `.lix` file, or `new FsBackend({ path })` for a filesystem workspace directory backed by `<path>/.lix/.internal/db.sqlite`.
61
76
  - The SDK is Node/native only right now; it is not browser-compatible.
62
77
  - The package is ESM-only.
63
78
  - The native addon is built from Rust and loaded by the TypeScript wrapper.
@@ -0,0 +1,6 @@
1
+ export type BundledPluginArchive = {
2
+ key: string;
3
+ fileName: string;
4
+ archiveBytes: Uint8Array;
5
+ };
6
+ export declare function bundledPluginArchives(): Promise<BundledPluginArchive[]>;
@@ -0,0 +1,33 @@
1
+ import { readFile } from "node:fs/promises";
2
+ const BUNDLED_PLUGIN_MANIFEST = [
3
+ {
4
+ key: "plugin_md_v2",
5
+ fileName: "plugin_md_v2.lixplugin",
6
+ },
7
+ {
8
+ key: "plugin_csv",
9
+ fileName: "plugin_csv.lixplugin",
10
+ },
11
+ ];
12
+ export async function bundledPluginArchives() {
13
+ return await Promise.all(BUNDLED_PLUGIN_MANIFEST.map(async (plugin) => ({
14
+ key: plugin.key,
15
+ fileName: plugin.fileName,
16
+ archiveBytes: await readBundledArchive(plugin.fileName),
17
+ })));
18
+ }
19
+ async function readBundledArchive(fileName) {
20
+ const urls = [
21
+ new URL(`./bundled-plugins/${fileName}`, import.meta.url),
22
+ new URL(`../dist/bundled-plugins/${fileName}`, import.meta.url),
23
+ ];
24
+ for (const url of urls) {
25
+ try {
26
+ return new Uint8Array(await readFile(url));
27
+ }
28
+ catch {
29
+ // Try the next build/source layout.
30
+ }
31
+ }
32
+ return new Uint8Array(await readFile(urls[0]));
33
+ }
package/dist/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
- export { Lix, LixTransaction, openLix, SqliteBackend } from "./open-lix.js";
1
+ export { FsBackend, Lix, LixTransaction, ObserveEvents, openLix, SqliteBackend, } from "./open-lix.js";
2
+ export { bundledPluginArchives, type BundledPluginArchive, } from "./bundled-plugins.js";
2
3
  export { Row } from "./result.js";
3
4
  export { Value } from "./value.js";
4
- export type { CreateBranchOptions, CreateBranchReceipt, ExecuteResult, JsonValue, LixValue, MergeBranchOptions, MergeBranchOutcome, MergeBranchPreview, MergeBranchReceipt, MergeChangeStats, MergeConflict, MergeConflictSide, OpenLixOptions, SqlParam, SqliteBackendOptions, SwitchBranchOptions, SwitchBranchReceipt, } from "./types.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";
package/dist/index.js CHANGED
@@ -1,3 +1,4 @@
1
- export { Lix, LixTransaction, openLix, SqliteBackend } from "./open-lix.js";
1
+ export { FsBackend, Lix, LixTransaction, ObserveEvents, openLix, SqliteBackend, } from "./open-lix.js";
2
+ export { bundledPluginArchives, } from "./bundled-plugins.js";
2
3
  export { Row } from "./result.js";
3
4
  export { Value } from "./value.js";
@@ -1,10 +1,16 @@
1
1
  import { wrapExecuteResult } from "./result.js";
2
2
  import { toNativeValue } from "./value.js";
3
- import type { CreateBranchOptions, CreateBranchReceipt, ExecuteResult, MergeBranchOptions, MergeBranchPreview, MergeBranchReceipt, OpenLixOptions, SqlParam, SqliteBackendOptions, SwitchBranchOptions, SwitchBranchReceipt } from "./types.js";
3
+ import type { CreateBranchOptions, CreateBranchReceipt, ExecuteResult, FsBackendOptions, MergeBranchOptions, MergeBranchPreview, MergeBranchReceipt, ObserveEvent, OpenLixOptions, SqlParam, SqliteBackendOptions, SwitchBranchOptions, SwitchBranchReceipt } from "./types.js";
4
4
  type NativeExecuteResult = Parameters<typeof wrapExecuteResult>[0];
5
+ type NativeObserveEvent = {
6
+ sequence: number;
7
+ mutationSequence: number;
8
+ rows: NativeExecuteResult;
9
+ };
5
10
  type NativeParam = ReturnType<typeof toNativeValue>;
6
11
  type NativeLix = {
7
12
  execute(sql: string, params: NativeParam[]): NativeExecuteResult;
13
+ observe(sql: string, params: NativeParam[]): NativeObserveEvents;
8
14
  beginTransaction(): NativeLixTransaction;
9
15
  activeBranchId(): string;
10
16
  createBranch(options: CreateBranchOptions): CreateBranchReceipt;
@@ -18,15 +24,24 @@ type NativeLixTransaction = {
18
24
  commit(): void;
19
25
  rollback(): void;
20
26
  };
27
+ type NativeObserveEvents = {
28
+ next(): Promise<NativeObserveEvent | null | undefined>;
29
+ close(): void;
30
+ };
21
31
  export declare class SqliteBackend {
22
32
  readonly path: string;
23
33
  constructor(options: SqliteBackendOptions);
24
34
  }
35
+ export declare class FsBackend {
36
+ readonly path: string;
37
+ constructor(options: FsBackendOptions);
38
+ }
25
39
  export declare function openLix(options?: OpenLixOptions): Promise<Lix>;
26
40
  export declare class Lix {
27
41
  private readonly native;
28
42
  constructor(native: NativeLix);
29
43
  execute(sql: string, params?: SqlParam[]): Promise<ExecuteResult>;
44
+ observe(sql: string, params?: SqlParam[]): ObserveEvents;
30
45
  beginTransaction(): Promise<LixTransaction>;
31
46
  activeBranchId(): Promise<string>;
32
47
  createBranch(options: CreateBranchOptions): Promise<CreateBranchReceipt>;
@@ -35,6 +50,12 @@ export declare class Lix {
35
50
  mergeBranch(options: MergeBranchOptions): Promise<MergeBranchReceipt>;
36
51
  close(): Promise<void>;
37
52
  }
53
+ export declare class ObserveEvents {
54
+ private readonly native;
55
+ constructor(native: NativeObserveEvents);
56
+ next(): Promise<ObserveEvent | undefined>;
57
+ close(): void;
58
+ }
38
59
  export declare class LixTransaction {
39
60
  private readonly native;
40
61
  constructor(native: NativeLixTransaction);
package/dist/open-lix.js CHANGED
@@ -5,12 +5,25 @@ import { normalizeParam, toNativeValue } from "./value.js";
5
5
  export class SqliteBackend {
6
6
  path;
7
7
  constructor(options) {
8
- if (!options || typeof options.path !== "string" || options.path.length === 0) {
8
+ if (!options ||
9
+ typeof options.path !== "string" ||
10
+ options.path.length === 0) {
9
11
  throw new TypeError("SqliteBackend requires a non-empty path");
10
12
  }
11
13
  this.path = options.path;
12
14
  }
13
15
  }
16
+ export class FsBackend {
17
+ path;
18
+ constructor(options) {
19
+ if (!options ||
20
+ typeof options.path !== "string" ||
21
+ options.path.length === 0) {
22
+ throw new TypeError("FsBackend requires a non-empty path");
23
+ }
24
+ this.path = options.path;
25
+ }
26
+ }
14
27
  export async function openLix(options = {}) {
15
28
  if (!options || typeof options !== "object") {
16
29
  throw new TypeError("openLix() options must be an object");
@@ -18,10 +31,13 @@ export async function openLix(options = {}) {
18
31
  if (options.backend === undefined) {
19
32
  return new Lix(addon.Lix.openMemory());
20
33
  }
21
- if (!(options.backend instanceof SqliteBackend)) {
22
- throw new TypeError("openLix() requires { backend: new SqliteBackend({ path }) }");
34
+ if (options.backend instanceof SqliteBackend) {
35
+ return new Lix(addon.Lix.openSqlite(options.backend.path));
36
+ }
37
+ if (options.backend instanceof FsBackend) {
38
+ return new Lix(addon.Lix.openFs(options.backend.path));
23
39
  }
24
- return new Lix(addon.Lix.openSqlite(options.backend.path));
40
+ throw new TypeError("openLix() requires { backend: new SqliteBackend({ path }) } or { backend: new FsBackend({ path }) }");
25
41
  }
26
42
  export class Lix {
27
43
  native;
@@ -32,6 +48,10 @@ export class Lix {
32
48
  assertExecuteArgs("lix", sql, params);
33
49
  return wrapExecuteResult(this.native.execute(sql, params.map((param, index) => toNativeValue(normalizeParam(param, index)))));
34
50
  }
51
+ observe(sql, params = []) {
52
+ assertSqlArgs("observe", "lix", sql, params);
53
+ return new ObserveEvents(this.native.observe(sql, params.map((param, index) => toNativeValue(normalizeParam(param, index)))));
54
+ }
35
55
  async beginTransaction() {
36
56
  return new LixTransaction(this.native.beginTransaction());
37
57
  }
@@ -56,6 +76,26 @@ export class Lix {
56
76
  return this.native.close();
57
77
  }
58
78
  }
79
+ export class ObserveEvents {
80
+ native;
81
+ constructor(native) {
82
+ this.native = native;
83
+ }
84
+ async next() {
85
+ const event = await this.native.next();
86
+ if (event == null) {
87
+ return undefined;
88
+ }
89
+ return {
90
+ sequence: event.sequence,
91
+ mutationSequence: event.mutationSequence,
92
+ result: wrapExecuteResult(event.rows),
93
+ };
94
+ }
95
+ close() {
96
+ this.native.close();
97
+ }
98
+ }
59
99
  export class LixTransaction {
60
100
  native;
61
101
  constructor(native) {
@@ -73,10 +113,13 @@ export class LixTransaction {
73
113
  }
74
114
  }
75
115
  function assertExecuteArgs(receiver, sql, params) {
116
+ assertSqlArgs("execute", receiver, sql, params);
117
+ }
118
+ function assertSqlArgs(operation, receiver, sql, params) {
76
119
  if (typeof sql !== "string") {
77
- throw invalidArgument("execute", "sql", "string", typeof sql, receiver);
120
+ throw invalidArgument(operation, "sql", "string", typeof sql, receiver);
78
121
  }
79
122
  if (!Array.isArray(params)) {
80
- throw invalidArgument("execute", "params", "array", typeof params, receiver);
123
+ throw invalidArgument(operation, "params", "array", typeof params, receiver);
81
124
  }
82
125
  }
package/dist/types.d.ts CHANGED
@@ -1,8 +1,11 @@
1
1
  export type SqliteBackendOptions = {
2
2
  path: string;
3
3
  };
4
+ export type FsBackendOptions = {
5
+ path: string;
6
+ };
4
7
  export type OpenLixOptions = {
5
- backend?: import("./open-lix.js").SqliteBackend;
8
+ backend?: import("./open-lix.js").SqliteBackend | import("./open-lix.js").FsBackend;
6
9
  };
7
10
  export type LixValue = {
8
11
  kind: "null";
@@ -40,6 +43,11 @@ export type ExecuteResult = {
40
43
  hint?: string;
41
44
  }>;
42
45
  };
46
+ export type ObserveEvent = {
47
+ sequence: number;
48
+ mutationSequence: number;
49
+ result: ExecuteResult;
50
+ };
43
51
  export type RowLike = {
44
52
  get(column: string): unknown;
45
53
  value(column: string): ValueLike;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@lix-js/sdk",
3
3
  "type": "module",
4
- "version": "0.6.2",
4
+ "version": "0.7.0",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
7
7
  "repository": {
@@ -18,22 +18,26 @@
18
18
  "dist"
19
19
  ],
20
20
  "scripts": {
21
- "build": "npm run build:native && npm run build:ts",
21
+ "build": "npm run build:native && npm run build:ts && npm run build:plugins",
22
22
  "build:native": "node ./scripts/build-native.js",
23
+ "build:plugins": "node ./scripts/build-bundled-plugins.js",
23
24
  "prepare:native-package": "node ./scripts/prepare-native-package.js",
24
25
  "build:ts": "tsc -p tsconfig.json",
25
26
  "test": "npm run build && vitest run",
26
27
  "typecheck": "tsc -p tsconfig.test.json --noEmit"
27
28
  },
28
29
  "optionalDependencies": {
29
- "@lix-js/sdk-darwin-arm64": "0.6.2",
30
- "@lix-js/sdk-linux-arm64": "0.6.2",
31
- "@lix-js/sdk-linux-x64": "0.6.2",
32
- "@lix-js/sdk-win32-x64": "0.6.2"
30
+ "@lix-js/sdk-darwin-arm64": "0.7.0",
31
+ "@lix-js/sdk-linux-arm64": "0.7.0",
32
+ "@lix-js/sdk-linux-x64": "0.7.0",
33
+ "@lix-js/sdk-win32-x64": "0.7.0"
33
34
  },
34
35
  "devDependencies": {
35
36
  "@types/node": "^24.10.2",
36
37
  "typescript": "^5.5.4",
37
38
  "vitest": "^4.0.18"
39
+ },
40
+ "dependencies": {
41
+ "fflate": "^0.8.3"
38
42
  }
39
43
  }