@zukhruf/sqlite-open 0.0.0-stage → 0.7.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.
Files changed (41) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +165 -2
  3. package/dist/index.d.ts +8 -0
  4. package/dist/index.d.ts.map +1 -0
  5. package/dist/index.js +7 -0
  6. package/dist/index.js.map +1 -0
  7. package/dist/journal.d.ts +10 -0
  8. package/dist/journal.d.ts.map +1 -0
  9. package/dist/journal.js +2 -0
  10. package/dist/journal.js.map +1 -0
  11. package/dist/open.d.ts +18 -0
  12. package/dist/open.d.ts.map +1 -0
  13. package/dist/open.js +36 -0
  14. package/dist/open.js.map +1 -0
  15. package/dist/persistent-write-ahead-log.d.ts +15 -0
  16. package/dist/persistent-write-ahead-log.d.ts.map +1 -0
  17. package/dist/persistent-write-ahead-log.js +37 -0
  18. package/dist/persistent-write-ahead-log.js.map +1 -0
  19. package/dist/rollback-journal.d.ts +12 -0
  20. package/dist/rollback-journal.d.ts.map +1 -0
  21. package/dist/rollback-journal.js +12 -0
  22. package/dist/rollback-journal.js.map +1 -0
  23. package/dist/unavailable-error.d.ts +11 -0
  24. package/dist/unavailable-error.d.ts.map +1 -0
  25. package/dist/unavailable-error.js +39 -0
  26. package/dist/unavailable-error.js.map +1 -0
  27. package/dist/unchanged-journal.d.ts +10 -0
  28. package/dist/unchanged-journal.d.ts.map +1 -0
  29. package/dist/unchanged-journal.js +9 -0
  30. package/dist/unchanged-journal.js.map +1 -0
  31. package/dist/write-ahead-log.d.ts +13 -0
  32. package/dist/write-ahead-log.d.ts.map +1 -0
  33. package/dist/write-ahead-log.js +25 -0
  34. package/dist/write-ahead-log.js.map +1 -0
  35. package/lib/darwin-arm64/persist_wal.dylib +0 -0
  36. package/lib/darwin-x64/persist_wal.dylib +0 -0
  37. package/lib/linux-arm64/persist_wal.so +0 -0
  38. package/lib/linux-x64/persist_wal.so +0 -0
  39. package/lib/win32-arm64/persist_wal.dll +0 -0
  40. package/lib/win32-x64/persist_wal.dll +0 -0
  41. package/package.json +44 -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,166 @@
