@lix-js/sdk 0.6.0-preview.0 → 0.6.0-preview.2

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.
Files changed (196) hide show
  1. package/README.md +9 -0
  2. package/SKILL.md +468 -0
  3. package/dist/engine-wasm/index.d.ts +15 -11
  4. package/dist/engine-wasm/index.js +105 -38
  5. package/dist/engine-wasm/wasm/lix_engine.d.ts +14 -2
  6. package/dist/engine-wasm/wasm/lix_engine.js +18 -17
  7. package/dist/engine-wasm/wasm/lix_engine.wasm +0 -0
  8. package/dist/engine-wasm/wasm/lix_engine.wasm.d.ts +2 -1
  9. package/dist/generated/builtin-schemas.d.ts +31 -41
  10. package/dist/generated/builtin-schemas.js +52 -56
  11. package/dist/open-lix.d.ts +141 -24
  12. package/dist/open-lix.js +199 -35
  13. package/dist/sqlite/index.js +99 -22
  14. package/dist-engine-src/README.md +18 -0
  15. package/dist-engine-src/src/backend/kv.rs +358 -0
  16. package/dist-engine-src/src/backend/mod.rs +12 -0
  17. package/dist-engine-src/src/backend/testing.rs +658 -0
  18. package/dist-engine-src/src/backend/types.rs +96 -0
  19. package/dist-engine-src/src/binary_cas/chunking.rs +31 -0
  20. package/dist-engine-src/src/binary_cas/codec.rs +346 -0
  21. package/dist-engine-src/src/binary_cas/context.rs +139 -0
  22. package/dist-engine-src/src/binary_cas/kv.rs +1063 -0
  23. package/dist-engine-src/src/binary_cas/mod.rs +11 -0
  24. package/dist-engine-src/src/binary_cas/types.rs +127 -0
  25. package/dist-engine-src/src/cel/context.rs +86 -0
  26. package/dist-engine-src/src/cel/error.rs +19 -0
  27. package/dist-engine-src/src/cel/mod.rs +8 -0
  28. package/dist-engine-src/src/cel/provider.rs +9 -0
  29. package/dist-engine-src/src/cel/runtime.rs +167 -0
  30. package/dist-engine-src/src/cel/value.rs +50 -0
  31. package/dist-engine-src/src/changelog/codec.rs +321 -0
  32. package/dist-engine-src/src/changelog/context.rs +92 -0
  33. package/dist-engine-src/src/changelog/materialization.rs +121 -0
  34. package/dist-engine-src/src/changelog/mod.rs +13 -0
  35. package/dist-engine-src/src/changelog/reader.rs +20 -0
  36. package/dist-engine-src/src/changelog/storage.rs +220 -0
  37. package/dist-engine-src/src/changelog/types.rs +38 -0
  38. package/dist-engine-src/src/commit_graph/context.rs +1588 -0
  39. package/dist-engine-src/src/commit_graph/mod.rs +12 -0
  40. package/dist-engine-src/src/commit_graph/types.rs +145 -0
  41. package/dist-engine-src/src/commit_graph/walker.rs +780 -0
  42. package/dist-engine-src/src/common/error.rs +313 -0
  43. package/dist-engine-src/src/common/fingerprint.rs +3 -0
  44. package/dist-engine-src/src/common/fs_path.rs +1336 -0
  45. package/dist-engine-src/src/common/identity.rs +135 -0
  46. package/dist-engine-src/src/common/metadata.rs +35 -0
  47. package/dist-engine-src/src/common/mod.rs +23 -0
  48. package/dist-engine-src/src/common/types.rs +105 -0
  49. package/dist-engine-src/src/common/wire.rs +222 -0
  50. package/dist-engine-src/src/engine.rs +239 -0
  51. package/dist-engine-src/src/entity_identity.rs +285 -0
  52. package/dist-engine-src/src/functions/context.rs +327 -0
  53. package/dist-engine-src/src/functions/deterministic.rs +113 -0
  54. package/dist-engine-src/src/functions/mod.rs +18 -0
  55. package/dist-engine-src/src/functions/provider.rs +130 -0
  56. package/dist-engine-src/src/functions/state.rs +363 -0
  57. package/dist-engine-src/src/functions/types.rs +37 -0
  58. package/dist-engine-src/src/init.rs +505 -0
  59. package/dist-engine-src/src/json_store/compression.rs +77 -0
  60. package/dist-engine-src/src/json_store/context.rs +129 -0
  61. package/dist-engine-src/src/json_store/encoded.rs +15 -0
  62. package/dist-engine-src/src/json_store/mod.rs +9 -0
  63. package/dist-engine-src/src/json_store/store.rs +236 -0
  64. package/dist-engine-src/src/json_store/types.rs +52 -0
  65. package/dist-engine-src/src/lib.rs +61 -0
  66. package/dist-engine-src/src/live_state/context.rs +2241 -0
  67. package/dist-engine-src/src/live_state/mod.rs +15 -0
  68. package/dist-engine-src/src/live_state/overlay.rs +75 -0
  69. package/dist-engine-src/src/live_state/reader.rs +23 -0
  70. package/dist-engine-src/src/live_state/types.rs +239 -0
  71. package/dist-engine-src/src/live_state/visibility.rs +218 -0
  72. package/dist-engine-src/src/plugin/archive.rs +441 -0
  73. package/dist-engine-src/src/plugin/component.rs +183 -0
  74. package/dist-engine-src/src/plugin/install.rs +637 -0
  75. package/dist-engine-src/src/plugin/manifest.rs +516 -0
  76. package/dist-engine-src/src/plugin/materializer.rs +477 -0
  77. package/dist-engine-src/src/plugin/mod.rs +33 -0
  78. package/dist-engine-src/src/plugin/plugin_manifest.json +119 -0
  79. package/dist-engine-src/src/plugin/storage.rs +74 -0
  80. package/dist-engine-src/src/schema/annotations/defaults.rs +280 -0
  81. package/dist-engine-src/src/schema/annotations/mod.rs +1 -0
  82. package/dist-engine-src/src/schema/builtin/lix_account.json +22 -0
  83. package/dist-engine-src/src/schema/builtin/lix_active_account.json +30 -0
  84. package/dist-engine-src/src/schema/builtin/lix_binary_blob_ref.json +30 -0
  85. package/dist-engine-src/src/schema/builtin/lix_change.json +62 -0
  86. package/dist-engine-src/src/schema/builtin/lix_change_author.json +46 -0
  87. package/dist-engine-src/src/schema/builtin/lix_change_set.json +18 -0
  88. package/dist-engine-src/src/schema/builtin/lix_change_set_element.json +75 -0
  89. package/dist-engine-src/src/schema/builtin/lix_commit.json +62 -0
  90. package/dist-engine-src/src/schema/builtin/lix_commit_edge.json +46 -0
  91. package/dist-engine-src/src/schema/builtin/lix_directory_descriptor.json +53 -0
  92. package/dist-engine-src/src/schema/builtin/lix_entity_label.json +63 -0
  93. package/dist-engine-src/src/schema/builtin/lix_file_descriptor.json +53 -0
  94. package/dist-engine-src/src/schema/builtin/lix_key_value.json +41 -0
  95. package/dist-engine-src/src/schema/builtin/lix_label.json +22 -0
  96. package/dist-engine-src/src/schema/builtin/lix_registered_schema.json +31 -0
  97. package/dist-engine-src/src/schema/builtin/lix_version_descriptor.json +35 -0
  98. package/dist-engine-src/src/schema/builtin/lix_version_ref.json +49 -0
  99. package/dist-engine-src/src/schema/builtin/mod.rs +271 -0
  100. package/dist-engine-src/src/schema/definition.json +157 -0
  101. package/dist-engine-src/src/schema/definition.rs +636 -0
  102. package/dist-engine-src/src/schema/key.rs +206 -0
  103. package/dist-engine-src/src/schema/mod.rs +20 -0
  104. package/dist-engine-src/src/schema/seed.rs +14 -0
  105. package/dist-engine-src/src/schema/tests.rs +739 -0
  106. package/dist-engine-src/src/schema_registry.rs +294 -0
  107. package/dist-engine-src/src/session/context.rs +366 -0
  108. package/dist-engine-src/src/session/create_version.rs +80 -0
  109. package/dist-engine-src/src/session/execute.rs +447 -0
  110. package/dist-engine-src/src/session/merge/analysis.rs +102 -0
  111. package/dist-engine-src/src/session/merge/apply.rs +23 -0
  112. package/dist-engine-src/src/session/merge/conflicts.rs +62 -0
  113. package/dist-engine-src/src/session/merge/mod.rs +11 -0
  114. package/dist-engine-src/src/session/merge/stats.rs +65 -0
  115. package/dist-engine-src/src/session/merge/version.rs +437 -0
  116. package/dist-engine-src/src/session/mod.rs +25 -0
  117. package/dist-engine-src/src/session/switch_version.rs +121 -0
  118. package/dist-engine-src/src/sql2/change_provider.rs +337 -0
  119. package/dist-engine-src/src/sql2/classify.rs +147 -0
  120. package/dist-engine-src/src/sql2/commit_derived_provider.rs +591 -0
  121. package/dist-engine-src/src/sql2/context.rs +307 -0
  122. package/dist-engine-src/src/sql2/directory_history_provider.rs +623 -0
  123. package/dist-engine-src/src/sql2/directory_provider.rs +2405 -0
  124. package/dist-engine-src/src/sql2/dml.rs +148 -0
  125. package/dist-engine-src/src/sql2/entity_history_provider.rs +444 -0
  126. package/dist-engine-src/src/sql2/entity_provider.rs +2700 -0
  127. package/dist-engine-src/src/sql2/error.rs +196 -0
  128. package/dist-engine-src/src/sql2/execute.rs +3379 -0
  129. package/dist-engine-src/src/sql2/file_history_provider.rs +902 -0
  130. package/dist-engine-src/src/sql2/file_provider.rs +3254 -0
  131. package/dist-engine-src/src/sql2/filesystem_planner.rs +1526 -0
  132. package/dist-engine-src/src/sql2/filesystem_predicates.rs +159 -0
  133. package/dist-engine-src/src/sql2/filesystem_visibility.rs +369 -0
  134. package/dist-engine-src/src/sql2/history_projection.rs +80 -0
  135. package/dist-engine-src/src/sql2/history_provider.rs +418 -0
  136. package/dist-engine-src/src/sql2/history_route.rs +643 -0
  137. package/dist-engine-src/src/sql2/lix_state_provider.rs +2430 -0
  138. package/dist-engine-src/src/sql2/mod.rs +43 -0
  139. package/dist-engine-src/src/sql2/read_only.rs +65 -0
  140. package/dist-engine-src/src/sql2/record_batch.rs +17 -0
  141. package/dist-engine-src/src/sql2/result_metadata.rs +29 -0
  142. package/dist-engine-src/src/sql2/runtime.rs +60 -0
  143. package/dist-engine-src/src/sql2/session.rs +135 -0
  144. package/dist-engine-src/src/sql2/udfs/common.rs +295 -0
  145. package/dist-engine-src/src/sql2/udfs/lix_active_version_commit_id.rs +53 -0
  146. package/dist-engine-src/src/sql2/udfs/lix_empty_blob.rs +47 -0
  147. package/dist-engine-src/src/sql2/udfs/lix_json.rs +100 -0
  148. package/dist-engine-src/src/sql2/udfs/lix_json_get.rs +99 -0
  149. package/dist-engine-src/src/sql2/udfs/lix_json_get_text.rs +99 -0
  150. package/dist-engine-src/src/sql2/udfs/lix_text_decode.rs +82 -0
  151. package/dist-engine-src/src/sql2/udfs/lix_text_encode.rs +85 -0
  152. package/dist-engine-src/src/sql2/udfs/lix_uuid_v7.rs +76 -0
  153. package/dist-engine-src/src/sql2/udfs/mod.rs +82 -0
  154. package/dist-engine-src/src/sql2/version_provider.rs +1187 -0
  155. package/dist-engine-src/src/sql2/version_scope.rs +394 -0
  156. package/dist-engine-src/src/sql2/write_normalization.rs +345 -0
  157. package/dist-engine-src/src/storage/context.rs +356 -0
  158. package/dist-engine-src/src/storage/mod.rs +14 -0
  159. package/dist-engine-src/src/storage/read_scope.rs +88 -0
  160. package/dist-engine-src/src/storage/types.rs +501 -0
  161. package/dist-engine-src/src/storage_bench.rs +3406 -0
  162. package/dist-engine-src/src/test_support.rs +81 -0
  163. package/dist-engine-src/src/tracked_state/by_file_index.rs +102 -0
  164. package/dist-engine-src/src/tracked_state/codec.rs +747 -0
  165. package/dist-engine-src/src/tracked_state/context.rs +983 -0
  166. package/dist-engine-src/src/tracked_state/diff.rs +494 -0
  167. package/dist-engine-src/src/tracked_state/materialization.rs +141 -0
  168. package/dist-engine-src/src/tracked_state/merge.rs +474 -0
  169. package/dist-engine-src/src/tracked_state/mod.rs +31 -0
  170. package/dist-engine-src/src/tracked_state/rebuild.rs +771 -0
  171. package/dist-engine-src/src/tracked_state/storage.rs +243 -0
  172. package/dist-engine-src/src/tracked_state/tree.rs +2744 -0
  173. package/dist-engine-src/src/tracked_state/tree_types.rs +176 -0
  174. package/dist-engine-src/src/tracked_state/types.rs +61 -0
  175. package/dist-engine-src/src/transaction/commit.rs +1224 -0
  176. package/dist-engine-src/src/transaction/context.rs +1307 -0
  177. package/dist-engine-src/src/transaction/live_state_overlay.rs +34 -0
  178. package/dist-engine-src/src/transaction/mod.rs +11 -0
  179. package/dist-engine-src/src/transaction/normalization.rs +1026 -0
  180. package/dist-engine-src/src/transaction/schema_resolver.rs +127 -0
  181. package/dist-engine-src/src/transaction/staging.rs +1436 -0
  182. package/dist-engine-src/src/transaction/types.rs +351 -0
  183. package/dist-engine-src/src/transaction/validation.rs +4811 -0
  184. package/dist-engine-src/src/untracked_state/codec.rs +363 -0
  185. package/dist-engine-src/src/untracked_state/context.rs +82 -0
  186. package/dist-engine-src/src/untracked_state/materialization.rs +157 -0
  187. package/dist-engine-src/src/untracked_state/mod.rs +17 -0
  188. package/dist-engine-src/src/untracked_state/storage.rs +348 -0
  189. package/dist-engine-src/src/untracked_state/types.rs +96 -0
  190. package/dist-engine-src/src/version/context.rs +52 -0
  191. package/dist-engine-src/src/version/mod.rs +12 -0
  192. package/dist-engine-src/src/version/refs.rs +421 -0
  193. package/dist-engine-src/src/version/stage_rows.rs +71 -0
  194. package/dist-engine-src/src/version/types.rs +21 -0
  195. package/dist-engine-src/src/wasm/mod.rs +60 -0
  196. package/package.json +68 -63
