@camstack/system 1.2.318 → 1.2.319
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/builtins/backup-orchestrator/backup-orchestrator.addon.js +2 -76
- package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.mjs +2 -76
- package/dist/builtins/composer/composer-apply-waits.d.ts +21 -0
- package/dist/builtins/composer/composer-live-set.d.ts +22 -0
- package/dist/builtins/composer/composer-runtime-factory.d.ts +22 -0
- package/dist/builtins/composer/composer-seeds.d.ts +22 -0
- package/dist/builtins/composer/composer.addon.js +1903 -1807
- package/dist/builtins/composer/composer.addon.mjs +1903 -1807
- package/dist/builtins/composer/composer.d.ts +3 -15
- package/dist/builtins/sqlite-storage/shm-lock-canary.d.ts +60 -0
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.d.ts +1 -0
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.js +0 -0
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +0 -0
- package/dist/builtins/sqlite-storage/wal-checkpoint-policy.d.ts +115 -34
- package/dist/builtins/sqlite-storage/wal-checkpointer.d.ts +6 -0
- package/dist/builtins/system-backup/system-backup.service.d.ts +2 -17
- package/dist/wal-checkpoint-policy-BahrTtyD.mjs +186 -0
- package/dist/wal-checkpoint-policy-d2z2qPqx.js +233 -0
- package/dist/wal-checkpoint-worker.js +63 -57
- package/dist/wal-checkpoint-worker.mjs +63 -57
- package/package.json +1 -1
- package/dist/wal-checkpoint-policy-CAcg63o-.mjs +0 -92
- package/dist/wal-checkpoint-policy-CCJZngds.js +0 -127
|
@@ -3168,7 +3168,6 @@ var SystemBackupService = class {
|
|
|
3168
3168
|
}
|
|
3169
3169
|
if (present.length === 0) throw new Error(`createArchive: no locations from {${requested.join(", ")}} exist under ${this.dataDir}`);
|
|
3170
3170
|
node_fs.mkdirSync(node_path.dirname(opts.archivePath), { recursive: true });
|
|
3171
|
-
await this.checkpointDatabasesUnder(present);
|
|
3172
3171
|
const entries = [];
|
|
3173
3172
|
for (const loc of present) {
|
|
3174
3173
|
throwIfCancelled(opts.signal);
|
|
@@ -3241,48 +3240,6 @@ var SystemBackupService = class {
|
|
|
3241
3240
|
};
|
|
3242
3241
|
}
|
|
3243
3242
|
/**
|
|
3244
|
-
* Best-effort `wal_checkpoint(TRUNCATE)` on every live SQLite database
|
|
3245
|
-
* under the locations about to be archived. Discovers a DB by its
|
|
3246
|
-
* `-wal` sidecar (only WAL-mode DBs have one, and only those need
|
|
3247
|
-
* folding), opens a short-lived second connection — SQLite permits
|
|
3248
|
-
* concurrent connections, and a checkpoint is a supported concurrent
|
|
3249
|
-
* operation against the hub's live writer — checkpoints, and closes.
|
|
3250
|
-
*
|
|
3251
|
-
* Every failure is swallowed: a `SQLITE_BUSY`, a locked file, a
|
|
3252
|
-
* missing native binding, or a non-DB `.db` file must not fail the
|
|
3253
|
-
* backup. A residual WAL still restores correctly; the checkpoint is a
|
|
3254
|
-
* consistency *improvement*, not a precondition.
|
|
3255
|
-
*/
|
|
3256
|
-
async checkpointDatabasesUnder(locations) {
|
|
3257
|
-
const dbPaths = /* @__PURE__ */ new Set();
|
|
3258
|
-
for (const loc of locations) await collectWalDatabases(node_path.isAbsolute(loc) ? loc : node_path.join(this.dataDir, loc), dbPaths, this.exclusionPredicate());
|
|
3259
|
-
if (dbPaths.size === 0) return;
|
|
3260
|
-
let Database;
|
|
3261
|
-
try {
|
|
3262
|
-
Database = (await import("better-sqlite3")).default;
|
|
3263
|
-
} catch (err) {
|
|
3264
|
-
this.logger.warn("createArchive: better-sqlite3 unavailable — skipping WAL checkpoint", { meta: {
|
|
3265
|
-
error: err instanceof Error ? err.message : String(err),
|
|
3266
|
-
dbCount: dbPaths.size
|
|
3267
|
-
} });
|
|
3268
|
-
return;
|
|
3269
|
-
}
|
|
3270
|
-
for (const dbPath of dbPaths) try {
|
|
3271
|
-
const db = new Database(dbPath);
|
|
3272
|
-
try {
|
|
3273
|
-
db.pragma("wal_checkpoint(PASSIVE)");
|
|
3274
|
-
} finally {
|
|
3275
|
-
db.close();
|
|
3276
|
-
}
|
|
3277
|
-
this.logger.debug("createArchive: checkpointed DB", { meta: { dbPath } });
|
|
3278
|
-
} catch (err) {
|
|
3279
|
-
this.logger.warn("createArchive: WAL checkpoint failed — archiving DB as-is", { meta: {
|
|
3280
|
-
dbPath,
|
|
3281
|
-
error: err instanceof Error ? err.message : String(err)
|
|
3282
|
-
} });
|
|
3283
|
-
}
|
|
3284
|
-
}
|
|
3285
|
-
/**
|
|
3286
3243
|
* without extracting payload files. Streams the tar.gz, parses the
|
|
3287
3244
|
* one entry we care about, then aborts.
|
|
3288
3245
|
*/
|
|
@@ -3402,45 +3359,14 @@ var SystemBackupService = class {
|
|
|
3402
3359
|
}
|
|
3403
3360
|
/**
|
|
3404
3361
|
* Absolute-path form of {@link isExcludedFromBackup}, anchored on
|
|
3405
|
-
* this service's dataDir. Shared by the manifest walk
|
|
3406
|
-
* stats
|
|
3407
|
-
* filter about what a backup contains.
|
|
3362
|
+
* this service's dataDir. Shared by the manifest walk and the location
|
|
3363
|
+
* stats so both agree with the tar filter about what a backup contains.
|
|
3408
3364
|
*/
|
|
3409
3365
|
exclusionPredicate() {
|
|
3410
3366
|
return (abs) => isExcludedFromBackup(node_path.relative(this.dataDir, abs));
|
|
3411
3367
|
}
|
|
3412
3368
|
};
|
|
3413
3369
|
/**
|
|
3414
|
-
* Recursively collect every SQLite database under `abs` that is in WAL
|
|
3415
|
-
* mode, identified by the presence of a `<db>-wal` sidecar. Only those
|
|
3416
|
-
* need a checkpoint; a rollback-journal DB has nothing to fold. Errors
|
|
3417
|
-
* (unreadable dir, broken symlink) are skipped silently — this feeds a
|
|
3418
|
-
* best-effort step.
|
|
3419
|
-
*/
|
|
3420
|
-
async function collectWalDatabases(abs, out, shouldSkip) {
|
|
3421
|
-
if (shouldSkip?.(abs)) return;
|
|
3422
|
-
let stat;
|
|
3423
|
-
try {
|
|
3424
|
-
stat = await node_fs_promises.stat(abs);
|
|
3425
|
-
} catch {
|
|
3426
|
-
return;
|
|
3427
|
-
}
|
|
3428
|
-
if (stat.isDirectory()) {
|
|
3429
|
-
let children;
|
|
3430
|
-
try {
|
|
3431
|
-
children = await node_fs_promises.readdir(abs);
|
|
3432
|
-
} catch {
|
|
3433
|
-
return;
|
|
3434
|
-
}
|
|
3435
|
-
for (const child of children) await collectWalDatabases(node_path.join(abs, child), out, shouldSkip);
|
|
3436
|
-
return;
|
|
3437
|
-
}
|
|
3438
|
-
if (abs.endsWith("-wal")) {
|
|
3439
|
-
const dbPath = abs.slice(0, -4);
|
|
3440
|
-
if (node_fs.existsSync(dbPath)) out.add(dbPath);
|
|
3441
|
-
}
|
|
3442
|
-
}
|
|
3443
|
-
/**
|
|
3444
3370
|
* Recursively walk a directory and produce one `ArchiveEntry` per
|
|
3445
3371
|
* filesystem node. `dataDir` is used to compute the archive-relative
|
|
3446
3372
|
* path. Symlinks are dereferenced (matches the `follow: true` flag we
|
|
@@ -3153,7 +3153,6 @@ var SystemBackupService = class {
|
|
|
3153
3153
|
}
|
|
3154
3154
|
if (present.length === 0) throw new Error(`createArchive: no locations from {${requested.join(", ")}} exist under ${this.dataDir}`);
|
|
3155
3155
|
fs.mkdirSync(path$1.dirname(opts.archivePath), { recursive: true });
|
|
3156
|
-
await this.checkpointDatabasesUnder(present);
|
|
3157
3156
|
const entries = [];
|
|
3158
3157
|
for (const loc of present) {
|
|
3159
3158
|
throwIfCancelled(opts.signal);
|
|
@@ -3226,48 +3225,6 @@ var SystemBackupService = class {
|
|
|
3226
3225
|
};
|
|
3227
3226
|
}
|
|
3228
3227
|
/**
|
|
3229
|
-
* Best-effort `wal_checkpoint(TRUNCATE)` on every live SQLite database
|
|
3230
|
-
* under the locations about to be archived. Discovers a DB by its
|
|
3231
|
-
* `-wal` sidecar (only WAL-mode DBs have one, and only those need
|
|
3232
|
-
* folding), opens a short-lived second connection — SQLite permits
|
|
3233
|
-
* concurrent connections, and a checkpoint is a supported concurrent
|
|
3234
|
-
* operation against the hub's live writer — checkpoints, and closes.
|
|
3235
|
-
*
|
|
3236
|
-
* Every failure is swallowed: a `SQLITE_BUSY`, a locked file, a
|
|
3237
|
-
* missing native binding, or a non-DB `.db` file must not fail the
|
|
3238
|
-
* backup. A residual WAL still restores correctly; the checkpoint is a
|
|
3239
|
-
* consistency *improvement*, not a precondition.
|
|
3240
|
-
*/
|
|
3241
|
-
async checkpointDatabasesUnder(locations) {
|
|
3242
|
-
const dbPaths = /* @__PURE__ */ new Set();
|
|
3243
|
-
for (const loc of locations) await collectWalDatabases(path$1.isAbsolute(loc) ? loc : path$1.join(this.dataDir, loc), dbPaths, this.exclusionPredicate());
|
|
3244
|
-
if (dbPaths.size === 0) return;
|
|
3245
|
-
let Database;
|
|
3246
|
-
try {
|
|
3247
|
-
Database = (await import("better-sqlite3")).default;
|
|
3248
|
-
} catch (err) {
|
|
3249
|
-
this.logger.warn("createArchive: better-sqlite3 unavailable — skipping WAL checkpoint", { meta: {
|
|
3250
|
-
error: err instanceof Error ? err.message : String(err),
|
|
3251
|
-
dbCount: dbPaths.size
|
|
3252
|
-
} });
|
|
3253
|
-
return;
|
|
3254
|
-
}
|
|
3255
|
-
for (const dbPath of dbPaths) try {
|
|
3256
|
-
const db = new Database(dbPath);
|
|
3257
|
-
try {
|
|
3258
|
-
db.pragma("wal_checkpoint(PASSIVE)");
|
|
3259
|
-
} finally {
|
|
3260
|
-
db.close();
|
|
3261
|
-
}
|
|
3262
|
-
this.logger.debug("createArchive: checkpointed DB", { meta: { dbPath } });
|
|
3263
|
-
} catch (err) {
|
|
3264
|
-
this.logger.warn("createArchive: WAL checkpoint failed — archiving DB as-is", { meta: {
|
|
3265
|
-
dbPath,
|
|
3266
|
-
error: err instanceof Error ? err.message : String(err)
|
|
3267
|
-
} });
|
|
3268
|
-
}
|
|
3269
|
-
}
|
|
3270
|
-
/**
|
|
3271
3228
|
* without extracting payload files. Streams the tar.gz, parses the
|
|
3272
3229
|
* one entry we care about, then aborts.
|
|
3273
3230
|
*/
|
|
@@ -3387,45 +3344,14 @@ var SystemBackupService = class {
|
|
|
3387
3344
|
}
|
|
3388
3345
|
/**
|
|
3389
3346
|
* Absolute-path form of {@link isExcludedFromBackup}, anchored on
|
|
3390
|
-
* this service's dataDir. Shared by the manifest walk
|
|
3391
|
-
* stats
|
|
3392
|
-
* filter about what a backup contains.
|
|
3347
|
+
* this service's dataDir. Shared by the manifest walk and the location
|
|
3348
|
+
* stats so both agree with the tar filter about what a backup contains.
|
|
3393
3349
|
*/
|
|
3394
3350
|
exclusionPredicate() {
|
|
3395
3351
|
return (abs) => isExcludedFromBackup(path$1.relative(this.dataDir, abs));
|
|
3396
3352
|
}
|
|
3397
3353
|
};
|
|
3398
3354
|
/**
|
|
3399
|
-
* Recursively collect every SQLite database under `abs` that is in WAL
|
|
3400
|
-
* mode, identified by the presence of a `<db>-wal` sidecar. Only those
|
|
3401
|
-
* need a checkpoint; a rollback-journal DB has nothing to fold. Errors
|
|
3402
|
-
* (unreadable dir, broken symlink) are skipped silently — this feeds a
|
|
3403
|
-
* best-effort step.
|
|
3404
|
-
*/
|
|
3405
|
-
async function collectWalDatabases(abs, out, shouldSkip) {
|
|
3406
|
-
if (shouldSkip?.(abs)) return;
|
|
3407
|
-
let stat;
|
|
3408
|
-
try {
|
|
3409
|
-
stat = await fsp.stat(abs);
|
|
3410
|
-
} catch {
|
|
3411
|
-
return;
|
|
3412
|
-
}
|
|
3413
|
-
if (stat.isDirectory()) {
|
|
3414
|
-
let children;
|
|
3415
|
-
try {
|
|
3416
|
-
children = await fsp.readdir(abs);
|
|
3417
|
-
} catch {
|
|
3418
|
-
return;
|
|
3419
|
-
}
|
|
3420
|
-
for (const child of children) await collectWalDatabases(path$1.join(abs, child), out, shouldSkip);
|
|
3421
|
-
return;
|
|
3422
|
-
}
|
|
3423
|
-
if (abs.endsWith("-wal")) {
|
|
3424
|
-
const dbPath = abs.slice(0, -4);
|
|
3425
|
-
if (fs.existsSync(dbPath)) out.add(dbPath);
|
|
3426
|
-
}
|
|
3427
|
-
}
|
|
3428
|
-
/**
|
|
3429
3355
|
* Recursively walk a directory and produce one `ArchiveEntry` per
|
|
3430
3356
|
* filesystem node. `dataDir` is used to compute the archive-relative
|
|
3431
3357
|
* path. Symlinks are dereferenced (matches the `follow: true` flag we
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { ComposerApplyInput, ComposerApplyResult, IScopedLogger } from '@camstack/types';
|
|
2
|
+
/** An apply either waits, naming why, or goes on with the reconciled integration. */
|
|
3
|
+
export type ApplyGate = {
|
|
4
|
+
readonly kind: 'wait';
|
|
5
|
+
readonly reason: string;
|
|
6
|
+
} | {
|
|
7
|
+
readonly kind: 'go';
|
|
8
|
+
readonly integrationId: string;
|
|
9
|
+
};
|
|
10
|
+
export declare class ApplyWaits {
|
|
11
|
+
private readonly logger;
|
|
12
|
+
/** Consecutive applies refused because `core_blocks` had not loaded: warned once per run. */
|
|
13
|
+
private storeWaitApplies;
|
|
14
|
+
/** Consecutive applies refused because the Blocks integration had not reconciled: warned once per run. */
|
|
15
|
+
private integrationWaitApplies;
|
|
16
|
+
constructor(logger: IScopedLogger);
|
|
17
|
+
/** Whether this apply must act on nothing (and why), or may go on. */
|
|
18
|
+
check(input: ComposerApplyInput, liveCount: number): ApplyGate;
|
|
19
|
+
}
|
|
20
|
+
/** Every block `starting`, naming the wait; nothing acted. */
|
|
21
|
+
export declare function waitingResult(blockIds: readonly string[], reason: string): ComposerApplyResult;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { ClaimGate, ClaimKey } from './claim-gate.js';
|
|
2
|
+
import { PlannedBlock } from './composer-plan.js';
|
|
3
|
+
import { LiveComposition } from './composer-verdict.js';
|
|
4
|
+
import { TrackedSourceRequest } from './source-tracker.js';
|
|
5
|
+
/** A release answered with one of these hands back nothing of ours: the gate forgets the claim. */
|
|
6
|
+
export declare const RELEASE_SETTLED_CODES: ReadonlySet<string>;
|
|
7
|
+
/**
|
|
8
|
+
* The source keys an operator action on `p` re-opens: every source its fields
|
|
9
|
+
* read, except its own target (re-resolved by `planExisting` already, never
|
|
10
|
+
* twice).
|
|
11
|
+
*/
|
|
12
|
+
export declare function operatorResetKeys(p: PlannedBlock): readonly string[];
|
|
13
|
+
/** Every source the live set reads, with the view each own cap is read in. */
|
|
14
|
+
export declare function trackedSourceRequests(live: Iterable<LiveComposition>): readonly TrackedSourceRequest[];
|
|
15
|
+
/**
|
|
16
|
+
* Rule 3: the claims every live customization WANTS this apply; everything
|
|
17
|
+
* else the gate knows is a release candidate, handed back once two applies
|
|
18
|
+
* agree. A block that cannot plan — waiting, unreadable, failed, or with its
|
|
19
|
+
* target gone — HOLDS its claims (rule 4, rule 5): they are wanted as they
|
|
20
|
+
* are. A disabled block wants none (rule 7).
|
|
21
|
+
*/
|
|
22
|
+
export declare function wantedClaims(live: ReadonlyMap<string, LiveComposition>, claimGate: Pick<ClaimGate, 'claimsOf'>): readonly ClaimKey[];
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { CapabilityLookup, IScopedLogger } from '@camstack/types';
|
|
2
|
+
import { ComposedDeviceHandle } from './composed-device.js';
|
|
3
|
+
import { PlannedBlock } from './composer-plan.js';
|
|
4
|
+
import { ComposedWriteTarget, CompositionEventEmitter, CompositionRuntime, DeadlineTimer } from './composition-runtime.js';
|
|
5
|
+
import { ComposerClaimsApi, ExistingTargetSeedRead } from './existing-target.js';
|
|
6
|
+
import { ClaimEvent } from './held-claims.js';
|
|
7
|
+
import { SourceTracker } from './source-tracker.js';
|
|
8
|
+
/** The composer dependencies a runtime and its write target are built from. */
|
|
9
|
+
export interface RuntimeFactoryDeps {
|
|
10
|
+
readonly logger: IScopedLogger;
|
|
11
|
+
readonly claims: ComposerClaimsApi;
|
|
12
|
+
readonly tracker: SourceTracker;
|
|
13
|
+
readonly events: CompositionEventEmitter;
|
|
14
|
+
readonly lookupCap: CapabilityLookup;
|
|
15
|
+
readonly now: () => number;
|
|
16
|
+
readonly newTimer: () => DeadlineTimer;
|
|
17
|
+
}
|
|
18
|
+
/** Where a block's own claim transitions are followed (C-1/N-1). */
|
|
19
|
+
export type ClaimEventSink = (deviceId: number, cap: string, owner: string, event: ClaimEvent) => void;
|
|
20
|
+
/** Where a block's runtime writes: its composed device, or a claim-backed target on the existing device. */
|
|
21
|
+
export declare function writerFor(deps: RuntimeFactoryDeps, p: PlannedBlock, device: ComposedDeviceHandle | null, onClaimEvent: ClaimEventSink): ComposedWriteTarget | null;
|
|
22
|
+
export declare function newCompositionRuntime(deps: RuntimeFactoryDeps, p: PlannedBlock, target: ComposedWriteTarget, seed?: ExistingTargetSeedRead): CompositionRuntime;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { FieldClaim, IScopedLogger } from '@camstack/types';
|
|
2
|
+
import { PersistedComposedRow } from './composition-declarations.js';
|
|
3
|
+
import { ConfirmedSeed } from './confirmed-seed.js';
|
|
4
|
+
import { ComposerClaimsApi } from './existing-target.js';
|
|
5
|
+
export interface ComposedRowSeedDeps {
|
|
6
|
+
readonly logger: IScopedLogger;
|
|
7
|
+
/** The composed rows that already exist (`listOwnDevices`). */
|
|
8
|
+
readonly readPersisted: () => Promise<readonly PersistedComposedRow[]>;
|
|
9
|
+
readonly onSeeded: (rows: readonly PersistedComposedRow[]) => void;
|
|
10
|
+
}
|
|
11
|
+
/** The rows the composer owns, read ONCE before its first withdrawal decision. */
|
|
12
|
+
export declare function composedRowSeed(deps: ComposedRowSeedDeps): ConfirmedSeed<PersistedComposedRow>;
|
|
13
|
+
export interface ClaimIndexSeedDeps {
|
|
14
|
+
readonly logger: IScopedLogger;
|
|
15
|
+
readonly claims: ComposerClaimsApi;
|
|
16
|
+
readonly onSeeded: (claims: readonly FieldClaim[]) => void;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* The claims the hub holds, read ONCE before the first release decision (rule
|
|
20
|
+
* 3). `not-loaded` is UNKNOWN (D315), never empty.
|
|
21
|
+
*/
|
|
22
|
+
export declare function claimIndexSeed(deps: ClaimIndexSeedDeps): ConfirmedSeed<FieldClaim>;
|