1
- # Temporary Holding Version
1
+ # @zukhruf/sqlite-open
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Programs keep their data in SQLite databases. One program, the owner, writes a database. Other programs read it, sometimes while the owner runs. Each program opens its connection with `new DatabaseSync` of `node:sqlite`, and then does the same setup again: it selects the journal, sets how long to wait for the lock of another connection, and finds which errors mean "the file is missing, or the system denies access". This package does that setup in one function, `open`.
4
+
5
+ Some readers cannot write the folder of the database, for example an agent that runs `sqlite3 -readonly` in a sandbox. In a write-ahead log, such a reader cannot open the database after the owner closes it, because SQLite deletes the `-wal` and `-shm` files at the last close, and the reader cannot make them again. Persistent WAL keeps the two files. `node:sqlite` has no API to turn it on, so this package loads a small native library that does it.
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 `node:sqlite`, and one native library for persistent WAL.
12
+
13
+ ```sh
14
+ npm install @zukhruf/sqlite-open
15
+ ```
16
+
17
+ ```ts
18
+ import { existsSync } from 'node:fs';
19
+ import { mkdtempDisposable } from 'node:fs/promises';
20
+ import { tmpdir } from 'node:os';
21
+ import { join } from 'node:path';
22
+
23
+ import {
24
+ PersistentWriteAheadLog,
25
+ UnavailableError,
26
+ open,
27
+ } from '@zukhruf/sqlite-open';
28
+
29
+ await using scratch = await mkdtempDisposable(join(tmpdir(), 'open-'));
30
+ const path = join(scratch.path, 'notes.sqlite');
31
+
32
+ {
33
+ // The owner writes the database, and then closes it.
34
+ using owner = open(path, { journal: new PersistentWriteAheadLog() });
35
+ owner.exec(`CREATE TABLE notes (title TEXT);
36
+ INSERT INTO notes VALUES ('first')`);
37
+ }
38
+
39
+ // The -shm file stays, so a reader that cannot write the folder can open the database.
40
+ console.log(existsSync(`${path}-shm`));
41
+
42
+ using reader = open(path, { readOnly: true });
43
+ console.log(reader.prepare('SELECT title FROM notes').all());
44
+ console.log(reader.prepare('PRAGMA journal_mode').get());
45
+
46
+ try {
47
+ open(join(scratch.path, 'missing', 'notes.sqlite'), { readOnly: true });
48
+ } catch (error) {
49
+ console.log(error instanceof UnavailableError);
50
+ }
51
+ ```
52
+
53
+ Output:
54
+
55
+ ```
56
+ true
57
+ [ [Object: null prototype] { title: 'first' } ]
58
+ [Object: null prototype] { journal_mode: 'wal' }
59
+ true
60
+ ```
61
+
62
+ ## The rules
63
+
64
+ - `open(path, options)` takes the options of `new DatabaseSync` of `node:sqlite`, except `open`, and gives a `DatabaseSync`. The caller owns the connection, and closes it.
65
+ - `readOnly: true` opens a reader. The package does no other step. A reader never writes to the database, and never changes its journal.
66
+ - Without `readOnly`, `open` opens the connection of an owner, and the `journal` option sets the journal. The default is `WriteAheadLog`. A reader cannot have a `journal` option. TypeScript refuses it.
67
+ - A database in memory (`:memory:`, `''`, or a `file::memory:` URI) has no file, so it gets no journal.
68
+ - The busy timeout is 5000 milliseconds, unless the caller gives `timeout`. With `timeout: 0`, a lock of another connection fails at once with `SQLITE_BUSY` (5).
69
+ - The owner opens with extension loading on, so that a journal can load the native library. After the journal step, extension loading is as the caller asked with `allowExtension`. The default is off.
70
+ - When the open or the journal step fails, `open` closes the connection and throws. An unavailable file throws `UnavailableError`. All other failures throw the error of SQLite.
71
+ - 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`.
72
+
73
+ `open` replaces `new DatabaseSync` at the call site, with three differences:
74
+
75
+ - It has no `open` option. `open` always opens the connection, because the journal step needs an open file.
76
+ - The busy timeout is 5000 milliseconds when the caller gives no `timeout`. `node:sqlite` uses 0.
77
+ - An owner opens with extension loading on for the journal step. After that step, extension loading is as the caller asked.
78
+
79
+ ## Journals
80
+
81
+ Each journal is a strategy object: the caller gives an instance, for example `open(path, { journal: new RollbackJournal() })`. A new journal is a class that implements `Journal`, with one method: `apply(database)`.
82
+
83
+ | Journal | What it does | Use it for |
84
+ | ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------- |
85
+ | `WriteAheadLog` (default) | `PRAGMA journal_mode = WAL` and `PRAGMA journal_size_limit = 67108864` (64 MiB). SQLite deletes `-wal` and `-shm` at the last close. | An owner whose readers can write the folder. |
86
+ | `PersistentWriteAheadLog` | The same steps, after the native library turns on persistent WAL. `-wal` and `-shm` stay after the last close. | An owner whose readers cannot write the folder, for example agents that run `sqlite3 -readonly` in a sandbox. |
87
+ | `RollbackJournal` | `PRAGMA journal_mode = DELETE`. The first access rolls back a hot journal. | A file that the owner builds and then renames or deletes. A copy that must lose a hot journal. |
88
+ | `UnchangedJournal` | Nothing. | An owner that sets its own journal, for example a lock file whose `PRAGMA` must wait for the lock. |
89
+
90
+ The size limit keeps a write-ahead log small after a large commit. Without it, the `-wal` file of a 20 MB commit stays 20 MB.
91
+
92
+ `WriteAheadLog` and `PersistentWriteAheadLog` take an optional page size, for example `new WriteAheadLog({ pageSize: 32768 })`. SQLite sets the page size of a file only before its first write, and never in a write-ahead log. The write-ahead log step is that first write, so the journal sets the page size just before it. An existing file keeps its own page size.
93
+
94
+ ## Errors
95
+
96
+ | Error | When |
97
+ | ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
98
+ | `UnavailableError` | The file is missing, or the system denies access to it: `SQLITE_CANTOPEN` (14) from the open, a privacy denial too, or `SQLITE_READONLY_DIRECTORY` (1544) from the journal step, when the folder denies the write of the journal. `path` is the path. `cause` is the error of SQLite. `UnavailableError.from(path, error)` also accepts `ENOENT`, `ENOTDIR`, `EACCES` and `EPERM` from the file system. |
99
+ | `Error` | `PersistentWriteAheadLog` runs on a platform that has no native library. The message names the platform, and the platforms that have one. |
100
+ | `Error` | SQLite did not change the journal to a write-ahead log. The message gives the journal that SQLite kept. |
101
+ | The error of SQLite | All other failures, for example a file that is not a database. |
102
+
103
+ ## Platforms
104
+
105
+ The native library is in `lib/<platform>-<arch>/` of the package, for `darwin-arm64`, `darwin-x64`, `linux-arm64`, `linux-x64` (glibc and musl), `win32-x64` and `win32-arm64`. Only `PersistentWriteAheadLog` loads it, at its first open.
106
+
107
+ A bundler that bundles this package does not copy the `lib` folder. A bundle that uses `PersistentWriteAheadLog` keeps `@zukhruf/sqlite-open` external, and ships it in `node_modules`. An Electron app also unpacks `lib` from its `asar` archive.
108
+
109
+ ## Why not a package from npm
110
+
111
+ | Candidate | Why it does not do the job |
112
+ | ------------------------ | ---------------------------------------------------------------------------------------------- |
113
+ | `node:sqlite` | It has no file control, so it cannot turn on persistent WAL. Its busy timeout is 0 by default. |
114
+ | `better-sqlite3` | It has no file control. Issues #1029 and #1101 ask for persistent WAL. They are open. |
115
+ | `@photostructure/sqlite` | A copy of the `node:sqlite` API. It has no file control. |
116
+ | `libsql` | The `better-sqlite3` API. It has no file control. |
117
+ | Bun's `bun:sqlite` | `db.fileControl(SQLITE_FCNTL_PERSIST_WAL, 1)` turns it on, but only in Bun. |
118
+
119
+ ## The same concepts in other systems
120
+
121
+ | System | What it does |
122
+ | --------------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
123
+ | Lix (a fork of Nix) | `LocalStore::openDB` turns on persistent WAL after it sets WAL, so users without write permission can read the store (NixOS/nix#10300). |
124
+ | Apple's system SQLite | It keeps persistent WAL by default. Its `sqlite3` leaves `-wal` and `-shm` after it closes. |
125
+ | `better-sqlite3` | Its busy timeout is 5000 milliseconds by default. |
126
+ | Python `sqlite3` | `sqlite3.connect(path, timeout=5.0)`: its busy timeout is 5 seconds by default. |
127
+ | Rails 8 | A SQLite database gets a write-ahead log and `journal_size_limit = 67108864` by default. |
128
+ | `sqlite3 -readonly` | It opens a reader. A missing file fails with "unable to open database file", the text of CANTOPEN. |
129
+
130
+ ## Documentation
131
+
132
+ - [0001 One package opens the reader and owns UnavailableError](./docs/adr/0001-one-package-opens-the-reader-and-owns-unavailable-error.md)
133
+ - [0002 One open for readers and owners](./docs/adr/0002-one-open-for-readers-and-owners.md)
134
+ - [0003 Persistent WAL through a native library](./docs/adr/0003-persistent-wal-through-a-native-library.md)
135
+ - [0004 The journal is a strategy](./docs/adr/0004-the-journal-is-a-strategy.md)
136
+ - [0005 One map of failures and a busy timeout of 5 seconds](./docs/adr/0005-one-map-of-failures-and-a-busy-timeout-of-5-seconds.md)
137
+ - [0006 Zig builds every native library in one Linux job](./docs/adr/0006-zig-builds-every-native-library-in-one-linux-job.md)
138
+
139
+ ## Development
140
+
141
+ This package is part of the [zukhruf](../../../README.md) workspace. Run the commands from the workspace root. The `native` target needs `zig` 0.16.
142
+
143
+ ```sh
144
+ npm install
145
+ npx nx run sqlite-open:test # builds the native libraries and dist, then runs the tests in src/
146
+ npx nx run sqlite-open:typecheck # formats, lints, then type checks
147
+ npx nx run sqlite-open:build # builds lib/ with zig, then compiles src/ to dist/
148
+ ```
149
+
150
+ ```
151
+ src/
152
+ index.ts the exports
153
+ open.ts open
154
+ journal.ts the Journal interface
155
+ write-ahead-log.ts WriteAheadLog
156
+ persistent-write-ahead-log.ts PersistentWriteAheadLog
157
+ rollback-journal.ts RollbackJournal
158
+ unchanged-journal.ts UnchangedJournal
159
+ unavailable-error.ts UnavailableError
160
+ open.test.ts the tests, on real SQLite files and processes
161
+ native/
162
+ persist_wal.c the native library
163
+ sqlite3.h, sqlite3ext.h the SQLite 3.54.0 headers
164
+ build.ts builds lib/ for each platform with zig
165
+ lib/ built by the native target, not in git
166
+ ```
@@ -0,0 +1,8 @@
1
+ export type { Journal } from './journal.ts';
2
+ export { open, type OwnerOptions, type ReaderOptions } from './open.ts';
3
+ export { PersistentWriteAheadLog } from './persistent-write-ahead-log.ts';
4
+ export { RollbackJournal } from './rollback-journal.ts';
5
+ export { UnavailableError } from './unavailable-error.ts';
6
+ export { UnchangedJournal } from './unchanged-journal.ts';
7
+ export { type PageSize, WriteAheadLog } from './write-ahead-log.ts';
8
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,EAAE,IAAI,EAAE,KAAK,YAAY,EAAE,KAAK,aAAa,EAAE,MAAM,WAAW,CAAC;AACxE,OAAO,EAAE,uBAAuB,EAAE,MAAM,iCAAiC,CAAC;AAC1E,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,EAAE,KAAK,QAAQ,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,7 @@
1
+ export { open } from "./open.js";
2
+ export { PersistentWriteAheadLog } from "./persistent-write-ahead-log.js";
3
+ export { RollbackJournal } from "./rollback-journal.js";
4
+ export { UnavailableError } from "./unavailable-error.js";
5
+ export { UnchangedJournal } from "./unchanged-journal.js";
6
+ export { WriteAheadLog } from "./write-ahead-log.js";
7
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,IAAI,EAAyC,MAAM,WAAW,CAAC;AACxE,OAAO,EAAE,uBAAuB,EAAE,MAAM,iCAAiC,CAAC;AAC1E,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,EAAiB,aAAa,EAAE,MAAM,sBAAsB,CAAC"}
@@ -0,0 +1,10 @@
1
+ import type { DatabaseSync } from 'node:sqlite';
2
+ /**
3
+ * How SQLite keeps a commit safe. `open` gives the connection of an owner to
4
+ * its journal, which sets it up (ADR 0004). A new journal is a class that
5
+ * implements this interface.
6
+ */
7
+ export interface Journal {
8
+ apply(database: DatabaseSync): void;
9
+ }
10
+ //# sourceMappingURL=journal.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"journal.d.ts","sourceRoot":"","sources":["../src/journal.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD;;;;GAIG;AACH,MAAM,WAAW,OAAO;IACtB,KAAK,CAAC,QAAQ,EAAE,YAAY,GAAG,IAAI,CAAC;CACrC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=journal.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"journal.js","sourceRoot":"","sources":["../src/journal.ts"],"names":[],"mappings":""}
package/dist/open.d.ts ADDED
@@ -0,0 +1,18 @@
1
+ import { DatabaseSync, type DatabaseSyncOptions } from 'node:sqlite';
2
+ import type { Journal } from './journal.ts';
3
+ type Path = ConstructorParameters<typeof DatabaseSync>[0];
4
+ type Settings = Omit<DatabaseSyncOptions, 'open' | 'readOnly'>;
5
+ /** A reader: a read-only connection that never changes the database. */
6
+ export type ReaderOptions = Settings & {
7
+ readonly readOnly: true;
8
+ readonly journal?: never;
9
+ };
10
+ /** The connection of the owner, which writes the database and sets its journal. */
11
+ export type OwnerOptions = Settings & {
12
+ readonly readOnly?: false;
13
+ readonly journal?: Journal;
14
+ };
15
+ /** Opens a connection: a reader with `readOnly: true`, else an owner (ADR 0002). */
16
+ export declare function open(path: Path, options?: ReaderOptions | OwnerOptions): DatabaseSync;
17
+ export {};
18
+ //# sourceMappingURL=open.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"open.d.ts","sourceRoot":"","sources":["../src/open.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,KAAK,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAErE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAI5C,KAAK,IAAI,GAAG,qBAAqB,CAAC,OAAO,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC;AAC1D,KAAK,QAAQ,GAAG,IAAI,CAAC,mBAAmB,EAAE,MAAM,GAAG,UAAU,CAAC,CAAC;AAE/D,wEAAwE;AACxE,MAAM,MAAM,aAAa,GAAG,QAAQ,GAAG;IACrC,QAAQ,CAAC,QAAQ,EAAE,IAAI,CAAC;IACxB,QAAQ,CAAC,OAAO,CAAC,EAAE,KAAK,CAAC;CAC1B,CAAC;AAEF,mFAAmF;AACnF,MAAM,MAAM,YAAY,GAAG,QAAQ,GAAG;IACpC,QAAQ,CAAC,QAAQ,CAAC,EAAE,KAAK,CAAC;IAC1B,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC;CAC5B,CAAC;AAEF,oFAAoF;AACpF,wBAAgB,IAAI,CAClB,IAAI,EAAE,IAAI,EACV,OAAO,GAAE,aAAa,GAAG,YAAiB,GACzC,YAAY,CAgBd"}
package/dist/open.js ADDED
@@ -0,0 +1,36 @@
1
+ import { DatabaseSync } from 'node:sqlite';
2
+ import { UnavailableError } from "./unavailable-error.js";
3
+ import { WriteAheadLog } from "./write-ahead-log.js";
4
+ /** Opens a connection: a reader with `readOnly: true`, else an owner (ADR 0002). */
5
+ export function open(path, options = {}) {
6
+ if (options.readOnly)
7
+ return connect(path, options);
8
+ const { journal = new WriteAheadLog(), ...settings } = options;
9
+ // The owner opens with extension loading on, so that a journal can load the
10
+ // native library; the step after the journal sets it as the caller asked.
11
+ const database = connect(path, { ...settings, allowExtension: true });
12
+ try {
13
+ // A database in memory (':memory:', '', a file::memory: URI) has no file,
14
+ // so it has no journal to set.
15
+ if (database.location() !== null)
16
+ journal.apply(database);
17
+ database.enableLoadExtension(settings.allowExtension ?? false);
18
+ return database;
19
+ }
20
+ catch (error) {
21
+ database.close();
22
+ throw UnavailableError.from(String(path), error) ?? error;
23
+ }
24
+ }
25
+ // The busy timeout when the caller gives none, in milliseconds, as
26
+ // better-sqlite3, Python's sqlite3 and Rails have it (ADR 0005).
27
+ const busyTimeout = 5000;
28
+ function connect(path, options) {
29
+ try {
30
+ return new DatabaseSync(path, { timeout: busyTimeout, ...options });
31
+ }
32
+ catch (error) {
33
+ throw UnavailableError.from(String(path), error) ?? error;
34
+ }
35
+ }
36
+ //# sourceMappingURL=open.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"open.js","sourceRoot":"","sources":["../src/open.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAA4B,MAAM,aAAa,CAAC;AAGrE,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAiBrD,oFAAoF;AACpF,MAAM,UAAU,IAAI,CAClB,IAAU,EACV,UAAwC,EAAE;IAE1C,IAAI,OAAO,CAAC,QAAQ;QAAE,OAAO,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;IACpD,MAAM,EAAE,OAAO,GAAG,IAAI,aAAa,EAAE,EAAE,GAAG,QAAQ,EAAE,GAAG,OAAO,CAAC;IAC/D,4EAA4E;IAC5E,0EAA0E;IAC1E,MAAM,QAAQ,GAAG,OAAO,CAAC,IAAI,EAAE,EAAE,GAAG,QAAQ,EAAE,cAAc,EAAE,IAAI,EAAE,CAAC,CAAC;IACtE,IAAI,CAAC;QACH,0EAA0E;QAC1E,+BAA+B;QAC/B,IAAI,QAAQ,CAAC,QAAQ,EAAE,KAAK,IAAI;YAAE,OAAO,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;QAC1D,QAAQ,CAAC,mBAAmB,CAAC,QAAQ,CAAC,cAAc,IAAI,KAAK,CAAC,CAAC;QAC/D,OAAO,QAAQ,CAAC;IAClB,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,QAAQ,CAAC,KAAK,EAAE,CAAC;QACjB,MAAM,gBAAgB,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,KAAK,CAAC,IAAI,KAAK,CAAC;IAC5D,CAAC;AACH,CAAC;AAED,mEAAmE;AACnE,iEAAiE;AACjE,MAAM,WAAW,GAAG,IAAI,CAAC;AAEzB,SAAS,OAAO,CAAC,IAAU,EAAE,OAA4B;IACvD,IAAI,CAAC;QACH,OAAO,IAAI,YAAY,CAAC,IAAI,EAAE,EAAE,OAAO,EAAE,WAAW,EAAE,GAAG,OAAO,EAAE,CAAC,CAAC;IACtE,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,gBAAgB,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,KAAK,CAAC,IAAI,KAAK,CAAC;IAC5D,CAAC;AACH,CAAC"}
@@ -0,0 +1,15 @@
1
+ import type { DatabaseSync } from 'node:sqlite';
2
+ import type { Journal } from './journal.ts';
3
+ import { type PageSize } from './write-ahead-log.ts';
4
+ /**
5
+ * A write-ahead log whose -wal and -shm files stay after the last connection
6
+ * closes, for readers that cannot write the folder (ADR 0003).
7
+ */
8
+ export declare class PersistentWriteAheadLog implements Journal {
9
+ #private;
10
+ constructor(options?: {
11
+ readonly pageSize?: PageSize;
12
+ });
13
+ apply(database: DatabaseSync): void;
14
+ }
15
+ //# sourceMappingURL=persistent-write-ahead-log.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"persistent-write-ahead-log.d.ts","sourceRoot":"","sources":["../src/persistent-write-ahead-log.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAGhD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,EAAE,KAAK,QAAQ,EAAiB,MAAM,sBAAsB,CAAC;AAWpE;;;GAGG;AACH,qBAAa,uBAAwB,YAAW,OAAO;;gBAGzC,OAAO,GAAE;QAAE,QAAQ,CAAC,QAAQ,CAAC,EAAE,QAAQ,CAAA;KAAO;IAI1D,KAAK,CAAC,QAAQ,EAAE,YAAY,GAAG,IAAI;CAsBpC"}
@@ -0,0 +1,37 @@
1
+ import { readdirSync } from 'node:fs';
2
+ import { fileURLToPath } from 'node:url';
3
+ import { WriteAheadLog } from "./write-ahead-log.js";
4
+ // The native libraries are at the package root, so this file finds them from
5
+ // src/ in the tests and from dist/ in the package (ADR 0006).
6
+ const lib = new URL('../lib/', import.meta.url);
7
+ const suffixes = {
8
+ darwin: 'dylib',
9
+ linux: 'so',
10
+ win32: 'dll',
11
+ };
12
+ /**
13
+ * A write-ahead log whose -wal and -shm files stay after the last connection
14
+ * closes, for readers that cannot write the folder (ADR 0003).
15
+ */
16
+ export class PersistentWriteAheadLog {
17
+ #log;
18
+ constructor(options = {}) {
19
+ this.#log = new WriteAheadLog(options);
20
+ }
21
+ apply(database) {
22
+ const platform = `${process.platform}-${process.arch}`;
23
+ const library = new URL(`${platform}/persist_wal.${suffixes[process.platform]}`, lib);
24
+ try {
25
+ database.loadExtension(fileURLToPath(library));
26
+ }
27
+ catch (cause) {
28
+ if (!(cause instanceof Error) ||
29
+ !('code' in cause) ||
30
+ cause.code !== 'ERR_LOAD_SQLITE_EXTENSION')
31
+ throw cause;
32
+ throw new Error(`@zukhruf/sqlite-open has no native library for ${platform}. It has one for ${readdirSync(lib).join(', ')}.`, { cause });
33
+ }
34
+ this.#log.apply(database);
35
+ }
36
+ }
37
+ //# sourceMappingURL=persistent-write-ahead-log.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"persistent-write-ahead-log.js","sourceRoot":"","sources":["../src/persistent-write-ahead-log.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAEtC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAGzC,OAAO,EAAiB,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAEpE,6EAA6E;AAC7E,8DAA8D;AAC9D,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,SAAS,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAChD,MAAM,QAAQ,GAA6C;IACzD,MAAM,EAAE,OAAO;IACf,KAAK,EAAE,IAAI;IACX,KAAK,EAAE,KAAK;CACb,CAAC;AAEF;;;GAGG;AACH,MAAM,OAAO,uBAAuB;IACzB,IAAI,CAAgB;IAE7B,YAAY,UAA4C,EAAE;QACxD,IAAI,CAAC,IAAI,GAAG,IAAI,aAAa,CAAC,OAAO,CAAC,CAAC;IACzC,CAAC;IAED,KAAK,CAAC,QAAsB;QAC1B,MAAM,QAAQ,GAAG,GAAG,OAAO,CAAC,QAAQ,IAAI,OAAO,CAAC,IAAI,EAAE,CAAC;QACvD,MAAM,OAAO,GAAG,IAAI,GAAG,CACrB,GAAG,QAAQ,gBAAgB,QAAQ,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,EACvD,GAAG,CACJ,CAAC;QACF,IAAI,CAAC;YACH,QAAQ,CAAC,aAAa,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC,CAAC;QACjD,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IACE,CAAC,CAAC,KAAK,YAAY,KAAK,CAAC;gBACzB,CAAC,CAAC,MAAM,IAAI,KAAK,CAAC;gBAClB,KAAK,CAAC,IAAI,KAAK,2BAA2B;gBAE1C,MAAM,KAAK,CAAC;YACd,MAAM,IAAI,KAAK,CACb,kDAAkD,QAAQ,oBAAoB,WAAW,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,EAC5G,EAAE,KAAK,EAAE,CACV,CAAC;QACJ,CAAC;QACD,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;IAC5B,CAAC;CACF"}
@@ -0,0 +1,12 @@
1
+ import type { DatabaseSync } from 'node:sqlite';
2
+ import type { Journal } from './journal.ts';
3
+ /**
4
+ * The rollback journal: SQLite copies each page to the -journal file before a
5
+ * commit changes it. Its first access rolls back a hot journal. For a copy
6
+ * that must lose a hot journal, and for a file that its owner builds and then
7
+ * renames or deletes, which must leave no -wal file behind (ADR 0004).
8
+ */
9
+ export declare class RollbackJournal implements Journal {
10
+ apply(database: DatabaseSync): void;
11
+ }
12
+ //# sourceMappingURL=rollback-journal.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rollback-journal.d.ts","sourceRoot":"","sources":["../src/rollback-journal.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAE5C;;;;;GAKG;AACH,qBAAa,eAAgB,YAAW,OAAO;IAC7C,KAAK,CAAC,QAAQ,EAAE,YAAY,GAAG,IAAI;CAGpC"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * The rollback journal: SQLite copies each page to the -journal file before a
3
+ * commit changes it. Its first access rolls back a hot journal. For a copy
4
+ * that must lose a hot journal, and for a file that its owner builds and then
5
+ * renames or deletes, which must leave no -wal file behind (ADR 0004).
6
+ */
7
+ export class RollbackJournal {
8
+ apply(database) {
9
+ database.exec('PRAGMA journal_mode = DELETE');
10
+ }
11
+ }
12
+ //# sourceMappingURL=rollback-journal.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rollback-journal.js","sourceRoot":"","sources":["../src/rollback-journal.ts"],"names":[],"mappings":"AAIA;;;;;GAKG;AACH,MAAM,OAAO,eAAe;IAC1B,KAAK,CAAC,QAAsB;QAC1B,QAAQ,CAAC,IAAI,CAAC,8BAA8B,CAAC,CAAC;IAChD,CAAC;CACF"}
@@ -0,0 +1,11 @@
1
+ /** The database file is missing, or this process has no access to it. */
2
+ export declare class UnavailableError extends Error {
3
+ readonly path: string;
4
+ constructor(path: string, cause: unknown);
5
+ /**
6
+ * The UnavailableError for an error of SQLite or of the file system that
7
+ * means the file is missing or denied (ADR 0005), else undefined.
8
+ */
9
+ static from(path: string, error: unknown): UnavailableError | undefined;
10
+ }
11
+ //# sourceMappingURL=unavailable-error.d.ts.map
@@ -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;IASxC;;;OAGG;IACH,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,gBAAgB,GAAG,SAAS;CAiBxE"}
@@ -0,0 +1,39 @@
1
+ // The file system codes for a file that is missing, or that the system denies.
2
+ const unavailableCodes = new Set(['ENOENT', 'ENOTDIR', 'EACCES', 'EPERM']);
3
+ // SQLite's CANTOPEN (14). A missing or denied file fails the open() syscall,
4
+ // and SQLite reports that as CANTOPEN, a privacy denial of macOS too (probed:
5
+ // a missing file or folder, chmod 000, a symlink loop and a TCC denial all
6
+ // give exactly 14).
7
+ const SQLITE_CANTOPEN = 14;
8
+ // SQLite's READONLY_DIRECTORY (1544): the folder denies the write that makes
9
+ // the journal, for example for an owner in a folder it cannot write. A plain
10
+ // READONLY (8), a reader that tries to write, is not unavailable.
11
+ const SQLITE_READONLY_DIRECTORY = 1544;
12
+ /** The database file is missing, or this process has no access to it. */
13
+ export class UnavailableError extends Error {
14
+ path;
15
+ constructor(path, cause) {
16
+ 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 });
17
+ this.name = 'UnavailableError';
18
+ this.path = path;
19
+ }
20
+ /**
21
+ * The UnavailableError for an error of SQLite or of the file system that
22
+ * means the file is missing or denied (ADR 0005), else undefined.
23
+ */
24
+ static from(path, error) {
25
+ if (!(error instanceof Error))
26
+ return undefined;
27
+ if ('errcode' in error &&
28
+ typeof error.errcode === 'number' &&
29
+ (error.errcode === SQLITE_CANTOPEN ||
30
+ error.errcode === SQLITE_READONLY_DIRECTORY))
31
+ return new UnavailableError(path, error);
32
+ if ('code' in error &&
33
+ typeof error.code === 'string' &&
34
+ unavailableCodes.has(error.code))
35
+ return new UnavailableError(path, error);
36
+ return undefined;
37
+ }
38
+ }
39
+ //# sourceMappingURL=unavailable-error.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"unavailable-error.js","sourceRoot":"","sources":["../src/unavailable-error.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,MAAM,gBAAgB,GAAG,IAAI,GAAG,CAAC,CAAC,QAAQ,EAAE,SAAS,EAAE,QAAQ,EAAE,OAAO,CAAC,CAAC,CAAC;AAE3E,6EAA6E;AAC7E,8EAA8E;AAC9E,2EAA2E;AAC3E,oBAAoB;AACpB,MAAM,eAAe,GAAG,EAAE,CAAC;AAC3B,6EAA6E;AAC7E,6EAA6E;AAC7E,kEAAkE;AAClE,MAAM,yBAAyB,GAAG,IAAI,CAAC;AAEvC,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;IAED;;;OAGG;IACH,MAAM,CAAC,IAAI,CAAC,IAAY,EAAE,KAAc;QACtC,IAAI,CAAC,CAAC,KAAK,YAAY,KAAK,CAAC;YAAE,OAAO,SAAS,CAAC;QAChD,IACE,SAAS,IAAI,KAAK;YAClB,OAAO,KAAK,CAAC,OAAO,KAAK,QAAQ;YACjC,CAAC,KAAK,CAAC,OAAO,KAAK,eAAe;gBAChC,KAAK,CAAC,OAAO,KAAK,yBAAyB,CAAC;YAE9C,OAAO,IAAI,gBAAgB,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;QAC3C,IACE,MAAM,IAAI,KAAK;YACf,OAAO,KAAK,CAAC,IAAI,KAAK,QAAQ;YAC9B,gBAAgB,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC;YAEhC,OAAO,IAAI,gBAAgB,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;QAC3C,OAAO,SAAS,CAAC;IACnB,CAAC;CACF"}
@@ -0,0 +1,10 @@
1
+ import type { Journal } from './journal.ts';
2
+ /**
3
+ * A journal that changes nothing, for an owner that sets its own journal: a
4
+ * lock file whose PRAGMA must wait for the lock, so it cannot run at the open
5
+ * (ADR 0004).
6
+ */
7
+ export declare class UnchangedJournal implements Journal {
8
+ apply(): void;
9
+ }
10
+ //# sourceMappingURL=unchanged-journal.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"unchanged-journal.d.ts","sourceRoot":"","sources":["../src/unchanged-journal.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAE5C;;;;GAIG;AACH,qBAAa,gBAAiB,YAAW,OAAO;IAC9C,KAAK,IAAI,IAAI;CACd"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * A journal that changes nothing, for an owner that sets its own journal: a
3
+ * lock file whose PRAGMA must wait for the lock, so it cannot run at the open
4
+ * (ADR 0004).
5
+ */
6
+ export class UnchangedJournal {
7
+ apply() { }
8
+ }
9
+ //# sourceMappingURL=unchanged-journal.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"unchanged-journal.js","sourceRoot":"","sources":["../src/unchanged-journal.ts"],"names":[],"mappings":"AAEA;;;;GAIG;AACH,MAAM,OAAO,gBAAgB;IAC3B,KAAK,KAAU,CAAC;CACjB"}
@@ -0,0 +1,13 @@
1
+ import type { DatabaseSync } from 'node:sqlite';
2
+ import type { Journal } from './journal.ts';
3
+ /** A page size that SQLite accepts: a power of two from 512 to 65536. */
4
+ export type PageSize = 512 | 1024 | 2048 | 4096 | 8192 | 16384 | 32768 | 65536;
5
+ /** The default journal of an owner: readers and the owner do not wait for each other. */
6
+ export declare class WriteAheadLog implements Journal {
7
+ #private;
8
+ constructor(options?: {
9
+ readonly pageSize?: PageSize;
10
+ });
11
+ apply(database: DatabaseSync): void;
12
+ }
13
+ //# sourceMappingURL=write-ahead-log.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"write-ahead-log.d.ts","sourceRoot":"","sources":["../src/write-ahead-log.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAK5C,yEAAyE;AACzE,MAAM,MAAM,QAAQ,GAAG,GAAG,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,KAAK,GAAG,KAAK,GAAG,KAAK,CAAC;AAE/E,yFAAyF;AACzF,qBAAa,aAAc,YAAW,OAAO;;gBAG/B,OAAO,GAAE;QAAE,QAAQ,CAAC,QAAQ,CAAC,EAAE,QAAQ,CAAA;KAAO;IAI1D,KAAK,CAAC,QAAQ,EAAE,YAAY,GAAG,IAAI;CAiBpC"}
@@ -0,0 +1,25 @@
1
+ // Keeps the -wal file small after a large commit, as Rails 8 does: 64 MiB.
2
+ const sizeLimit = 64 * 1024 * 1024;
3
+ /** The default journal of an owner: readers and the owner do not wait for each other. */
4
+ export class WriteAheadLog {
5
+ #pageSize;
6
+ constructor(options = {}) {
7
+ this.#pageSize = options.pageSize;
8
+ }
9
+ apply(database) {
10
+ // SQLite takes the page size of a file only before its first write, and
11
+ // never in a write-ahead log. The WAL step writes the header of a new
12
+ // file, so the page size comes first. An existing file keeps its own.
13
+ if (this.#pageSize !== undefined)
14
+ database.exec(`PRAGMA page_size = ${this.#pageSize}`);
15
+ // The statement sets its own row shape: the caller's returnArrays and
16
+ // readBigInts apply to the connection, and must not change this read.
17
+ const statement = database.prepare('PRAGMA journal_mode = WAL');
18
+ statement.setReturnArrays(false);
19
+ const journal = statement.get()?.journal_mode;
20
+ if (journal !== 'wal')
21
+ throw new Error(`SQLite kept the journal ${JSON.stringify(journal)} of ${JSON.stringify(database.location())}, not a write-ahead log.`);
22
+ database.exec(`PRAGMA journal_size_limit = ${sizeLimit}`);
23
+ }
24
+ }
25
+ //# sourceMappingURL=write-ahead-log.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"write-ahead-log.js","sourceRoot":"","sources":["../src/write-ahead-log.ts"],"names":[],"mappings":"AAIA,2EAA2E;AAC3E,MAAM,SAAS,GAAG,EAAE,GAAG,IAAI,GAAG,IAAI,CAAC;AAKnC,yFAAyF;AACzF,MAAM,OAAO,aAAa;IACf,SAAS,CAAuB;IAEzC,YAAY,UAA4C,EAAE;QACxD,IAAI,CAAC,SAAS,GAAG,OAAO,CAAC,QAAQ,CAAC;IACpC,CAAC;IAED,KAAK,CAAC,QAAsB;QAC1B,wEAAwE;QACxE,sEAAsE;QACtE,sEAAsE;QACtE,IAAI,IAAI,CAAC,SAAS,KAAK,SAAS;YAC9B,QAAQ,CAAC,IAAI,CAAC,sBAAsB,IAAI,CAAC,SAAS,EAAE,CAAC,CAAC;QACxD,sEAAsE;QACtE,sEAAsE;QACtE,MAAM,SAAS,GAAG,QAAQ,CAAC,OAAO,CAAC,2BAA2B,CAAC,CAAC;QAChE,SAAS,CAAC,eAAe,CAAC,KAAK,CAAC,CAAC;QACjC,MAAM,OAAO,GAAG,SAAS,CAAC,GAAG,EAAE,EAAE,YAAY,CAAC;QAC9C,IAAI,OAAO,KAAK,KAAK;YACnB,MAAM,IAAI,KAAK,CACb,2BAA2B,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,OAAO,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,QAAQ,EAAE,CAAC,0BAA0B,CACvH,CAAC;QACJ,QAAQ,CAAC,IAAI,CAAC,+BAA+B,SAAS,EAAE,CAAC,CAAC;IAC5D,CAAC;CACF"}
Binary file
Binary file
Binary file
Binary file
Binary file
package/package.json CHANGED
@@ -1,6 +1,46 @@
1
1
  {
2
2
  "name": "@zukhruf/sqlite-open",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.7.0",
4
+ "description": "One open for every node:sqlite connection: a reader, or an owner whose journal is a strategy (WAL by default, persistent WAL for readers that cannot write the folder), a 5 s busy timeout, and UnavailableError.",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "https://github.com/JanuaryLabs/zukhruf",
9
+ "directory": "packages/sqlite/open"
10
+ },
11
+ "bugs": {
12
+ "url": "https://github.com/JanuaryLabs/zukhruf/issues"
13
+ },
14
+ "homepage": "https://github.com/JanuaryLabs/zukhruf/tree/main/packages/sqlite/open#readme",
15
+ "keywords": [
16
+ "sqlite",
17
+ "node-sqlite",
18
+ "wal",
19
+ "persistent-wal",
20
+ "read-only",
21
+ "busy-timeout"
22
+ ],
23
+ "type": "module",
24
+ "exports": {
25
+ "./package.json": "./package.json",
26
+ ".": {
27
+ "@zukhruf/source": "./src/index.ts",
28
+ "types": "./dist/index.d.ts",
29
+ "import": "./dist/index.js",
30
+ "default": "./dist/index.js"
31
+ }
32
+ },
33
+ "files": [
34
+ "dist",
35
+ "lib",
36
+ "!dist/spec/**",
37
+ "!**/*.tsbuildinfo",
38
+ "!**/*.test.*"
39
+ ],
40
+ "publishConfig": {
41
+ "access": "public"
42
+ },
43
+ "devDependencies": {
44
+ "@zukhruf/testing": "^0.7.0"
45
+ }
46
+ }