@jwilleke/ngdpbase-types 5.18.0 → 5.20.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.
- package/lib/src/managers/AddonsManager.d.ts +31 -0
- package/lib/src/managers/AuthManager.d.ts +10 -1
- package/lib/src/managers/BaseManager.d.ts +38 -2
- package/lib/src/managers/BaseManager.js +2 -2
- package/lib/src/managers/ConfigurationManager.d.ts +11 -1
- package/lib/src/managers/DatabaseManager.d.ts +99 -0
- package/lib/src/managers/DatabaseManager.js +355 -1
- package/lib/src/providers/sqliteBackup.d.ts +128 -0
- package/lib/src/providers/sqliteBackup.js +365 -0
- package/lib/src/types/WikiEngine.d.ts +12 -1
- package/lib/src/utils/addonConfigLayer.d.ts +32 -1
- package/lib/src/utils/addonConfigLayer.js +62 -8
- package/lib/src/utils/auditEventNames.generated.d.ts +6 -0
- package/lib/src/utils/auditEventNames.generated.js +6 -0
- package/package.json +1 -1
|
@@ -17,6 +17,11 @@ import type { WikiEngine } from '../types/WikiEngine.js';
|
|
|
17
17
|
import type { User } from '../types/User.js';
|
|
18
18
|
import { type StoreOwnerState } from '../utils/privateStoreDoor.js';
|
|
19
19
|
import { type AddonHost } from '../utils/addonHost.js';
|
|
20
|
+
/**
|
|
21
|
+
* #1883: what visitors see while a site-critical add-on that did not load
|
|
22
|
+
* holds the site in maintenance mode. Empty leaves only the page's heading.
|
|
23
|
+
*/
|
|
24
|
+
export declare const SITE_CRITICAL_PUBLIC_MESSAGE_KEY = "ngdpbase.managers.addons-manager.site-critical.public-message";
|
|
20
25
|
/**
|
|
21
26
|
* Type passed to `AddonModule.profileSection()` (#534). Matches what
|
|
22
27
|
* `UserManager.getUser()` returns at runtime — `User` minus the password
|
|
@@ -280,6 +285,8 @@ declare class AddonsManager extends BaseManager {
|
|
|
280
285
|
private dashboardCards;
|
|
281
286
|
/** Name of the first domain addon loaded — only one is permitted */
|
|
282
287
|
private domainAddonName;
|
|
288
|
+
/** #1883: the enabled add-ons that did not load this start, with why — each reported once. */
|
|
289
|
+
private readonly loadFailures;
|
|
283
290
|
constructor(engine: WikiEngine);
|
|
284
291
|
/**
|
|
285
292
|
* Initialize the AddonsManager
|
|
@@ -351,6 +358,30 @@ declare class AddonsManager extends BaseManager {
|
|
|
351
358
|
* bundled/drop-in addon wins.
|
|
352
359
|
*/
|
|
353
360
|
private registerAddonFromDir;
|
|
361
|
+
/**
|
|
362
|
+
* Register an add-on that will not be loaded, so Admin → Add-ons shows it
|
|
363
|
+
* with the reason, and report it when it is enabled (#1883). The first
|
|
364
|
+
* add-on of a slug wins, as for a loadable one.
|
|
365
|
+
*/
|
|
366
|
+
private refuseAddon;
|
|
367
|
+
/**
|
|
368
|
+
* #1883 (operator, 2026-10-18): the one rule for an enabled add-on that does
|
|
369
|
+
* not load, whatever the reason. Admins get a notification at the most
|
|
370
|
+
* severe level naming it and the reason, which says how to fix it where the
|
|
371
|
+
* reason does. A site-critical add-on (`ngdpbase.addons.<slug>.site-critical`)
|
|
372
|
+
* also holds the whole site in maintenance mode through core's survivable
|
|
373
|
+
* path (#1152): visitors see only the configured public message, with no
|
|
374
|
+
* details, and administrators can still sign in and reach /admin. Once per
|
|
375
|
+
* add-on per start. Nothing is opened in its place.
|
|
376
|
+
*/
|
|
377
|
+
private recordLoadFailure;
|
|
378
|
+
/**
|
|
379
|
+
* #1883: an enabled add-on that discovery never found — its install failed
|
|
380
|
+
* (#1830), or only a stray folder answers it — did not load either.
|
|
381
|
+
*/
|
|
382
|
+
private reportUndiscoveredAddons;
|
|
383
|
+
/** A notification to administrators; best-effort, like recordSeedFailure. */
|
|
384
|
+
private notifyAdmins;
|
|
354
385
|
/**
|
|
355
386
|
* Why an addon cannot run on this core (#1757): its manifest's `minVersion`
|
|
356
387
|
* is newer than this release. Null when it can, or when it states none.
|
|
@@ -1052,6 +1052,15 @@ declare class AuthManager extends BaseManager {
|
|
|
1052
1052
|
unlockVaults(username: string, password: string): Promise<string | undefined>;
|
|
1053
1053
|
private vaultUnlocker;
|
|
1054
1054
|
private warnedNoUnlocker;
|
|
1055
|
+
/**
|
|
1056
|
+
* The stored spelling of a username a sign-in brought (#1882), or null when
|
|
1057
|
+
* no account has it. Lookups compare normalized (#1436), so "ADMIN" finds
|
|
1058
|
+
* admin's record; the session, the audit record, the roles and the vault
|
|
1059
|
+
* paths must carry the stored name, never the typed variant, which is a
|
|
1060
|
+
* roleless twin. The one rule, asked by every door a sign-in passes:
|
|
1061
|
+
* `authenticate`, `beginTwoStep` and `completeSignIn`.
|
|
1062
|
+
*/
|
|
1063
|
+
private storedUsername;
|
|
1055
1064
|
/** A fresh session for a sign-in (#1043); the one already on it ends first (#1626, #1670). */
|
|
1056
1065
|
private regenerate;
|
|
1057
1066
|
/**
|
|
@@ -1109,7 +1118,7 @@ declare class AuthManager extends BaseManager {
|
|
|
1109
1118
|
ip?: string;
|
|
1110
1119
|
userAgent?: string;
|
|
1111
1120
|
privateStoreHandle?: string;
|
|
1112
|
-
}): string
|
|
1121
|
+
}): Promise<string>;
|
|
1113
1122
|
/** The pending sign-in, for the browser that started it; null when gone, expired, or another browser. */
|
|
1114
1123
|
pendingSignIn(handle: string | undefined, binding: string | undefined): PendingSignIn | null;
|
|
1115
1124
|
/**
|
|
@@ -18,6 +18,7 @@ import { type PathPreflightResult, type PathPreflightOptions } from '../utils/Pa
|
|
|
18
18
|
import type { WikiEngine } from '../types/WikiEngine.js';
|
|
19
19
|
import type { ManagerFetchOptions } from '../utils/managerUtils.js';
|
|
20
20
|
import type { FormOption } from '../utils/pluginFormatters.js';
|
|
21
|
+
import type { ActorContext } from '../context/ActorContext.js';
|
|
21
22
|
/**
|
|
22
23
|
* Backup data structure returned by backup() method
|
|
23
24
|
*/
|
|
@@ -34,9 +35,44 @@ export interface BackupData {
|
|
|
34
35
|
providerBackup?: unknown;
|
|
35
36
|
/** Optional note about the backup */
|
|
36
37
|
note?: string;
|
|
38
|
+
/**
|
|
39
|
+
* Parts of this manager's backup that failed while the rest was written
|
|
40
|
+
* (#1723) — a database that could not be copied, say. BackupManager tells the
|
|
41
|
+
* admins about each one: a backup is never silently partial.
|
|
42
|
+
*/
|
|
43
|
+
warnings?: string[];
|
|
37
44
|
/** Allow additional properties */
|
|
38
45
|
[key: string]: unknown;
|
|
39
46
|
}
|
|
47
|
+
/**
|
|
48
|
+
* What BackupManager hands each manager's `backup()` (#1723): who asked, and
|
|
49
|
+
* where the backup document is being written. A manager whose data is too
|
|
50
|
+
* large for the JSON document (the databases) writes its own files into
|
|
51
|
+
* `directory`, each named `<stem>.<something>`, and references them from its
|
|
52
|
+
* entry; BackupManager prunes them with the document.
|
|
53
|
+
*/
|
|
54
|
+
export interface ManagerBackupOptions {
|
|
55
|
+
ctx: ActorContext;
|
|
56
|
+
/** The backup directory the document is written to. */
|
|
57
|
+
directory: string;
|
|
58
|
+
/** The document's filename without `.json.gz` / `.json`. */
|
|
59
|
+
stem: string;
|
|
60
|
+
}
|
|
61
|
+
/** What BackupManager hands each manager's `restore()` (#1723): who asked, and the directory the backup document was read from. */
|
|
62
|
+
export interface ManagerRestoreOptions {
|
|
63
|
+
ctx: ActorContext;
|
|
64
|
+
directory: string;
|
|
65
|
+
/** The backup document's filename. */
|
|
66
|
+
source?: string;
|
|
67
|
+
/**
|
|
68
|
+
* Replace what an earlier restore staged and the restart has not applied
|
|
69
|
+
* yet (#1723, D5). Without it a manager that stages refuses, so a staged
|
|
70
|
+
* restore is never replaced silently.
|
|
71
|
+
*/
|
|
72
|
+
replaceStaged?: boolean;
|
|
73
|
+
/** The backup of the live data BackupManager took just before this restore (#1723, D6), by filename. */
|
|
74
|
+
safetyBackup?: string;
|
|
75
|
+
}
|
|
40
76
|
/**
|
|
41
77
|
* Base class for all managers
|
|
42
78
|
*
|
|
@@ -279,7 +315,7 @@ declare abstract class BaseManager {
|
|
|
279
315
|
* };
|
|
280
316
|
* }
|
|
281
317
|
*/
|
|
282
|
-
backup(): Promise<BackupData>;
|
|
318
|
+
backup(_options?: ManagerBackupOptions): Promise<BackupData>;
|
|
283
319
|
/**
|
|
284
320
|
* Restore manager data from backup
|
|
285
321
|
*
|
|
@@ -298,7 +334,7 @@ declare abstract class BaseManager {
|
|
|
298
334
|
* this.settings = backupData.data.settings;
|
|
299
335
|
* }
|
|
300
336
|
*/
|
|
301
|
-
restore(backupData: BackupData): Promise<void>;
|
|
337
|
+
restore(backupData: BackupData, _options?: ManagerRestoreOptions): Promise<void>;
|
|
302
338
|
/**
|
|
303
339
|
* Return a plain-text string suitable for use as MarqueePlugin banner text.
|
|
304
340
|
*
|
|
@@ -265,7 +265,7 @@ class BaseManager {
|
|
|
265
265
|
* };
|
|
266
266
|
* }
|
|
267
267
|
*/
|
|
268
|
-
async backup() {
|
|
268
|
+
async backup(_options) {
|
|
269
269
|
// Default implementation returns empty object
|
|
270
270
|
// Managers with data MUST override this method
|
|
271
271
|
return {
|
|
@@ -292,7 +292,7 @@ class BaseManager {
|
|
|
292
292
|
* this.settings = backupData.data.settings;
|
|
293
293
|
* }
|
|
294
294
|
*/
|
|
295
|
-
async restore(backupData) {
|
|
295
|
+
async restore(backupData, _options) {
|
|
296
296
|
// Default implementation does nothing
|
|
297
297
|
// Managers with data MUST override this method
|
|
298
298
|
if (!backupData) {
|
|
@@ -60,10 +60,12 @@ declare class ConfigurationManager extends BaseManager {
|
|
|
60
60
|
private addonLayers;
|
|
61
61
|
/** #1559: add-on system-categories refused, and operator values an owner's declaration overrode; #1860: database declarations refused or overridden. */
|
|
62
62
|
private systemCategoryProblems;
|
|
63
|
-
/** #1559: add-ons that must not load because an entry they declare was refused, by slug, with why; #1860: or their database declaration. */
|
|
63
|
+
/** #1559: add-ons that must not load because an entry they declare was refused, by slug, with why; #1860: or their database declaration; #1883: or a site-critical value that is not a boolean. */
|
|
64
64
|
private refusedAddons;
|
|
65
65
|
/** #1860: every installed add-on's sound database declaration, on or off. */
|
|
66
66
|
private addonDatabases;
|
|
67
|
+
/** #1883: which add-ons put the site in maintenance mode when they fail to load, by slug. */
|
|
68
|
+
private siteCriticalAddons;
|
|
67
69
|
private environment;
|
|
68
70
|
private defaultConfigPath;
|
|
69
71
|
private customConfigPath;
|
|
@@ -282,6 +284,14 @@ declare class ConfigurationManager extends BaseManager {
|
|
|
282
284
|
* ({@link getAddonRefusal}). DatabaseManager is the reader.
|
|
283
285
|
*/
|
|
284
286
|
getAddonDatabaseDeclarations(): readonly AddonDatabaseDeclaration[];
|
|
287
|
+
/**
|
|
288
|
+
* #1883: whether this add-on's failure to load puts the whole site in
|
|
289
|
+
* maintenance mode — `ngdpbase.addons.<slug>.site-critical`, from the
|
|
290
|
+
* operator's file or else the add-on's own, read even when the add-on was
|
|
291
|
+
* refused. A value that is not exactly true or false counts as true (and
|
|
292
|
+
* refuses the add-on, {@link getAddonRefusal}).
|
|
293
|
+
*/
|
|
294
|
+
isAddonSiteCritical(slug: string): boolean;
|
|
285
295
|
/**
|
|
286
296
|
* #1813: what one enabled addon's own `config/default-config.json` declares,
|
|
287
297
|
* exactly as it was folded into the merge — not the merged view, so the
|
|
@@ -1,9 +1,52 @@
|
|
|
1
1
|
import BaseManager from './BaseManager.js';
|
|
2
2
|
import type { WikiEngine } from '../types/WikiEngine.js';
|
|
3
3
|
import type { Migration } from '../providers/sqliteMigrations.js';
|
|
4
|
+
import { type ActorContext } from '../context/ActorContext.js';
|
|
5
|
+
import type { BackupData, ManagerBackupOptions, ManagerRestoreOptions } from './BaseManager.js';
|
|
4
6
|
import { type AddonDatabaseDeclaration } from '../utils/addonDatabaseRules.js';
|
|
7
|
+
import { type StagedRestoreManifest } from '../providers/sqliteBackup.js';
|
|
5
8
|
/** The environment variable holding the SQLCipher key. Never a config key: config is readable from the admin screens. */
|
|
6
9
|
export declare const DATABASE_KEY_ENV = "NGDPBASE_DATABASE_KEY";
|
|
10
|
+
/**
|
|
11
|
+
* The environment variable holding the key every database backup is encrypted
|
|
12
|
+
* under (#1723). Its own secret, not the database key: the copy that travels
|
|
13
|
+
* and the copy that stays should not fall to the same compromise. Never a
|
|
14
|
+
* config key. Unset, no database is backed up, and admins are told.
|
|
15
|
+
*/
|
|
16
|
+
export declare const BACKUP_KEY_ENV = "NGDPBASE_BACKUP_KEY";
|
|
17
|
+
/** How long a database backup or restore staging may run on its worker before it is stopped, when not configured. */
|
|
18
|
+
export declare const DEFAULT_BACKUP_WORKER_TIMEOUT_MS = 3600000;
|
|
19
|
+
/**
|
|
20
|
+
* A short fingerprint of a backup key (#1723, D3): the first 16 hex digits of
|
|
21
|
+
* an HMAC of a fixed label under the key. Recorded in each backup so a restore
|
|
22
|
+
* under another key says so plainly; it never reveals the key.
|
|
23
|
+
*/
|
|
24
|
+
export declare function backupKeyFingerprint(key: string): string;
|
|
25
|
+
/** One database in a backup, as DatabaseManager's entry in the backup document lists it. */
|
|
26
|
+
export interface DatabaseBackupEntry {
|
|
27
|
+
/** `application`, or `addon-<owner>`. */
|
|
28
|
+
name: string;
|
|
29
|
+
kind: 'application' | 'addon';
|
|
30
|
+
owner?: string;
|
|
31
|
+
/** The encrypted file beside the backup document: `<stem>.<name>.db`. */
|
|
32
|
+
file: string;
|
|
33
|
+
sizeBytes: number;
|
|
34
|
+
tables: number;
|
|
35
|
+
}
|
|
36
|
+
/** DatabaseManager's `data` in a backup document. */
|
|
37
|
+
export interface DatabaseBackupData {
|
|
38
|
+
format: 1;
|
|
39
|
+
/** `backupKeyFingerprint()` of the key the files are encrypted under. */
|
|
40
|
+
keyFingerprint?: string;
|
|
41
|
+
databases: DatabaseBackupEntry[];
|
|
42
|
+
/** Declared databases with no file yet (an add-on that never opened it). */
|
|
43
|
+
absent: string[];
|
|
44
|
+
/** Databases that could not be copied; the backup carries the others. */
|
|
45
|
+
failed: Array<{
|
|
46
|
+
name: string;
|
|
47
|
+
error: string;
|
|
48
|
+
}>;
|
|
49
|
+
}
|
|
7
50
|
/** What an add-on hands over to have its database opened and migrated. */
|
|
8
51
|
export interface AddonDatabaseOptions {
|
|
9
52
|
/** Dated migrations, strictly ascending by id — the same rules as core's ledger. */
|
|
@@ -37,6 +80,10 @@ declare class DatabaseManager extends BaseManager {
|
|
|
37
80
|
private databaseFile;
|
|
38
81
|
private busyTimeoutMs;
|
|
39
82
|
private readonly addonDatabases;
|
|
83
|
+
/** What the start did with a staged restore (#1723), reported once the notifications exist. */
|
|
84
|
+
private restoreAtStart;
|
|
85
|
+
/** Why this manager is degraded, one entry per cause, so one cause clearing leaves the others. */
|
|
86
|
+
private readonly faults;
|
|
40
87
|
constructor(engine: WikiEngine);
|
|
41
88
|
initialize(config?: Record<string, unknown>): Promise<void>;
|
|
42
89
|
/** Whether an application database is open. */
|
|
@@ -81,6 +128,58 @@ declare class DatabaseManager extends BaseManager {
|
|
|
81
128
|
private declarations;
|
|
82
129
|
/** The one rule: a database declared `encrypt: "required"` is never opened without the key. */
|
|
83
130
|
private static keyRefusal;
|
|
131
|
+
/**
|
|
132
|
+
* Copy every database into the backup BackupManager is writing: the
|
|
133
|
+
* application database and every declared add-on database, on or off, at
|
|
134
|
+
* one point in time, on a worker thread. Each goes into its own file,
|
|
135
|
+
* `<stem>.<name>.db`, beside the document, encrypted under
|
|
136
|
+
* `NGDPBASE_BACKUP_KEY`; the document references them. A database that
|
|
137
|
+
* fails is named in `warnings`, and the others are still written.
|
|
138
|
+
*
|
|
139
|
+
* Throws when there is no backup key: nothing is ever written unencrypted.
|
|
140
|
+
*/
|
|
141
|
+
backup(options?: ManagerBackupOptions): Promise<BackupData>;
|
|
142
|
+
/**
|
|
143
|
+
* Stage a restore of the databases in a backup, for the next start. Each
|
|
144
|
+
* backed-up database is opened under the backup key, integrity-checked, and
|
|
145
|
+
* copied into `<live>.staged` under this site's database key. All or
|
|
146
|
+
* nothing: if any fails, none is staged. A database the backup does not
|
|
147
|
+
* contain is left as it is. Live files are never touched here.
|
|
148
|
+
*
|
|
149
|
+
* BackupManager takes a fresh backup of the live data before calling this.
|
|
150
|
+
* A backed-up add-on database that no add-on on this site declares is not
|
|
151
|
+
* staged, and admins are told.
|
|
152
|
+
*/
|
|
153
|
+
restore(backupData: BackupData, options?: ManagerRestoreOptions): Promise<void>;
|
|
154
|
+
/** The restore staged and waiting for the next start, or null. */
|
|
155
|
+
getStagedRestore(): StagedRestoreManifest | null;
|
|
156
|
+
/**
|
|
157
|
+
* Throw away a staged restore before the restart applies it (#1723, D5):
|
|
158
|
+
* the staged files and the manifest are removed, the live databases are
|
|
159
|
+
* untouched. Recorded first (`backup-restore-cancel`, refuse): a cancel that
|
|
160
|
+
* cannot be recorded does not happen.
|
|
161
|
+
*/
|
|
162
|
+
cancelStagedRestore(ctx: ActorContext): Promise<StagedRestoreManifest>;
|
|
163
|
+
/**
|
|
164
|
+
* Tell admins what the start found (#1723): what it did with a staged
|
|
165
|
+
* restore, and a backup key that is missing or the database key. Called by
|
|
166
|
+
* the engine once every manager exists: DatabaseManager starts before the
|
|
167
|
+
* notifications do.
|
|
168
|
+
*/
|
|
169
|
+
reportAtBoot(): Promise<void>;
|
|
170
|
+
/** The application database and every declared add-on database (one per owner), on or off. */
|
|
171
|
+
private backupSources;
|
|
172
|
+
/** Put a staged restore in place, before any database opens; loud when it cannot. */
|
|
173
|
+
private applyRestoreAtStart;
|
|
174
|
+
private backupKey;
|
|
175
|
+
/** What is wrong with the backup key: missing, or the database key itself (D3). Null when sound. */
|
|
176
|
+
private backupKeyProblem;
|
|
177
|
+
/** `ngdpbase.database.backup.worker-timeout-ms`, read at use. */
|
|
178
|
+
private workerTimeoutMs;
|
|
179
|
+
/** Degrade for one cause; the dashboard shows every cause still standing. */
|
|
180
|
+
private fault;
|
|
181
|
+
private clearFault;
|
|
182
|
+
private notify;
|
|
84
183
|
/** Whether every open database passes SQLite's quick check; true when none is configured. */
|
|
85
184
|
integrityOk(): boolean;
|
|
86
185
|
/** Closes the add-on databases, then the application database. */
|