package/README.md ADDED
@@ -0,0 +1,9 @@
1
+ # @lix-js/sdk
2
+
3
+ WASM-backed JavaScript SDK for Lix.
4
+
5
+ ## Agent Guidance
6
+
7
+ If you are an AI coding agent using this package, read [`SKILL.md`](./SKILL.md) before building examples, demos, tests, or applications with `@lix-js/sdk`.
8
+
9
+ The skill documents the current preview API, recommended SQLite backend setup, schema registration flow, entity-table writes, version workflows, merge behavior, and known sharp edges.
package/SKILL.md ADDED
@@ -0,0 +1,468 @@
1
+ ---
2
+ name: lix-js-sdk
3
+ description: Use this skill when building examples, demos, tests, or applications with @lix-js/sdk: opening a Lix, registering schemas, writing entities through generated SQL tables, creating named versions, merging, and querying change history.
4
+ ---
5
+
6
+ # Lix JS SDK Skill
7
+
8
+ ## What Is Lix
9
+
10
+ Lix is an embeddable version control system for structured application state. It gives apps named versions, merge, and an immutable SQL-queryable change journal without asking the app to build those systems from scratch.
11
+
12
+ Current `@lix-js/sdk` capabilities:
13
+
14
+ - Register JSON schemas as tracked entity tables.
15
+ - Read and write entities through generated SQL tables.
16
+ - Create named versions of state and write/read across versions.
17
+ - Merge one version into the active version.
18
+ - Query `lix_change` for history, audit, activity feeds, and undo-style features.
19
+ - Store files as bytes with `lix_file` and version them like other entities.
20
+
21
+ Product direction:
22
+
23
+ - Lix is designed to version files of any kind by parsing them into typed entities on write.
24
+ - Parser plugins that turn file contents into app entities are not shipped through the JS SDK yet. Do not promise this behavior in demos. Today, `lix_file` versions bytes, while app entities are modeled directly through registered schemas.
25
+
26
+ Every row in every registered schema is a tracked entity. Merge granularity is currently per-entity, not per-field: two versions editing different rows merge cleanly; two versions editing the same row conflict, even if the fields are disjoint. Model collaborative domains as many small entities, such as sections, blocks, paragraphs, message keys, or line items.
27
+
28
+ Use Lix vocabulary in user-facing copy. What Git calls a branch is called a **version** in Lix because that language makes sense to non-developers.
29
+
30
+ ## When To Use This Skill
31
+
32
+ Use this skill when you need to write or debug consumer code using `@lix-js/sdk`:
33
+
34
+ - Opening a persistent `.lix` file.
35
+ - Registering schemas.
36
+ - Writing and reading generated SQL entity tables.
37
+ - Reading `execute()` results.
38
+ - Creating, switching, previewing, and merging versions.
39
+ - Querying history through `lix_change`.
40
+ - Building app demos, examples, smoke tests, or product flows around the SDK.
41
+
42
+ Do not use this skill for raw SQLite access, private engine/wasm internals, SDK publishing, SDK build pipelines, or unreleased file-parser plugin behavior.
43
+
44
+ ## Agent Quick Start
45
+
46
+ 1. Install `@lix-js/sdk` and `better-sqlite3`.
47
+ 2. Open with `createBetterSqlite3Backend({ path })`; do not open `.lix` with raw SQLite.
48
+ 3. Register a schema with `x-lix-key`, `x-lix-version`, `x-lix-primary-key`, and `additionalProperties: false`.
49
+ 4. Write rows through the generated table named by `x-lix-key`.
50
+ 5. Use `<schema>_by_version` plus `lixcol_version_id` for side-by-side version reads/writes.
51
+ 6. Query `lix_change` for audit/history instead of hand-rolling audit tables.
52
+ 7. Wrap `mergeVersion()` in `try/catch` whenever conflicts are possible.
53
+
54
+ ## Core Rules
55
+
56
+ - Use the public `@lix-js/sdk` API only.
57
+ - Use `createBetterSqlite3Backend()` for persistent apps, demos, and tests.
58
+ - Use numbered SQL placeholders: `$1`, `$2`, `$3`; bare `?` is rejected.
59
+ - Use `lix_json($1)` when inserting JSON text into JSON-typed columns.
60
+ - Use stable, namespaced, lowercase schema keys like `acme_section`, not generic names like `task`.
61
+ - Always include `x-lix-primary-key` and `additionalProperties: false` on app schemas.
62
+ - Use version names from the user's vocabulary, such as `"Marketing edit"` or `"Q3 pricing draft"`.
63
+ - Model concurrent-edit domains as collections of small rows because merge is per-row today.
64
+ - Prefer `_by_version` tables for demos, sync, agent inspection, and side-by-side diffs.
65
+ - Close handles in scripts and tests with `await lix.close()`.
66
+
67
+ ## Install And Open
68
+
69
+ ```sh
70
+ npm i @lix-js/sdk better-sqlite3
71
+ ```
72
+
73
+ ```ts
74
+ import { openLix } from "@lix-js/sdk";
75
+ import { createBetterSqlite3Backend } from "@lix-js/sdk/sqlite";
76
+
77
+ const lix = await openLix({
78
+ backend: createBetterSqlite3Backend({ path: "/path/to/app.lix" }),
79
+ });
80
+ ```
81
+
82
+ `better-sqlite3` is an optional peer dependency. Install it in projects that import `@lix-js/sdk/sqlite`.
83
+
84
+ `openLix()` without a backend is in-memory and dies with the process. For anything that should persist, pass a real `.lix` path. Reopening the same path picks up existing state.
85
+
86
+ For tests and demos, use an isolated temp directory per run:
87
+
88
+ ```ts
89
+ import { mkdtempSync } from "node:fs";
90
+ import { tmpdir } from "node:os";
91
+ import path from "node:path";
92
+ import { openLix } from "@lix-js/sdk";
93
+ import { createBetterSqlite3Backend } from "@lix-js/sdk/sqlite";
94
+
95
+ const dir = mkdtempSync(path.join(tmpdir(), "lix-"));
96
+ const lix = await openLix({
97
+ backend: createBetterSqlite3Backend({ path: path.join(dir, "demo.lix") }),
98
+ });
99
+ ```
100
+
101
+ Use the version of this skill that ships with the installed `@lix-js/sdk` package. If behavior is unclear, inspect the installed package before guessing. The npm package bundles matching engine source under `node_modules/@lix-js/sdk/dist-engine-src/`.
102
+
103
+ Useful installed-package references:
104
+
105
+ - `dist-engine-src/src/sql2/entity_provider.rs` - registered schema SQL surfaces.
106
+ - `dist-engine-src/src/sql2/change_provider.rs` - `lix_change` projection.
107
+ - `dist-engine-src/src/sql2/version_provider.rs` - writable `lix_version` surface.
108
+ - `dist-engine-src/src/transaction/validation.rs` - primary-key, unique, foreign-key, and shape validation.
109
+ - `dist-engine-src/src/schema/definition.json` - Lix schema-definition meta-schema.
110
+ - `dist-engine-src/src/schema/builtin/` - built-in entity table shapes.
111
+ - `dist-engine-src/src/sql2/udfs/` - registered SQL functions.
112
+
113
+ Do not import from `@lix-js/sdk/engine-wasm`, do not call private wasm helpers, and do not open the `.lix` SQLite file directly.
114
+
115
+ ## Minimal Entity Example
116
+
117
+ This is the smallest useful consumer pattern: open, register a schema, write a row, read it back, and close.
118
+
119
+ ```ts
120
+ import { mkdtempSync } from "node:fs";
121
+ import { tmpdir } from "node:os";
122
+ import path from "node:path";
123
+ import { openLix } from "@lix-js/sdk";
124
+ import { createBetterSqlite3Backend } from "@lix-js/sdk/sqlite";
125
+
126
+ const dir = mkdtempSync(path.join(tmpdir(), "lix-"));
127
+ const lix = await openLix({
128
+ backend: createBetterSqlite3Backend({ path: path.join(dir, "demo.lix") }),
129
+ });
130
+
131
+ await lix.execute(
132
+ "INSERT INTO lix_registered_schema (value) VALUES (lix_json($1))",
133
+ [
134
+ JSON.stringify({
135
+ $schema: "https://json-schema.org/draft/2020-12/schema",
136
+ "x-lix-key": "acme_note",
137
+ "x-lix-version": "1",
138
+ "x-lix-primary-key": ["/id"],
139
+ type: "object",
140
+ required: ["id", "title", "done"],
141
+ properties: {
142
+ id: { type: "string" },
143
+ title: { type: "string" },
144
+ done: { type: "boolean" },
145
+ },
146
+ additionalProperties: false,
147
+ }),
148
+ ],
149
+ );
150
+
151
+ await lix.execute(
152
+ "INSERT INTO acme_note (id, title, done) VALUES ($1, $2, $3)",
153
+ ["n1", "Draft launch copy", false],
154
+ );
155
+
156
+ const result = await lix.execute(
157
+ "SELECT title, done FROM acme_note WHERE id = $1",
158
+ ["n1"],
159
+ );
160
+
161
+ const row = result.rows[0]!;
162
+ console.log(row.value("title").asText(), row.value("done").asBoolean());
163
+
164
+ await lix.close();
165
+ ```
166
+
167
+ ## Reading Results
168
+
169
+ `lix.execute()` returns one shape for every statement:
170
+
171
+ ```ts
172
+ type ExecuteResult = {
173
+ columns: string[];
174
+ rows: Row[];
175
+ rowsAffected: number;
176
+ notices: LixNotice[];
177
+ };
178
+ ```
179
+
180
+ There is no `result.kind`. `SELECT` fills `columns` and `rows`; `INSERT`, `UPDATE`, and `DELETE` usually return `rows: []` and set `rowsAffected`.
181
+
182
+ Each row is a `Row` object. Use `row.value("column")` or `row.valueAt(index)` to get a `Value`, then call typed accessors:
183
+
184
+ ```ts
185
+ const r = await lix.execute("SELECT id, title, done FROM acme_note");
186
+ for (const row of r.rows) {
187
+ const id = row.value("id").asText();
188
+ const title = row.value("title").asText();
189
+ const done = row.value("done").asBoolean();
190
+ }
191
+ ```
192
+
193
+ | Method | Returns | Use for |
194
+ | ------------- | ------------------------- | ----------------------------------------- |
195
+ | `asText()` | `string \| undefined` | strings; note `asText`, not `asString` |
196
+ | `asBoolean()` | `boolean \| undefined` | booleans |
197
+ | `asInteger()` | `number \| undefined` | integer fields |
198
+ | `asReal()` | `number \| undefined` | decimal/real fields |
199
+ | `asJson()` | `JsonValue \| undefined` | objects and arrays |
200
+ | `asBlob()` | `Uint8Array \| undefined` | binary data |
201
+
202
+ Accessors return `undefined` when the cell kind does not match. Branch on `value.kind` if a column can hold multiple types. Public kind strings are `"null"`, `"boolean"`, `"integer"`, `"real"`, `"text"`, `"json"`, and `"blob"`.
203
+
204
+ `Row` also has convenience methods when native JS values are enough: `get(name)`, `tryGet(name)`, `getAt(index)`, `toObject()`, and `toValueMap()`.
205
+
206
+ ## Registering Schemas
207
+
208
+ Register app schemas by inserting JSON into `lix_registered_schema.value`:
209
+
210
+ ```ts
211
+ await lix.execute(
212
+ "INSERT INTO lix_registered_schema (value) VALUES (lix_json($1))",
213
+ [JSON.stringify(schema)],
214
+ );
215
+ ```
216
+
217
+ Schema basics:
218
+
219
+ - `x-lix-key` becomes the generated SQL table name.
220
+ - `x-lix-version` versions your schema contract.
221
+ - `x-lix-primary-key` tells Lix how to derive entity identity.
222
+ - Primary-key entries are JSON Pointers with a leading slash, such as `["/id"]` or `["/owner/email"]`.
223
+ - Use `additionalProperties: false` so accidental fields fail fast.
224
+
225
+ Without `x-lix-primary-key`, table-style INSERTs fail with an error like `requires lixcol_entity_id because the schema has no x-lix-primary-key`.
226
+
227
+ Uniqueness is not inferred from ordinary JSON Schema fields. If a non-primary-key field must be unique, declare it explicitly:
228
+
229
+ ```ts
230
+ const companyDomainSchema = {
231
+ "x-lix-key": "crm_company_domain",
232
+ "x-lix-version": "1",
233
+ "x-lix-primary-key": ["/id"],
234
+ "x-lix-unique": [["/domain"]],
235
+ type: "object",
236
+ required: ["id", "domain"],
237
+ properties: {
238
+ id: { type: "string" },
239
+ domain: { type: "string" },
240
+ },
241
+ additionalProperties: false,
242
+ };
243
+ ```
244
+
245
+ Do not add generic `created_at` or `updated_at` fields by default. Lix already records lifecycle history through `lix_change` and `lixcol_*` metadata. Add timestamp fields only when they are domain data, such as `due_at`, `published_at`, or `occurred_at`.
246
+
247
+ Discover live schemas before guessing:
248
+
249
+ ```ts
250
+ const schemas = await lix.execute(
251
+ "SELECT lixcol_entity_id, value FROM lix_registered_schema ORDER BY lixcol_entity_id",
252
+ );
253
+
254
+ for (const row of schemas.rows) {
255
+ const schema = row.get("value") as { "x-lix-key"?: string };
256
+ console.log(schema["x-lix-key"]);
257
+ }
258
+ ```
259
+
260
+ ## Versions And `_by_version`
261
+
262
+ Capture the initial active version id instead of hardcoding `"main"`:
263
+
264
+ ```ts
265
+ const published = await lix.activeVersionId();
266
+ ```
267
+
268
+ Create versions with names from the user's domain:
269
+
270
+ ```ts
271
+ const marketing = await lix.createVersion({ name: "Marketing edit" });
272
+ const legal = await lix.createVersion({ name: "Legal review" });
273
+ ```
274
+
275
+ Every registered schema `X` gets a sibling table `X_by_version` with `lixcol_version_id`. Use it for side-by-side reads and for writes to non-active versions.
276
+
277
+ ```ts
278
+ await lix.execute(
279
+ `UPDATE acme_note_by_version
280
+ SET title = $1
281
+ WHERE id = $2 AND lixcol_version_id = $3`,
282
+ ["Sharper launch copy", "n1", marketing.id],
283
+ );
284
+
285
+ const sideBySide = await lix.execute(
286
+ `SELECT v.name, n.title
287
+ FROM acme_note_by_version n
288
+ JOIN lix_version v ON v.id = n.lixcol_version_id
289
+ WHERE n.id = $1
290
+ AND n.lixcol_version_id IN ($2, $3)
291
+ ORDER BY v.name`,
292
+ ["n1", published, marketing.id],
293
+ );
294
+ ```
295
+
296
+ Rules for `_by_version`:
297
+
298
+ - Reads filter by `lixcol_version_id`, or omit the filter to scan all versions.
299
+ - INSERTs require `lixcol_version_id`.
300
+ - UPDATEs and DELETEs must include `lixcol_version_id` in the WHERE clause.
301
+ - The non-suffixed table is the active-version view.
302
+
303
+ `switchVersion()` is for app code with a current working version concept. `mergeVersion()` always merges into the active version, so switch first if you need a different target.
304
+
305
+ ## Merging
306
+
307
+ `mergeVersion()` merges the source version into the currently active version:
308
+
309
+ ```ts
310
+ try {
311
+ const merge = await lix.mergeVersion({ sourceVersionId: marketing.id });
312
+ console.log(merge.outcome, merge.changeStats.total);
313
+ } catch (error) {
314
+ console.error("Merge conflict", error);
315
+ }
316
+ ```
317
+
318
+ Common outcomes:
319
+
320
+ - `"alreadyUpToDate"` - source has no commits the target lacks.
321
+ - `"fastForward"` - target advanced to source without a merge commit.
322
+ - `"mergeCommitted"` - a new merge commit was created.
323
+
324
+ `mergeVersionPreview()` reports the same merge decision without advancing refs, staging changes, or creating commits. Merge conflicts are returned as preview data.
325
+
326
+ Conflicts throw from `mergeVersion()`. If both versions modified the same entity since their merge base, Lix raises a `LixError`. Conflict detection is row-level today, not field-level. To reproduce a conflict in a demo, fork all contending versions from the same base before merging any of them.
327
+
328
+ ## Demo Pattern To Imitate
329
+
330
+ For richer demos, show these four things:
331
+
332
+ 1. Isolation: one SELECT against `<schema>_by_version` shows several versions side by side.
333
+ 2. Clean parallel merges: two reviewers edit different entities and both land.
334
+ 3. Audit history: `lix_change` is queryable SQL.
335
+ 4. Conflict handling: two versions edit the same entity and `mergeVersion()` throws.
336
+
337
+ Shape the domain as a collection of small entities:
338
+
339
+ - Good: brochure sections, document blocks, paragraph rows, message keys, line items.
340
+ - Risky: one huge document row with many editable fields.
341
+
342
+ Demo recipe:
343
+
344
+ 1. Register a schema such as `acme_section`.
345
+ 2. Seed several rows in the published version.
346
+ 3. Create all reviewer versions up front from the same base.
347
+ 4. Write each reviewer's changes through `acme_section_by_version`.
348
+ 5. Read side by side by joining `acme_section_by_version` to `lix_version`.
349
+ 6. Merge non-overlapping row edits successfully.
350
+ 7. Query `lix_change`.
351
+ 8. Catch the deliberate same-row conflict.
352
+
353
+ ## Files With `lix_file`
354
+
355
+ `lix_file` stores files as versioned bytes. Parent directories are created automatically.
356
+
357
+ ```ts
358
+ await lix.execute("INSERT INTO lix_file (id, path, data) VALUES ($1, $2, $3)", [
359
+ "file-readme",
360
+ "/docs/readme.md",
361
+ new TextEncoder().encode("# Hello\n"),
362
+ ]);
363
+
364
+ const result = await lix.execute(
365
+ "SELECT path, data FROM lix_file WHERE id = $1",
366
+ ["file-readme"],
367
+ );
368
+
369
+ const file = result.rows[0]!;
370
+ console.log(
371
+ file.value("path").asText(),
372
+ new TextDecoder().decode(file.value("data").asBlob()!),
373
+ );
374
+ ```
375
+
376
+ Columns consumers usually need:
377
+
378
+ | Column | What it is |
379
+ | ---------- | --------------------------------------------------------------------- |
380
+ | `id` | Stable identity for the file. |
381
+ | `path` | Absolute path like `/docs/readme.md`. |
382
+ | `data` | File contents as bytes. |
383
+ | `hidden` | UI hint; does not affect storage. |
384
+ | `lixcol_*` | Version/change metadata, including `lixcol_version_id` where exposed. |
385
+
386
+ `lix_file_by_version` exists for cross-version file reads and writes. Files-as-parsed-entities are product direction, not current JS SDK behavior.
387
+
388
+ ## The Change Journal
389
+
390
+ `lix_change` is an immutable SQL table of changes across registered schemas and versions. Use it for audit logs, blame, history, activity feeds, and undo-style UI.
391
+
392
+ Important columns include `id`, `entity_id`, `schema_key`, `schema_version`, `snapshot_content`, `created_at`, and `lixcol_*` metadata.
393
+
394
+ ```ts
395
+ // Audit log for one entity, oldest to newest.
396
+ await lix.execute(
397
+ `SELECT created_at, snapshot_content
398
+ FROM lix_change
399
+ WHERE schema_key = $1 AND entity_id = $2
400
+ ORDER BY created_at`,
401
+ ["acme_note", "n1"],
402
+ );
403
+
404
+ // Latest activity across a schema.
405
+ await lix.execute(
406
+ `SELECT created_at, entity_id, snapshot_content
407
+ FROM lix_change
408
+ WHERE schema_key = $1
409
+ ORDER BY created_at DESC
410
+ LIMIT 20`,
411
+ ["acme_note"],
412
+ );
413
+ ```
414
+
415
+ `snapshot_content` can be null or absent for tombstones, removals, or rows where content was not materialized. In the JS SDK, read it with `row.value("snapshot_content").asJson()` or `row.get("snapshot_content")`, then handle null. Do not blindly `JSON.parse` it as text.
416
+
417
+ ## Built-In Tables And UDFs
418
+
419
+ Common tables:
420
+
421
+ | Table | What it gives consumers |
422
+ | ----------------------- | ------------------------------------------------------------------------------------------------------- |
423
+ | `lix_version` | Writable version surface: `id`, `name`, `hidden`, `commit_id`. |
424
+ | `lix_change` | Immutable change journal. |
425
+ | `lix_file` | Versioned byte storage for files. |
426
+ | `lix_registered_schema` | Registry of app schemas plus built-ins; also exposes the Lix schema-definition meta-schema at runtime. |
427
+
428
+ `lix_version` can be updated for admin flows:
429
+
430
+ ```ts
431
+ await lix.execute("UPDATE lix_version SET hidden = true WHERE id = $1", [
432
+ marketing.id,
433
+ ]);
434
+ ```
435
+
436
+ There is no documented `deleteVersion()` helper in this preview. If the product wants reversible cleanup, hide the version. If it wants removal, `DELETE FROM lix_version WHERE id = $1` is the SQL surface; the engine rejects deleting the global version and active version.
437
+
438
+ Use `lix_json($1)` to parse JSON text parameters when writing JSON-typed columns:
439
+
440
+ ```ts
441
+ await lix.execute(
442
+ "INSERT INTO lix_registered_schema (value) VALUES (lix_json($1))",
443
+ [JSON.stringify(schema)],
444
+ );
445
+ ```
446
+
447
+ Other UDFs, such as `lix_json_get`, `lix_uuid_v7`, `lix_text_encode`, and `lix_empty_blob`, live in `dist-engine-src/src/sql2/udfs/` in the installed package.
448
+
449
+ ## Do And Avoid
450
+
451
+ | Do | Avoid |
452
+ | --- | --- |
453
+ | Use `createBetterSqlite3Backend({ path })` for persistent state. | Opening `.lix` files with raw SQLite libraries. |
454
+ | Use public imports from `@lix-js/sdk` and `@lix-js/sdk/sqlite`. | Importing `engine-wasm` or private internals. |
455
+ | Use `$1`, `$2`, `$3` placeholders. | Bare `?` placeholders. |
456
+ | Use `lix_json($1)` for JSON parameters. | Inlining stringified JSON directly into SQL. |
457
+ | Use `_by_version` for cross-version reads/writes. | Switching versions just to render a side-by-side view. |
458
+ | Name versions in user vocabulary. | User-facing words like branch, branch-1, or generic Draft. |
459
+ | Model collaborative data as small rows. | One giant row when multiple reviewers edit different parts. |
460
+ | Add `x-lix-unique` for non-primary unique fields. | Assuming JSON Schema property metadata creates uniqueness. |
461
+ | Read `snapshot_content` as JSON/native and handle null. | Blindly `JSON.parse(row.value(...).asText())`. |
462
+ | Wrap `mergeVersion()` in `try/catch`. | Assuming merges cannot conflict. |
463
+
464
+ ## Reporting SDK Friction
465
+
466
+ If you encounter an SDK bug, missing API, confusing error, documentation gap, or large implementation friction while using this skill, pause and ask the user whether they want you to open a GitHub issue via the `gh` CLI installed on their computer. Do not file an issue without confirmation.
467
+
468
+ Before filing, scan existing issues to avoid duplicates. If the user approves a report, include a minimal reproduction, expected behavior, actual behavior, the installed `@lix-js/sdk` version, runtime details, and relevant error output. Do not include private data, customer content, credentials, tokens, local paths, database contents, or proprietary schemas.
@@ -4,18 +4,18 @@ import type { InitInput } from "./wasm/lix_engine.js";
4
4
  export type JsonValue = null | boolean | number | string | JsonValue[] | {
5
5
  [key: string]: JsonValue;
6
6
  };
