@cutils/sqlite 0.1.0 → 0.2.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
@@ -112,11 +112,27 @@ no perf claims until the bench cell lands):
112
112
  ## Node usage
113
113
 
114
114
  The npm package ships `dist/esm`. Under Node the emitted `shim_*` calls resolve
115
- on `globalThis` — `test/koffi-binding.mjs` shows loading
116
- `build/libcutils_sqlite.so` through [koffi](https://koffi.dev) (built by
117
- `pnpm build:shared`; the `.so` is linked `-Wl,-Bsymbolic` so the vendored
118
- amalgamation's `sqlite3_*` symbols can't interpose against a host's sqlite —
119
- this is load-bearing, Node ≥ 22 exports its own sqlite3 symbols).
115
+ on `globalThis` — an official [koffi](https://koffi.dev) binding loads
116
+ `build/libcutils_sqlite.so` for you:
117
+
118
+ ```sh
119
+ # one-time, inside the installed package (needs a C toolchain):
120
+ npm run build:shared --prefix node_modules/@cutils/sqlite
121
+ ```
122
+
123
+ ```ts
124
+ import "koffi"; // ensure the optional peer is installed
125
+ import "@cutils/sqlite/node-binding"; // installs the shim_* globals
126
+ import { Database } from "@cutils/sqlite";
127
+ ```
128
+
129
+ `koffi` is an **optional peer dependency** — install it when you run under
130
+ Node; scriptc builds never see it. The `.so` is linked `-Wl,-Bsymbolic` so the
131
+ vendored amalgamation's `sqlite3_*` symbols can't interpose against a host's
132
+ sqlite — this is load-bearing, Node ≥ 22 exports its own sqlite3 symbols.
133
+
134
+ Node ≥ 22 is the runtime floor; the test suite also runs there (no
135
+ `using`-syntax — dispose paths are exercised via `[Symbol.dispose]()`).
120
136
 
121
137
  ## Subset quirks
122
138
 
Binary file
@@ -0,0 +1,120 @@
1
+ // Installs the `shim_*` globals the emitted dist/esm/internal/ffi.js calls,
2
+ // backed by the shared build (build/libcutils_sqlite.so) through koffi.
3
+ // The scriptc span ABI means string/bytes params cross as (ptr,len) pairs —
4
+ // koffi can't produce that layout from a JS string, so each binding is
5
+ // wrapped in a JS adapter that encodes the span explicitly. Handles cross
6
+ // as uint64 bigints (pointer-sized on x86_64/aarch64).
7
+
8
+ import koffi from "koffi";
9
+ import { fileURLToPath } from "node:url";
10
+ import path from "node:path";
11
+
12
+ const dir = path.dirname(fileURLToPath(import.meta.url));
13
+ const lib = koffi.load(path.join(dir, "..", "..", "build", "libcutils_sqlite.so"));
14
+
15
+ const g = globalThis;
16
+ const enc = new TextEncoder();
17
+
18
+ const f = {
19
+ open: lib.func("uint64 shim_open(const uint8_t *path, uint64 plen, int32 flags)"),
20
+ close: lib.func("int32 shim_close(uint64 db)"),
21
+ errcode: lib.func("int32 shim_errcode(uint64 db)"),
22
+ errmsg: lib.func("int32 shim_errmsg(uint64 db, uint8_t *buf, uint64 len)"),
23
+ inTransaction: lib.func("int32 shim_in_transaction(uint64 db)"),
24
+ exec: lib.func("int32 shim_exec(uint64 db, const uint8_t *sql, uint64 slen)"),
25
+ begin: lib.func("int32 shim_begin(uint64 db)"),
26
+ commit: lib.func("int32 shim_commit(uint64 db)"),
27
+ rollback: lib.func("int32 shim_rollback(uint64 db)"),
28
+ savepoint: lib.func(
29
+ "int32 shim_savepoint(uint64 db, const uint8_t *name, uint64 nlen, int32 op)",
30
+ ),
31
+ prepare: lib.func("uint64 shim_prepare(uint64 db, const uint8_t *sql, uint64 slen)"),
32
+ step: lib.func("int32 shim_step(uint64 stmt)"),
33
+ reset: lib.func("int32 shim_reset(uint64 stmt)"),
34
+ finalize: lib.func("int32 shim_finalize(uint64 stmt)"),
35
+ clearBindings: lib.func("int32 shim_clear_bindings(uint64 stmt)"),
36
+ stmtReadonly: lib.func("int32 shim_stmt_readonly(uint64 stmt)"),
37
+ expandedSql: lib.func("int32 shim_expanded_sql(uint64 stmt, uint8_t *buf, uint64 len)"),
38
+ bindCount: lib.func("int32 shim_bind_count(uint64 stmt)"),
39
+ bindIndex: lib.func("int32 shim_bind_index(uint64 stmt, const uint8_t *name, uint64 nlen)"),
40
+ bindInt: lib.func("int32 shim_bind_int(uint64 stmt, int32 idx, int64 val)"),
41
+ bindDbl: lib.func("int32 shim_bind_dbl(uint64 stmt, int32 idx, double val)"),
42
+ bindText: lib.func(
43
+ "int32 shim_bind_text(uint64 stmt, int32 idx, const uint8_t *val, uint64 vlen)",
44
+ ),
45
+ bindBlob: lib.func(
46
+ "int32 shim_bind_blob(uint64 stmt, int32 idx, const uint8_t *val, uint64 vlen)",
47
+ ),
48
+ bindNull: lib.func("int32 shim_bind_null(uint64 stmt, int32 idx)"),
49
+ columnCount: lib.func("int32 shim_column_count(uint64 stmt)"),
50
+ columnName: lib.func("int32 shim_column_name(uint64 stmt, int32 idx, uint8_t *buf, uint64 len)"),
51
+ columnMeta: lib.func(
52
+ "int32 shim_column_meta(uint64 stmt, int32 idx, int32 which, uint8_t *buf, uint64 len)",
53
+ ),
54
+ columnType: lib.func("int32 shim_column_type(uint64 stmt, int32 idx)"),
55
+ columnInt64: lib.func("int64 shim_column_int64(uint64 stmt, int32 idx)"),
56
+ columnDbl: lib.func("double shim_column_dbl(uint64 stmt, int32 idx)"),
57
+ columnText: lib.func("int32 shim_column_text(uint64 stmt, int32 idx, uint8_t *buf, uint64 len)"),
58
+ columnBlob: lib.func("int32 shim_column_blob(uint64 stmt, int32 idx, uint8_t *buf, uint64 len)"),
59
+ lastInsertRowid: lib.func("int64 shim_last_insert_rowid(uint64 db)"),
60
+ changes: lib.func("int32 shim_changes(uint64 db)"),
61
+ };
62
+
63
+ function span(s) {
64
+ const b = enc.encode(s);
65
+ return [b, BigInt(b.length)];
66
+ }
67
+
68
+ g.shim_open = (path, flags) => {
69
+ const [b, n] = span(path);
70
+ return f.open(b, n, flags);
71
+ };
72
+ g.shim_close = (db) => f.close(db);
73
+ g.shim_errcode = (db) => f.errcode(db);
74
+ g.shim_errmsg = (db, buf) => f.errmsg(db, buf, BigInt(buf.length));
75
+ g.shim_in_transaction = (db) => f.inTransaction(db);
76
+ g.shim_exec = (db, sql) => {
77
+ const [b, n] = span(sql);
78
+ return f.exec(db, b, n);
79
+ };
80
+ g.shim_begin = (db) => f.begin(db);
81
+ g.shim_commit = (db) => f.commit(db);
82
+ g.shim_rollback = (db) => f.rollback(db);
83
+ g.shim_savepoint = (db, name, op) => {
84
+ const [b, n] = span(name);
85
+ return f.savepoint(db, b, n, op);
86
+ };
87
+ g.shim_prepare = (db, sql) => {
88
+ const [b, n] = span(sql);
89
+ return f.prepare(db, b, n);
90
+ };
91
+ g.shim_step = (stmt) => f.step(stmt);
92
+ g.shim_reset = (stmt) => f.reset(stmt);
93
+ g.shim_finalize = (stmt) => f.finalize(stmt);
94
+ g.shim_clear_bindings = (stmt) => f.clearBindings(stmt);
95
+ g.shim_stmt_readonly = (stmt) => f.stmtReadonly(stmt);
96
+ g.shim_expanded_sql = (stmt, buf) => f.expandedSql(stmt, buf, BigInt(buf.length));
97
+ g.shim_bind_count = (stmt) => f.bindCount(stmt);
98
+ g.shim_bind_index = (stmt, name) => {
99
+ const [b, n] = span(name);
100
+ return f.bindIndex(stmt, b, n);
101
+ };
102
+ g.shim_bind_int = (stmt, idx, val) => f.bindInt(stmt, idx, val);
103
+ g.shim_bind_dbl = (stmt, idx, val) => f.bindDbl(stmt, idx, val);
104
+ g.shim_bind_text = (stmt, idx, val) => {
105
+ const [b, n] = span(val);
106
+ return f.bindText(stmt, idx, b, n);
107
+ };
108
+ g.shim_bind_blob = (stmt, idx, val) => f.bindBlob(stmt, idx, val, BigInt(val.length));
109
+ g.shim_bind_null = (stmt, idx) => f.bindNull(stmt, idx);
110
+ g.shim_column_count = (stmt) => f.columnCount(stmt);
111
+ g.shim_column_name = (stmt, idx, buf) => f.columnName(stmt, idx, buf, BigInt(buf.length));
112
+ g.shim_column_meta = (stmt, idx, which, buf) =>
113
+ f.columnMeta(stmt, idx, which, buf, BigInt(buf.length));
114
+ g.shim_column_type = (stmt, idx) => f.columnType(stmt, idx);
115
+ g.shim_column_int64 = (stmt, idx) => f.columnInt64(stmt, idx);
116
+ g.shim_column_dbl = (stmt, idx) => f.columnDbl(stmt, idx);
117
+ g.shim_column_text = (stmt, idx, buf) => f.columnText(stmt, idx, buf, BigInt(buf.length));
118
+ g.shim_column_blob = (stmt, idx, buf) => f.columnBlob(stmt, idx, buf, BigInt(buf.length));
119
+ g.shim_last_insert_rowid = (db) => f.lastInsertRowid(db);
120
+ g.shim_changes = (db) => f.changes(db);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cutils/sqlite",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Embedded SQLite for chttp/scriptc apps — vendored amalgamation, FFI shim, Standard-Schema row seam. Compiles to a single static ELF.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -22,7 +22,8 @@
22
22
  "types": "./dist/esm/index.d.ts",
23
23
  "import": "./dist/esm/index.js"
24
24
  },
