@zukhruf/sqlite-reader 0.0.0-stage → 0.3.54
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/LICENSE +21 -0
- package/README.md +93 -2
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/open-reader.d.ts +3 -0
- package/dist/open-reader.d.ts.map +1 -0
- package/dist/open-reader.js +20 -0
- package/dist/open-reader.js.map +1 -0
- package/dist/unavailable-error.d.ts +6 -0
- package/dist/unavailable-error.d.ts.map +1 -0
- package/dist/unavailable-error.js +10 -0
- package/dist/unavailable-error.js.map +1 -0
- package/package.json +42 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 ezzabuzaid
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,94 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @zukhruf/sqlite-reader
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Some programs keep their data in a SQLite database, and write it while they run. Another program can read that database. It opens a read-only connection, a reader. The open can fail because the file is missing, or because the system denies access to it. The caller can fix these failures, so the reader must name them. This package opens the reader, and throws `UnavailableError` for these failures.
|
|
4
|
+
|
|
5
|
+
`@zukhruf/sqlite-snapshot` and `@zukhruf/sqlite-data-version` open their connections with this package. Each of them exports the same `UnavailableError`, so a catch with `instanceof` works with the package that the caller imports.
|
|
6
|
+
|
|
7
|
+
The words in these documents have one meaning each. See the glossary in [CONTEXT.md](./CONTEXT.md).
|
|
8
|
+
|
|
9
|
+
## Use it
|
|
10
|
+
|
|
11
|
+
This project is an experiment. It uses only `node:sqlite`.
|
|
12
|
+
|
|
13
|
+
```sh
|
|
14
|
+
npm install @zukhruf/sqlite-reader
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
import { mkdtempDisposable } from 'node:fs/promises';
|
|
19
|
+
import { tmpdir } from 'node:os';
|
|
20
|
+
import { join } from 'node:path';
|
|
21
|
+
import { DatabaseSync } from 'node:sqlite';
|
|
22
|
+
|
|
23
|
+
import { UnavailableError, openReader } from '@zukhruf/sqlite-reader';
|
|
24
|
+
|
|
25
|
+
await using scratch = await mkdtempDisposable(join(tmpdir(), 'reader-'));
|
|
26
|
+
const path = join(scratch.path, 'notes.sqlite');
|
|
27
|
+
|
|
28
|
+
// The owner: another connection that writes the database through a WAL.
|
|
29
|
+
using owner = new DatabaseSync(path);
|
|
30
|
+
owner.exec(`PRAGMA journal_mode = WAL;
|
|
31
|
+
CREATE TABLE notes (title TEXT);
|
|
32
|
+
INSERT INTO notes VALUES ('first')`);
|
|
33
|
+
|
|
34
|
+
using reader = openReader(path);
|
|
35
|
+
console.log(reader.prepare('SELECT title FROM notes').all());
|
|
36
|
+
|
|
37
|
+
try {
|
|
38
|
+
openReader(join(scratch.path, 'missing', 'notes.sqlite'));
|
|
39
|
+
} catch (error) {
|
|
40
|
+
console.log(error instanceof UnavailableError);
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Output:
|
|
45
|
+
|
|
46
|
+
```
|
|
47
|
+
[ [Object: null prototype] { title: 'first' } ]
|
|
48
|
+
true
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## The rules
|
|
52
|
+
|
|
53
|
+
- `openReader(path)` gives a `DatabaseSync` of `node:sqlite`, opened with `readOnly: true`. The caller owns the connection, and closes it.
|
|
54
|
+
- The reader never writes. A write fails with the error of SQLite.
|
|
55
|
+
- The package throws its own `UnavailableError`. A host that needs its own error translates the error of the package at its boundary, and keeps the error of the package as `cause`.
|
|
56
|
+
|
|
57
|
+
## Errors
|
|
58
|
+
|
|
59
|
+
| Error | When |
|
|
60
|
+
| ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
61
|
+
| `UnavailableError` | SQLite cannot open the file: `SQLITE_CANTOPEN` (14), for a missing file or a file that the system denies, a privacy denial too. `path` is the path. `cause` is the error of SQLite. |
|
|
62
|
+
| The error of SQLite | All other failures. |
|
|
63
|
+
|
|
64
|
+
`UnavailableError` takes `(path, cause)`. Another package can throw it for the same failure from a different source. For example, `lastWrite` of `@zukhruf/sqlite-data-version` throws it when the file system cannot give the times of the file.
|
|
65
|
+
|
|
66
|
+
## The same concepts in other systems
|
|
67
|
+
|
|
68
|
+
| System | What it does |
|
|
69
|
+
| ------------------- | -------------------------------------------------------------------------------------------------------------- |
|
|
70
|
+
| `sqlite3 -readonly` | It opens a database read-only. A missing file fails with "unable to open database file", the text of CANTOPEN. |
|
|
71
|
+
| Python `sqlite3` | `sqlite3.connect('file:path?mode=ro', uri=True)` opens a database read-only, and raises `OperationalError`. |
|
|
72
|
+
|
|
73
|
+
## Documentation
|
|
74
|
+
|
|
75
|
+
- [0001 One package opens the reader and owns UnavailableError](./docs/adr/0001-one-package-opens-the-reader-and-owns-unavailable-error.md)
|
|
76
|
+
|
|
77
|
+
## Development
|
|
78
|
+
|
|
79
|
+
This package is part of the [zukhruf](../../../README.md) workspace. Run the commands from the workspace root.
|
|
80
|
+
|
|
81
|
+
```sh
|
|
82
|
+
npm install
|
|
83
|
+
npx nx run sqlite-reader:test # builds, then runs the tests in src/
|
|
84
|
+
npx nx run sqlite-reader:typecheck # formats, lints, then type checks
|
|
85
|
+
npx nx run sqlite-reader:build # compiles src/ to dist/
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
```
|
|
89
|
+
src/
|
|
90
|
+
index.ts the exports
|
|
91
|
+
open-reader.ts openReader
|
|
92
|
+
unavailable-error.ts UnavailableError
|
|
93
|
+
open-reader.test.ts the tests, on real SQLite files
|
|
94
|
+
```
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"open-reader.d.ts","sourceRoot":"","sources":["../src/open-reader.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAK3C,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY,CAgBrD"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { DatabaseSync } from 'node:sqlite';
|
|
2
|
+
import { UnavailableError } from "./unavailable-error.js";
|
|
3
|
+
// A read-only connection to a database that another process owns.
|
|
4
|
+
export function openReader(path) {
|
|
5
|
+
try {
|
|
6
|
+
return new DatabaseSync(path, { readOnly: true });
|
|
7
|
+
}
|
|
8
|
+
catch (cause) {
|
|
9
|
+
if (cause instanceof Error &&
|
|
10
|
+
'errcode' in cause &&
|
|
11
|
+
// SQLite's CANTOPEN (14). A missing or denied file fails the open()
|
|
12
|
+
// syscall, and SQLite reports that as CANTOPEN, a privacy denial too.
|
|
13
|
+
// SQLite gives AUTH only for an authorizer, and this private connection
|
|
14
|
+
// has none. Any other failure is not one that access can fix.
|
|
15
|
+
cause.errcode === 14)
|
|
16
|
+
throw new UnavailableError(path, cause);
|
|
17
|
+
throw cause;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=open-reader.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"open-reader.js","sourceRoot":"","sources":["../src/open-reader.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAE1D,kEAAkE;AAClE,MAAM,UAAU,UAAU,CAAC,IAAY;IACrC,IAAI,CAAC;QACH,OAAO,IAAI,YAAY,CAAC,IAAI,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC;IACpD,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IACE,KAAK,YAAY,KAAK;YACtB,SAAS,IAAI,KAAK;YAClB,oEAAoE;YACpE,sEAAsE;YACtE,wEAAwE;YACxE,8DAA8D;YAC9D,KAAK,CAAC,OAAO,KAAK,EAAE;YAEpB,MAAM,IAAI,gBAAgB,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;QAC1C,MAAM,KAAK,CAAC;IACd,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"unavailable-error.d.ts","sourceRoot":"","sources":["../src/unavailable-error.ts"],"names":[],"mappings":"AAAA,yEAAyE;AACzE,qBAAa,gBAAiB,SAAQ,KAAK;IACzC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;gBAEV,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO;CAQzC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** The database file is missing, or this process has no access to it. */
|
|
2
|
+
export class UnavailableError extends Error {
|
|
3
|
+
path;
|
|
4
|
+
constructor(path, cause) {
|
|
5
|
+
super(`The database at ${JSON.stringify(path)} cannot be opened: the file is missing, or this process has no access to it. Give the process access, or check the path.`, { cause });
|
|
6
|
+
this.name = 'UnavailableError';
|
|
7
|
+
this.path = path;
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
//# sourceMappingURL=unavailable-error.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"unavailable-error.js","sourceRoot":"","sources":["../src/unavailable-error.ts"],"names":[],"mappings":"AAAA,yEAAyE;AACzE,MAAM,OAAO,gBAAiB,SAAQ,KAAK;IAChC,IAAI,CAAS;IAEtB,YAAY,IAAY,EAAE,KAAc;QACtC,KAAK,CACH,mBAAmB,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,0HAA0H,EACjK,EAAE,KAAK,EAAE,CACV,CAAC;QACF,IAAI,CAAC,IAAI,GAAG,kBAAkB,CAAC;QAC/B,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;IACnB,CAAC;CACF"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,44 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zukhruf/sqlite-reader",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.3.54",
|
|
4
|
+
"description": "A read-only connection to a SQLite database that another process owns and writes. It opens the database with node:sqlite, and gives UnavailableError when the file is missing or the system denies access, a failure that the caller can fix.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "https://github.com/JanuaryLabs/zukhruf",
|
|
9
|
+
"directory": "packages/sqlite/reader"
|
|
10
|
+
},
|
|
11
|
+
"bugs": {
|
|
12
|
+
"url": "https://github.com/JanuaryLabs/zukhruf/issues"
|
|
13
|
+
},
|
|
14
|
+
"homepage": "https://github.com/JanuaryLabs/zukhruf/tree/main/packages/sqlite/reader#readme",
|
|
15
|
+
"keywords": [
|
|
16
|
+
"sqlite",
|
|
17
|
+
"node-sqlite",
|
|
18
|
+
"read-only",
|
|
19
|
+
"reader",
|
|
20
|
+
"wal"
|
|
21
|
+
],
|
|
22
|
+
"type": "module",
|
|
23
|
+
"exports": {
|
|
24
|
+
"./package.json": "./package.json",
|
|
25
|
+
".": {
|
|
26
|
+
"@zukhruf/source": "./src/index.ts",
|
|
27
|
+
"types": "./dist/index.d.ts",
|
|
28
|
+
"import": "./dist/index.js",
|
|
29
|
+
"default": "./dist/index.js"
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
"files": [
|
|
33
|
+
"dist",
|
|
34
|
+
"!dist/spec/**",
|
|
35
|
+
"!**/*.tsbuildinfo",
|
|
36
|
+
"!**/*.test.*"
|
|
37
|
+
],
|
|
38
|
+
"publishConfig": {
|
|
39
|
+
"access": "public"
|
|
40
|
+
},
|
|
41
|
+
"devDependencies": {
|
|
42
|
+
"@zukhruf/testing": "^0.3.54"
|
|
43
|
+
}
|
|
44
|
+
}
|