@delali/sirannon-db 0.2.3-next.37 → 0.2.3-next.39
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/dist/backup/index.d.ts +6 -84
- package/dist/backup/index.mjs +6 -584
- package/dist/backup-scheduler/index.d.ts +58 -8
- package/dist/backup-scheduler/index.mjs +3 -2
- package/dist/{change-tracker-DXHPFe9J.d.ts → change-tracker-uG9gWvsY.d.ts} +1 -1
- package/dist/chunk-2QCPVE67.mjs +143 -0
- package/dist/chunk-63VBFSCS.mjs +525 -0
- package/dist/chunk-7SC2TZAS.mjs +41 -0
- package/dist/{chunk-72I5UI5C.mjs → chunk-DVS4QSIZ.mjs} +51 -23
- package/dist/{chunk-BQTVNI5H.mjs → chunk-DXXCLTFF.mjs} +205 -45
- package/dist/{chunk-BQFQ65OL.mjs → chunk-LXCGPZPA.mjs} +9 -1
- package/dist/{chunk-ZKDBGX7H.mjs → chunk-OJQHM4MT.mjs} +365 -357
- package/dist/chunk-U445A3C4.mjs +88 -0
- package/dist/chunk-W77EEVOT.mjs +156 -0
- package/dist/client/index.d.ts +8 -8
- package/dist/client/topology.d.ts +4 -4
- package/dist/{client-base-CGBpjZUB.d.ts → client-base-C-B8u5Lm.d.ts} +2 -2
- package/dist/core/index.d.ts +9 -9
- package/dist/core/index.mjs +228 -48
- package/dist/{database-955qag_W.d.ts → database-BlhUdJAO.d.ts} +46 -39
- package/dist/{chain-queries-D5aZ-8v2.d.ts → database-backup-C090lMHc.d.ts} +52 -2
- package/dist/driver/better-sqlite3.d.ts +1 -1
- package/dist/driver/better-sqlite3.mjs +9 -6
- package/dist/driver/bun.d.ts +1 -1
- package/dist/driver/expo.d.ts +1 -1
- package/dist/driver/node.d.ts +1 -1
- package/dist/driver/node.mjs +9 -6
- package/dist/driver/wa-sqlite.d.ts +1 -1
- package/dist/file-migrations/index.d.ts +1 -1
- package/dist/{protocol-BfiujBsE.d.ts → protocol-QekTGoKn.d.ts} +2 -2
- package/dist/replication/index.d.ts +7 -7
- package/dist/restore-options-C9MW9gpm.d.ts +83 -0
- package/dist/server/index.d.ts +102 -9
- package/dist/server/index.mjs +355 -3
- package/dist/{server-options-D4VmfTHw.d.ts → server-options-DeK6ZG8u.d.ts} +8 -1
- package/dist/{sirannon-DaGUawHs.d.ts → sirannon-C9cTgFrw.d.ts} +48 -5
- package/dist/transport/grpc.d.ts +3 -3
- package/dist/transport/memory.d.ts +3 -3
- package/dist/{types-BEjS69rs.d.ts → types-C1yBGTdx.d.ts} +2 -2
- package/dist/{types-Xb0L3o3w.d.ts → types-DApb7dvw.d.ts} +266 -23
- package/package.json +8 -7
- package/dist/chunk-GBM4AU24.mjs +0 -42
package/dist/core/index.mjs
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import { planBackupRestore, backupPiecesSafeToDelete } from '../chunk-YNTDOFTN.mjs';
|
|
2
|
-
export { backupPiecesSafeToDelete, planBackupRestore } from '../chunk-YNTDOFTN.mjs';
|
|
3
1
|
import { Transaction, LWWResolver, REMOTE_ORIGIN_NODE_ID, BatchReader, BatchApplier, recordLocalColumnVersions, HLC, loadPersistedHlc, isWellFormedHlc, HLC_CLOCK_META_KEY } from '../chunk-P2VJYRVY.mjs';
|
|
4
2
|
export { Transaction } from '../chunk-P2VJYRVY.mjs';
|
|
5
3
|
import { executeBatch, executeBatchSummary, execute, bindParams, executeGroup, query, queryForWire, queryOne } from '../chunk-HCCGEIZ2.mjs';
|
|
@@ -7,15 +5,19 @@ export { execute, executeBatch, query, queryOne } from '../chunk-HCCGEIZ2.mjs';
|
|
|
7
5
|
import { tokenizeSql, unsupported, findClauses, readSource, readSortTerms, readWindow, readSelectItems, parameterName } from '../chunk-SBL6GN43.mjs';
|
|
8
6
|
import { reconcileMigrationChecksums, migrationContentChecksum, ensureCdcEpoch, SubscriptionBuilderImpl, ChangeTracker, SubscriptionManager, startPolling, runBulkLoad, TransactionGrouper, PrimedSubscription } from '../chunk-ECEGYTVF.mjs';
|
|
9
7
|
export { ChangeTracker, isBulkLoadDurability, runBulkLoad } from '../chunk-ECEGYTVF.mjs';
|
|
8
|
+
import { planBackupRestore, backupPiecesSafeToDelete } from '../chunk-YNTDOFTN.mjs';
|
|
9
|
+
export { backupPiecesSafeToDelete, planBackupRestore } from '../chunk-YNTDOFTN.mjs';
|
|
10
10
|
import { ensureMigrationsTable, prepareUpdateMigrationChecksum, prepareInsertMigration, selectAppliedMigrationsNewestFirst, prepareDeleteMigration, selectAppliedMigrations, tableInfoRows, selectTableSql, selectTableExists, selectMetaValue, upsertMetaValue, SEQ_STRING_RE, replaceMigrationHistory, highestMigrationVersion, selectCountOutboundChanges, ensureMetaTable, ensureChangesTable, ensureBatchApplyTables, PkResolver, selectMaxChangeSeq, selectMaxAppliedSourceSeq, insertMetaValueIfAbsent, selectMaxChangeHlc, updateUnstampedChangeStampsSql, UPSERT_META_VALUE_SQL, setForeignKeysEnabled, IDENTIFIER_RE, validateDdlSafety, ensureStagedChangesTable, upsertStagedChangeSql, selectFirstStagedTransactionEnd, deleteStagedChangesUpToSeq, selectMaxStagedSeq, deleteAllStagedChanges, deleteMetaValue, assertSafeIdentifier, selectStagedChangesInRange } from '../chunk-7FQRQH5Z.mjs';
|
|
11
11
|
import { ReplicationError } from '../chunk-2QLXDHAP.mjs';
|
|
12
12
|
import { MIGRATIONS_TABLE, LIVE_PROBE_TABLE_PREFIX, META_TABLE, CHANGES_TABLE, assertSqlAllowed, INTERNAL_TABLE_PREFIX, encodeTaggedValues, decodeTaggedValues } from '../chunk-7R4ER4FB.mjs';
|
|
13
13
|
import '../chunk-GVCNMPOS.mjs';
|
|
14
|
-
|
|
15
|
-
export {
|
|
14
|
+
import { defaultStagingDir } from '../chunk-LXCGPZPA.mjs';
|
|
15
|
+
export { defineDriver } from '../chunk-LXCGPZPA.mjs';
|
|
16
16
|
import { startCopyWithoutHoldingWriter } from '../chunk-W67A4H2E.mjs';
|
|
17
17
|
import { DEFAULT_SYNCHRONOUS } from '../chunk-OUSWVNWT.mjs';
|
|
18
18
|
export { DEFAULT_SYNCHRONOUS, isSynchronousLevel, synchronousPragmaValue } from '../chunk-OUSWVNWT.mjs';
|
|
19
|
+
import { DEFAULT_CHAIN_NAME } from '../chunk-YVF2ZJTE.mjs';
|
|
20
|
+
export { DEFAULT_CHAIN_NAME, readBackupChains } from '../chunk-YVF2ZJTE.mjs';
|
|
19
21
|
import { randomHex } from '../chunk-V5FUJEYY.mjs';
|
|
20
22
|
import { LAZY_DOWN_SQL, parseMigrationFilename } from '../chunk-7C36BCSN.mjs';
|
|
21
23
|
export { MIGRATION_FILENAME_PATTERN, parseMigrationFilename } from '../chunk-7C36BCSN.mjs';
|
|
@@ -202,6 +204,7 @@ var DatabaseBackupController = class {
|
|
|
202
204
|
}
|
|
203
205
|
cancellers = [];
|
|
204
206
|
cycle = null;
|
|
207
|
+
cycleOptions = null;
|
|
205
208
|
cycleStarted = Promise.resolve();
|
|
206
209
|
require() {
|
|
207
210
|
if (!this.engine) {
|
|
@@ -215,7 +218,7 @@ var DatabaseBackupController = class {
|
|
|
215
218
|
requireCycle() {
|
|
216
219
|
if (!this.cycle) {
|
|
217
220
|
throw new SirannonError(
|
|
218
|
-
`Database '${this.databaseId}' opened without the backups option, so it captures no change log and
|
|
221
|
+
`Database '${this.databaseId}' opened without the backups option, so it captures no change log and extends no chain`,
|
|
219
222
|
"BACKUP_UNSUPPORTED"
|
|
220
223
|
);
|
|
221
224
|
}
|
|
@@ -228,12 +231,13 @@ var DatabaseBackupController = class {
|
|
|
228
231
|
this.engine?.streamsToDestination() ?? false
|
|
229
232
|
);
|
|
230
233
|
}
|
|
231
|
-
backup(destPath) {
|
|
234
|
+
async backup(destPath) {
|
|
232
235
|
const engine = this.require();
|
|
233
|
-
|
|
236
|
+
const copy = await startCopyWithoutHoldingWriter(
|
|
234
237
|
this.runExclusive,
|
|
235
238
|
(onFirstStep) => engine.backup(this.acquireWriter(), destPath, onFirstStep)
|
|
236
239
|
);
|
|
240
|
+
return { ...copy, databaseId: this.databaseId, sourcePath: this.sourcePath };
|
|
237
241
|
}
|
|
238
242
|
backupTo(options) {
|
|
239
243
|
const engine = this.require();
|
|
@@ -249,7 +253,14 @@ var DatabaseBackupController = class {
|
|
|
249
253
|
}
|
|
250
254
|
schedule(options) {
|
|
251
255
|
const engine = this.require();
|
|
252
|
-
this.cancellers.push(
|
|
256
|
+
this.cancellers.push(
|
|
257
|
+
engine.schedule(this.acquireWriter(), {
|
|
258
|
+
...options,
|
|
259
|
+
databaseId: this.databaseId,
|
|
260
|
+
sourcePath: this.sourcePath,
|
|
261
|
+
runExclusive: this.runExclusive
|
|
262
|
+
})
|
|
263
|
+
);
|
|
253
264
|
}
|
|
254
265
|
/**
|
|
255
266
|
* Builds the cycle and starts it in the background. Its first turn copies the
|
|
@@ -258,6 +269,7 @@ var DatabaseBackupController = class {
|
|
|
258
269
|
*/
|
|
259
270
|
startCycle(options) {
|
|
260
271
|
const engine = this.require();
|
|
272
|
+
this.cycleOptions = options;
|
|
261
273
|
this.cycle = engine.createCycle({
|
|
262
274
|
...options,
|
|
263
275
|
databaseId: this.databaseId,
|
|
@@ -281,6 +293,27 @@ var DatabaseBackupController = class {
|
|
|
281
293
|
chains() {
|
|
282
294
|
return this.requireCycle().chains();
|
|
283
295
|
}
|
|
296
|
+
status() {
|
|
297
|
+
return this.requireCycle().status();
|
|
298
|
+
}
|
|
299
|
+
location() {
|
|
300
|
+
const options = this.cycleOptions;
|
|
301
|
+
if (!options) {
|
|
302
|
+
throw new SirannonError(
|
|
303
|
+
`Database '${this.databaseId}' opened without the backups option, so it captures no change log and extends no chain`,
|
|
304
|
+
"BACKUP_UNSUPPORTED"
|
|
305
|
+
);
|
|
306
|
+
}
|
|
307
|
+
return {
|
|
308
|
+
destination: options.destination,
|
|
309
|
+
chainName: options.chainName ?? DEFAULT_CHAIN_NAME,
|
|
310
|
+
stagingDir: options.stagingDir ?? defaultStagingDir(this.sourcePath),
|
|
311
|
+
...options.destinationTimeoutMs === void 0 ? {} : { destinationTimeoutMs: options.destinationTimeoutMs }
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
verify(name) {
|
|
315
|
+
return this.requireCycle().verify(name);
|
|
316
|
+
}
|
|
284
317
|
async restorePlan(moment) {
|
|
285
318
|
return planBackupRestore(await this.chains(), moment);
|
|
286
319
|
}
|
|
@@ -2138,10 +2171,11 @@ var DatabaseBackups = class extends DatabaseLifecycle {
|
|
|
2138
2171
|
* runs in the gap between two steps rather than waiting for the whole copy.
|
|
2139
2172
|
*
|
|
2140
2173
|
* @param destPath - Path the copy is written to.
|
|
2174
|
+
* @returns The run identifier, the timings, the pages it moved, and the bytes the file holds.
|
|
2141
2175
|
*/
|
|
2142
2176
|
async backup(destPath) {
|
|
2143
2177
|
this.ensureOpen();
|
|
2144
|
-
|
|
2178
|
+
return this.runtime.backups.backup(destPath);
|
|
2145
2179
|
}
|
|
2146
2180
|
/**
|
|
2147
2181
|
* Copies this database to a destination you supply, in fixed-size pieces,
|
|
@@ -2175,7 +2209,7 @@ var DatabaseBackups = class extends DatabaseLifecycle {
|
|
|
2175
2209
|
/**
|
|
2176
2210
|
* Starts repeating backups on a cron schedule, keeping a bounded number of files.
|
|
2177
2211
|
*
|
|
2178
|
-
* @param options - Cron expression, destination directory, retention, time zone, and failure
|
|
2212
|
+
* @param options - Cron expression, destination directory, retention, and time zone, along with the callbacks Sirannon calls after each finished copy and after each failure.
|
|
2179
2213
|
*/
|
|
2180
2214
|
scheduleBackup(options) {
|
|
2181
2215
|
this.ensureOpen();
|
|
@@ -2205,6 +2239,54 @@ var DatabaseBackups = class extends DatabaseLifecycle {
|
|
|
2205
2239
|
this.ensureNotClosed();
|
|
2206
2240
|
return this.runtime.backups.chains();
|
|
2207
2241
|
}
|
|
2242
|
+
/**
|
|
2243
|
+
* Reads what this database's backup cycle is doing at this moment, and what
|
|
2244
|
+
* its recent turns produced.
|
|
2245
|
+
*
|
|
2246
|
+
* A caller that triggers a turn with {@link DatabaseBackups.captureBackupChanges}
|
|
2247
|
+
* without waiting on it would read this until that turn finishes. A full copy
|
|
2248
|
+
* of a large database reports its pages as SQLite moves them.
|
|
2249
|
+
*
|
|
2250
|
+
* @returns Whether a turn is under way, how far it has got, and the last run, skip, and failure.
|
|
2251
|
+
*/
|
|
2252
|
+
backupStatus() {
|
|
2253
|
+
this.ensureNotClosed();
|
|
2254
|
+
return this.runtime.backups.status();
|
|
2255
|
+
}
|
|
2256
|
+
/**
|
|
2257
|
+
* Reads one of this database's backups back out of the destination and
|
|
2258
|
+
* compares it against the record the backup that wrote it left behind.
|
|
2259
|
+
*
|
|
2260
|
+
* A restore would fail on a damaged piece only once that restore had already
|
|
2261
|
+
* begun, so name the backup here beforehand. Sirannon then fetches every
|
|
2262
|
+
* piece in order and folds a SHA-256 over the bytes as they arrive, and it
|
|
2263
|
+
* compares that digest and the byte count against the record. Only one piece
|
|
2264
|
+
* is in memory at any moment, so a check over a large full copy needs no
|
|
2265
|
+
* local storage of its own.
|
|
2266
|
+
*
|
|
2267
|
+
* A missing piece, a byte count that differs from the recorded one, and a
|
|
2268
|
+
* digest that differs from the recorded one will each fail with
|
|
2269
|
+
* `BACKUP_DESTINATION_ERROR`.
|
|
2270
|
+
*
|
|
2271
|
+
* @param name - Name the backup is stored under, which every entry {@link DatabaseBackups.backupChain} returns states.
|
|
2272
|
+
* @returns The pieces read, the bytes they add up to, and the digest where the backup recorded one.
|
|
2273
|
+
*/
|
|
2274
|
+
async verifyBackup(name) {
|
|
2275
|
+
this.ensureNotClosed();
|
|
2276
|
+
return this.runtime.backups.verify(name);
|
|
2277
|
+
}
|
|
2278
|
+
/**
|
|
2279
|
+
* Reads where this database's backups are stored.
|
|
2280
|
+
*
|
|
2281
|
+
* `restoreBackup` accepts what you get back, and it then rebuilds this
|
|
2282
|
+
* database from its own backups at whatever path you name.
|
|
2283
|
+
*
|
|
2284
|
+
* @returns The destination, the name its chains are listed under, and the directory Sirannon stages captures in.
|
|
2285
|
+
*/
|
|
2286
|
+
backupLocation() {
|
|
2287
|
+
this.ensureNotClosed();
|
|
2288
|
+
return this.runtime.backups.location();
|
|
2289
|
+
}
|
|
2208
2290
|
/**
|
|
2209
2291
|
* Works out what a restore to a given moment has to read.
|
|
2210
2292
|
*
|
|
@@ -3858,17 +3940,85 @@ function migrationsFromFiles(files, options) {
|
|
|
3858
3940
|
return applyBaselineOption(migrations, options?.baseline);
|
|
3859
3941
|
}
|
|
3860
3942
|
|
|
3943
|
+
// src/core/migrations/registry-set.ts
|
|
3944
|
+
var RegistryMigrationSet = class {
|
|
3945
|
+
constructor(source) {
|
|
3946
|
+
this.source = source;
|
|
3947
|
+
}
|
|
3948
|
+
loading = null;
|
|
3949
|
+
/**
|
|
3950
|
+
* Reads the set, loading it where a function supplies it.
|
|
3951
|
+
*
|
|
3952
|
+
* @returns The migrations, which is an empty list where the registry has none.
|
|
3953
|
+
* @throws When the function returns anything but a list.
|
|
3954
|
+
*/
|
|
3955
|
+
load() {
|
|
3956
|
+
const source = this.source;
|
|
3957
|
+
if (source === void 0 || Array.isArray(source)) {
|
|
3958
|
+
return Promise.resolve(source ?? []);
|
|
3959
|
+
}
|
|
3960
|
+
if (this.loading) return this.loading;
|
|
3961
|
+
const loading = (async () => {
|
|
3962
|
+
const set = await source();
|
|
3963
|
+
if (!Array.isArray(set)) {
|
|
3964
|
+
throw new MigrationError(
|
|
3965
|
+
"The migrations source must return an array of migrations",
|
|
3966
|
+
0,
|
|
3967
|
+
"MIGRATION_SOURCE_INVALID"
|
|
3968
|
+
);
|
|
3969
|
+
}
|
|
3970
|
+
return set;
|
|
3971
|
+
})();
|
|
3972
|
+
loading.catch(() => {
|
|
3973
|
+
if (this.loading === loading) this.loading = null;
|
|
3974
|
+
});
|
|
3975
|
+
this.loading = loading;
|
|
3976
|
+
return loading;
|
|
3977
|
+
}
|
|
3978
|
+
/**
|
|
3979
|
+
* Applies the set to one database. A database that refuses writes takes none.
|
|
3980
|
+
*
|
|
3981
|
+
* @param db - The database to migrate.
|
|
3982
|
+
*/
|
|
3983
|
+
async applyTo(db) {
|
|
3984
|
+
if (this.source === void 0 || db.readOnly) return;
|
|
3985
|
+
const migrations = await this.load();
|
|
3986
|
+
if (migrations.length === 0) return;
|
|
3987
|
+
await db.migrate(migrations);
|
|
3988
|
+
}
|
|
3989
|
+
};
|
|
3990
|
+
|
|
3991
|
+
// src/core/sirannon-offline.ts
|
|
3992
|
+
async function takeDatabaseOffline(offline) {
|
|
3993
|
+
await offline.database.close();
|
|
3994
|
+
let outcome;
|
|
3995
|
+
try {
|
|
3996
|
+
outcome = { ok: true, value: await offline.action(offline.path) };
|
|
3997
|
+
} catch (err) {
|
|
3998
|
+
outcome = { ok: false, failure: err };
|
|
3999
|
+
}
|
|
4000
|
+
try {
|
|
4001
|
+
await offline.reopen();
|
|
4002
|
+
} catch (err) {
|
|
4003
|
+
outcome.reopenFailure = err;
|
|
4004
|
+
}
|
|
4005
|
+
return outcome;
|
|
4006
|
+
}
|
|
4007
|
+
|
|
3861
4008
|
// src/core/sirannon.ts
|
|
3862
4009
|
var Sirannon = class {
|
|
3863
4010
|
dbs = /* @__PURE__ */ new Map();
|
|
4011
|
+
openedWith = /* @__PURE__ */ new Map();
|
|
4012
|
+
offline = /* @__PURE__ */ new Set();
|
|
4013
|
+
offlineWork = /* @__PURE__ */ new Set();
|
|
3864
4014
|
opening = /* @__PURE__ */ new Set();
|
|
3865
4015
|
resolving = /* @__PURE__ */ new Map();
|
|
3866
|
-
migrationSet = null;
|
|
3867
4016
|
_shutdown = false;
|
|
3868
4017
|
_driver;
|
|
3869
4018
|
hookRegistry;
|
|
3870
4019
|
metricsCollector;
|
|
3871
4020
|
lifecycleManager;
|
|
4021
|
+
migrations;
|
|
3872
4022
|
/** The driver, hooks, metrics, lifecycle, migrations, and writer-worker default this registry was built with. */
|
|
3873
4023
|
options;
|
|
3874
4024
|
/**
|
|
@@ -3880,6 +4030,7 @@ var Sirannon = class {
|
|
|
3880
4030
|
this.options = options;
|
|
3881
4031
|
this._driver = options.driver;
|
|
3882
4032
|
this.hookRegistry = new HookRegistry(options.hooks);
|
|
4033
|
+
this.migrations = new RegistryMigrationSet(options.migrations);
|
|
3883
4034
|
this.metricsCollector = options.metrics ? new MetricsCollector(options.metrics) : null;
|
|
3884
4035
|
this.lifecycleManager = options.lifecycle ? new LifecycleManager(options.lifecycle, {
|
|
3885
4036
|
open: (id, path, opts) => this.open(id, path, opts),
|
|
@@ -3902,17 +4053,18 @@ var Sirannon = class {
|
|
|
3902
4053
|
* @throws When the identifier is already registered.
|
|
3903
4054
|
*/
|
|
3904
4055
|
async open(id, path, options) {
|
|
3905
|
-
this.
|
|
4056
|
+
this.ensureOpenAllowed(id);
|
|
3906
4057
|
if (this.dbs.has(id) || this.opening.has(id)) {
|
|
3907
4058
|
throw new DatabaseAlreadyExistsError(id);
|
|
3908
4059
|
}
|
|
3909
4060
|
this.opening.add(id);
|
|
4061
|
+
const resolvedOptions = this.withRegistryDefaults(options);
|
|
3910
4062
|
let db;
|
|
3911
4063
|
try {
|
|
3912
4064
|
if (this.hookRegistry.has("beforeConnect")) {
|
|
3913
4065
|
this.hookRegistry.invokeSync("beforeConnect", { databaseId: id, path });
|
|
3914
4066
|
}
|
|
3915
|
-
db = await Database.create(id, path, this._driver,
|
|
4067
|
+
db = await Database.create(id, path, this._driver, resolvedOptions, {
|
|
3916
4068
|
parentHooks: this.hookRegistry,
|
|
3917
4069
|
metrics: this.metricsCollector ?? void 0
|
|
3918
4070
|
});
|
|
@@ -3925,7 +4077,7 @@ var Sirannon = class {
|
|
|
3925
4077
|
);
|
|
3926
4078
|
}
|
|
3927
4079
|
try {
|
|
3928
|
-
await this.
|
|
4080
|
+
await this.migrations.applyTo(db);
|
|
3929
4081
|
} catch (err) {
|
|
3930
4082
|
await db.close().catch(() => {
|
|
3931
4083
|
});
|
|
@@ -3937,13 +4089,14 @@ var Sirannon = class {
|
|
|
3937
4089
|
} finally {
|
|
3938
4090
|
this.opening.delete(id);
|
|
3939
4091
|
}
|
|
3940
|
-
if (this._shutdown) {
|
|
4092
|
+
if (this._shutdown && !this.offline.has(id)) {
|
|
3941
4093
|
await db.close().catch(() => {
|
|
3942
4094
|
});
|
|
3943
4095
|
throw new SirannonError("Sirannon has been shut down", "SHUTDOWN");
|
|
3944
4096
|
}
|
|
3945
4097
|
db.addCloseListener(() => {
|
|
3946
4098
|
this.dbs.delete(id);
|
|
4099
|
+
this.openedWith.delete(id);
|
|
3947
4100
|
this.lifecycleManager?.untrack(id);
|
|
3948
4101
|
if (this.hookRegistry.has("databaseClose")) {
|
|
3949
4102
|
try {
|
|
@@ -3959,6 +4112,7 @@ var Sirannon = class {
|
|
|
3959
4112
|
});
|
|
3960
4113
|
});
|
|
3961
4114
|
this.dbs.set(id, db);
|
|
4115
|
+
this.openedWith.set(id, resolvedOptions === void 0 ? { path } : { path, options: resolvedOptions });
|
|
3962
4116
|
this.lifecycleManager?.markActive(id);
|
|
3963
4117
|
if (this.hookRegistry.has("databaseOpen")) {
|
|
3964
4118
|
try {
|
|
@@ -3992,6 +4146,52 @@ var Sirannon = class {
|
|
|
3992
4146
|
}
|
|
3993
4147
|
await db.close();
|
|
3994
4148
|
}
|
|
4149
|
+
/**
|
|
4150
|
+
* Closes one database, calls an action against the file behind it, and then
|
|
4151
|
+
* opens that database again under the same identifier with the settings it
|
|
4152
|
+
* had before.
|
|
4153
|
+
*
|
|
4154
|
+
* A restore rebuilds a database at the path it already occupies, so no
|
|
4155
|
+
* connection may be open on that file while Sirannon replaces its bytes. The
|
|
4156
|
+
* identifier answers nothing until that database is open again, though an
|
|
4157
|
+
* action that failed still leaves a database open at the end.
|
|
4158
|
+
*
|
|
4159
|
+
* @param id - Identifier of the database to take offline.
|
|
4160
|
+
* @param action - Runs with the database closed, and receives its file path.
|
|
4161
|
+
* @returns Whether the action returned, what it produced or threw, and what a failed reopen threw.
|
|
4162
|
+
* @throws When no database is open under the identifier, when it refuses writes, or when the close fails.
|
|
4163
|
+
*
|
|
4164
|
+
* @internal
|
|
4165
|
+
*/
|
|
4166
|
+
async withDatabaseOffline(id, action) {
|
|
4167
|
+
this.ensureRunning();
|
|
4168
|
+
const db = this.dbs.get(id);
|
|
4169
|
+
const opened = this.openedWith.get(id);
|
|
4170
|
+
if (!db || !opened) throw new DatabaseNotFoundError(id);
|
|
4171
|
+
if (db.readOnly) throw new ReadOnlyError(id);
|
|
4172
|
+
this.dbs.delete(id);
|
|
4173
|
+
this.opening.add(id);
|
|
4174
|
+
this.offline.add(id);
|
|
4175
|
+
const work = takeDatabaseOffline({
|
|
4176
|
+
database: db,
|
|
4177
|
+
path: opened.path,
|
|
4178
|
+
action,
|
|
4179
|
+
reopen: async () => {
|
|
4180
|
+
this.opening.delete(id);
|
|
4181
|
+
try {
|
|
4182
|
+
return await this.open(id, opened.path, opened.options);
|
|
4183
|
+
} finally {
|
|
4184
|
+
this.offline.delete(id);
|
|
4185
|
+
}
|
|
4186
|
+
}
|
|
4187
|
+
}).finally(() => {
|
|
4188
|
+
this.opening.delete(id);
|
|
4189
|
+
this.offline.delete(id);
|
|
4190
|
+
this.offlineWork.delete(work);
|
|
4191
|
+
});
|
|
4192
|
+
this.offlineWork.add(work);
|
|
4193
|
+
return work;
|
|
4194
|
+
}
|
|
3995
4195
|
/**
|
|
3996
4196
|
* Returns an already-open database.
|
|
3997
4197
|
*
|
|
@@ -4011,7 +4211,7 @@ var Sirannon = class {
|
|
|
4011
4211
|
async resolve(id) {
|
|
4012
4212
|
const db = this.get(id);
|
|
4013
4213
|
if (db) return db;
|
|
4014
|
-
if (this._shutdown) return void 0;
|
|
4214
|
+
if (this._shutdown || this.offline.has(id)) return void 0;
|
|
4015
4215
|
const manager = this.lifecycleManager;
|
|
4016
4216
|
if (!manager) return void 0;
|
|
4017
4217
|
const pending = this.resolving.get(id);
|
|
@@ -4024,38 +4224,7 @@ var Sirannon = class {
|
|
|
4024
4224
|
}
|
|
4025
4225
|
/** @internal */
|
|
4026
4226
|
registryMigrations() {
|
|
4027
|
-
return this.
|
|
4028
|
-
}
|
|
4029
|
-
async applyRegistryMigrations(db) {
|
|
4030
|
-
if (this.options.migrations === void 0 || db.readOnly) return;
|
|
4031
|
-
const migrations = await this.loadMigrationSet();
|
|
4032
|
-
if (migrations.length === 0) return;
|
|
4033
|
-
await db.migrate(migrations);
|
|
4034
|
-
}
|
|
4035
|
-
loadMigrationSet() {
|
|
4036
|
-
const source = this.options.migrations;
|
|
4037
|
-
if (source === void 0 || Array.isArray(source)) {
|
|
4038
|
-
return Promise.resolve(source ?? []);
|
|
4039
|
-
}
|
|
4040
|
-
if (this.migrationSet) return this.migrationSet;
|
|
4041
|
-
const loading = (async () => {
|
|
4042
|
-
const set = await source();
|
|
4043
|
-
if (!Array.isArray(set)) {
|
|
4044
|
-
throw new MigrationError(
|
|
4045
|
-
"The migrations source must return an array of migrations",
|
|
4046
|
-
0,
|
|
4047
|
-
"MIGRATION_SOURCE_INVALID"
|
|
4048
|
-
);
|
|
4049
|
-
}
|
|
4050
|
-
return set;
|
|
4051
|
-
})();
|
|
4052
|
-
loading.catch(() => {
|
|
4053
|
-
if (this.migrationSet === loading) {
|
|
4054
|
-
this.migrationSet = null;
|
|
4055
|
-
}
|
|
4056
|
-
});
|
|
4057
|
-
this.migrationSet = loading;
|
|
4058
|
-
return loading;
|
|
4227
|
+
return this.migrations.load();
|
|
4059
4228
|
}
|
|
4060
4229
|
/**
|
|
4061
4230
|
* Reports whether a database is open under an identifier.
|
|
@@ -4076,10 +4245,17 @@ var Sirannon = class {
|
|
|
4076
4245
|
}
|
|
4077
4246
|
/**
|
|
4078
4247
|
* Closes every open database and stops the lifecycle timers.
|
|
4248
|
+
*
|
|
4249
|
+
* A database whose file Sirannon is replacing delays the shutdown until that
|
|
4250
|
+
* work finishes, since a process that exits part-way through would leave the
|
|
4251
|
+
* path with no database on it at all.
|
|
4079
4252
|
*/
|
|
4080
4253
|
async shutdown() {
|
|
4081
4254
|
if (this._shutdown) return;
|
|
4082
4255
|
this._shutdown = true;
|
|
4256
|
+
while (this.offlineWork.size > 0) {
|
|
4257
|
+
await Promise.allSettled([...this.offlineWork]);
|
|
4258
|
+
}
|
|
4083
4259
|
this.lifecycleManager?.dispose();
|
|
4084
4260
|
const errors = [];
|
|
4085
4261
|
const snapshot = [...this.dbs.values()];
|
|
@@ -4140,6 +4316,10 @@ var Sirannon = class {
|
|
|
4140
4316
|
throw new SirannonError("Sirannon has been shut down", "SHUTDOWN");
|
|
4141
4317
|
}
|
|
4142
4318
|
}
|
|
4319
|
+
ensureOpenAllowed(id) {
|
|
4320
|
+
if (this.offline.has(id)) return;
|
|
4321
|
+
this.ensureRunning();
|
|
4322
|
+
}
|
|
4143
4323
|
};
|
|
4144
4324
|
|
|
4145
4325
|
// src/core/operation-registry.ts
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { B as BackupCapabilities, a as
|
|
2
|
-
import {
|
|
3
|
-
import { C as ChangeTracker } from './change-tracker-
|
|
1
|
+
import { D as DatabaseBackupController, B as BackupCapabilities, a as BackupChainLocation, b as BackupRestorePlan, c as BackupSafeToDeleteOptions } from './database-backup-C090lMHc.js';
|
|
2
|
+
import { S as SQLiteDriver, U as SynchronousLevel, ak as WorkerHostOptions, I as SQLiteConnection, T as Transaction, al as WriterContext, M as Migration, W as MigrationResult, X as RollbackResult, af as QueryHookContext, a2 as ConnectionHookContext, $ as BeforeSubscribeHook, a7 as HookConfig, a9 as MetricsConfig, ag as QueryMetrics, a3 as ConnectionMetrics, a0 as CDCMetrics, D as DatabaseOptions, p as BackupFileReport, y as BackupToDestinationOptions, f as BackupRunReport, O as BackupScheduleOptions, g as BackupChain, o as BackupCycleStatus, z as BackupVerifyResult, k as BackupChainRecord, b as BeforeQueryHook, A as AfterQueryHook } from './types-DApb7dvw.js';
|
|
3
|
+
import { C as ChangeTracker } from './change-tracker-uG9gWvsY.js';
|
|
4
4
|
import { C as ChangeEvent, S as Subscription, c as SubscriptionBuilder, P as Params, Q as QueryOptions, E as ExecuteResult } from './query-types-Cv8N7pXj.js';
|
|
5
5
|
import { H as HLCTimestamp, c as ReplicationChange, C as ConflictResolver, A as ApplyResult, R as ReplicationBatch } from './types-CjhxcjhA.js';
|
|
6
|
-
import { h as AppliedMigrationRow, b as BulkLoadOptions, a as BulkLoadResult } from './server-options-
|
|
6
|
+
import { h as AppliedMigrationRow, b as BulkLoadOptions, a as BulkLoadResult } from './server-options-DeK6ZG8u.js';
|
|
7
7
|
import { L as LiveQueryOptions, a as LiveQuery } from './types-BCejqzNA.js';
|
|
8
8
|
|
|
9
9
|
/**
|
|
@@ -43,39 +43,6 @@ declare class ConnectionPool {
|
|
|
43
43
|
close(): Promise<void>;
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
-
type RunExclusive$2 = (op: () => Promise<void>) => Promise<void>;
|
|
47
|
-
declare class DatabaseBackupController {
|
|
48
|
-
private readonly runExclusive;
|
|
49
|
-
private readonly acquireWriter;
|
|
50
|
-
private readonly driverCapabilities;
|
|
51
|
-
private readonly databaseId;
|
|
52
|
-
private readonly sourcePath;
|
|
53
|
-
private readonly engine?;
|
|
54
|
-
private readonly cancellers;
|
|
55
|
-
private cycle;
|
|
56
|
-
private cycleStarted;
|
|
57
|
-
constructor(runExclusive: RunExclusive$2, acquireWriter: () => SQLiteConnection, driverCapabilities: DriverCapabilities, databaseId: string, sourcePath: string, engine?: BackupEngine | undefined);
|
|
58
|
-
private require;
|
|
59
|
-
private requireCycle;
|
|
60
|
-
capabilities(): BackupCapabilities;
|
|
61
|
-
backup(destPath: string): Promise<void>;
|
|
62
|
-
backupTo(options: BackupToDestinationOptions): Promise<BackupRunReport>;
|
|
63
|
-
schedule(options: BackupScheduleOptions): void;
|
|
64
|
-
/**
|
|
65
|
-
* Builds the cycle and starts it in the background. Its first turn copies the
|
|
66
|
-
* whole database, and an open cannot wait on that. A failure reaches the
|
|
67
|
-
* caller through the cycle's own error callback.
|
|
68
|
-
*/
|
|
69
|
-
startCycle(options: BackupCycleOptions): void;
|
|
70
|
-
captureChanges(): Promise<BackupRunReport | undefined>;
|
|
71
|
-
chains(): Promise<BackupChain[]>;
|
|
72
|
-
restorePlan(moment: number): Promise<BackupRestorePlan>;
|
|
73
|
-
piecesSafeToDelete(options?: BackupSafeToDeleteOptions): Promise<BackupChainRecord[]>;
|
|
74
|
-
/** Stops the cycle, capturing the log one final time so nothing written since the previous turn is lost. */
|
|
75
|
-
stopCycle(): Promise<void>;
|
|
76
|
-
cancelAll(): void;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
46
|
declare class SubscriptionManager {
|
|
80
47
|
private nextId;
|
|
81
48
|
private readonly subscriptions;
|
|
@@ -645,8 +612,9 @@ declare class DatabaseBackups extends DatabaseLifecycle {
|
|
|
645
612
|
* runs in the gap between two steps rather than waiting for the whole copy.
|
|
646
613
|
*
|
|
647
614
|
* @param destPath - Path the copy is written to.
|
|
615
|
+
* @returns The run identifier, the timings, the pages it moved, and the bytes the file holds.
|
|
648
616
|
*/
|
|
649
|
-
backup(destPath: string): Promise<
|
|
617
|
+
backup(destPath: string): Promise<BackupFileReport>;
|
|
650
618
|
/**
|
|
651
619
|
* Copies this database to a destination you supply, in fixed-size pieces,
|
|
652
620
|
* while it stays open for reads and writes.
|
|
@@ -674,7 +642,7 @@ declare class DatabaseBackups extends DatabaseLifecycle {
|
|
|
674
642
|
/**
|
|
675
643
|
* Starts repeating backups on a cron schedule, keeping a bounded number of files.
|
|
676
644
|
*
|
|
677
|
-
* @param options - Cron expression, destination directory, retention, time zone, and failure
|
|
645
|
+
* @param options - Cron expression, destination directory, retention, and time zone, along with the callbacks Sirannon calls after each finished copy and after each failure.
|
|
678
646
|
*/
|
|
679
647
|
scheduleBackup(options: BackupScheduleOptions): void;
|
|
680
648
|
/**
|
|
@@ -695,6 +663,45 @@ declare class DatabaseBackups extends DatabaseLifecycle {
|
|
|
695
663
|
* @returns One entry per chain, newest first, each with its full copy and its change pieces in order.
|
|
696
664
|
*/
|
|
697
665
|
backupChain(): Promise<BackupChain[]>;
|
|
666
|
+
/**
|
|
667
|
+
* Reads what this database's backup cycle is doing at this moment, and what
|
|
668
|
+
* its recent turns produced.
|
|
669
|
+
*
|
|
670
|
+
* A caller that triggers a turn with {@link DatabaseBackups.captureBackupChanges}
|
|
671
|
+
* without waiting on it would read this until that turn finishes. A full copy
|
|
672
|
+
* of a large database reports its pages as SQLite moves them.
|
|
673
|
+
*
|
|
674
|
+
* @returns Whether a turn is under way, how far it has got, and the last run, skip, and failure.
|
|
675
|
+
*/
|
|
676
|
+
backupStatus(): BackupCycleStatus;
|
|
677
|
+
/**
|
|
678
|
+
* Reads one of this database's backups back out of the destination and
|
|
679
|
+
* compares it against the record the backup that wrote it left behind.
|
|
680
|
+
*
|
|
681
|
+
* A restore would fail on a damaged piece only once that restore had already
|
|
682
|
+
* begun, so name the backup here beforehand. Sirannon then fetches every
|
|
683
|
+
* piece in order and folds a SHA-256 over the bytes as they arrive, and it
|
|
684
|
+
* compares that digest and the byte count against the record. Only one piece
|
|
685
|
+
* is in memory at any moment, so a check over a large full copy needs no
|
|
686
|
+
* local storage of its own.
|
|
687
|
+
*
|
|
688
|
+
* A missing piece, a byte count that differs from the recorded one, and a
|
|
689
|
+
* digest that differs from the recorded one will each fail with
|
|
690
|
+
* `BACKUP_DESTINATION_ERROR`.
|
|
691
|
+
*
|
|
692
|
+
* @param name - Name the backup is stored under, which every entry {@link DatabaseBackups.backupChain} returns states.
|
|
693
|
+
* @returns The pieces read, the bytes they add up to, and the digest where the backup recorded one.
|
|
694
|
+
*/
|
|
695
|
+
verifyBackup(name: string): Promise<BackupVerifyResult>;
|
|
696
|
+
/**
|
|
697
|
+
* Reads where this database's backups are stored.
|
|
698
|
+
*
|
|
699
|
+
* `restoreBackup` accepts what you get back, and it then rebuilds this
|
|
700
|
+
* database from its own backups at whatever path you name.
|
|
701
|
+
*
|
|
702
|
+
* @returns The destination, the name its chains are listed under, and the directory Sirannon stages captures in.
|
|
703
|
+
*/
|
|
704
|
+
backupLocation(): BackupChainLocation;
|
|
698
705
|
/**
|
|
699
706
|
* Works out what a restore to a given moment has to read.
|
|
700
707
|
*
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { h as BackupChainBase, i as BackupChainChange, g as BackupChain, k as BackupChainRecord, B as BackupDestination, I as SQLiteConnection, P as DriverCapabilities, Q as BackupEngine, p as BackupFileReport, y as BackupToDestinationOptions, f as BackupRunReport, O as BackupScheduleOptions, n as BackupCycleOptions, o as BackupCycleStatus, z as BackupVerifyResult } from './types-DApb7dvw.js';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* What one runtime supports of the backup operations, so a caller learns
|
|
@@ -88,4 +88,54 @@ declare function planBackupRestore(chains: readonly BackupChain[], moment: numbe
|
|
|
88
88
|
*/
|
|
89
89
|
declare function backupPiecesSafeToDelete(chains: readonly BackupChain[], options?: BackupSafeToDeleteOptions): BackupChainRecord[];
|
|
90
90
|
|
|
91
|
-
|
|
91
|
+
type RunExclusive = (op: () => Promise<void>) => Promise<void>;
|
|
92
|
+
/** Where one database's backups are stored, and what a restore of that database reads.
|
|
93
|
+
* @public
|
|
94
|
+
*/
|
|
95
|
+
interface BackupChainLocation {
|
|
96
|
+
/** The destination its pieces and its chain records go to. */
|
|
97
|
+
destination: BackupDestination;
|
|
98
|
+
/** Name that destination lists its chains under. */
|
|
99
|
+
chainName: string;
|
|
100
|
+
/** Directory the cycle stages each capture in before that capture goes out. */
|
|
101
|
+
stagingDir: string;
|
|
102
|
+
/** Deadline the operator set on every call to that destination, where they set one. */
|
|
103
|
+
destinationTimeoutMs?: number;
|
|
104
|
+
}
|
|
105
|
+
declare class DatabaseBackupController {
|
|
106
|
+
private readonly runExclusive;
|
|
107
|
+
private readonly acquireWriter;
|
|
108
|
+
private readonly driverCapabilities;
|
|
109
|
+
private readonly databaseId;
|
|
110
|
+
private readonly sourcePath;
|
|
111
|
+
private readonly engine?;
|
|
112
|
+
private readonly cancellers;
|
|
113
|
+
private cycle;
|
|
114
|
+
private cycleOptions;
|
|
115
|
+
private cycleStarted;
|
|
116
|
+
constructor(runExclusive: RunExclusive, acquireWriter: () => SQLiteConnection, driverCapabilities: DriverCapabilities, databaseId: string, sourcePath: string, engine?: BackupEngine | undefined);
|
|
117
|
+
private require;
|
|
118
|
+
private requireCycle;
|
|
119
|
+
capabilities(): BackupCapabilities;
|
|
120
|
+
backup(destPath: string): Promise<BackupFileReport>;
|
|
121
|
+
backupTo(options: BackupToDestinationOptions): Promise<BackupRunReport>;
|
|
122
|
+
schedule(options: BackupScheduleOptions): void;
|
|
123
|
+
/**
|
|
124
|
+
* Builds the cycle and starts it in the background. Its first turn copies the
|
|
125
|
+
* whole database, and an open cannot wait on that. A failure reaches the
|
|
126
|
+
* caller through the cycle's own error callback.
|
|
127
|
+
*/
|
|
128
|
+
startCycle(options: BackupCycleOptions): void;
|
|
129
|
+
captureChanges(): Promise<BackupRunReport | undefined>;
|
|
130
|
+
chains(): Promise<BackupChain[]>;
|
|
131
|
+
status(): BackupCycleStatus;
|
|
132
|
+
location(): BackupChainLocation;
|
|
133
|
+
verify(name: string): Promise<BackupVerifyResult>;
|
|
134
|
+
restorePlan(moment: number): Promise<BackupRestorePlan>;
|
|
135
|
+
piecesSafeToDelete(options?: BackupSafeToDeleteOptions): Promise<BackupChainRecord[]>;
|
|
136
|
+
/** Stops the cycle, capturing the log one final time so nothing written since the previous turn is lost. */
|
|
137
|
+
stopCycle(): Promise<void>;
|
|
138
|
+
cancelAll(): void;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export { type BackupCapabilities as B, DatabaseBackupController as D, type BackupChainLocation as a, type BackupRestorePlan as b, type BackupSafeToDeleteOptions as c, backupPiecesSafeToDelete as d, planBackupRestore as p };
|
|
@@ -1,12 +1,15 @@
|
|
|
1
|
-
import { nodeResolveExtensionPath, nodeWriterContext, nodeBackupEngine, nodeStreamingSupport, WriterWorker, createStatementCache, loadThroughRuntime, narrowSafeBigInt, narrowRowIntegers, narrowRowsIntegers } from '../chunk-
|
|
2
|
-
import '../chunk-
|
|
3
|
-
import '../chunk-
|
|
4
|
-
import '../chunk-
|
|
5
|
-
import { defineDriver } from '../chunk-BQFQ65OL.mjs';
|
|
6
|
-
import '../chunk-YVF2ZJTE.mjs';
|
|
1
|
+
import { nodeResolveExtensionPath, nodeWriterContext, nodeBackupEngine, nodeStreamingSupport, WriterWorker, createStatementCache, loadThroughRuntime, narrowSafeBigInt, narrowRowIntegers, narrowRowsIntegers } from '../chunk-OJQHM4MT.mjs';
|
|
2
|
+
import '../chunk-7SC2TZAS.mjs';
|
|
3
|
+
import '../chunk-DXXCLTFF.mjs';
|
|
4
|
+
import { defineDriver } from '../chunk-LXCGPZPA.mjs';
|
|
7
5
|
import '../chunk-W67A4H2E.mjs';
|
|
8
6
|
import '../chunk-YLAVDZCS.mjs';
|
|
7
|
+
import '../chunk-W77EEVOT.mjs';
|
|
8
|
+
import '../chunk-U445A3C4.mjs';
|
|
9
|
+
import '../chunk-2QCPVE67.mjs';
|
|
10
|
+
import '../chunk-DVS4QSIZ.mjs';
|
|
9
11
|
import { synchronousPragmaValue } from '../chunk-OUSWVNWT.mjs';
|
|
12
|
+
import '../chunk-YVF2ZJTE.mjs';
|
|
10
13
|
import '../chunk-V5FUJEYY.mjs';
|
|
11
14
|
import { BackupError } from '../chunk-PBRXXISQ.mjs';
|
|
12
15
|
|