25
- "./package.json": "./package.json"
25
+ "./package.json": "./package.json",
26
+ "./node-binding": "./dist/esm/node-binding.mjs"
26
27
  },
27
28
  "devDependencies": {
28
29
  "@chttp/val": "^0.2.1",
@@ -35,6 +36,14 @@
35
36
  "engines": {
36
37
  "node": ">=22"
37
38
  },
39
+ "peerDependencies": {
40
+ "koffi": "^2.14.1"
41
+ },
42
+ "peerDependenciesMeta": {
43
+ "koffi": {
44
+ "optional": true
45
+ }
46
+ },
38
47
  "author": "Joaquin Terrasa & contributors",
39
48
  "keywords": [
40
49
  "sqlite",
@@ -56,9 +65,9 @@
56
65
  "build:lib": "ranlib build/libsqlite3-shim.a",
57
66
  "build:shared": "cc -O2 -fPIC -DSQLITE_THREADSAFE=0 -DSQLITE_OMIT_LOAD_EXTENSION -DSQLITE_ENABLE_COLUMN_METADATA -Wl,-Bsymbolic -shared vendor/shim/sqlite_shim.c vendor/sqlite-amalgamation/sqlite3.c -o build/libcutils_sqlite.so",
58
67
  "build:ts": "tsc -p tsconfig.json",
59
- "build": "mkdir -p build && npm run build:c && npm run build:sqlite && npm run build:archive && npm run build:lib && npm run build:ts",
68
+ "build": "mkdir -p build && npm run build:c && npm run build:sqlite && npm run build:archive && npm run build:lib && npm run build:ts && sed 's|\"\\.\\.\", \"build\"|\"..\", \"..\", \"build\"|' test/koffi-binding.mjs > dist/esm/node-binding.mjs",
60
69
  "typecheck": "tsc -p tsconfig.json --noEmit",
61
- "test": "npm run build && npm run build:shared && npm run test:c && npm run test:node",
70
+ "test": "npm run build && npm run build:shared && npm run test:c && npm run test:node && bash test/elf.test.sh",
62
71
  "test:c": "cc -O2 -DSQLITE_THREADSAFE=0 -DSQLITE_OMIT_LOAD_EXTENSION -DSQLITE_ENABLE_COLUMN_METADATA test/shim.test.c build/libsqlite3-shim.a -o build/shim_test -lm && ./build/shim_test",
63
72
  "test:node": "node --test test/*.test.js test/*.test.mjs"
64
73
  }
@@ -235,9 +235,14 @@ int32_t shim_bind_dbl(void *stmt, int32_t idx, double val) {
235
235
  }
236
236
 
237
237
  /* The text span is length-delimited; SQLITE_TRANSIENT copies the bytes
238
- * inside the call — required because the borrow is call-scoped. */
238
+ * inside the call — required because the borrow is call-scoped.
239
+ * An EMPTY span may arrive as a NULL pointer (koffi marshals an empty
240
+ * Uint8Array as NULL; the ELF span for "" can also carry address 0) and
241
+ * sqlite3_bind_text(NULL, n) binds SQL NULL — substitute a non-NULL
242
+ * literal so '' stays ''. */
239
243
  int32_t shim_bind_text(void *stmt, int32_t idx, const uint8_t *val,
240
244
  size_t vlen) {
245
+ if (val == NULL) val = (const uint8_t *)"";
241
246
  return (int32_t)sqlite3_bind_text((sqlite3_stmt *)stmt, (int)idx,
242
247
  (const char *)val, (int)vlen,
243
248
  SQLITE_TRANSIENT);
@@ -245,6 +250,8 @@ int32_t shim_bind_text(void *stmt, int32_t idx, const uint8_t *val,
245
250
 
246
251
  int32_t shim_bind_blob(void *stmt, int32_t idx, const uint8_t *val,
247
252
  size_t vlen) {
253
+ /* Same empty-span NULL trap as shim_bind_text. */
254
+ if (val == NULL) val = (const uint8_t *)"";
248
255
  return (int32_t)sqlite3_bind_blob((sqlite3_stmt *)stmt, (int)idx, val,
249
256
  (int)vlen, SQLITE_TRANSIENT);
250
257
  }