@lix-js/sdk 0.6.0-preview.0 → 0.6.0-preview.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.
@@ -52,30 +52,6 @@ export const LixActiveAccountSchema = {
52
52
  ],
53
53
  "additionalProperties": false
54
54
  };
55
- export const LixActiveVersionSchema = {
56
- "x-lix-key": "lix_active_version",
57
- "x-lix-version": "1",
58
- "description": "Singleton tracking which version is currently checked out in the local runtime. Exactly one row exists at all times; writing to this surface is how clients switch versions.",
59
- "x-lix-primary-key": [
60
- "/id"
61
- ],
62
- "type": "object",
63
- "properties": {
64
- "id": {
65
- "type": "string",
66
- "description": "Fixed singleton key (constant, not a UUID); the primary-key column exists to satisfy entity-model requirements and enforces the one-row invariant."
67
- },
68
- "version_id": {
69
- "type": "string",
70
- "description": "The currently active version; references `lix_version_descriptor.id` and drives the default scope for reads and writes in the session."
71
- }
72
- },
73
- "required": [
74
- "id",
75
- "version_id"
76
- ],
77
- "additionalProperties": false
78
- };
79
55
  export const LixBinaryBlobRefSchema = {
80
56
  "x-lix-key": "lix_binary_blob_ref",
81
57
  "x-lix-version": "1",
@@ -109,7 +85,7 @@ export const LixBinaryBlobRefSchema = {
109
85
  export const LixChangeSchema = {
110
86
  "x-lix-key": "lix_change",
111
87
  "x-lix-version": "1",
112
- "description": "Canonical immutable change fact. This surface represents the authoritative stream of journaled change facts. Commit lineage and non-commit scope are modeled separately around these facts. The public `untracked` field is retained as convenience metadata and should not be treated as proof that trackedness is intrinsic to the canonical fact row itself.",
88
+ "description": "Canonical immutable change fact. This surface represents the authoritative stream of journaled change facts. Commit lineage and non-commit scope are modeled separately around these facts.",
113
89
  "x-lix-primary-key": [
114
90
  "/id"
115
91
  ],
@@ -139,13 +115,6 @@ export const LixChangeSchema = {
139
115
  ],
140
116
  "description": "Filesystem-scoped file identifier when the change belongs to a file; NULL for engine-internal entities (commits, versions, settings)."
141
117
  },
142
- "plugin_key": {
143
- "type": [
144
- "string",
145
- "null"
146
- ],
147
- "description": "Plugin that authored the change; NULL for engine-internal changes."
148
- },
149
118
  "metadata": {
150
119
  "type": [
151
120
  "object",
@@ -157,10 +126,6 @@ export const LixChangeSchema = {
157
126
  "type": "string",
158
127
  "description": "ISO-8601 timestamp at which the change was recorded (set via `lix_timestamp()` at write time)."
159
128
  },
160
- "untracked": {
161
- "type": "boolean",
162
- "description": "Public convenience field indicating that the change currently participates in non-commit visibility rather than commit lineage. This is intended to become derived metadata rather than intrinsic canonical storage truth."
163
- },
164
129
  "snapshot_content": {
165
130
  "type": [
166
131
  "object",
@@ -175,9 +140,7 @@ export const LixChangeSchema = {
175
140
  "schema_key",
176
141
  "schema_version",
177
142
  "file_id",
178
- "plugin_key",
179
- "created_at",
180
- "untracked"
143
+ "created_at"
181
144
  ],
182
145
  "additionalProperties": false
183
146
  };
@@ -248,7 +211,7 @@ export const LixChangeSetSchema = {
248
211
  export const LixChangeSetElementSchema = {
249
212
  "x-lix-key": "lix_change_set_element",
250
213
  "x-lix-version": "1",
251
- "description": "Derived relational index mapping a change_set to the canonical changes it contains. Rebuildable from `lix_commit.change_ids` plus the referenced `lix_change` rows; the unique constraint enforces at most one change per (entity, schema, file) tuple within a single change_set.",
214
+ "description": "Derived relational index mapping a change_set to the canonical changes introduced by its commit relative to the commit's first parent. Rebuildable from `lix_commit.change_ids` plus the referenced `lix_change` rows; the unique constraint enforces at most one change per (entity, schema, file) tuple within a single change_set.",
252
215
  "x-lix-primary-key": [
253
216
  "/change_set_id",
254
217
  "/change_id"
@@ -293,7 +256,7 @@ export const LixChangeSetElementSchema = {
293
256
  },
294
257
  "change_id": {
295
258
  "type": "string",
296
- "description": "The canonical change included in the change_set (references `lix_change.id`)."
259
+ "description": "The canonical change introduced/adopted by the change_set's commit (references `lix_change.id`)."
297
260
  },
298
261
  "entity_id": {
299
262
  "type": "string",
@@ -323,7 +286,7 @@ export const LixChangeSetElementSchema = {
323
286
  export const LixCommitSchema = {
324
287
  "x-lix-key": "lix_commit",
325
288
  "x-lix-version": "1",
326
- "description": "Commit header change. Canonical commit membership is stored in change_ids as the ordered list of non-header member changes for this commit. Derived projections such as lix_change_set_element must be rebuildable from this header plus the referenced change rows. Version head pointers such as lix_version_ref are maintained separately and are not commit members.",
289
+ "description": "Commit header change. Canonical commit membership is stored in change_ids as the ordered list of canonical changes whose effects this commit introduces relative to its first parent. A merge commit may reference canonical changes originally authored in another parent instead of copying them. Derived projections such as lix_change_set_element must be rebuildable from this header plus the referenced change rows. Version head pointers such as lix_version_ref are maintained separately and are not commit members.",
327
290
  "x-lix-primary-key": [
328
291
  "/id"
329
292
  ],
@@ -357,7 +320,7 @@ export const LixCommitSchema = {
357
320
  "items": {
358
321
  "type": "string"
359
322
  },
360
- "description": "Canonical ordered membership list of non-header change IDs that belong to this commit. Derived indexes such as lix_change_set_element must be rebuildable from this list. This does not include lix_version_ref pointer rows."
323
+ "description": "Canonical ordered membership list of non-header change IDs whose effects this commit introduces relative to its first parent. Merge commits may reference existing changes from another parent instead of minting equivalent copies. Derived indexes such as lix_change_set_element must be rebuildable from this list. This does not include lix_version_ref pointer rows."
361
324
  },
362
325
  "author_account_ids": {
363
326
  "type": "array",
@@ -440,6 +403,19 @@ export const LixDirectoryDescriptorSchema = {
440
403
  "/name"
441
404
  ]
442
405
  ],
406
+ "x-lix-foreign-keys": [
407
+ {
408
+ "properties": [
409
+ "/parent_id"
410
+ ],
411
+ "references": {
412
+ "schemaKey": "lix_directory_descriptor",
413
+ "properties": [
414
+ "/id"
415
+ ]
416
+ }
417
+ }
418
+ ],
443
419
  "type": "object",
444
420
  "properties": {
445
421
  "id": {
@@ -540,10 +516,22 @@ export const LixFileDescriptorSchema = {
540
516
  "x-lix-unique": [
541
517
  [
542
518
  "/directory_id",
543
- "/name",
544
- "/extension"
519
+ "/name"
545
520
  ]
546
521
  ],
522
+ "x-lix-foreign-keys": [
523
+ {
524
+ "properties": [
525
+ "/directory_id"
526
+ ],
527
+ "references": {
528
+ "schemaKey": "lix_directory_descriptor",
529
+ "properties": [
530
+ "/id"
531
+ ]
532
+ }
533
+ }
534
+ ],
547
535
  "type": "object",
548
536
  "properties": {
549
537
  "id": {
@@ -558,14 +546,7 @@ export const LixFileDescriptorSchema = {
558
546
  },
559
547
  "name": {
560
548
  "type": "string",
561
- "pattern": "^[^/\\\\]+$"
562
- },
563
- "extension": {
564
- "type": [
565
- "string",
566
- "null"
567
- ],
568
- "pattern": "^[^./\\\\]+$"
549
+ "pattern": "^(?!\\.{1,2}$)[^/\\\\]+$"
569
550
  },
570
551
  "hidden": {
571
552
  "type": "boolean",
@@ -575,8 +556,7 @@ export const LixFileDescriptorSchema = {
575
556
  "required": [
576
557
  "id",
577
558
  "directory_id",
578
- "name",
579
- "extension"
559
+ "name"
580
560
  ],
581
561
  "additionalProperties": false
582
562
  };
@@ -681,12 +661,17 @@ export const LixVersionDescriptorSchema = {
681
661
  "x-lix-primary-key": [
682
662
  "/id"
683
663
  ],
664
+ "x-lix-unique": [
665
+ [
666
+ "/name"
667
+ ]
668
+ ],
684
669
  "type": "object",
685
670
  "properties": {
686
671
  "id": {
687
672
  "type": "string",
688
673
  "x-lix-default": "lix_uuid_v7()",
689
- "description": "Stable version identifier (UUIDv7). Referenced by `lix_version_ref.id` and `lix_active_version.version_id`."
674
+ "description": "Stable version identifier (UUIDv7). Referenced by `lix_version_ref.id`."
690
675
  },
691
676
  "name": {
692
677
  "type": "string",
@@ -712,6 +697,17 @@ export const LixVersionRefSchema = {
712
697
  "/id"
713
698
  ],
714
699
  "x-lix-foreign-keys": [
700
+ {
701
+ "properties": [
702
+ "/id"
703
+ ],
704
+ "references": {
705
+ "schemaKey": "lix_version_descriptor",
706
+ "properties": [
707
+ "/id"
708
+ ]
709
+ }
710
+ },
715
711
  {
716
712
  "properties": [
717
713
  "/commit_id"
@@ -3,17 +3,33 @@ export type JsonValue = null | boolean | number | string | JsonValue[] | {
3
3
  [key: string]: JsonValue;
4
4
  };
5
5
  export type LixRuntimeValue = JsonValue | Uint8Array | ArrayBuffer | Value;
6
- export type RowSet = {
6
+ export type LixNativeValue = JsonValue | Uint8Array;
7
+ export type ExecuteResult = {
7
8
  columns: string[];
8
- rows: Value[][];
9
+ rows: Row[];
10
+ rowsAffected: number;
11
+ notices: LixNotice[];
9
12
  };
10
- export type ExecuteResult = {
11
- kind: "rows";
12
- rows: RowSet;
13
- } | {
14
- kind: "affectedRows";
15
- affectedRows: number;
13
+ export type LixNotice = {
14
+ code: string;
15
+ message: string;
16
+ hint?: string;
16
17
  };
18
+ export declare class Row {
19
+ readonly columns: string[];
20
+ private readonly valuesByIndex;
21
+ constructor(columns: string[], values: Value[]);
22
+ get(columnName: string): LixNativeValue;
23
+ tryGet(columnName: string): LixNativeValue | undefined;
24
+ value(columnName: string): Value;
25
+ tryValue(columnName: string): Value | undefined;
26
+ getAt(index: number): LixNativeValue;
27
+ valueAt(index: number): Value;
28
+ values(): Value[];
29
+ toObject(): Record<string, LixNativeValue>;
30
+ toValueMap(): Record<string, Value>;
31
+ private availableColumns;
32
+ }
17
33
  export type TransactionBeginMode = "read" | "write" | "deferred";
18
34
  export type KvScanRange = {
19
35
  kind: "prefix";
@@ -47,9 +63,13 @@ export type OpenLixOptions = {
47
63
  export type CreateVersionOptions = {
48
64
  id?: string;
49
65
  name: string;
66
+ fromCommitId?: string;
50
67
  };
51
68
  export type CreateVersionResult = {
52
- versionId: string;
69
+ id: string;
70
+ name: string;
71
+ hidden: boolean;
72
+ commitId: string;
53
73
  };
54
74
  export type SwitchVersionOptions = {
55
75
  versionId: string;
@@ -60,7 +80,7 @@ export type SwitchVersionResult = {
60
80
  export type MergeVersionOptions = {
61
81
  sourceVersionId: string;
62
82
  };
63
- export type MergeVersionOutcome = "alreadyUpToDate" | "mergeCommitted";
83
+ export type MergeVersionOutcome = "alreadyUpToDate" | "fastForward" | "mergeCommitted";
64
84
  export type MergeVersionResult = {
65
85
  outcome: MergeVersionOutcome;
66
86
  targetVersionId: string;
@@ -73,6 +93,14 @@ export type MergeVersionResult = {
73
93
  appliedChangeCount: number;
74
94
  };
75
95
  export type Lix = {
96
+ /**
97
+ * Executes one DataFusion SQL statement against this Lix session.
98
+ *
99
+ * This is not SQLite SQL. Use the DataFusion SQL dialect; positional
100
+ * placeholders are `$1`, `$2`, and so on. SQLite-specific catalog tables and
101
+ * transaction statements such as `sqlite_master`, `BEGIN`, and `COMMIT` are
102
+ * not available. Use `information_schema` for catalog inspection.
103
+ */
76
104
  execute(sql: string, params?: ReadonlyArray<LixRuntimeValue>): Promise<ExecuteResult>;
77
105
  activeVersionId(): Promise<string>;
78
106
  createVersion(options: CreateVersionOptions): Promise<CreateVersionResult>;
package/dist/open-lix.js CHANGED
@@ -1,5 +1,74 @@
1
1
  import init, { resolveEngineWasmModuleOrPath, Value, } from "./engine-wasm/index.js";
2
2
  import * as wasmModule from "./engine-wasm/index.js";
3
+ export class Row {
4
+ columns;
5
+ valuesByIndex;
6
+ constructor(columns, values) {
7
+ this.columns = columns;
8
+ this.valuesByIndex = values;
9
+ }
10
+ get(columnName) {
11
+ return valueToNative(this.value(columnName));
12
+ }
13
+ tryGet(columnName) {
14
+ const value = this.tryValue(columnName);
15
+ return value === undefined ? undefined : valueToNative(value);
16
+ }
17
+ value(columnName) {
18
+ const index = this.columns.indexOf(columnName);
19
+ if (index === -1) {
20
+ throw createLixError("LIX_COLUMN_NOT_FOUND", `Column "${columnName}" does not exist. Available columns: ${this.availableColumns()}`);
21
+ }
22
+ const value = this.valuesByIndex[index];
23
+ if (value === undefined) {
24
+ throw createLixError("LIX_COLUMN_NOT_FOUND", `Column "${columnName}" is outside row width ${this.valuesByIndex.length}.`);
25
+ }
26
+ return value;
27
+ }
28
+ tryValue(columnName) {
29
+ const index = this.columns.indexOf(columnName);
30
+ return index === -1 ? undefined : this.valuesByIndex[index];
31
+ }
32
+ getAt(index) {
33
+ return valueToNative(this.valueAt(index));
34
+ }
35
+ valueAt(index) {
36
+ const value = this.valuesByIndex[index];
37
+ if (value === undefined) {
38
+ throw createLixError("LIX_COLUMN_NOT_FOUND", `Column index ${index} is outside row width ${this.valuesByIndex.length}.`);
39
+ }
40
+ return value;
41
+ }
42
+ values() {
43
+ return [...this.valuesByIndex];
44
+ }
45
+ toObject() {
46
+ return Object.fromEntries(this.columns.map((column, index) => [
47
+ column,
48
+ valueToNative(this.valueAt(index)),
49
+ ]));
50
+ }
51
+ toValueMap() {
52
+ return Object.fromEntries(this.columns.map((column, index) => [column, this.valueAt(index)]));
53
+ }
54
+ availableColumns() {
55
+ return this.columns.length === 0 ? "<none>" : this.columns.join(", ");
56
+ }
57
+ }
58
+ function valueToNative(value) {
59
+ switch (value.kind) {
60
+ case "null":
61
+ return null;
62
+ case "boolean":
63
+ case "integer":
64
+ case "real":
65
+ case "text":
66
+ case "json":
67
+ return value.value;
68
+ case "blob":
69
+ return value.asBlob() ?? new Uint8Array();
70
+ }
71
+ }
3
72
  let wasmReady = null;
4
73
  async function ensureWasmReady() {
5
74
  if (!wasmReady) {
@@ -13,7 +82,7 @@ export async function openLix(options = {}) {
13
82
  await ensureWasmReady();
14
83
  try {
15
84
  const wasmLix = (await wasmModule.openLix(options));
16
- return createLixHandle(wasmLix, options.backend);
85
+ return createLixHandle(wasmLix);
17
86
  }
18
87
  catch (error) {
19
88
  try {
@@ -22,17 +91,11 @@ export async function openLix(options = {}) {
22
91
  catch {
23
92
  // Preserve the original open failure.
24
93
  }
25
- throw error;
94
+ throw normalizeThrownError(error);
26
95
  }
27
96
  }
28
- function createLixHandle(wasmLix, backend) {
29
- let closed = false;
97
+ function createLixHandle(wasmLix) {
30
98
  let operationQueue = Promise.resolve();
31
- const ensureOpen = (methodName) => {
32
- if (closed) {
33
- throw new Error(`lix is closed; ${methodName}() is unavailable`);
34
- }
35
- };
36
99
  const acquireOperationSlot = async () => {
37
100
  const previous = operationQueue;
38
101
  let releaseCurrent;
@@ -48,57 +111,155 @@ function createLixHandle(wasmLix, backend) {
48
111
  try {
49
112
  return await operation();
50
113
  }
114
+ catch (error) {
115
+ throw normalizeThrownError(error);
116
+ }
51
117
  finally {
52
118
  release();
53
119
  }
54
120
  };
55
121
  return {
56
122
  async execute(sql, params = []) {
57
- ensureOpen("execute");
58
- const result = await runQueued(() => wasmLix.execute(sql, params.map((param) => Value.from(param))));
123
+ validateExecuteArguments(sql, params);
124
+ const values = params.map((param, index) => valueFromExecuteParam(param, index));
125
+ const result = await runQueued(() => wasmLix.execute(sql, values));
59
126
  return normalizeExecuteResult(result);
60
127
  },
61
128
  async activeVersionId() {
62
- ensureOpen("activeVersionId");
63
129
  return await runQueued(() => wasmLix.activeVersionId());
64
130
  },
65
131
  async createVersion(options) {
66
- ensureOpen("createVersion");
67
132
  return await runQueued(() => wasmLix.createVersion(options));
68
133
  },
69
134
  async switchVersion(options) {
70
- ensureOpen("switchVersion");
71
135
  return await runQueued(() => wasmLix.switchVersion(options));
72
136
  },
73
137
  async mergeVersion(options) {
74
- ensureOpen("mergeVersion");
75
138
  return await runQueued(() => wasmLix.mergeVersion(options));
76
139
  },
77
140
  async close() {
78
- if (closed)
79
- return;
80
- try {
81
- await runQueued(() => wasmLix.close());
82
- }
83
- finally {
84
- backend?.close?.();
85
- closed = true;
86
- }
141
+ await runQueued(() => wasmLix.close());
87
142
  },
88
143
  };
89
144
  }
90
- function normalizeExecuteResult(result) {
91
- if (result.kind === "rows") {
92
- return {
93
- kind: "rows",
94
- rows: {
95
- columns: [...result.rows.columns],
96
- rows: result.rows.rows.map((row) => row.map((value) => Value.from(value))),
97
- },
98
- };
145
+ function validateExecuteArguments(sql, params) {
146
+ if (typeof sql !== "string") {
147
+ throw invalidArgumentError("execute", "sql", "string", sql);
99
148
  }
149
+ if (!Array.isArray(params)) {
150
+ throw invalidArgumentError("execute", "params", "array", params);
151
+ }
152
+ }
153
+ function invalidArgumentError(operation, argument, expected, actualValue) {
154
+ return createLixError("LIX_INVALID_ARGUMENT", `lix.${operation}() expected ${argument} to be ${expectedArticle(expected)} ${expected}`, {
155
+ details: {
156
+ operation,
157
+ argument,
158
+ expected,
159
+ actual: runtimeTypeName(actualValue),
160
+ },
161
+ });
162
+ }
163
+ function valueFromExecuteParam(param, index) {
164
+ try {
165
+ return Value.from(param);
166
+ }
167
+ catch (error) {
168
+ throw invalidParamError(index, param, error);
169
+ }
170
+ }
171
+ function invalidParamError(index, actualValue, cause) {
172
+ const message = cause instanceof Error && cause.message
173
+ ? cause.message
174
+ : "parameter is not a valid Lix SQL value";
175
+ return createLixError("LIX_INVALID_PARAM", `lix.execute() invalid parameter $${index + 1}: ${message}`, {
176
+ details: {
177
+ operation: "execute",
178
+ parameter_index: index + 1,
179
+ argument: `params[${index}]`,
180
+ actual: runtimeTypeName(actualValue),
181
+ },
182
+ cause,
183
+ });
184
+ }
185
+ function expectedArticle(expected) {
186
+ return /^[aeiou]/i.test(expected) ? "an" : "a";
187
+ }
188
+ function runtimeTypeName(value) {
189
+ if (value === null)
190
+ return "null";
191
+ if (Array.isArray(value))
192
+ return "array";
193
+ if (value instanceof Date)
194
+ return "Date";
195
+ if (value instanceof ArrayBuffer)
196
+ return "ArrayBuffer";
197
+ if (ArrayBuffer.isView(value))
198
+ return value.constructor.name;
199
+ return typeof value;
200
+ }
201
+ function normalizeExecuteResult(result) {
202
+ const columns = [...result.columns];
100
203
  return {
101
- kind: "affectedRows",
102
- affectedRows: result.affectedRows,
204
+ columns,
205
+ rows: result.rows.map((row) => new Row(columns, row.map((value) => Value.from(value)))),
206
+ rowsAffected: result.rowsAffected,
207
+ notices: result.notices ?? [],
103
208
  };
104
209
  }
210
+ function createLixError(code, message, options = {}) {
211
+ const error = new Error(message);
212
+ error.name = "LixError";
213
+ error.code = code;
214
+ if (options.hint !== undefined) {
215
+ error.hint = options.hint;
216
+ }
217
+ if (options.details !== undefined) {
218
+ error.details = options.details;
219
+ }
220
+ if (options.cause !== undefined) {
221
+ error.cause = options.cause;
222
+ }
223
+ return error;
224
+ }
225
+ function normalizeThrownError(error) {
226
+ if (isLixErrorLike(error)) {
227
+ const hint = typeof error.hint === "string"
228
+ ? error.hint
229
+ : extractHintFromMessage(error.message);
230
+ const details = "details" in error ? error.details : undefined;
231
+ if (error instanceof Error) {
232
+ if (hint !== undefined && error.hint === undefined) {
233
+ error.hint = hint;
234
+ }
235
+ if (details !== undefined && error.details === undefined) {
236
+ error.details = details;
237
+ }
238
+ return error;
239
+ }
240
+ const message = typeof error.message === "string" ? error.message : error.code;
241
+ return createLixError(error.code, message, { hint, details });
242
+ }
243
+ if (error instanceof WebAssembly.RuntimeError) {
244
+ return createLixError("LIX_WASM_RUNTIME_ERROR", error.message, {
245
+ hint: "The Lix engine encountered a WebAssembly runtime trap. Please report this as an engine bug with the SQL statement or API call that triggered it.",
246
+ cause: error,
247
+ });
248
+ }
249
+ if (error instanceof Error) {
250
+ return createLixError("LIX_ERROR_UNKNOWN", error.message, { cause: error });
251
+ }
252
+ return createLixError("LIX_ERROR_UNKNOWN", String(error));
253
+ }
254
+ function extractHintFromMessage(message) {
255
+ if (typeof message !== "string")
256
+ return undefined;
257
+ const match = message.match(/(?:^|\n)hint:\s*(.+)$/s);
258
+ return match?.[1]?.trim();
259
+ }
260
+ function isLixErrorLike(error) {
261
+ return (typeof error === "object" &&
262
+ error !== null &&
263
+ typeof error.code === "string" &&
264
+ error.code.startsWith("LIX_"));
265
+ }
package/package.json CHANGED
@@ -1,11 +1,12 @@
1
1
  {
2
2
  "name": "@lix-js/sdk",
3
3
  "type": "module",
4
- "version": "0.6.0-preview.0",
4
+ "version": "0.6.0-preview.1",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
7
7
  "files": [
8
- "dist"
8
+ "dist",
9
+ "SKILL.md"
9
10
  ],
10
11
  "exports": {
11
12
  ".": {