inillucent 0.1.4
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 +102 -0
- package/bin/inillucent-mcp.mjs +33 -0
- package/bin/inillucent-migrate.mjs +33 -0
- package/bin/inillucent-shell.mjs +33 -0
- package/bin/inillucent.mjs +33 -0
- package/index.mjs +197 -0
- package/package.json +52 -0
- package/resolve.mjs +114 -0
package/README.md
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# inillucent
|
|
2
|
+
|
|
3
|
+
An embedded SQL database that speaks SQLite's dialect on its own storage, with
|
|
4
|
+
full-text and vector search built in — the job of PostgreSQL + pgvector + an
|
|
5
|
+
embedding server, in one process and one file.
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
npm install -g inillucent
|
|
9
|
+
# or, without installing anything permanently:
|
|
10
|
+
npx inillucent help
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
No native build step and no postinstall download: the binaries ship as
|
|
14
|
+
per-platform packages that npm installs only where they run, so `npm ci` works
|
|
15
|
+
offline and behind a proxy.
|
|
16
|
+
|
|
17
|
+
## Four programs
|
|
18
|
+
|
|
19
|
+
| | |
|
|
20
|
+
|---|---|
|
|
21
|
+
| `inillucent` | the command line: `query`, `exec`, `describe`, `import`, `export`, `search`, and twenty more |
|
|
22
|
+
| `inillucent-shell` | an interactive shell shaped like `sqlite3`, with all 63 of its dot commands |
|
|
23
|
+
| `inillucent-mcp` | the same commands served to an AI agent over MCP |
|
|
24
|
+
| `inillucent-migrate` | builds an inillucent database from a SQLite file |
|
|
25
|
+
|
|
26
|
+
## From a shell
|
|
27
|
+
|
|
28
|
+
```sh
|
|
29
|
+
inillucent create app.rdb
|
|
30
|
+
inillucent --db app.rdb exec "CREATE TABLE notes (id INTEGER PRIMARY KEY, body TEXT)"
|
|
31
|
+
inillucent --db app.rdb exec "INSERT INTO notes (body) VALUES (?1)" --params '["hello"]'
|
|
32
|
+
inillucent --db app.rdb query "SELECT * FROM notes"
|
|
33
|
+
inillucent --db app.rdb describe notes
|
|
34
|
+
inillucent help
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Exit codes carry meaning: `0` success, `1` failed, `2` a command line nobody
|
|
38
|
+
could act on, and **`3` a construct the engine has not built yet** — so a script
|
|
39
|
+
can branch on "not yet" without matching on a message.
|
|
40
|
+
|
|
41
|
+
## From Node
|
|
42
|
+
|
|
43
|
+
```js
|
|
44
|
+
import { query, inillucent } from 'inillucent';
|
|
45
|
+
|
|
46
|
+
const rows = await query('SELECT id, body FROM notes WHERE id > ?1', {
|
|
47
|
+
db: 'app.rdb',
|
|
48
|
+
params: [3],
|
|
49
|
+
});
|
|
50
|
+
// [{ id: 4, body: 'hello' }]
|
|
51
|
+
|
|
52
|
+
const described = await inillucent('describe', { db: 'app.rdb', table: 'notes' });
|
|
53
|
+
console.log(described.ddl, described.indexes, described.row_count_in_table);
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Each call is a process, so this is the right tool for the dozen calls a build
|
|
57
|
+
script or a tool wrapper makes and the wrong one for a loop over a million rows.
|
|
58
|
+
For that, write a binding over the C ABI — the header ships in this package's
|
|
59
|
+
platform dependency under `include/`, and
|
|
60
|
+
[`drivers/README.md`](https://github.com/Black-Rainbow-Labs/Inillucent/blob/main/drivers/README.md)
|
|
61
|
+
is written to be followed.
|
|
62
|
+
|
|
63
|
+
## For an agent
|
|
64
|
+
|
|
65
|
+
```json
|
|
66
|
+
{
|
|
67
|
+
"mcpServers": {
|
|
68
|
+
"inillucent": {
|
|
69
|
+
"command": "npx",
|
|
70
|
+
"args": ["-y", "inillucent-mcp", "--db", "app.rdb"]
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
27 tools, generated from the same command table the CLI reads, so the two can
|
|
77
|
+
never drift. `--readonly` refuses every statement that changes something, and
|
|
78
|
+
`--root DIR` refuses every path outside a directory.
|
|
79
|
+
|
|
80
|
+
## Licence
|
|
81
|
+
|
|
82
|
+
MIT. Source: <https://github.com/Black-Rainbow-Labs/Inillucent>
|
|
83
|
+
|
|
84
|
+
## The API
|
|
85
|
+
|
|
86
|
+
Every method this binding has. The worked example each one appears in is the link; nothing here is
|
|
87
|
+
a summary of a method that does not exist, because
|
|
88
|
+
`cargo test -p inillucent-compat --test documentation` reads this table and fails on a name the
|
|
89
|
+
binding source does not declare.
|
|
90
|
+
|
|
91
|
+
| what | one line |
|
|
92
|
+
|---|---|
|
|
93
|
+
| `inillucent(command, options)` | run one command of the command line and return its parsed JSON. `options.db` names the file, `options.args` the rest. |
|
|
94
|
+
| `query(sql, options)` | run one `SELECT` and return its rows. `options.params` binds `?1`, `?2`; `options.limit` caps the rows kept. |
|
|
95
|
+
| `resolveBinary(program)` | the path to one of the four programs on this platform, from the platform package npm installed. |
|
|
96
|
+
| `platformPackage()` | the name of the platform package this machine needs, which is what an install failure should name. |
|
|
97
|
+
| `PROGRAMS` | the four programs and what each is for, as an object. |
|
|
98
|
+
|
|
99
|
+
Every call goes through the command line rather than through the C ABI: the four programs are what
|
|
100
|
+
the platform package ships, and `--output json` is the same object every other binding sees. That
|
|
101
|
+
is why there is no `Connection` here and no transaction - a command is one process, and a
|
|
102
|
+
transaction that spanned two of them would be a transaction nothing held open.
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// The `inillucent-mcp` shim: resolve the platform binary and become it.
|
|
3
|
+
//
|
|
4
|
+
// `spawn` with `stdio: 'inherit'` rather than an exec, because Node has no
|
|
5
|
+
// execve and this has to work on Windows. The child's exit code and its signal
|
|
6
|
+
// are both passed back up: a wrapper that always exited 0 would break every
|
|
7
|
+
// script that branches on the exit code, and inillucent's exit codes carry
|
|
8
|
+
// meaning - 3 is "the engine has not built that".
|
|
9
|
+
import { spawn } from 'node:child_process';
|
|
10
|
+
import { resolveBinary } from '../resolve.mjs';
|
|
11
|
+
|
|
12
|
+
let binary;
|
|
13
|
+
try {
|
|
14
|
+
binary = resolveBinary('inillucent-mcp');
|
|
15
|
+
} catch (why) {
|
|
16
|
+
process.stderr.write(String(why.message ?? why) + '\n');
|
|
17
|
+
process.exit(1);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const child = spawn(binary, process.argv.slice(2), { stdio: 'inherit' });
|
|
21
|
+
child.on('error', (why) => {
|
|
22
|
+
process.stderr.write(`inillucent: could not run ${binary}: ${why.message}\n`);
|
|
23
|
+
process.exit(1);
|
|
24
|
+
});
|
|
25
|
+
child.on('exit', (code, signal) => {
|
|
26
|
+
if (signal) {
|
|
27
|
+
// Re-raise it on ourselves so a shell sees the same thing it would have
|
|
28
|
+
// seen from the real program, rather than a plain exit.
|
|
29
|
+
process.kill(process.pid, signal);
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
process.exit(code ?? 0);
|
|
33
|
+
});
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// The `inillucent-migrate` shim: resolve the platform binary and become it.
|
|
3
|
+
//
|
|
4
|
+
// `spawn` with `stdio: 'inherit'` rather than an exec, because Node has no
|
|
5
|
+
// execve and this has to work on Windows. The child's exit code and its signal
|
|
6
|
+
// are both passed back up: a wrapper that always exited 0 would break every
|
|
7
|
+
// script that branches on the exit code, and inillucent's exit codes carry
|
|
8
|
+
// meaning - 3 is "the engine has not built that".
|
|
9
|
+
import { spawn } from 'node:child_process';
|
|
10
|
+
import { resolveBinary } from '../resolve.mjs';
|
|
11
|
+
|
|
12
|
+
let binary;
|
|
13
|
+
try {
|
|
14
|
+
binary = resolveBinary('inillucent-migrate');
|
|
15
|
+
} catch (why) {
|
|
16
|
+
process.stderr.write(String(why.message ?? why) + '\n');
|
|
17
|
+
process.exit(1);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const child = spawn(binary, process.argv.slice(2), { stdio: 'inherit' });
|
|
21
|
+
child.on('error', (why) => {
|
|
22
|
+
process.stderr.write(`inillucent: could not run ${binary}: ${why.message}\n`);
|
|
23
|
+
process.exit(1);
|
|
24
|
+
});
|
|
25
|
+
child.on('exit', (code, signal) => {
|
|
26
|
+
if (signal) {
|
|
27
|
+
// Re-raise it on ourselves so a shell sees the same thing it would have
|
|
28
|
+
// seen from the real program, rather than a plain exit.
|
|
29
|
+
process.kill(process.pid, signal);
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
process.exit(code ?? 0);
|
|
33
|
+
});
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// The `inillucent-shell` shim: resolve the platform binary and become it.
|
|
3
|
+
//
|
|
4
|
+
// `spawn` with `stdio: 'inherit'` rather than an exec, because Node has no
|
|
5
|
+
// execve and this has to work on Windows. The child's exit code and its signal
|
|
6
|
+
// are both passed back up: a wrapper that always exited 0 would break every
|
|
7
|
+
// script that branches on the exit code, and inillucent's exit codes carry
|
|
8
|
+
// meaning - 3 is "the engine has not built that".
|
|
9
|
+
import { spawn } from 'node:child_process';
|
|
10
|
+
import { resolveBinary } from '../resolve.mjs';
|
|
11
|
+
|
|
12
|
+
let binary;
|
|
13
|
+
try {
|
|
14
|
+
binary = resolveBinary('inillucent-shell');
|
|
15
|
+
} catch (why) {
|
|
16
|
+
process.stderr.write(String(why.message ?? why) + '\n');
|
|
17
|
+
process.exit(1);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const child = spawn(binary, process.argv.slice(2), { stdio: 'inherit' });
|
|
21
|
+
child.on('error', (why) => {
|
|
22
|
+
process.stderr.write(`inillucent: could not run ${binary}: ${why.message}\n`);
|
|
23
|
+
process.exit(1);
|
|
24
|
+
});
|
|
25
|
+
child.on('exit', (code, signal) => {
|
|
26
|
+
if (signal) {
|
|
27
|
+
// Re-raise it on ourselves so a shell sees the same thing it would have
|
|
28
|
+
// seen from the real program, rather than a plain exit.
|
|
29
|
+
process.kill(process.pid, signal);
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
process.exit(code ?? 0);
|
|
33
|
+
});
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// The `inillucent` shim: resolve the platform binary and become it.
|
|
3
|
+
//
|
|
4
|
+
// `spawn` with `stdio: 'inherit'` rather than an exec, because Node has no
|
|
5
|
+
// execve and this has to work on Windows. The child's exit code and its signal
|
|
6
|
+
// are both passed back up: a wrapper that always exited 0 would break every
|
|
7
|
+
// script that branches on the exit code, and inillucent's exit codes carry
|
|
8
|
+
// meaning - 3 is "the engine has not built that".
|
|
9
|
+
import { spawn } from 'node:child_process';
|
|
10
|
+
import { resolveBinary } from '../resolve.mjs';
|
|
11
|
+
|
|
12
|
+
let binary;
|
|
13
|
+
try {
|
|
14
|
+
binary = resolveBinary('inillucent');
|
|
15
|
+
} catch (why) {
|
|
16
|
+
process.stderr.write(String(why.message ?? why) + '\n');
|
|
17
|
+
process.exit(1);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const child = spawn(binary, process.argv.slice(2), { stdio: 'inherit' });
|
|
21
|
+
child.on('error', (why) => {
|
|
22
|
+
process.stderr.write(`inillucent: could not run ${binary}: ${why.message}\n`);
|
|
23
|
+
process.exit(1);
|
|
24
|
+
});
|
|
25
|
+
child.on('exit', (code, signal) => {
|
|
26
|
+
if (signal) {
|
|
27
|
+
// Re-raise it on ourselves so a shell sees the same thing it would have
|
|
28
|
+
// seen from the real program, rather than a plain exit.
|
|
29
|
+
process.kill(process.pid, signal);
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
process.exit(code ?? 0);
|
|
33
|
+
});
|
package/index.mjs
ADDED
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
// inillucent, from Node.
|
|
2
|
+
//
|
|
3
|
+
// **This is not a database binding.** It is the four programs, plus one
|
|
4
|
+
// convenience for calling the command line and getting a typed result back.
|
|
5
|
+
// A real binding would go through the C ABI in
|
|
6
|
+
// `drivers/inillucent-driver-capi/include/inillucent_driver.h` with koffi or an
|
|
7
|
+
// N-API addon, and `drivers/README.md` says exactly how to write one - it is a
|
|
8
|
+
// worthwhile thing to have and it is not this.
|
|
9
|
+
//
|
|
10
|
+
// What this is for: a script or an agent harness that wants to run a query
|
|
11
|
+
// without shelling out by hand and parsing a table. Each call is a process, so
|
|
12
|
+
// it is the wrong tool for a loop over a million rows and the right one for the
|
|
13
|
+
// dozen calls a build script or a tool wrapper makes.
|
|
14
|
+
|
|
15
|
+
import { execFile } from 'node:child_process';
|
|
16
|
+
import { promisify } from 'node:util';
|
|
17
|
+
|
|
18
|
+
import { resolveBinary, PROGRAMS, platformPackage } from './resolve.mjs';
|
|
19
|
+
|
|
20
|
+
const run = promisify(execFile);
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Encodes one bound value as the JSON text the command line reads.
|
|
24
|
+
*
|
|
25
|
+
* **`JSON.stringify` loses three things a parameter can be (task-1979, D13 and
|
|
26
|
+
* D16).** A NaN and an Infinity both become `null`, so a REAL column silently
|
|
27
|
+
* stored NULL and nothing said so; `-0` becomes `0`, so the sign of negative
|
|
28
|
+
* zero was gone before the value left Node; and a byte string has no JSON form
|
|
29
|
+
* at all, so there was no way to bind a BLOB.
|
|
30
|
+
*
|
|
31
|
+
* What is written instead: a non-finite number throws here, where the caller
|
|
32
|
+
* can see which value it was, rather than turning into a NULL nobody asked for.
|
|
33
|
+
* Negative zero is written `-0.0`, which JSON's own grammar carries and the
|
|
34
|
+
* command line's parser reads back as a negative zero. Bytes are written
|
|
35
|
+
* `{"blob":"<hex>"}`.
|
|
36
|
+
*
|
|
37
|
+
* @param value - one element of `params`
|
|
38
|
+
*/
|
|
39
|
+
function encodeParam(value) {
|
|
40
|
+
if (typeof value === 'number') {
|
|
41
|
+
if (!Number.isFinite(value)) {
|
|
42
|
+
throw new TypeError(
|
|
43
|
+
`a parameter cannot be ${value}: SQL has no spelling for NaN or Infinity.`,
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
return Object.is(value, -0) ? '-0.0' : JSON.stringify(value);
|
|
47
|
+
}
|
|
48
|
+
if (value instanceof Uint8Array) {
|
|
49
|
+
const hex = Buffer.from(value).toString('hex');
|
|
50
|
+
return JSON.stringify({ blob: hex });
|
|
51
|
+
}
|
|
52
|
+
if (Array.isArray(value)) {
|
|
53
|
+
return `[${value.map(encodeParam).join(',')}]`;
|
|
54
|
+
}
|
|
55
|
+
return JSON.stringify(value === undefined ? null : value);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Encodes the whole `params` array as JSON text.
|
|
60
|
+
*
|
|
61
|
+
* @param values - the bound values, in order
|
|
62
|
+
*/
|
|
63
|
+
function encodeParams(values) {
|
|
64
|
+
return `[${values.map(encodeParam).join(',')}]`;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export { resolveBinary, PROGRAMS, platformPackage };
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Runs a program, writing `stdin` to it when there is any.
|
|
71
|
+
*
|
|
72
|
+
* `execFile` cannot write to standard input, so a call that has something to
|
|
73
|
+
* write goes through `spawn` and the two are answered the same way: `{ stdout,
|
|
74
|
+
* stderr }`, or a rejection carrying both plus the exit code, which is the
|
|
75
|
+
* shape the caller below already handles.
|
|
76
|
+
*
|
|
77
|
+
* @param binary - the program to run
|
|
78
|
+
* @param args - its command line
|
|
79
|
+
* @param stdin - what to write to its standard input, or null
|
|
80
|
+
*/
|
|
81
|
+
async function spawnWith(binary, args, stdin) {
|
|
82
|
+
if (stdin === null) {
|
|
83
|
+
return run(binary, args, { maxBuffer: 256 * 1024 * 1024 });
|
|
84
|
+
}
|
|
85
|
+
const { spawn } = await import('node:child_process');
|
|
86
|
+
return new Promise((resolve, reject) => {
|
|
87
|
+
const child = spawn(binary, args, { stdio: ['pipe', 'pipe', 'pipe'] });
|
|
88
|
+
let stdout = '';
|
|
89
|
+
let stderr = '';
|
|
90
|
+
child.stdout.on('data', (chunk) => {
|
|
91
|
+
stdout += chunk;
|
|
92
|
+
});
|
|
93
|
+
child.stderr.on('data', (chunk) => {
|
|
94
|
+
stderr += chunk;
|
|
95
|
+
});
|
|
96
|
+
child.on('error', reject);
|
|
97
|
+
child.on('close', (code) => {
|
|
98
|
+
if (code === 0) {
|
|
99
|
+
resolve({ stdout, stderr });
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
const why = new Error(`inillucent exited ${code}`);
|
|
103
|
+
why.stdout = stdout;
|
|
104
|
+
why.stderr = stderr;
|
|
105
|
+
why.code = code;
|
|
106
|
+
reject(why);
|
|
107
|
+
});
|
|
108
|
+
child.stdin.on('error', () => {});
|
|
109
|
+
child.stdin.end(stdin);
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Runs one inillucent command and returns its result object.
|
|
115
|
+
*
|
|
116
|
+
* The command line's `--output json` contract: `{ ok, command, columns, rows,
|
|
117
|
+
* total, more, changes, last_insert_rowid, elapsed_ms, text }` on success, and
|
|
118
|
+
* `{ ok: false, status, message, ... }` on failure. `status` is one of the
|
|
119
|
+
* driver's thirteen names, and `unsupported` is its own - a construct the
|
|
120
|
+
* engine has not built is not a syntax error and should not be handled as one.
|
|
121
|
+
*
|
|
122
|
+
* A failure is returned rather than thrown, because the interesting failures
|
|
123
|
+
* here are answers: "no such table", "this construct is not built yet". Only a
|
|
124
|
+
* broken invocation throws.
|
|
125
|
+
*
|
|
126
|
+
* @param command - the verb, such as "query" or "describe"
|
|
127
|
+
* @param options - the named arguments the verb takes, plus `db`
|
|
128
|
+
*/
|
|
129
|
+
export async function inillucent(command, options = {}) {
|
|
130
|
+
const { db, ...rest } = options;
|
|
131
|
+
const args = [command, '--output', 'json'];
|
|
132
|
+
let stdin = null;
|
|
133
|
+
if (db) {
|
|
134
|
+
args.push('--db', db);
|
|
135
|
+
}
|
|
136
|
+
for (const [name, value] of Object.entries(rest)) {
|
|
137
|
+
if (value === undefined || value === null) {
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
if (value === true) {
|
|
141
|
+
args.push(`--${name}`);
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
if (value === false) {
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
// **`params` travels on standard input (task-1979, D17).** A command line
|
|
148
|
+
// has a length ceiling - about 32 KB on Windows - and a parameter past it
|
|
149
|
+
// failed with an operating system error rather than with anything about
|
|
150
|
+
// SQL. `--params-file -` has no such limit, and it is also what lets the
|
|
151
|
+
// encoding above carry bytes and a negative zero.
|
|
152
|
+
if (name === 'params' && Array.isArray(value)) {
|
|
153
|
+
stdin = encodeParams(value);
|
|
154
|
+
args.push('--params-file', '-');
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
// Any other array is a JSON argument - `vector` is the one - and the
|
|
158
|
+
// command line reads it as JSON, so it is serialised rather than joined.
|
|
159
|
+
args.push(`--${name}`, Array.isArray(value) ? JSON.stringify(value) : String(value));
|
|
160
|
+
}
|
|
161
|
+
const binary = resolveBinary('inillucent');
|
|
162
|
+
try {
|
|
163
|
+
const { stdout } = await spawnWith(binary, args, stdin);
|
|
164
|
+
return JSON.parse(stdout);
|
|
165
|
+
} catch (why) {
|
|
166
|
+
// A non-zero exit still prints the result object on standard output when
|
|
167
|
+
// `--output json` was asked for, so the failure the caller wants is in
|
|
168
|
+
// there. Only a truly broken invocation has nothing to parse.
|
|
169
|
+
if (typeof why.stdout === 'string' && why.stdout.trim().startsWith('{')) {
|
|
170
|
+
return JSON.parse(why.stdout);
|
|
171
|
+
}
|
|
172
|
+
throw new Error(
|
|
173
|
+
`inillucent ${command} could not be run: ${why.stderr || why.message}`,
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Runs a query and returns its rows as objects keyed by column name.
|
|
180
|
+
*
|
|
181
|
+
* The shape most callers actually want. `inillucent()` is there for the ones
|
|
182
|
+
* that need the counts, the timing or the failure class.
|
|
183
|
+
*
|
|
184
|
+
* @param sql - the statement
|
|
185
|
+
* @param options - `db`, `params`, `limit`
|
|
186
|
+
*/
|
|
187
|
+
export async function query(sql, options = {}) {
|
|
188
|
+
const result = await inillucent('query', { sql, ...options });
|
|
189
|
+
if (!result.ok) {
|
|
190
|
+
const error = new Error(result.message);
|
|
191
|
+
error.status = result.status;
|
|
192
|
+
error.feature = result.feature;
|
|
193
|
+
throw error;
|
|
194
|
+
}
|
|
195
|
+
const names = result.columns.map((column) => column.name);
|
|
196
|
+
return result.rows.map((row) => Object.fromEntries(names.map((name, at) => [name, row[at]])));
|
|
197
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "inillucent",
|
|
3
|
+
"version": "0.1.4",
|
|
4
|
+
"description": "Embedded SQL database with full-text and vector search, an MCP server, and a sqlite3-shaped shell. No native build step: the binaries ship as platform packages.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"database",
|
|
7
|
+
"sql",
|
|
8
|
+
"sqlite",
|
|
9
|
+
"vector",
|
|
10
|
+
"embedding",
|
|
11
|
+
"search",
|
|
12
|
+
"mcp",
|
|
13
|
+
"cli",
|
|
14
|
+
"rag"
|
|
15
|
+
],
|
|
16
|
+
"homepage": "https://github.com/Black-Rainbow-Labs/Inillucent#readme",
|
|
17
|
+
"bugs": "https://github.com/Black-Rainbow-Labs/Inillucent/issues",
|
|
18
|
+
"repository": {
|
|
19
|
+
"type": "git",
|
|
20
|
+
"url": "git+https://github.com/Black-Rainbow-Labs/Inillucent.git",
|
|
21
|
+
"directory": "packages/npm/inillucent"
|
|
22
|
+
},
|
|
23
|
+
"license": "MIT",
|
|
24
|
+
"author": "Black Rainbow Labs",
|
|
25
|
+
"type": "module",
|
|
26
|
+
"engines": {
|
|
27
|
+
"node": ">=18"
|
|
28
|
+
},
|
|
29
|
+
"bin": {
|
|
30
|
+
"inillucent": "bin/inillucent.mjs",
|
|
31
|
+
"inillucent-shell": "bin/inillucent-shell.mjs",
|
|
32
|
+
"inillucent-mcp": "bin/inillucent-mcp.mjs",
|
|
33
|
+
"inillucent-migrate": "bin/inillucent-migrate.mjs"
|
|
34
|
+
},
|
|
35
|
+
"main": "index.mjs",
|
|
36
|
+
"exports": {
|
|
37
|
+
".": "./index.mjs"
|
|
38
|
+
},
|
|
39
|
+
"files": [
|
|
40
|
+
"bin",
|
|
41
|
+
"index.mjs",
|
|
42
|
+
"resolve.mjs",
|
|
43
|
+
"README.md"
|
|
44
|
+
],
|
|
45
|
+
"optionalDependencies": {
|
|
46
|
+
"@blackrainbowlabs/cli-win32-x64": "0.1.4",
|
|
47
|
+
"@blackrainbowlabs/cli-darwin-arm64": "0.1.4",
|
|
48
|
+
"@blackrainbowlabs/cli-darwin-x64": "0.1.4",
|
|
49
|
+
"@blackrainbowlabs/cli-linux-x64": "0.1.4",
|
|
50
|
+
"@blackrainbowlabs/cli-linux-arm64": "0.1.4"
|
|
51
|
+
}
|
|
52
|
+
}
|
package/resolve.mjs
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
// Finds the binary for the machine this is running on.
|
|
2
|
+
//
|
|
3
|
+
// The arrangement is esbuild's, and it is the right one: the binaries live in
|
|
4
|
+
// per-platform packages listed as `optionalDependencies`, npm installs only the
|
|
5
|
+
// one that matches, and this package's `bin` entries are tiny shims that
|
|
6
|
+
// resolve it and hand over. There is no postinstall script and nothing is
|
|
7
|
+
// downloaded at install time, which means `npm ci` works offline, in a locked
|
|
8
|
+
// CI, and behind a registry proxy - none of which is true of a package that
|
|
9
|
+
// fetches a binary from GitHub when it is installed.
|
|
10
|
+
//
|
|
11
|
+
// The cost is that a platform without a published package cannot install, and
|
|
12
|
+
// the failure has to say so in a sentence somebody can act on rather than as an
|
|
13
|
+
// unresolved import. That is what `resolveBinary` is for.
|
|
14
|
+
|
|
15
|
+
import { createRequire } from 'node:module';
|
|
16
|
+
import { accessSync, constants, existsSync } from 'node:fs';
|
|
17
|
+
import { dirname, join } from 'node:path';
|
|
18
|
+
|
|
19
|
+
const require = createRequire(import.meta.url);
|
|
20
|
+
|
|
21
|
+
/** The platform packages, by the `process.platform`-`process.arch` pair each serves. */
|
|
22
|
+
const PACKAGES = {
|
|
23
|
+
'win32-x64': '@inillucent/cli-win32-x64',
|
|
24
|
+
'darwin-arm64': '@inillucent/cli-darwin-arm64',
|
|
25
|
+
'darwin-x64': '@inillucent/cli-darwin-x64',
|
|
26
|
+
'linux-x64': '@inillucent/cli-linux-x64',
|
|
27
|
+
'linux-arm64': '@inillucent/cli-linux-arm64',
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
/** The four programs the release ships, and what each one is for. */
|
|
31
|
+
export const PROGRAMS = {
|
|
32
|
+
inillucent: 'the command line: query, exec, describe, import, export, search',
|
|
33
|
+
'inillucent-shell': 'the interactive sqlite3-shaped shell',
|
|
34
|
+
'inillucent-mcp': 'the MCP server, for an agent',
|
|
35
|
+
'inillucent-migrate': 'builds an inillucent database from a SQLite file',
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Returns the platform package name for this machine, or null if there is none.
|
|
40
|
+
*/
|
|
41
|
+
export function platformPackage() {
|
|
42
|
+
return PACKAGES[`${process.platform}-${process.arch}`] ?? null;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Returns the absolute path of one of the four programs on this machine.
|
|
47
|
+
*
|
|
48
|
+
* @param program - which program, as it is named in PROGRAMS
|
|
49
|
+
*/
|
|
50
|
+
export function resolveBinary(program) {
|
|
51
|
+
if (!(program in PROGRAMS)) {
|
|
52
|
+
throw new Error(`inillucent has no program called ${program}`);
|
|
53
|
+
}
|
|
54
|
+
// **`INILLUCENT_BIN` wins, the way it already does for the Go and PHP
|
|
55
|
+
// wrappers (task-1969, 4.5).** It names the `inillucent` binary; the others
|
|
56
|
+
// are looked for beside it, which is where a build and an install both put
|
|
57
|
+
// them. Without this the wrappers stage of `tools/validate` could point the
|
|
58
|
+
// other two languages at a freshly built binary and had no way to point this
|
|
59
|
+
// one, so the only npm test that could run was the one that reads the
|
|
60
|
+
// platform table as text.
|
|
61
|
+
//
|
|
62
|
+
// It throws rather than falling through when the named binary is not there:
|
|
63
|
+
// a caller who says where the binary is and is wrong wants to know, not to
|
|
64
|
+
// have the resolver quietly go looking somewhere else.
|
|
65
|
+
const named = process.env.INILLUCENT_BIN;
|
|
66
|
+
if (named) {
|
|
67
|
+
const suffix = process.platform === 'win32' ? '.exe' : '';
|
|
68
|
+
const beside = program === 'inillucent'
|
|
69
|
+
? named
|
|
70
|
+
: join(dirname(named), `${program}${suffix}`);
|
|
71
|
+
if (!existsSync(beside)) {
|
|
72
|
+
throw new Error(
|
|
73
|
+
`INILLUCENT_BIN is set and ${beside} is not there.
|
|
74
|
+
` +
|
|
75
|
+
` Unset INILLUCENT_BIN to look for an installed copy instead.`,
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
return beside;
|
|
79
|
+
}
|
|
80
|
+
const name = platformPackage();
|
|
81
|
+
if (!name) {
|
|
82
|
+
throw new Error(
|
|
83
|
+
`inillucent has no prebuilt binary for ${process.platform}-${process.arch}.\n` +
|
|
84
|
+
` Build it from source instead: cargo install inillucent-cli\n` +
|
|
85
|
+
` Or open an issue: https://github.com/Black-Rainbow-Labs/Inillucent/issues`,
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
const suffix = process.platform === 'win32' ? '.exe' : '';
|
|
89
|
+
let path;
|
|
90
|
+
try {
|
|
91
|
+
// The platform package's own entry point tells us where its bin directory
|
|
92
|
+
// is, rather than this package guessing at a node_modules layout - which
|
|
93
|
+
// pnpm, yarn's pnp and a hoisted npm tree all arrange differently.
|
|
94
|
+
path = require.resolve(`${name}/bin/${program}${suffix}`);
|
|
95
|
+
} catch {
|
|
96
|
+
throw new Error(
|
|
97
|
+
`inillucent's binary for ${process.platform}-${process.arch} is not installed.\n` +
|
|
98
|
+
` The package ${name} should have been installed as an optional dependency.\n` +
|
|
99
|
+
` If your installer was run with --no-optional, install it directly:\n` +
|
|
100
|
+
` npm install ${name}\n`,
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
try {
|
|
104
|
+
accessSync(path, constants.X_OK);
|
|
105
|
+
} catch {
|
|
106
|
+
// An npm tarball does not always preserve the executable bit, and the
|
|
107
|
+
// failure it produces otherwise is EACCES from execve with no explanation.
|
|
108
|
+
throw new Error(
|
|
109
|
+
`${path} is not executable.\n Fix it with: chmod +x ${path}\n` +
|
|
110
|
+
` Then please report it: https://github.com/Black-Rainbow-Labs/Inillucent/issues`,
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
return path;
|
|
114
|
+
}
|