7
- export type ValueKind = "null" | "bool" | "int" | "float" | "text" | "json" | "blob";
7
+ export type ValueKind = "null" | "boolean" | "integer" | "real" | "text" | "json" | "blob";
8
8
  export type LixValue = {
9
9
  kind: "null";
10
10
  value: null;
11
11
  } | {
12
- kind: "bool";
12
+ kind: "boolean";
13
13
  value: boolean;
14
14
  } | {
15
- kind: "int";
15
+ kind: "integer";
16
16
  value: number;
17
17
  } | {
18
- kind: "float";
18
+ kind: "real";
19
19
  value: number;
20
20
  } | {
21
21
  kind: "text";
@@ -40,7 +40,6 @@ export declare class Value {
40
40
  static json(value: JsonValue): Value;
41
41
  static blob(value: Uint8Array): Value;
42
42
  static from(raw: unknown): Value;
43
- kindValue(): ValueKind;
44
43
  asInteger(): number | undefined;
45
44
  asBoolean(): boolean | undefined;
46
45
  asReal(): number | undefined;
@@ -49,16 +48,20 @@ export declare class Value {
49
48
  asBlob(): Uint8Array | undefined;
50
49
  toJSON(): LixValue;
51
50
  }
52
- export type QueryResult = {
53
- rows: LixValue[][];
51
+ export type ExecuteResult = {
54
52
  columns: string[];
53
+ rows: LixValue[][];
54
+ rowsAffected: number;
55
+ notices: LixNotice[];
55
56
  };
56
- export type ExecuteResult = {
57
- statements: QueryResult[];
57
+ export type LixNotice = {
58
+ code: string;
59
+ message: string;
60
+ hint?: string;
58
61
  };
59
62
  /**
60
63
  * Error thrown by the Lix engine. Extends the standard `Error` with a
61
- * machine-readable `code` and an optional `hint` suggesting how to recover.
64
+ * machine-readable `code`, optional `hint`, and optional structured `details`.
62
65
  *
63
66
  * Hints follow the Postgres/rustc convention: `message` states what went
64
67
  * wrong in factual terms; `hint` offers a fix when one is known. Consumers
@@ -68,10 +71,11 @@ export type ExecuteResult = {
68
71
  export interface LixError extends Error {
69
72
  code: string;
70
73
  hint?: string;
74
+ details?: unknown;
71
75
  }
72
76
  /**
73
77
  * Type guard: returns `true` when `err` is a Lix-produced error carrying a
74
- * structured `code` field (all engine codes start with `LIX_ERROR_`).
78
+ * structured `code` field (all engine codes start with `LIX_`).
75
79
  */
76
80
  export declare function isLixError(err: unknown): err is LixError;
77
81
  /**