@delali/sirannon-db 0.3.4-next.61 → 0.3.4-next.64
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 +79 -56
- package/dist/backup/index.d.ts +44 -45
- package/dist/backup/index.mjs +7 -7
- package/dist/backup-scheduler/index.d.ts +42 -41
- package/dist/backup-scheduler/index.mjs +4 -4
- package/dist/baseline-Co3iIsN_.d.ts +19 -0
- package/dist/change-tracker-DqCdeEyx.d.ts +128 -0
- package/dist/{chunk-VOSJBZ6Q.mjs → chunk-2ES3DT4A.mjs} +71 -33
- package/dist/{chunk-6CMCO7S4.mjs → chunk-2O3PP4CF.mjs} +1 -1
- package/dist/{chunk-E7Q7HE46.mjs → chunk-3BZLZ47R.mjs} +26 -26
- package/dist/chunk-4KASJKKK.mjs +2501 -0
- package/dist/{chunk-MYNI2WFS.mjs → chunk-52XW3Z76.mjs} +40 -8
- package/dist/chunk-55YRLSM2.mjs +57 -0
- package/dist/{chunk-KDAHYFW7.mjs → chunk-6AOJCNRU.mjs} +5 -5
- package/dist/{chunk-KXTYR2OQ.mjs → chunk-A3PHCO6W.mjs} +39 -111
- package/dist/chunk-ADNWEHGZ.mjs +299 -0
- package/dist/{chunk-2QLXDHAP.mjs → chunk-BLMRNSUG.mjs} +11 -2
- package/dist/chunk-BVMZJOVM.mjs +5 -0
- package/dist/{chunk-IWGIYDMZ.mjs → chunk-BZWDPF4G.mjs} +1 -1
- package/dist/{chunk-YVF2ZJTE.mjs → chunk-CZL4D3BM.mjs} +1 -1
- package/dist/{chunk-7SC2TZAS.mjs → chunk-DX4WGJL7.mjs} +2 -2
- package/dist/{chunk-45MKELYA.mjs → chunk-ESTZZPT5.mjs} +132 -13
- package/dist/{chunk-QP5L5DFJ.mjs → chunk-FJPR2CJX.mjs} +3 -3
- package/dist/chunk-HR7K3GZW.mjs +36 -0
- package/dist/{chunk-LXCGPZPA.mjs → chunk-IEKIYO4V.mjs} +1 -1
- package/dist/{chunk-TUD5CJ76.mjs → chunk-KB4OLJAD.mjs} +26 -19
- package/dist/{chunk-PBRXXISQ.mjs → chunk-KINDFBQP.mjs} +3 -2
- package/dist/{chunk-E6IMX7RS.mjs → chunk-KJLNYOOL.mjs} +2 -2
- package/dist/{chunk-BTTFW4Z4.mjs → chunk-KU32OT4H.mjs} +2 -10
- package/dist/{chunk-JQWGHSEH.mjs → chunk-NVSQ2T5Q.mjs} +71 -70
- package/dist/{chunk-W4EUTJNT.mjs → chunk-NZGSDALX.mjs} +94 -96
- package/dist/{chunk-U445A3C4.mjs → chunk-QGVRRKB2.mjs} +1 -1
- package/dist/{chunk-GYCEJNU4.mjs → chunk-R7GBXNTO.mjs} +1 -1
- package/dist/{chunk-7C36BCSN.mjs → chunk-VDN6FHU4.mjs} +1 -1
- package/dist/{chunk-6IYS4WLH.mjs → chunk-VO3ZWA2G.mjs} +142 -115
- package/dist/chunk-WOMADPM4.mjs +12 -0
- package/dist/{chunk-OUSWVNWT.mjs → chunk-YCMB6GR4.mjs} +1 -1
- package/dist/{chunk-YNTDOFTN.mjs → chunk-YW7IKQ7T.mjs} +1 -1
- package/dist/{chunk-M2BEAH5I.mjs → chunk-ZN4HI5FK.mjs} +126 -72
- package/dist/{chunk-SBL6GN43.mjs → chunk-ZYWI45EN.mjs} +1 -1
- package/dist/client/index.d.ts +135 -137
- package/dist/client/index.mjs +177 -82
- package/dist/client/topology.d.ts +31 -31
- package/dist/client/topology.mjs +42 -23
- package/dist/client-base-BxTocU1x.d.ts +398 -0
- package/dist/client-streams-YKANXMWO.mjs +112 -0
- package/dist/codegen/cli.mjs +4 -4
- package/dist/codegen/index.d.ts +36 -29
- package/dist/codegen/index.mjs +4 -4
- package/dist/core/index.d.ts +130 -115
- package/dist/core/index.mjs +387 -297
- package/dist/core/writer-worker.mjs +3 -3
- package/dist/database-CGDsXW1K.d.ts +959 -0
- package/dist/database-backup-DcLhxoa1.d.ts +143 -0
- package/dist/driver/better-sqlite3.d.ts +11 -10
- package/dist/driver/better-sqlite3.mjs +11 -11
- package/dist/driver/bun.d.ts +9 -8
- package/dist/driver/expo.d.ts +6 -7
- package/dist/driver/node.d.ts +11 -10
- package/dist/driver/node.mjs +13 -11
- package/dist/driver/wa-sqlite.d.ts +6 -6
- package/dist/errors-CJUkQ-KL.d.ts +199 -0
- package/dist/file-migrations/index.d.ts +20 -18
- package/dist/file-migrations/index.mjs +3 -3
- package/dist/forward-rpc-VRWFZXHN.mjs +44 -0
- package/dist/operation-registry-CGhNT-Sn.d.ts +96 -0
- package/dist/primary-wins-CHNOYuN7.d.ts +81 -0
- package/dist/protocol-DFpNNsIT.d.ts +153 -0
- package/dist/query-types-5B02ixH0.d.ts +129 -0
- package/dist/react/index.d.ts +22 -22
- package/dist/replication/coordinator/etcd.d.ts +37 -31
- package/dist/replication/coordinator/etcd.mjs +296 -131
- package/dist/replication/index.d.ts +201 -161
- package/dist/replication/index.mjs +630 -529
- package/dist/restore-options-CH8mE-Tb.d.ts +84 -0
- package/dist/server/index.d.ts +201 -191
- package/dist/server/index.mjs +736 -619
- package/dist/server-options-aYtCkefX.d.ts +317 -0
- package/dist/server-streams-24XL4T43.mjs +254 -0
- package/dist/sirannon-J3EgPVD7.d.ts +179 -0
- package/dist/transport/grpc.d.ts +37 -24
- package/dist/transport/grpc.mjs +83 -3199
- package/dist/transport/memory.d.ts +25 -26
- package/dist/transport/memory.mjs +18 -9
- package/dist/types-BrAiDGOb.d.ts +505 -0
- package/dist/types-CEJG0eFS.d.ts +88 -0
- package/dist/types-Dc8oWlxj.d.ts +1580 -0
- package/dist/types-DqODKeOq.d.ts +123 -0
- package/dist/types-guEKSzMr.d.ts +367 -0
- package/package.json +7 -7
- package/dist/baseline-D93hcIEE.d.ts +0 -17
- package/dist/change-tracker-CjRFuZdB.d.ts +0 -144
- package/dist/client-base-Cmd9EVme.d.ts +0 -397
- package/dist/database-BLQvqjsw.d.ts +0 -917
- package/dist/database-backup-B7oszQ2n.d.ts +0 -141
- package/dist/errors-Dei4GdBb.d.ts +0 -196
- package/dist/operation-registry-oiPuSYI6.d.ts +0 -94
- package/dist/primary-wins-B0np8JS3.d.ts +0 -85
- package/dist/protocol-HI-nY8DD.d.ts +0 -152
- package/dist/query-types-p1liOP-b.d.ts +0 -127
- package/dist/restore-options-CksYsH1l.d.ts +0 -83
- package/dist/server-options-Bj5iJnjx.d.ts +0 -292
- package/dist/sirannon-BPxC5tC2.d.ts +0 -178
- package/dist/types-B0EhgDbr.d.ts +0 -340
- package/dist/types-CbjfRwrK.d.ts +0 -499
- package/dist/types-CjhxcjhA.d.ts +0 -123
- package/dist/types-DPSmjU54.d.ts +0 -1518
- package/dist/types-DTO4CYhW.d.ts +0 -88
package/README.md
CHANGED
|
@@ -6,9 +6,9 @@
|
|
|
6
6
|
[](https://www.npmjs.com/package/@delali/sirannon-db)
|
|
7
7
|
[](https://github.com/assetcorp/sirannon-db/blob/main/LICENSE)
|
|
8
8
|
|
|
9
|
-
Build a networked SQLite service with connection pooling, change data capture, live queries, migrations, backups, device sync, and a client SDK. Applications reach Sirannon over HTTP or WebSocket,
|
|
9
|
+
Build a networked SQLite service with connection pooling, change data capture, live queries, migrations, backups, device sync, and a client SDK. Applications reach Sirannon over HTTP or WebSocket, while a primary replicates its changes to other Sirannon nodes over gRPC.
|
|
10
10
|
|
|
11
|
-
**Read the full documentation at [sirannon.sondelali.com/docs](https://sirannon.sondelali.com/docs).**
|
|
11
|
+
**Read the full documentation at [sirannon.sondelali.com/docs](https://sirannon.sondelali.com/docs).** Use this page to get started, and turn to the [guides](#documentation) for the detail. The benchmark harness that measures Sirannon against Postgres 17 is under [`benchmarks/server`](https://github.com/assetcorp/sirannon-db/tree/main/benchmarks/server), where a Node load generator drives both engines and a Python step joins their results. The write-up generator rewrites [BENCHMARKS.md](https://github.com/assetcorp/sirannon-db/blob/main/BENCHMARKS.md) from the latest committed run.
|
|
12
12
|
|
|
13
13
|
The core engine, server, client, and primary-replica replication are stable. Coordinator-backed failover, device sync, and the Bun and Expo drivers are experimental.
|
|
14
14
|
|
|
@@ -25,13 +25,33 @@ Then add the SQLite driver for your runtime:
|
|
|
25
25
|
| Driver | Import | Runtime | Install |
|
|
26
26
|
| --- | --- | --- | --- |
|
|
27
27
|
| better-sqlite3 | `@delali/sirannon-db/driver/better-sqlite3` | Node.js | `pnpm add -E better-sqlite3` |
|
|
28
|
-
| Node built-in | `@delali/sirannon-db/driver/node` | Node.js >= 22 | None (flag
|
|
28
|
+
| Node built-in | `@delali/sirannon-db/driver/node` | Node.js >= 22 | None (no flag needed from 22.13.0 and 23.4.0) |
|
|
29
29
|
| wa-sqlite | `@delali/sirannon-db/driver/wa-sqlite` | Browser (IndexedDB) | `pnpm add -E wa-sqlite` |
|
|
30
|
-
| Bun | `@delali/sirannon-db/driver/bun` | Bun | None (
|
|
30
|
+
| Bun | `@delali/sirannon-db/driver/bun` | Bun | None (built on `bun:sqlite`) |
|
|
31
31
|
| Expo | `@delali/sirannon-db/driver/expo` | React Native | `pnpm add -E expo-sqlite` |
|
|
32
32
|
|
|
33
33
|
Write a custom driver by passing `capabilities` and an `open` function to `defineDriver`.
|
|
34
34
|
|
|
35
|
+
To serve a registry over HTTP and WebSocket through `@delali/sirannon-db/server`, add uWebSockets.js as well. The npm registry has no package named `uWebSockets.js`, so install the tagged GitHub release v20.69.0, which is the version in Sirannon's own development dependencies:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
pnpm add -E "uWebSockets.js@github:uNetworking/uWebSockets.js#v20.69.0"
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
When the process cannot load uWebSockets.js, `server.listen()` fails with code `SERVER_DEPENDENCY_MISSING` and a message that gives this install command.
|
|
42
|
+
|
|
43
|
+
## Add Sirannon with a coding agent
|
|
44
|
+
|
|
45
|
+
The [Sirannon skill](https://github.com/assetcorp/sirannon-db/tree/main/skills/sirannon) tells a coding agent which driver suits the app's runtime, which packages to install alongside it, and which server settings keep the data safe. Install the skill into the coding agents on your machine with the skills CLI:
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
npx skills add assetcorp/sirannon-db
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
The skill has the agent read the Sirannon version that your project installs, and it sends the agent to that version's types and documentation.
|
|
52
|
+
|
|
53
|
+
On the [agents page](https://sirannon.sondelali.com/agents), you can copy a prompt for one of six database tasks and paste it into your coding agent.
|
|
54
|
+
|
|
35
55
|
## Quick start
|
|
36
56
|
|
|
37
57
|
```ts
|
|
@@ -47,36 +67,36 @@ await db.execute('INSERT INTO users (name, email) VALUES (?, ?)', ['Ada', 'ada@e
|
|
|
47
67
|
const users = await db.query<{ id: number; name: string }>('SELECT * FROM users')
|
|
48
68
|
```
|
|
49
69
|
|
|
50
|
-
In the browser, open the database
|
|
70
|
+
In the browser, open the database through the same registry with the browser driver:
|
|
51
71
|
|
|
52
72
|
```ts
|
|
53
|
-
import {
|
|
73
|
+
import { Sirannon } from '@delali/sirannon-db'
|
|
54
74
|
import { waSqlite } from '@delali/sirannon-db/driver/wa-sqlite'
|
|
55
75
|
|
|
56
|
-
const
|
|
57
|
-
const db = await
|
|
76
|
+
const sirannon = new Sirannon({ driver: waSqlite({ vfs: 'IDBBatchAtomicVFS' }) })
|
|
77
|
+
const db = await sirannon.open('app', '/app.db', { walMode: false })
|
|
58
78
|
```
|
|
59
79
|
|
|
60
|
-
React Native
|
|
80
|
+
On React Native, open the database the same way with `expoSqlite()`.
|
|
61
81
|
|
|
62
82
|
## Package exports
|
|
63
83
|
|
|
64
84
|
| Import | What you get |
|
|
65
85
|
| --- | --- |
|
|
66
|
-
| `@delali/sirannon-db` | Core library: queries, transactions, CDC, live queries, migrations, backups, hooks, metrics, lifecycle |
|
|
67
|
-
| `@delali/sirannon-db/driver/*` | SQLite driver adapters
|
|
68
|
-
| `@delali/sirannon-db/file-migrations` |
|
|
86
|
+
| `@delali/sirannon-db` | Core library: queries, transactions, CDC, live queries, migrations, backups, hooks, metrics, and lifecycle |
|
|
87
|
+
| `@delali/sirannon-db/driver/*` | SQLite driver adapters, listed in the table above |
|
|
88
|
+
| `@delali/sirannon-db/file-migrations` | A loader for `.up.sql` and `.down.sql` files in a directory |
|
|
69
89
|
| `@delali/sirannon-db/backup` | Backup destination types, chain records, and `restoreBackup` |
|
|
70
|
-
| `@delali/sirannon-db/backup-scheduler` |
|
|
71
|
-
| `@delali/sirannon-db/server` | HTTP and WebSocket server
|
|
72
|
-
| `@delali/sirannon-db/client` |
|
|
73
|
-
| `@delali/sirannon-db/client/topology` |
|
|
74
|
-
| `@delali/sirannon-db/react` | `useLiveQuery` and `useCommand` hooks |
|
|
90
|
+
| `@delali/sirannon-db/backup-scheduler` | A cron-scheduled backup runner with file rotation |
|
|
91
|
+
| `@delali/sirannon-db/server` | An HTTP and WebSocket server built on uWebSockets.js |
|
|
92
|
+
| `@delali/sirannon-db/client` | A client SDK with automatic reconnection, subscription restore, and device sync |
|
|
93
|
+
| `@delali/sirannon-db/client/topology` | A topology-aware client that routes across a replication group |
|
|
94
|
+
| `@delali/sirannon-db/react` | The `useLiveQuery` and `useCommand` hooks |
|
|
75
95
|
| `@delali/sirannon-db/codegen` | Typed operation references generated from your server's registry |
|
|
76
|
-
| `@delali/sirannon-db/replication` |
|
|
77
|
-
| `@delali/sirannon-db/replication/coordinator/etcd` | etcd-backed coordinator for primary authority and failover |
|
|
78
|
-
| `@delali/sirannon-db/transport/grpc` | gRPC replication transport with TLS support |
|
|
79
|
-
| `@delali/sirannon-db/transport/memory` |
|
|
96
|
+
| `@delali/sirannon-db/replication` | The replication engine, conflict resolvers, topologies, and HLC |
|
|
97
|
+
| `@delali/sirannon-db/replication/coordinator/etcd` | An etcd-backed coordinator for primary authority and failover |
|
|
98
|
+
| `@delali/sirannon-db/transport/grpc` | A gRPC replication transport with TLS support |
|
|
99
|
+
| `@delali/sirannon-db/transport/memory` | An in-memory transport for tests |
|
|
80
100
|
|
|
81
101
|
## Queries and transactions
|
|
82
102
|
|
|
@@ -94,11 +114,11 @@ const balance = await db.transaction(async tx => {
|
|
|
94
114
|
})
|
|
95
115
|
```
|
|
96
116
|
|
|
97
|
-
|
|
117
|
+
`bulkLoad` writes a large import in one transaction under relaxed durability, and then it restores the durability level that you configured. Read the [bulk load guide](https://sirannon.sondelali.com/docs/bulk-load) for the details, and the guides on [migrations](https://sirannon.sondelali.com/docs/migrations) and on [hooks, metrics, and the multi-tenant lifecycle](https://sirannon.sondelali.com/docs/hooks-metrics-and-lifecycle) for the rest of the core engine. Backups have a [guide of their own](https://sirannon.sondelali.com/docs/backups).
|
|
98
118
|
|
|
99
119
|
## Change data capture and live queries
|
|
100
120
|
|
|
101
|
-
|
|
121
|
+
Subscribe to a table to receive each row that changes:
|
|
102
122
|
|
|
103
123
|
```ts
|
|
104
124
|
await db.watch('orders')
|
|
@@ -109,9 +129,9 @@ const subscription = db
|
|
|
109
129
|
.subscribe(event => console.log(event.type, event.table, event.row, event.oldRow, event.seq))
|
|
110
130
|
```
|
|
111
131
|
|
|
112
|
-
|
|
132
|
+
With a filter, you receive each change in a row's membership of the set that the filter matches. When an order's `status` changes from `pending` to `shipped`, the handler receives an insert, because the row joins the set. When an order's `status` changes away from `shipped`, the handler receives a delete with the previous row in `oldRow`. Read `event.type` as the row joining or leaving the set, because the handler receives the same event for a real insert and for a row that joins the set.
|
|
113
133
|
|
|
114
|
-
A live query
|
|
134
|
+
A live query holds the current result of a query and updates its rows from those same events:
|
|
115
135
|
|
|
116
136
|
```ts
|
|
117
137
|
const pending = await db.live<{ id: number; total: number }>(
|
|
@@ -122,11 +142,11 @@ const pending = await db.live<{ id: number; total: number }>(
|
|
|
122
142
|
pending.subscribe(() => render(pending.getState()))
|
|
123
143
|
```
|
|
124
144
|
|
|
125
|
-
|
|
145
|
+
Read the [live queries guide](https://sirannon.sondelali.com/docs/live-queries) for the kinds of update, the statements that a live query can maintain, and the React hooks.
|
|
126
146
|
|
|
127
147
|
## Serve it over the network
|
|
128
148
|
|
|
129
|
-
A server
|
|
149
|
+
A server rejects SQL from the network by default. Callers call the reads and writes that you register on it by name:
|
|
130
150
|
|
|
131
151
|
```ts
|
|
132
152
|
import { createServer } from '@delali/sirannon-db/server'
|
|
@@ -164,16 +184,16 @@ const client = new SirannonClient('http://localhost:9876', { transport: 'websock
|
|
|
164
184
|
const db = client.database('app')
|
|
165
185
|
|
|
166
186
|
const users = await db.query(activeUsers, {})
|
|
167
|
-
const
|
|
187
|
+
const liveUsers = await db.live(activeUsers, {})
|
|
168
188
|
```
|
|
169
189
|
|
|
170
|
-
|
|
190
|
+
The `sirannon-codegen` command generates those references from the registry, so you can skip writing them by hand. Set `acceptSql: true` when you want the server to execute the statements that a client sends. A client streams a table's changes through `db.on(table).subscribe` only from a server with `acceptSql: true` or an `onBeforeSubscribe` hook, which admits or refuses each table. Read the [registered operations guide](https://sirannon.sondelali.com/docs/registered-operations) and the [code generation guide](https://sirannon.sondelali.com/docs/code-generation) for operations and their references, the [server guide](https://sirannon.sondelali.com/docs/server) for `acceptSql`, the routes, and the messages, and the [client guide](https://sirannon.sondelali.com/docs/client-sdk) for the transports.
|
|
171
191
|
|
|
172
192
|
## Security
|
|
173
193
|
|
|
174
|
-
|
|
194
|
+
With registered operations, your SQL stays on the server, so a caller of a registered read or write supplies only the arguments that you declared for it. With `acceptSql: true`, the server executes the statements that an admitted caller sends, so put that server behind an application layer or a private network boundary, or give it a `resolveExecutionTarget` that accepts only the statements that you know.
|
|
175
195
|
|
|
176
|
-
Authenticate every request through the `authenticate` hook. Return the caller's identity
|
|
196
|
+
Authenticate every request through the `authenticate` hook. Return the caller's identity from the hook so that the server can fill each `fromIdentity` argument of a registered operation, and throw to reject the request:
|
|
177
197
|
|
|
178
198
|
```ts
|
|
179
199
|
import { RequestDeniedError } from '@delali/sirannon-db'
|
|
@@ -192,7 +212,7 @@ const server = createServer<Identity>(sirannon, {
|
|
|
192
212
|
})
|
|
193
213
|
```
|
|
194
214
|
|
|
195
|
-
A Node client attaches `headers` to the WebSocket upgrade as well as to HTTP requests, so
|
|
215
|
+
A Node client attaches `headers` to the WebSocket upgrade as well as to HTTP requests, so your hook can read `headers.authorization` on both transports:
|
|
196
216
|
|
|
197
217
|
```ts
|
|
198
218
|
const client = new SirannonClient('https://api.example.com', {
|
|
@@ -200,7 +220,7 @@ const client = new SirannonClient('https://api.example.com', {
|
|
|
200
220
|
})
|
|
201
221
|
```
|
|
202
222
|
|
|
203
|
-
A browser
|
|
223
|
+
A browser cannot attach a header to `new WebSocket(...)`, so give a browser client a short-lived ticket in `webSocketProtocols`:
|
|
204
224
|
|
|
205
225
|
```ts
|
|
206
226
|
const client = new SirannonClient('https://api.example.com', {
|
|
@@ -208,45 +228,48 @@ const client = new SirannonClient('https://api.example.com', {
|
|
|
208
228
|
})
|
|
209
229
|
```
|
|
210
230
|
|
|
211
|
-
Pass both options when a browser client needs each of them, as the [entitlements example](examples/distributed-entitlements) does
|
|
231
|
+
When you construct a browser client on the WebSocket transport with `headers` alone, the constructor throws `INVALID_ARGUMENT`, because the browser would leave that credential out of the handshake. Pass both options when a browser client needs each of them, as the [entitlements example](https://github.com/assetcorp/sirannon-db/tree/main/packages/ts/examples/distributed-entitlements) does. Its topology client sends `headers` with the discovery request to `GET /db/{id}/cluster`, while it sends the ticket with the socket handshake.
|
|
212
232
|
|
|
213
|
-
|
|
233
|
+
Because the client offers the plain `sirannon.v1` identifier ahead of your values and the server selects it, the ticket stays out of the handshake response. Check the `Origin` header in the same hook. When the hook rejects an upgrade with status 401 or 403, the server closes the connection with code 4401 or 4403, and the client raises `UNAUTHORIZED` or `FORBIDDEN` and leaves that connection closed.
|
|
214
234
|
|
|
215
235
|
- Bind to `127.0.0.1` or a private interface unless a proxy enforces TLS and access control.
|
|
216
|
-
- Use HTTPS and WSS for
|
|
236
|
+
- Use HTTPS and WSS for traffic beyond the local machine, because the built-in server listens on plain HTTP.
|
|
217
237
|
- Authenticate every HTTP database route and every WebSocket upgrade, and check `Origin` against an allowlist.
|
|
218
|
-
- Keep user input in parameters, which the driver binds
|
|
219
|
-
- Restrict CORS to known origins
|
|
238
|
+
- Keep user input in parameters, which the driver binds separately from the SQL text.
|
|
239
|
+
- Restrict CORS to known origins, since `cors: true` allows every origin and is for local development only.
|
|
220
240
|
- Keep long-lived secrets out of browser-visible configuration, and redact credentials from access logs.
|
|
221
241
|
- Add rate limits, audit logs, and abuse monitoring at the application or edge layer.
|
|
222
242
|
|
|
223
|
-
|
|
243
|
+
Read the [security guide](https://sirannon.sondelali.com/docs/security) for each of these in full.
|
|
224
244
|
|
|
225
245
|
## Documentation
|
|
226
246
|
|
|
227
|
-
| Guide |
|
|
247
|
+
| Guide | Topics |
|
|
228
248
|
| --- | --- |
|
|
229
|
-
| [
|
|
230
|
-
| [
|
|
231
|
-
| [
|
|
232
|
-
| [
|
|
233
|
-
| [
|
|
234
|
-
| [
|
|
235
|
-
| [
|
|
236
|
-
| [
|
|
237
|
-
| [
|
|
238
|
-
| [
|
|
239
|
-
|
|
240
|
-
|
|
249
|
+
| [Queries and transactions](https://sirannon.sondelali.com/docs/queries-and-transactions) | Parameterised SQL, batches, transactions, and the connection pool |
|
|
250
|
+
| [Bulk load](https://sirannon.sondelali.com/docs/bulk-load) | One-transaction imports under relaxed durability, over the server, and through the client SDK |
|
|
251
|
+
| [Migrations](https://sirannon.sondelali.com/docs/migrations) | File-based, programmatic, and bundled migrations, rollback, checksums, baselines, concurrency, and registry migrations |
|
|
252
|
+
| [Hooks, metrics, and lifecycle](https://sirannon.sondelali.com/docs/hooks-metrics-and-lifecycle) | Before and after hooks, metrics callbacks, and the multi-tenant lifecycle |
|
|
253
|
+
| [Backups](https://sirannon.sondelali.com/docs/backups) | Copies of an open database to a file and on a schedule, followed by the guides on [destinations](https://sirannon.sondelali.com/docs/backup-destinations), [continuous backups](https://sirannon.sondelali.com/docs/backup-chains), and [restores](https://sirannon.sondelali.com/docs/backup-restore) |
|
|
254
|
+
| [Server](https://sirannon.sondelali.com/docs/server) | Registered operations on the server, caller identity, size limits, write shapes, `acceptSql`, the backup routes, HTTP routes, and the WebSocket protocol |
|
|
255
|
+
| [Registered operations](https://sirannon.sondelali.com/docs/registered-operations) | Named reads and writes, identity-filled arguments, calls over HTTP and WebSocket, capabilities, and refusals |
|
|
256
|
+
| [Live queries](https://sirannon.sondelali.com/docs/live-queries) | Maintained query results locally, over the network, and in React |
|
|
257
|
+
| [Client SDK](https://sirannon.sondelali.com/docs/client-sdk) | Reads, writes, subscriptions, live queries, bulk imports, read concern, refused credentials, and client options |
|
|
258
|
+
| [Device sync](https://sirannon.sondelali.com/docs/device-sync) | Offline-first two-way sync between a device's local database and a server |
|
|
259
|
+
| [Distributed replication](https://sirannon.sondelali.com/docs/distributed-replication) | Certificates, first sync, write and read concerns, conflict resolution, coordinator failover, and schema changes |
|
|
260
|
+
| [Configuration reference](https://sirannon.sondelali.com/docs/configuration) | Every option for the registry, a database, backups, the server, the client, device sync, and replication |
|
|
261
|
+
| [Error codes](https://sirannon.sondelali.com/docs/error-codes) | Every error code, whether a retry is safe, its HTTP status, and the WebSocket close codes |
|
|
262
|
+
|
|
263
|
+
The wire formats, the value encodings, and the replication invariants that every implementation follows are in the [specification](https://github.com/assetcorp/sirannon-db/tree/main/packages/spec).
|
|
241
264
|
|
|
242
265
|
## Example projects
|
|
243
266
|
|
|
244
267
|
| Example | Runtime | What it demonstrates |
|
|
245
268
|
| --- | --- | --- |
|
|
246
|
-
| [`node`](examples/node
|
|
247
|
-
| [`web-wa-sqlite`](examples/web-wa-sqlite
|
|
248
|
-
| [`web-client`](examples/web-client
|
|
249
|
-
| [`distributed-entitlements`](examples/distributed-entitlements
|
|
269
|
+
| [`node`](https://github.com/assetcorp/sirannon-db/tree/main/packages/ts/examples/node) | Node.js >= 22 | Schema, migrations, CRUD, transactions, CDC, live queries, pools, metrics, multi-tenant lifecycle, hooks, backup, and shutdown |
|
|
270
|
+
| [`web-wa-sqlite`](https://github.com/assetcorp/sirannon-db/tree/main/packages/ts/examples/web-wa-sqlite) | Browser and Node.js | Offline-first device sync with a local database in the browser, snapshot load, offline writes, conflict resolution, and a local live query |
|
|
271
|
+
| [`web-client`](https://github.com/assetcorp/sirannon-db/tree/main/packages/ts/examples/web-client) | Browser and Node.js | Registered operations, code generation, remote live queries, and the React hooks |
|
|
272
|
+
| [`distributed-entitlements`](https://github.com/assetcorp/sirannon-db/tree/main/packages/ts/examples/distributed-entitlements) | Node.js and browser | Three-node coordinator-backed replication over gRPC with etcd authority, mTLS, and Toxiproxy failure controls |
|
|
250
273
|
|
|
251
274
|
```bash
|
|
252
275
|
pnpm install && pnpm --filter @delali/sirannon-db build
|
package/dist/backup/index.d.ts
CHANGED
|
@@ -1,70 +1,69 @@
|
|
|
1
|
-
import { B as BackupDestination, f as BackupRunReport } from '../types-
|
|
2
|
-
export { g as BackupChain, h as BackupChainBase, i as BackupChainChange, j as BackupChainPosition, k as BackupChainRecord, l as BackupCycle, m as BackupCycleError, n as BackupCycleOptions, o as BackupCycleStatus, p as BackupFileReport, q as BackupGroupMembership, r as BackupGroupSource, s as BackupLogPosition, t as BackupNodePreference, u as BackupPiece, v as BackupProgress, w as BackupSkip, x as BackupSkipReason, y as BackupToDestinationOptions, z as BackupVerifyResult, C as DEFAULT_CHAIN_NAME, E as chainLogName, F as readBackupChains, G as verifyBackupRecord } from '../types-
|
|
3
|
-
export { B as BackupCapabilities, a as BackupChainLocation, b as BackupRestorePlan, c as BackupSafeToDeleteOptions, d as backupPiecesSafeToDelete, p as planBackupRestore } from '../database-backup-
|
|
4
|
-
import { B as BackupRestoreOptions, a as BackupRestoreReport } from '../restore-options-
|
|
5
|
-
export { b as BackupRestoreProgress } from '../restore-options-
|
|
6
|
-
import '../query-types-
|
|
1
|
+
import { B as BackupDestination, f as BackupRunReport } from '../types-Dc8oWlxj.js';
|
|
2
|
+
export { g as BackupChain, h as BackupChainBase, i as BackupChainChange, j as BackupChainPosition, k as BackupChainRecord, l as BackupCycle, m as BackupCycleError, n as BackupCycleOptions, o as BackupCycleStatus, p as BackupFileReport, q as BackupGroupMembership, r as BackupGroupSource, s as BackupLogPosition, t as BackupNodePreference, u as BackupPiece, v as BackupProgress, w as BackupSkip, x as BackupSkipReason, y as BackupToDestinationOptions, z as BackupVerifyResult, C as DEFAULT_CHAIN_NAME, E as chainLogName, F as readBackupChains, G as verifyBackupRecord } from '../types-Dc8oWlxj.js';
|
|
3
|
+
export { B as BackupCapabilities, a as BackupChainLocation, b as BackupRestorePlan, c as BackupSafeToDeleteOptions, d as backupPiecesSafeToDelete, p as planBackupRestore } from '../database-backup-DcLhxoa1.js';
|
|
4
|
+
import { B as BackupRestoreOptions, a as BackupRestoreReport } from '../restore-options-CH8mE-Tb.js';
|
|
5
|
+
export { b as BackupRestoreProgress } from '../restore-options-CH8mE-Tb.js';
|
|
6
|
+
import '../query-types-5B02ixH0.js';
|
|
7
7
|
|
|
8
|
-
/**
|
|
8
|
+
/** The byte and piece counts for one file that Sirannon assembles from a destination.
|
|
9
9
|
* @public
|
|
10
10
|
*/
|
|
11
11
|
interface AssembleResult {
|
|
12
|
-
/**
|
|
12
|
+
/** The number of bytes that Sirannon writes to the local file. */
|
|
13
13
|
bytesWritten: number;
|
|
14
|
-
/**
|
|
14
|
+
/** The number of pieces that Sirannon reads from the destination. */
|
|
15
15
|
pieceCount: number;
|
|
16
|
-
/** SHA-256 of the assembled file, where the
|
|
16
|
+
/** The SHA-256 of the assembled file, present only where the backup report holds a fingerprint to check it against. */
|
|
17
17
|
fingerprint?: string;
|
|
18
18
|
}
|
|
19
19
|
/**
|
|
20
|
-
*
|
|
21
|
-
* against
|
|
20
|
+
* Rebuilds a local file from the pieces at a destination, and checks the result
|
|
21
|
+
* against the report of the backup that stored them.
|
|
22
22
|
*
|
|
23
|
-
* @param destination -
|
|
24
|
-
* @param report -
|
|
25
|
-
* @param destPath -
|
|
26
|
-
* @returns The bytes and pieces
|
|
23
|
+
* @param destination - The destination that holds the pieces.
|
|
24
|
+
* @param report - The report of the backup that stored those pieces.
|
|
25
|
+
* @param destPath - The path that Sirannon writes the assembled file to.
|
|
26
|
+
* @returns The bytes and pieces that Sirannon writes, and the fingerprint that it computes.
|
|
27
27
|
*
|
|
28
28
|
* @public
|
|
29
29
|
*/
|
|
30
30
|
declare function assembleFromDestination(destination: BackupDestination, report: BackupRunReport, destPath: string): Promise<AssembleResult>;
|
|
31
31
|
|
|
32
32
|
/**
|
|
33
|
-
* Rebuilds a database
|
|
34
|
-
*
|
|
33
|
+
* Rebuilds a database at the moment that you name, and writes it to a path
|
|
34
|
+
* that you choose.
|
|
35
35
|
*
|
|
36
36
|
* Sirannon reads the chain records at your destination and takes the newest
|
|
37
|
-
* full copy finished at or before that moment. It then
|
|
38
|
-
* piece captured
|
|
39
|
-
* piece and applying it before it
|
|
40
|
-
*
|
|
37
|
+
* full copy that finished at or before that moment. It then applies every
|
|
38
|
+
* change piece that it captured after that copy up to the same moment,
|
|
39
|
+
* fetching one stored piece and applying it before it requests the next, so a
|
|
40
|
+
* restore holds one stored piece in memory however large the database is.
|
|
41
41
|
*
|
|
42
|
-
*
|
|
43
|
-
* `BACKUP_CHAIN_BROKEN
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
42
|
+
* A gap fails the call in one of two ways. A missing change piece in the chain
|
|
43
|
+
* throws `BACKUP_CHAIN_BROKEN`, naming the sequence number where the chain
|
|
44
|
+
* stops, and a missing stored piece of a file throws
|
|
45
|
+
* `BACKUP_DESTINATION_ERROR`, naming that piece. Sirannon also checks each file
|
|
46
|
+
* against the byte count and the fingerprint in its record, both of which cover
|
|
47
|
+
* the whole file.
|
|
48
48
|
*
|
|
49
|
-
* Sirannon
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
* write-ahead log
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
* rebuilt database at that path and nothing of the one it replaced.
|
|
49
|
+
* Sirannon builds the database next to the path that you name, and renames it
|
|
50
|
+
* onto that path after the checkpoint of the last batch. When the restore fails
|
|
51
|
+
* or the machine stops part-way, the path therefore keeps its previous
|
|
52
|
+
* contents. Where a database already exists at that path, Sirannon checkpoints
|
|
53
|
+
* its write-ahead log into it before the rename, so that a crash between those
|
|
54
|
+
* two steps leaves that database complete. Where the checkpoint cannot empty
|
|
55
|
+
* that log, because another connection holds the database or SQLite cannot open
|
|
56
|
+
* the file, Sirannon deletes that database and its log, so that a crash at that
|
|
57
|
+
* point leaves the path empty, with no database there that lacks its last
|
|
58
|
+
* commits. A database at that path stops the call unless you set
|
|
59
|
+
* `replaceExisting`, because the rename replaces that database entirely.
|
|
61
60
|
*
|
|
62
|
-
* The disk
|
|
63
|
-
* log Sirannon writes for one batch of change pieces
|
|
64
|
-
*
|
|
61
|
+
* The restore needs disk space for the finished database, one stored piece,
|
|
62
|
+
* and the log that Sirannon writes for one batch of change pieces, whose size
|
|
63
|
+
* `batchSize` sets.
|
|
65
64
|
*
|
|
66
|
-
* @param options -
|
|
67
|
-
* @returns The chain
|
|
65
|
+
* @param options - The destination to read from, the moment to restore to, and the path for the result.
|
|
66
|
+
* @returns The chain that Sirannon reads, the moment that the rebuilt database reflects, and the counts of what the restore fetches and applies.
|
|
68
67
|
*
|
|
69
68
|
* @public
|
|
70
69
|
*/
|
package/dist/backup/index.mjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
export { assembleFromDestination, restoreBackup } from '../chunk-
|
|
2
|
-
export { backupPiecesSafeToDelete, planBackupRestore } from '../chunk-
|
|
3
|
-
export { verifyBackupRecord } from '../chunk-
|
|
4
|
-
import '../chunk-
|
|
5
|
-
import '../chunk-
|
|
6
|
-
export { DEFAULT_CHAIN_NAME, chainLogName, readBackupChains } from '../chunk-
|
|
1
|
+
export { assembleFromDestination, restoreBackup } from '../chunk-3BZLZ47R.mjs';
|
|
2
|
+
export { backupPiecesSafeToDelete, planBackupRestore } from '../chunk-YW7IKQ7T.mjs';
|
|
3
|
+
export { verifyBackupRecord } from '../chunk-DX4WGJL7.mjs';
|
|
4
|
+
import '../chunk-QGVRRKB2.mjs';
|
|
5
|
+
import '../chunk-R7GBXNTO.mjs';
|
|
6
|
+
export { DEFAULT_CHAIN_NAME, chainLogName, readBackupChains } from '../chunk-CZL4D3BM.mjs';
|
|
7
7
|
import '../chunk-QBBLSQHQ.mjs';
|
|
8
|
-
import '../chunk-
|
|
8
|
+
import '../chunk-KINDFBQP.mjs';
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import { I as SQLiteConnection, J as BackupFileCopy, K as BackupRunRequest, f as BackupRunReport, L as BackupScheduleRequest } from '../types-
|
|
2
|
-
export { p as BackupFileReport, O as BackupScheduleOptions, R as RunExclusive } from '../types-
|
|
3
|
-
import '../query-types-
|
|
1
|
+
import { I as SQLiteConnection, J as BackupFileCopy, K as BackupRunRequest, f as BackupRunReport, L as BackupScheduleRequest } from '../types-Dc8oWlxj.js';
|
|
2
|
+
export { p as BackupFileReport, O as BackupScheduleOptions, R as RunExclusive } from '../types-Dc8oWlxj.js';
|
|
3
|
+
import '../query-types-5B02ixH0.js';
|
|
4
4
|
|
|
5
|
-
/** What
|
|
5
|
+
/** What a runtime supplies so that Sirannon can send a copy to a destination without writing a local file.
|
|
6
6
|
* @internal
|
|
7
7
|
*/
|
|
8
8
|
interface BackupStreamingSupport {
|
|
9
|
-
/**
|
|
9
|
+
/** The absolute path of the compiled SQLite extension that queues the bytes of the copy for Sirannon to send. */
|
|
10
10
|
extensionPath: string;
|
|
11
|
-
/** Opens the connection the
|
|
11
|
+
/** Opens the connection that Sirannon runs the statements of the extension on. */
|
|
12
12
|
openConnection(): Promise<SQLiteConnection>;
|
|
13
13
|
}
|
|
14
14
|
|
|
@@ -16,35 +16,34 @@ declare class BackupManager {
|
|
|
16
16
|
private readonly streaming?;
|
|
17
17
|
constructor(streaming?: BackupStreamingSupport | undefined);
|
|
18
18
|
/**
|
|
19
|
-
* Copies the database
|
|
20
|
-
*
|
|
19
|
+
* Copies the database that a connection has open to a file, while other
|
|
20
|
+
* callers go on reading from and writing to that database.
|
|
21
21
|
*
|
|
22
|
-
* @param conn -
|
|
23
|
-
* @param destPath -
|
|
24
|
-
* @param onFirstStep - Called once the
|
|
25
|
-
* @returns
|
|
22
|
+
* @param conn - The writer connection, which SQLite runs the copy on.
|
|
23
|
+
* @param destPath - The path to write the copy to. Sirannon refuses a path where a file already exists.
|
|
24
|
+
* @param onFirstStep - Called once after the first step of the copy, so that the caller can release the writer.
|
|
25
|
+
* @returns The pages that SQLite copies, the time that the copy takes, and the number of times that SQLite restarts it from page one.
|
|
26
26
|
*/
|
|
27
27
|
backup(conn: SQLiteConnection, destPath: string, onFirstStep?: () => void): Promise<BackupFileCopy>;
|
|
28
28
|
/**
|
|
29
|
-
* Reads the size of the file a copy
|
|
29
|
+
* Reads the size of the file that a copy writes.
|
|
30
30
|
*
|
|
31
|
-
* @param resolved -
|
|
32
|
-
* @param destPath -
|
|
33
|
-
* @returns
|
|
31
|
+
* @param resolved - The absolute path of that file.
|
|
32
|
+
* @param destPath - The path that the caller names, which Sirannon quotes in any error.
|
|
33
|
+
* @returns The size of the file, in bytes.
|
|
34
34
|
*/
|
|
35
35
|
private fileBytes;
|
|
36
36
|
/**
|
|
37
|
-
* Runs the copy
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
* one nothing can restore.
|
|
37
|
+
* Runs the copy and deletes its file when the copy fails, so that a partly
|
|
38
|
+
* written database never stays on disk. Rotation would otherwise count that
|
|
39
|
+
* file among the copies it keeps, and it would delete a complete copy to make
|
|
40
|
+
* room for it.
|
|
42
41
|
*
|
|
43
|
-
* Sirannon stops waiting on
|
|
44
|
-
*
|
|
45
|
-
* leave a truncated file in place, and on Windows
|
|
46
|
-
* Sirannon therefore
|
|
47
|
-
*
|
|
42
|
+
* When the stall deadline passes, Sirannon stops waiting on the copy while
|
|
43
|
+
* SQLite goes on writing to the file. Deleting the file under a live copy
|
|
44
|
+
* would leave a truncated file in place, and on Windows the delete would fail.
|
|
45
|
+
* Sirannon therefore deletes the file once the copy stops, although the caller
|
|
46
|
+
* receives the failure straight away.
|
|
48
47
|
*/
|
|
49
48
|
private copyOrClearUp;
|
|
50
49
|
copyToDestination(conn: SQLiteConnection, request: BackupRunRequest): Promise<BackupRunReport>;
|
|
@@ -54,7 +53,7 @@ declare class BackupManager {
|
|
|
54
53
|
}
|
|
55
54
|
|
|
56
55
|
/**
|
|
57
|
-
*
|
|
56
|
+
* Copies a database to a file on a cron schedule, and deletes the oldest copies beyond a set number.
|
|
58
57
|
*
|
|
59
58
|
* @public
|
|
60
59
|
*/
|
|
@@ -64,29 +63,31 @@ declare class BackupScheduler {
|
|
|
64
63
|
/**
|
|
65
64
|
* Starts repeating backups and returns a function that stops them.
|
|
66
65
|
*
|
|
67
|
-
* @param conn -
|
|
68
|
-
* @param request -
|
|
66
|
+
* @param conn - The connection to the database that Sirannon copies.
|
|
67
|
+
* @param request - The cron expression, destination directory, retention, and time zone, with the callbacks and the details of the source database.
|
|
69
68
|
* @returns A function that stops the schedule.
|
|
70
69
|
*/
|
|
71
70
|
schedule(conn: SQLiteConnection, request: BackupScheduleRequest): () => void;
|
|
72
71
|
/**
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
*
|
|
76
|
-
*
|
|
72
|
+
* Returns the database identifier and the source file that every report
|
|
73
|
+
* names. The caller can supply either of them. Where the caller names no
|
|
74
|
+
* source file, Sirannon reads the path of the file that SQLite has open, and
|
|
75
|
+
* it runs that query on the writer connection while no other operation holds
|
|
76
|
+
* the writer.
|
|
77
77
|
*
|
|
78
|
-
* @param conn -
|
|
79
|
-
* @param run - The schedule, which holds whichever of the two the caller
|
|
80
|
-
* @param runExclusive - Runs the
|
|
81
|
-
* @returns The database identifier and the path of the file
|
|
82
|
-
* @throws A `BACKUP_ERROR` where the caller
|
|
78
|
+
* @param conn - The connection that Sirannon copies from.
|
|
79
|
+
* @param run - The schedule, which holds whichever of the two the caller names.
|
|
80
|
+
* @param runExclusive - Runs the query while no other operation holds the writer.
|
|
81
|
+
* @returns The database identifier and the path of the source file.
|
|
82
|
+
* @throws A `BACKUP_ERROR` where the caller names no source file and SQLite reports no file open on the connection.
|
|
83
83
|
*/
|
|
84
84
|
private namesFor;
|
|
85
85
|
/**
|
|
86
|
-
* Passes one finished copy to the
|
|
87
|
-
*
|
|
86
|
+
* Passes the report of one finished copy to the completion callback of the
|
|
87
|
+
* caller, and fails with `BACKUP_ERROR` once the deadline of that callback
|
|
88
|
+
* passes. A deadline of zero waits for the callback without a limit.
|
|
88
89
|
*
|
|
89
|
-
* @param report -
|
|
90
|
+
* @param report - The report of the finished copy.
|
|
90
91
|
* @param run - The schedule, which holds the callback and its deadline.
|
|
91
92
|
*/
|
|
92
93
|
private handToCaller;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
export { BackupScheduler } from '../chunk-
|
|
1
|
+
export { BackupScheduler } from '../chunk-NVSQ2T5Q.mjs';
|
|
2
2
|
import '../chunk-W67A4H2E.mjs';
|
|
3
|
-
import '../chunk-
|
|
4
|
-
import '../chunk-
|
|
3
|
+
import '../chunk-KJLNYOOL.mjs';
|
|
4
|
+
import '../chunk-R7GBXNTO.mjs';
|
|
5
5
|
import '../chunk-QBBLSQHQ.mjs';
|
|
6
|
-
import '../chunk-
|
|
6
|
+
import '../chunk-KINDFBQP.mjs';
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Marks one migration file as a baseline, which a new database runs in place of every version up to and including `through`.
|
|
3
|
+
*
|
|
4
|
+
* A database that already has migration history skips the baseline and applies only the versions that it lacks.
|
|
5
|
+
*
|
|
6
|
+
* @public
|
|
7
|
+
*/
|
|
8
|
+
interface BaselineFileOption {
|
|
9
|
+
/**
|
|
10
|
+
* The version of the migration that acts as the baseline.
|
|
11
|
+
*/
|
|
12
|
+
version: number;
|
|
13
|
+
/**
|
|
14
|
+
* The highest earlier version that a new database skips in favour of the baseline.
|
|
15
|
+
*/
|
|
16
|
+
through: number;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export type { BaselineFileOption as B };
|