@camstack/system 1.2.423 → 1.2.425

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (97) hide show
  1. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.js +1 -1
  2. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.mjs +1 -1
  3. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.js +1 -1
  4. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.mjs +1 -1
  5. package/dist/builtins/alerts/alerts.addon.js +1 -1
  6. package/dist/builtins/alerts/alerts.addon.mjs +1 -1
  7. package/dist/builtins/autotrack/index.js +1 -1
  8. package/dist/builtins/autotrack/index.mjs +1 -1
  9. package/dist/builtins/backup-orchestrator/backup-databases.d.ts +51 -0
  10. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.d.ts +13 -0
  11. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.js +248 -17
  12. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.mjs +248 -17
  13. package/dist/builtins/backup-orchestrator/manifest-store.d.ts +23 -3
  14. package/dist/builtins/backup-orchestrator/run-queue.d.ts +3 -0
  15. package/dist/builtins/backup-orchestrator/schedule-store.d.ts +7 -1
  16. package/dist/builtins/backup-orchestrator/seed-marker-store.d.ts +34 -0
  17. package/dist/builtins/calendars/calendars.addon.js +1 -1
  18. package/dist/builtins/calendars/calendars.addon.mjs +1 -1
  19. package/dist/builtins/camera-grid/index.js +1 -1
  20. package/dist/builtins/camera-grid/index.mjs +1 -1
  21. package/dist/builtins/composer/composer.addon.js +2 -2
  22. package/dist/builtins/composer/composer.addon.mjs +2 -2
  23. package/dist/builtins/console-logging/index.js +1 -1
  24. package/dist/builtins/console-logging/index.mjs +1 -1
  25. package/dist/builtins/core-blocks/core-blocks.addon.js +2 -2
  26. package/dist/builtins/core-blocks/core-blocks.addon.mjs +2 -2
  27. package/dist/builtins/device-manager/device-manager.addon.js +2 -2
  28. package/dist/builtins/device-manager/device-manager.addon.mjs +2 -2
  29. package/dist/builtins/hub-forwarder/index.js +1 -1
  30. package/dist/builtins/hub-forwarder/index.mjs +1 -1
  31. package/dist/builtins/keychain/index.js +3 -3
  32. package/dist/builtins/keychain/index.mjs +3 -3
  33. package/dist/builtins/liveness-monitor/liveness-monitor.addon.js +1 -1
  34. package/dist/builtins/liveness-monitor/liveness-monitor.addon.mjs +1 -1
  35. package/dist/builtins/local-auth/local-auth.addon.js +2 -2
  36. package/dist/builtins/local-auth/local-auth.addon.mjs +2 -2
  37. package/dist/builtins/local-network/local-network.addon.js +1 -1
  38. package/dist/builtins/local-network/local-network.addon.mjs +1 -1
  39. package/dist/builtins/loki-logging/index.js +1 -1
  40. package/dist/builtins/loki-logging/index.mjs +1 -1
  41. package/dist/builtins/native-metrics/native-metrics.addon.js +1 -1
  42. package/dist/builtins/native-metrics/native-metrics.addon.mjs +1 -1
  43. package/dist/builtins/platform-probe/index.js +1 -1
  44. package/dist/builtins/platform-probe/index.mjs +1 -1
  45. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.js +1 -1
  46. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.mjs +1 -1
  47. package/dist/builtins/snapshot/index.js +1 -1
  48. package/dist/builtins/snapshot/index.mjs +1 -1
  49. package/dist/builtins/sqlite-storage/database-snapshot-provider.d.ts +18 -0
  50. package/dist/builtins/sqlite-storage/file-host-client.d.ts +59 -0
  51. package/dist/builtins/sqlite-storage/file-host-handlers.d.ts +23 -0
  52. package/dist/builtins/sqlite-storage/file-host-protocol.d.ts +151 -0
  53. package/dist/builtins/sqlite-storage/file-host-worker.d.ts +1 -0
  54. package/dist/builtins/sqlite-storage/filesystem-storage.addon.js +1 -1
  55. package/dist/builtins/sqlite-storage/filesystem-storage.addon.mjs +1 -1
  56. package/dist/builtins/sqlite-storage/index.js +21 -20
  57. package/dist/builtins/sqlite-storage/index.mjs +2 -1
  58. package/dist/builtins/sqlite-storage/owned-sqlite-file.d.ts +136 -0
  59. package/dist/builtins/sqlite-storage/placement-errors.d.ts +60 -0
  60. package/dist/builtins/sqlite-storage/placement-router.d.ts +80 -0
  61. package/dist/builtins/sqlite-storage/retired-collections.d.ts +7 -0
  62. package/dist/builtins/sqlite-storage/sqlite-settings-backend.d.ts +68 -8
  63. package/dist/builtins/sqlite-storage/sqlite-settings.addon.d.ts +23 -26
  64. package/dist/builtins/sqlite-storage/sqlite-settings.addon.js +0 -0
  65. package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +0 -0
  66. package/dist/builtins/sqlite-storage/vector-provider.d.ts +24 -0
  67. package/dist/builtins/sqlite-storage/wal-maintenance.d.ts +17 -0
  68. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.js +3 -1
  69. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.mjs +3 -1
  70. package/dist/builtins/system-backup/system-backup.service.d.ts +48 -15
  71. package/dist/builtins/system-config/system-config.addon.js +2 -2
  72. package/dist/builtins/system-config/system-config.addon.mjs +2 -2
  73. package/dist/builtins/winston-logging/index.js +1 -1
  74. package/dist/builtins/winston-logging/index.mjs +1 -1
  75. package/dist/{composition-sources-CgiAeG-2.js → composition-sources-197keifF.js} +1 -1
  76. package/dist/{composition-sources-BXDchWFn.mjs → composition-sources-DmzTSy26.mjs} +1 -1
  77. package/dist/{constants-1_7QtPNo.js → constants-B5Jd8Zch.js} +1 -1
  78. package/dist/{constants-C2dhkIuF.mjs → constants-CkNLi-XW.mjs} +1 -1
  79. package/dist/{dist-DimotS8e.mjs → dist-DMWeqeTF.mjs} +863 -750
  80. package/dist/{dist-VIIxVsBW.js → dist-DnALrQF-.js} +866 -747
  81. package/dist/file-host-worker.js +144 -0
  82. package/dist/file-host-worker.mjs +144 -0
  83. package/dist/index.js +9 -8
  84. package/dist/index.mjs +6 -5
  85. package/dist/{jwt-signing-key-C8nMdNYW.mjs → jwt-signing-key-CCHXAtrs.mjs} +1 -1
  86. package/dist/{jwt-signing-key-C8s9syVg.js → jwt-signing-key-DeqG7LD9.js} +1 -1
  87. package/dist/{restore-archive-DjppfrIB.js → restore-archive-C4UhQOsg.js} +156 -65
  88. package/dist/{restore-archive-bOMZwY0D.mjs → restore-archive-GevpOHw8.mjs} +151 -66
  89. package/dist/{retired-settings-keys-_muTvNyh.js → retired-settings-keys-0FOsnzIE.js} +1 -1
  90. package/dist/{retired-settings-keys-BYnjMBE-.mjs → retired-settings-keys-CdVFl7m2.mjs} +1 -1
  91. package/dist/sqlite-snapshot-worker.js +1 -1
  92. package/dist/sqlite-snapshot-worker.mjs +1 -1
  93. package/dist/wal-checkpoint-worker.js +1 -1
  94. package/dist/wal-checkpoint-worker.mjs +1 -1
  95. package/dist/wal-maintenance-BtfyfuT5.mjs +0 -0
  96. package/dist/wal-maintenance-DJV1rde2.js +0 -0
  97. package/package.json +1 -1
@@ -0,0 +1,136 @@
1
+ /**
2
+ * One database FILE of the `sqlite-settings` engine, with everything D447,
3
+ * D452, D679 and D693 already require per file (D851):
4
+ *
5
+ * - a serving thread — the engine's main thread for `settings`, a dedicated
6
+ * worker for `events` and `history`;
7
+ * - a WAL checkpointer, a `-shm` lock canary and the idle-gated WAL
8
+ * maintenance;
9
+ * - a verified snapshot written by the owner, never a file copy.
10
+ *
11
+ * The router (`placement-router.ts`) holds one of these per open file and
12
+ * sends each call to the file its collection lives in. Nothing outside this
13
+ * package knows a file path.
14
+ */
15
+ import type { DatabaseSnapshotResult, DataStoreDatabase, IScopedLogger } from "@camstack/types";
16
+ import { type FileHostThreadFactory } from "./file-host-client.js";
17
+ import type { DataStoreInput, DataStoreOp, DataStoreOutput, OwnedSqliteFileSpec, VectorInput, VectorOp, VectorOutput } from "./file-host-protocol.js";
18
+ import { SqliteSettingsBackend } from "./sqlite-settings-backend.js";
19
+ import type { SqliteVecVectorIndex } from "./vector-index-vec.js";
20
+ export type { OwnedSqliteFileSpec } from "./file-host-protocol.js";
21
+ /** A table a retired-collection purge dropped, and the rows that went with it. */
22
+ export interface DroppedRetiredTable {
23
+ readonly rows: number;
24
+ }
25
+ export interface OwnedSqliteFile {
26
+ readonly databaseId: DataStoreDatabase;
27
+ readonly livePath: string;
28
+ call<K extends DataStoreOp>(op: K, input: DataStoreInput<K>): Promise<DataStoreOutput<K>>;
29
+ /** `vector-store` calls; refused on a file that does not load sqlite-vec. */
30
+ vector<K extends VectorOp>(op: K, input: VectorInput<K>): Promise<VectorOutput<K>>;
31
+ /** The collections this file's own `_collection_declarations` names. */
32
+ declaredCollections(): ReadonlySet<string>;
33
+ /** Does a table of this name exist in this file (declared or not)? */
34
+ hasTable(name: string): Promise<boolean>;
35
+ dropRetiredTable(name: string): Promise<DroppedRetiredTable | null>;
36
+ /** Which thread serves this file: 0 is the engine's main thread. */
37
+ servingThreadId(): Promise<number>;
38
+ /** The owner's verified copy of this file (D693). */
39
+ snapshot(): Promise<DatabaseSnapshotResult>;
40
+ /** Checkpointer, canary, maintenance, then the connection. */
41
+ stop(): Promise<void>;
42
+ }
43
+ /** What the per-file lifecycle needs, injectable so a spec can count and substitute. */
44
+ export interface FileLifecycle {
45
+ readonly checkpointer: {
46
+ start(): void;
47
+ stop(): Promise<void>;
48
+ };
49
+ readonly canary: {
50
+ start(): void;
51
+ stop(): void;
52
+ };
53
+ readonly snapshotter: {
54
+ sweep(): Promise<unknown>;
55
+ write(): Promise<DatabaseSnapshotResult>;
56
+ };
57
+ }
58
+ export interface OwnedFileDeps {
59
+ /** The runner's logger; per-file loggers are derived from it. */
60
+ readonly logger: IScopedLogger;
61
+ /** The runner is respawned on a serving thread's death (D5/D6). Production: `process.exit(1)`. */
62
+ readonly onFatal: (reason: string) => void;
63
+ /** Runtime defaults seeded into a fresh `settings` file. */
64
+ readonly runtimeDefaults?: Record<string, unknown>;
65
+ readonly workerPath?: string;
66
+ readonly workerExecArgv?: readonly string[];
67
+ readonly threadFactory?: FileHostThreadFactory;
68
+ readonly maxInFlight?: number;
69
+ /** Replaces the real checkpointer / canary / snapshotter (specs). */
70
+ readonly lifecycle?: (spec: OwnedSqliteFileSpec, logger: (scope: string) => IScopedLogger) => FileLifecycle;
71
+ /**
72
+ * Runs once the file is open and BEFORE its lifecycle is armed — the slot
73
+ * for the boot-time purges, which must happen before anything can read.
74
+ */
75
+ readonly afterOpen?: (file: OwnedSqliteFile) => Promise<void>;
76
+ }
77
+ /** Shared by both thread flavours: the per-file lifecycle. */
78
+ declare abstract class OwnedFileBase implements OwnedSqliteFile {
79
+ protected readonly spec: OwnedSqliteFileSpec;
80
+ protected readonly deps: OwnedFileDeps;
81
+ readonly databaseId: DataStoreDatabase;
82
+ readonly livePath: string;
83
+ protected readonly log: (scope: string) => IScopedLogger;
84
+ protected lifecycle: FileLifecycle | null;
85
+ constructor(spec: OwnedSqliteFileSpec, deps: OwnedFileDeps);
86
+ abstract call<K extends DataStoreOp>(op: K, input: DataStoreInput<K>): Promise<DataStoreOutput<K>>;
87
+ abstract vector<K extends VectorOp>(op: K, input: VectorInput<K>): Promise<VectorOutput<K>>;
88
+ abstract declaredCollections(): ReadonlySet<string>;
89
+ abstract hasTable(name: string): Promise<boolean>;
90
+ abstract dropRetiredTable(name: string): Promise<DroppedRetiredTable | null>;
91
+ abstract servingThreadId(): Promise<number>;
92
+ protected abstract stopConnection(): Promise<void>;
93
+ protected abstract stopMaintenance(): void;
94
+ /**
95
+ * Arm the checkpointer, the canary and the snapshotter. Called after the
96
+ * connection is open and the boot purges have run.
97
+ */
98
+ protected armLifecycle(): Promise<void>;
99
+ snapshot(): Promise<DatabaseSnapshotResult>;
100
+ stop(): Promise<void>;
101
+ }
102
+ /** `camstack.db`: served in place on the engine's main thread. */
103
+ export declare class InlineOwnedFile extends OwnedFileBase {
104
+ readonly backend: SqliteSettingsBackend;
105
+ private declared;
106
+ private vectorIndex;
107
+ private vectorProvider;
108
+ private maintenance;
109
+ constructor(spec: OwnedSqliteFileSpec, deps: OwnedFileDeps);
110
+ open(): Promise<void>;
111
+ getVectorIndex(): SqliteVecVectorIndex | null;
112
+ call<K extends DataStoreOp>(op: K, input: DataStoreInput<K>): Promise<DataStoreOutput<K>>;
113
+ vector<K extends VectorOp>(op: K, input: VectorInput<K>): Promise<VectorOutput<K>>;
114
+ declaredCollections(): ReadonlySet<string>;
115
+ hasTable(name: string): Promise<boolean>;
116
+ dropRetiredTable(name: string): Promise<DroppedRetiredTable | null>;
117
+ servingThreadId(): Promise<number>;
118
+ protected stopMaintenance(): void;
119
+ protected stopConnection(): Promise<void>;
120
+ }
121
+ /** `events.db`, `history.db`: served by a dedicated worker thread. */
122
+ export declare class WorkerOwnedFile extends OwnedFileBase {
123
+ private readonly client;
124
+ constructor(spec: OwnedSqliteFileSpec, deps: OwnedFileDeps);
125
+ open(): Promise<void>;
126
+ call<K extends DataStoreOp>(op: K, input: DataStoreInput<K>): Promise<DataStoreOutput<K>>;
127
+ vector<K extends VectorOp>(op: K, input: VectorInput<K>): Promise<VectorOutput<K>>;
128
+ declaredCollections(): ReadonlySet<string>;
129
+ hasTable(name: string): Promise<boolean>;
130
+ dropRetiredTable(name: string): Promise<DroppedRetiredTable | null>;
131
+ servingThreadId(): Promise<number>;
132
+ protected stopMaintenance(): void;
133
+ protected stopConnection(): Promise<void>;
134
+ }
135
+ /** Open a file on the thread its spec names. */
136
+ export declare function openOwnedSqliteFile(spec: OwnedSqliteFileSpec, deps: OwnedFileDeps): Promise<InlineOwnedFile | WorkerOwnedFile>;
@@ -0,0 +1,60 @@
1
+ /**
2
+ * The typed errors of the owned-file layer (D851).
3
+ *
4
+ * They cross a thread boundary by NAME: a worker serialises `{ name, message,
5
+ * code? }` and the main thread rebuilds the class with {@link rehydrateError},
6
+ * so a caller's `instanceof CollectionPlacementError` survives the hop.
7
+ */
8
+ import type { DataStoreDatabase } from "@camstack/types";
9
+ /**
10
+ * A collection was declared for one file while its table already lives in
11
+ * another. The engine never moves a table: moving is the offline split
12
+ * script's job (D853). This is the safety net that turns an accidental deploy
13
+ * to an unconverted hub into a loud failure of ONE addon instead of a silent
14
+ * empty file beside the real rows.
15
+ */
16
+ export declare class CollectionPlacementError extends Error {
17
+ readonly table: string;
18
+ readonly livesIn: DataStoreDatabase;
19
+ readonly declaredFor: DataStoreDatabase;
20
+ constructor(table: string, livesIn: DataStoreDatabase, declaredFor: DataStoreDatabase);
21
+ }
22
+ /** A collection declared in two files at boot: the layout is corrupt and is not served. */
23
+ export declare class DuplicatePlacementError extends Error {
24
+ readonly table: string;
25
+ readonly first: DataStoreDatabase;
26
+ readonly second: DataStoreDatabase;
27
+ constructor(table: string, first: DataStoreDatabase, second: DataStoreDatabase);
28
+ }
29
+ /** A `transact` whose ops resolve to more than one database file. */
30
+ export declare class CrossDatabaseTransactError extends Error {
31
+ readonly databases: readonly DataStoreDatabase[];
32
+ constructor(databases: readonly DataStoreDatabase[]);
33
+ }
34
+ /** A worker already has its in-flight bound of calls queued; the call is refused, never queued. */
35
+ export declare class DatabaseBusyError extends Error {
36
+ readonly databaseId: DataStoreDatabase;
37
+ readonly inFlight: number;
38
+ constructor(databaseId: DataStoreDatabase, inFlight: number);
39
+ }
40
+ /** An id-addressed call (get/set/update/delete/insert/upsert/guard) on a composite-keyed collection. */
41
+ export declare class CompositeKeyError extends Error {
42
+ readonly table: string;
43
+ readonly op: string;
44
+ readonly keyColumns: readonly string[];
45
+ constructor(table: string, op: string, keyColumns: readonly string[]);
46
+ }
47
+ /** The wire form of an error that crossed the worker boundary. */
48
+ export interface SerializedError {
49
+ readonly name: string;
50
+ readonly message: string;
51
+ readonly code?: string;
52
+ }
53
+ /** Flatten any thrown value for `postMessage`. */
54
+ export declare function serializeError(err: unknown): SerializedError;
55
+ /**
56
+ * Rebuild an error on the main thread. A name this module knows comes back as
57
+ * an instance of its class (`instanceof` works); anything else comes back as a
58
+ * plain Error with the name and code preserved.
59
+ */
60
+ export declare function rehydrateError(serialized: SerializedError): Error;
@@ -0,0 +1,80 @@
1
+ /**
2
+ * PLACEMENT (D851): which database file serves a collection.
3
+ *
4
+ * The router IS the engine's `data-store-provider`: the storage orchestrator
5
+ * dispatches to one engine, and this engine fans out to the files it owns.
6
+ * The rule it enforces is small and absolute:
7
+ *
8
+ * - a collection's file is declared by its owner on `declareCollection`
9
+ * (`database`, absent = `settings`) and persisted in THAT file's own
10
+ * `_collection_declarations`;
11
+ * - at boot the router rebuilds `collection → file` from those tables, and a
12
+ * collection declared in two files is a boot ERROR naming both — a corrupt
13
+ * layout is not served;
14
+ * - a declaration for a file other than the one the table already lives in is
15
+ * REFUSED ({@link CollectionPlacementError}), never moved. Moving is the
16
+ * offline split script's job, once (D853). This is the safety net that turns
17
+ * an accidental deploy to an unconverted hub into a loud failure of ONE
18
+ * addon instead of a silent empty file beside the real rows;
19
+ * - every other op resolves its file from the map; an undeclared collection
20
+ * (the canonical KV set and its `<addonId>:<canonical>` variants) is
21
+ * `settings`;
22
+ * - a `transact` spans ONE file ({@link CrossDatabaseTransactError}).
23
+ *
24
+ * A file other than `settings` is created on the FIRST declaration that names
25
+ * it, so an installation where nothing names `events` / `history` grows no new
26
+ * file.
27
+ */
28
+ import type { DataStoreDatabase, DataStoreEngineInfo, IDataStoreProvider, IScopedLogger } from "@camstack/types";
29
+ import type { DataStoreInput, DataStoreOutput } from "./file-host-protocol.js";
30
+ import type { OwnedSqliteFile } from "./owned-sqlite-file.js";
31
+ /** Opens (creating it if needed) the file for a database id. Called at most once per id. */
32
+ export type OpenDatabaseFile = (databaseId: DataStoreDatabase) => Promise<OwnedSqliteFile>;
33
+ export interface PlacementRouterOptions {
34
+ /** Files already open at boot — `settings` always, `events` / `history` if they exist on disk. */
35
+ readonly files: readonly OwnedSqliteFile[];
36
+ /** How a not-yet-open file is created on first declaration. */
37
+ readonly open: OpenDatabaseFile;
38
+ readonly logger: IScopedLogger;
39
+ }
40
+ export declare class PlacementRouter implements IDataStoreProvider {
41
+ private readonly files;
42
+ private readonly opening;
43
+ /** `events` / `history` collections only; absence means `settings`. */
44
+ private readonly placement;
45
+ private readonly open;
46
+ private readonly logger;
47
+ constructor(options: PlacementRouterOptions);
48
+ /**
49
+ * Rebuild `collection → file` from each file's own declarations. A table
50
+ * declared in two files is refused: nothing downstream could tell which is
51
+ * the real one.
52
+ */
53
+ private rebuildPlacement;
54
+ /** The open file of an id; `settings` is always there. */
55
+ private fileOf;
56
+ /** The database a table is served from. */
57
+ databaseOf(table: string): DataStoreDatabase;
58
+ /** The files currently open, settings first. */
59
+ openFiles(): readonly OwnedSqliteFile[];
60
+ private openFile;
61
+ private route;
62
+ declareCollection(input: DataStoreInput<"declareCollection">): Promise<void>;
63
+ transact(input: DataStoreInput<"transact">): Promise<DataStoreOutput<"transact">>;
64
+ getEngineInfo(): Promise<DataStoreEngineInfo>;
65
+ get(input: DataStoreInput<"get">): Promise<DataStoreOutput<"get">>;
66
+ set(input: DataStoreInput<"set">): Promise<DataStoreOutput<"set">>;
67
+ query(input: DataStoreInput<"query">): Promise<DataStoreOutput<"query">>;
68
+ insert(input: DataStoreInput<"insert">): Promise<DataStoreOutput<"insert">>;
69
+ upsert(input: DataStoreInput<"upsert">): Promise<DataStoreOutput<"upsert">>;
70
+ insertMany(input: DataStoreInput<"insertMany">): Promise<DataStoreOutput<"insertMany">>;
71
+ upsertMany(input: DataStoreInput<"upsertMany">): Promise<DataStoreOutput<"upsertMany">>;
72
+ update(input: DataStoreInput<"update">): Promise<DataStoreOutput<"update">>;
73
+ delete(input: DataStoreInput<"delete">): Promise<DataStoreOutput<"delete">>;
74
+ deleteWhere(input: DataStoreInput<"deleteWhere">): Promise<DataStoreOutput<"deleteWhere">>;
75
+ updateWhere(input: DataStoreInput<"updateWhere">): Promise<DataStoreOutput<"updateWhere">>;
76
+ count(input: DataStoreInput<"count">): Promise<DataStoreOutput<"count">>;
77
+ aggregate(input: DataStoreInput<"aggregate">): Promise<DataStoreOutput<"aggregate">>;
78
+ histogram(input: DataStoreInput<"histogram">): Promise<DataStoreOutput<"histogram">>;
79
+ isEmpty(input: DataStoreInput<"isEmpty">): Promise<DataStoreOutput<"isEmpty">>;
80
+ }
@@ -67,6 +67,7 @@
67
67
  * here — this module drops TABLES, and deleting somebody's database file is a
68
68
  * different kind of act.
69
69
  */
70
+ import type { OwnedSqliteFile } from "./owned-sqlite-file.js";
70
71
  import type { RetiredKeyLogger } from "./retired-settings-keys.js";
71
72
  import type { DroppedTable, SqliteSettingsBackend } from "./sqlite-settings-backend.js";
72
73
  /** One retired-table declaration. */
@@ -132,6 +133,12 @@ export declare const RETIRED_COLLECTIONS: readonly RetiredCollectionSpec[];
132
133
  */
133
134
  export declare function retiredCollectionStoreOf(backend: SqliteSettingsBackend): RetiredCollectionStore;
134
135
  /**
136
+ * The same adapter over an {@link OwnedSqliteFile} — the purge runs against
137
+ * EVERY open database file (D851), and a file served by a worker answers the
138
+ * drop through its thread.
139
+ */
140
+ export declare function retiredCollectionStoreOfFile(file: OwnedSqliteFile): RetiredCollectionStore;
141
+ /**
135
142
  * Drop every retired table that is still present. Returns one entry per table
136
143
  * actually dropped — empty when the schema is already clean, which is the
137
144
  * steady state after the first boot that ran it.
@@ -1,7 +1,25 @@
1
- import type { BulkRecord, CollectionColumn, CollectionIndex, DataStoreEngineInfo, HistogramBucket, IScopedLogger, ISettingsBackend, QueryFilter, SettingsAggregateInput, SettingsAggregateResult, SettingsCountInput, SettingsDeleteInput, SettingsGetInput, SettingsHistogramInput, SettingsInsertInput, SettingsIsEmptyInput, SettingsQueryInput, SettingsRecord, SettingsSetInput, SettingsUpdateInput, SettingsUpsertInput } from "@camstack/types";
1
+ import type { BulkRecord, DataStoreDatabase, DataStoreEngineInfo, DeclareCollectionInput, HistogramBucket, IScopedLogger, ISettingsBackend, QueryFilter, SettingsAggregateInput, SettingsAggregateResult, SettingsCountInput, SettingsDeleteInput, SettingsGetInput, SettingsHistogramInput, SettingsInsertInput, SettingsIsEmptyInput, SettingsQueryInput, SettingsRecord, SettingsSetInput, SettingsUpdateInput, SettingsUpsertInput } from "@camstack/types";
2
2
  import Database from "better-sqlite3";
3
3
  import { type MutationFilterInput } from "./filter-compiler.js";
4
4
  import { SqliteOpProfiler } from "./query-profiler.js";
5
+ /** How one file of the engine is opened. All optional: the defaults are `camstack.db`'s. */
6
+ export interface SqliteSettingsBackendOptions {
7
+ /** Which file this is — names it in log lines. Default `settings`. */
8
+ readonly databaseId?: DataStoreDatabase;
9
+ /**
10
+ * `incremental` sets `auto_vacuum = INCREMENTAL` — but ONLY on a file this
11
+ * open CREATES: the mode is fixed when the first table is written, and
12
+ * changing it on an existing file means a full `VACUUM`, which is the ops
13
+ * script's job (D853), never a boot step. Default `none`.
14
+ */
15
+ readonly autoVacuum?: "none" | "incremental";
16
+ /**
17
+ * Declare the canonical key/value collections and seed the defaults. True
18
+ * only for `settings`: canonical KV collections are settings by definition,
19
+ * and a second copy of them in `events.db` would be a table nobody reads.
20
+ */
21
+ readonly canonicalKv?: boolean;
22
+ }
5
23
  /** Input for {@link SqliteSettingsBackend.insertMany}. */
6
24
  interface SettingsInsertManyInput {
7
25
  readonly namespace?: string;
@@ -11,6 +29,16 @@ interface SettingsInsertManyInput {
11
29
  interface SettingsInsertManyResult {
12
30
  readonly inserted: number;
13
31
  }
32
+ /** Input for {@link SqliteSettingsBackend.upsertMany}. */
33
+ interface SettingsUpsertManyInput {
34
+ readonly namespace?: string;
35
+ readonly collection: string;
36
+ readonly conflict: readonly string[];
37
+ readonly records: readonly BulkRecord[];
38
+ }
39
+ interface SettingsUpsertManyResult {
40
+ readonly upserted: number;
41
+ }
14
42
  /** Input for {@link SqliteSettingsBackend.transact}. */
15
43
  interface SettingsTransactInput {
16
44
  readonly namespace?: string;
@@ -33,6 +61,11 @@ interface SettingsTransactInput {
33
61
  readonly collection: string;
34
62
  readonly id: string;
35
63
  readonly data: Record<string, unknown>;
64
+ } | {
65
+ readonly op: "upsertMany";
66
+ readonly collection: string;
67
+ readonly conflict: readonly string[];
68
+ readonly records: readonly BulkRecord[];
36
69
  })[];
37
70
  }
38
71
  interface SettingsTransactResult {
@@ -157,7 +190,8 @@ export declare class SqliteSettingsBackend implements ISettingsBackend {
157
190
  * is the failure this was built for (`query-profiler.ts`).
158
191
  */
159
192
  private readonly profiler;
160
- constructor(dbPath: string, runtimeDefaults?: Record<string, unknown>, logger?: IScopedLogger);
193
+ private readonly options;
194
+ constructor(dbPath: string, runtimeDefaults?: Record<string, unknown>, logger?: IScopedLogger, options?: SqliteSettingsBackendOptions);
161
195
  initialize(): Promise<void>;
162
196
  private requireDeclared;
163
197
  /**
@@ -229,6 +263,18 @@ export declare class SqliteSettingsBackend implements ISettingsBackend {
229
263
  /** The synchronous body of {@link insertMany}, shared with {@link transact}. */
230
264
  private insertManySync;
231
265
  /**
266
+ * Upsert a batch in ONE transaction, on ONE prepared statement.
267
+ *
268
+ * {@link upsert}'s batch twin: `INSERT … ON CONFLICT(<slot>) DO UPDATE`. One
269
+ * statement shape means every record must name the SAME declared columns and
270
+ * every `conflict` column — a record that mentioned fewer would otherwise
271
+ * bind NULL over a value the caller never meant to touch, so a heterogeneous
272
+ * batch is refused whole, before any SQL runs.
273
+ */
274
+ upsertMany({ namespace, collection, conflict, records }: SettingsUpsertManyInput): Promise<SettingsUpsertManyResult>;
275
+ /** The synchronous body of {@link upsertMany}, shared with {@link transact}. */
276
+ private upsertManySync;
277
+ /**
232
278
  * Several writes, one transaction, behind an optional guard.
233
279
  *
234
280
  * `better-sqlite3`'s `transaction()` is synchronous and rolls the whole call
@@ -372,6 +418,25 @@ export declare class SqliteSettingsBackend implements ISettingsBackend {
372
418
  * Returns null if the backend has not been initialized yet.
373
419
  */
374
420
  getDatabase(): Database.Database | null;
421
+ /** Which file this backend serves. */
422
+ getDatabaseId(): DataStoreDatabase;
423
+ /**
424
+ * The tables this file's own `_collection_declarations` names — the
425
+ * placement authority (D851). Read from DISK, not from memory, so a table
426
+ * created and declared by an earlier life of the process still counts.
427
+ */
428
+ declaredCollectionNames(): ReadonlySet<string>;
429
+ /** Does a table of this name exist in THIS file? */
430
+ hasTable(name: string): boolean;
431
+ /** Refuse an id-addressed call on a composite-keyed collection. */
432
+ private requireSingleKey;
433
+ /**
434
+ * The composite key of a declaration, validated: every name is a declared
435
+ * column, no column also claims `primaryKey: true`, and `withoutRowid` has a
436
+ * key to be keyed by. A single-name `primaryKey` is the single-column key it
437
+ * always was and returns empty.
438
+ */
439
+ private resolveKeyColumns;
375
440
  /**
376
441
  * `EXPLAIN QUERY PLAN` for a statement, as one string per plan row.
377
442
  *
@@ -494,12 +559,7 @@ export declare class SqliteSettingsBackend implements ISettingsBackend {
494
559
  * had the chance to re-declare — which on a respawn none of them will.
495
560
  */
496
561
  private restorePersistedDeclarations;
497
- declareCollection(input: {
498
- namespace?: string;
499
- collection: string;
500
- columns: readonly CollectionColumn[];
501
- indexes?: readonly CollectionIndex[];
502
- }): Promise<void>;
562
+ declareCollection(declared: DeclareCollectionInput): Promise<void>;
503
563
  /** Serialise per-column values for SQL binding: objects → JSON, booleans → 0/1. */
504
564
  private serializeColumnValue;
505
565
  private ensureTable;
@@ -1,7 +1,8 @@
1
1
  import type { ProviderRegistration } from "@camstack/types";
2
2
  import { BaseAddon } from "@camstack/types";
3
- import { SqliteSettingsBackend } from "./sqlite-settings-backend.js";
4
- import { SqliteVecVectorIndex } from "./vector-index-vec.js";
3
+ import { PlacementRouter } from "./placement-router.js";
4
+ import type { SqliteSettingsBackend } from "./sqlite-settings-backend.js";
5
+ import type { SqliteVecVectorIndex } from "./vector-index-vec.js";
5
6
  /**
6
7
  * SQLite Settings addon — the relational ENGINE behind the data door.
7
8
  *
@@ -12,22 +13,25 @@ import { SqliteVecVectorIndex } from "./vector-index-vec.js";
12
13
  * engine only through the door — which is the point, because it is what
13
14
  * makes the engine replaceable.
14
15
  *
16
+ * ONE engine process, SEVERAL owned files ([D851](../../../../../docs/decisions/adr-0851-one-sqlite-engine-serves-several-owned-files-one-thread-per-file.md)):
17
+ * `camstack.db` (settings) is served on this thread exactly as it always was;
18
+ * `events.db` and `history.db`, when a collection names them, are each served
19
+ * by a worker thread. A {@link PlacementRouter} is the provider the door sees.
20
+ * The layout of the files is documented in `docs/architecture/storage-files.md`.
21
+ *
15
22
  * It also serves `database-snapshot` (singleton, internal): the verified copy
16
23
  * of its database that the nightly backup archives in place of the live files
17
24
  * (D693).
18
25
  *
19
- * It ALSO serves `vector-store` (singleton, internal), from the same database
20
- * file and the same open handle. That capability first shipped as its own addon
21
- * and the separation cost two round trips per write — caller → door → engine —
22
- * plus a runner process, to reach a database this addon already has open. The
26
+ * It ALSO serves `vector-store` (singleton, internal), from the settings file's
27
+ * open handle. That capability first shipped as its own addon and the
28
+ * separation cost two round trips per write — caller → door → engine — plus a
29
+ * runner process, to reach a database this addon already has open. The
23
30
  * capability is what keeps the backend swappable; the package never was.
24
31
  */
25
32
  export declare class SqliteSettingsAddon extends BaseAddon {
26
- private backend;
27
- private vectorIndex;
28
- private walMaintenance;
29
- private walCheckpointer;
30
- private shmLockCanary;
33
+ private settingsFile;
34
+ private router;
31
35
  constructor();
32
36
  /**
33
37
  * The engine behind `settings-store` cannot read that door during
@@ -40,23 +44,16 @@ export declare class SqliteSettingsAddon extends BaseAddon {
40
44
  */
41
45
  protected override resolveConfig(): Promise<void>;
42
46
  protected onInitialize(): Promise<ProviderRegistration[]>;
43
- protected onShutdown(): Promise<void>;
44
- getBackend(): SqliteSettingsBackend | null;
45
47
  /**
46
- * Build the vector index.
47
- *
48
- * ONE implementation, deliberately. A second backend behind a flag means two
49
- * code paths that can disagree, two sets of gotchas, and a silent fallback
50
- * that turns "the extension is missing" into "search is mysteriously slow" —
51
- * the failure this codebase keeps paying for. The capability is the swap
52
- * point: another provider replaces this one wholesale, it does not hide
53
- * inside it.
54
- *
55
- * So a failed extension load THROWS. A node without the native binary must
56
- * fail at boot with a message naming sqlite-vec, not serve degraded results
57
- * nobody knows are degraded.
48
+ * Housekeeping that must happen BEFORE anything can read a file: the
49
+ * retired-key purge (settings only) and the retired-table purge (every
50
+ * open file).
58
51
  */
59
- private createVectorIndex;
52
+ private purgeBeforeServing;
53
+ protected onShutdown(): Promise<void>;
54
+ getBackend(): SqliteSettingsBackend | null;
55
+ /** The placement router; null before `onInitialize`. */
56
+ getRouter(): PlacementRouter | null;
60
57
  /** Exposed for tests; null before `onInitialize`. */
61
58
  getVectorIndex(): SqliteVecVectorIndex | null;
62
59
  }
@@ -0,0 +1,24 @@
1
+ import type { IScopedLogger, IVectorStoreProvider } from "@camstack/types";
2
+ import type { SqliteSettingsBackend } from "./sqlite-settings-backend.js";
3
+ import { SqliteVecVectorIndex } from "./vector-index-vec.js";
4
+ /**
5
+ * Build the vector index over a backend's connection.
6
+ *
7
+ * ONE implementation, deliberately. A second backend behind a flag means two
8
+ * code paths that can disagree, two sets of gotchas, and a silent fallback
9
+ * that turns "the extension is missing" into "search is mysteriously slow" —
10
+ * the failure this codebase keeps paying for. The capability is the swap
11
+ * point: another provider replaces this one wholesale, it does not hide
12
+ * inside it.
13
+ *
14
+ * So a failed extension load THROWS. A node without the native binary must
15
+ * fail at boot with a message naming sqlite-vec, not serve degraded results
16
+ * nobody knows are degraded.
17
+ */
18
+ export declare function createVectorIndex(backend: SqliteSettingsBackend, logger: IScopedLogger): SqliteVecVectorIndex;
19
+ /**
20
+ * The `vector-store` provider surface over one sqlite-vec index. Extracted
21
+ * from the addon so the main thread (settings) and the file-host worker
22
+ * (events) serve the SAME mapping.
23
+ */
24
+ export declare function vectorProviderOf(vectorIndex: SqliteVecVectorIndex): IVectorStoreProvider;
@@ -74,6 +74,14 @@ export interface WalMaintenanceOptions {
74
74
  readonly idleQuietMs?: number;
75
75
  readonly intervalMs?: number;
76
76
  readonly busyTimeoutMs?: number;
77
+ /**
78
+ * Pages to hand back to the filesystem per idle tick with
79
+ * `PRAGMA incremental_vacuum(N)`. Absent or 0 = never. Only a file created
80
+ * with `auto_vacuum = INCREMENTAL` (D851) has a freelist this can shrink; on
81
+ * any other the pragma is a no-op, and the tick checks the mode first so it
82
+ * does not even try.
83
+ */
84
+ readonly incrementalVacuumPages?: number;
77
85
  }
78
86
  /**
79
87
  * Periodic, heavily gated `wal_checkpoint(TRUNCATE)`.
@@ -92,11 +100,20 @@ export declare class WalMaintenance {
92
100
  private readonly idleQuietMs;
93
101
  private readonly intervalMs;
94
102
  private readonly busyTimeoutMs;
103
+ private readonly incrementalVacuumPages;
95
104
  private timer;
96
105
  constructor(options: WalMaintenanceOptions);
97
106
  start(): void;
98
107
  stop(): void;
99
108
  isRunning(): boolean;
109
+ /**
110
+ * Give up to `incrementalVacuumPages` free pages back, when the file is in
111
+ * incremental auto-vacuum mode, has a freelist, and nothing has written for
112
+ * the idle window (the same gate the checkpoint uses — reclaiming pages
113
+ * takes the writer lock). Returns the pages released; 0 when it did nothing.
114
+ * Never throws.
115
+ */
116
+ vacuumOnce(): number;
100
117
  /** Evaluate the gate once and act. Never throws. */
101
118
  runOnce(): WalMaintenanceOutcome;
102
119
  private evaluate;
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  const require_chunk = require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-VIIxVsBW.js");
6
+ const require_dist = require("../../dist-DnALrQF-.js");
7
7
  let zod = require("zod");
8
8
  let node_crypto = require("node:crypto");
9
9
  let _camstack_types_node = require("@camstack/types/node");
@@ -1237,6 +1237,7 @@ function createDataStoreDispatch(getEngines, getRemote) {
1237
1237
  insert: async (input) => (await engine()).insert(input),
1238
1238
  upsert: async (input) => (await engine()).upsert(input),
1239
1239
  insertMany: async (input) => (await engine()).insertMany(input),
1240
+ upsertMany: async (input) => (await engine()).upsertMany(input),
1240
1241
  transact: async (input) => (await engine()).transact(input),
1241
1242
  update: async (input) => (await engine()).update(input),
1242
1243
  delete: async (input) => (await engine()).delete(input),
@@ -1554,6 +1555,7 @@ function createHubSettingsForward(forward) {
1554
1555
  insert: (input) => send("insert", input),
1555
1556
  upsert: (input) => send("upsert", input),
1556
1557
  insertMany: (input) => send("insertMany", input),
1558
+ upsertMany: (input) => send("upsertMany", input),
1557
1559
  transact: (input) => send("transact", input),
1558
1560
  update: (input) => send("update", input),
1559
1561
  delete: (input) => send("delete", input),
@@ -1,4 +1,4 @@
1
- import { $r as transformSecretWrite, $t as mayReadLocation, B as StorageLocationModeSchema, Dt as evictionPolicyOfLocation, Gn as withLocationMode, H as StorageMigrationJobSchema, Ir as isSecretMarker, Jr as secretTreeFromPaths, Kr as resolveSecretMarkers, Kt as legacyModeOf, Mn as storageCapability, Nn as storageMigrationCapability, Pt as gbToBytes, Sn as resolveLocationMode, Sr as collectSecretConfigKeys, V as StorageLocationTypeSchema, Vr as parseJsonObject, cr as BaseAddon, ei as EventCategory, en as mayWriteToLocation, fr as REDACTED_SECRET, k as LegacyStorageLocationDefaultSchema, kn as settingsStoreCapability, rn as modeMayWrite, z as StorageCleanupJobSchema, zt as isLocationEnabled } from "../../dist-DimotS8e.mjs";
1
+ import { A as LegacyStorageLocationDefaultSchema, An as settingsStoreCapability, B as StorageCleanupJobSchema, Bt as isLocationEnabled, Cn as resolveLocationMode, Cr as collectSecretConfigKeys, Ft as gbToBytes, H as StorageLocationTypeSchema, Hr as parseJsonObject, Kn as withLocationMode, Lr as isSecretMarker, Nn as storageCapability, Ot as evictionPolicyOfLocation, Pn as storageMigrationCapability, U as StorageMigrationJobSchema, V as StorageLocationModeSchema, Yr as secretTreeFromPaths, ei as transformSecretWrite, en as mayReadLocation, in as modeMayWrite, lr as BaseAddon, pr as REDACTED_SECRET, qr as resolveSecretMarkers, qt as legacyModeOf, ti as EventCategory, tn as mayWriteToLocation } from "../../dist-DMWeqeTF.mjs";
2
2
  import { z } from "zod";
3
3
  import { randomUUID } from "node:crypto";
4
4
  import { containsOrEquals, physicalRootOf } from "@camstack/types/node";
@@ -1232,6 +1232,7 @@ function createDataStoreDispatch(getEngines, getRemote) {
1232
1232
  insert: async (input) => (await engine()).insert(input),
1233
1233
  upsert: async (input) => (await engine()).upsert(input),
1234
1234
  insertMany: async (input) => (await engine()).insertMany(input),
1235
+ upsertMany: async (input) => (await engine()).upsertMany(input),
1235
1236
  transact: async (input) => (await engine()).transact(input),
1236
1237
  update: async (input) => (await engine()).update(input),
1237
1238
  delete: async (input) => (await engine()).delete(input),
@@ -1549,6 +1550,7 @@ function createHubSettingsForward(forward) {
1549
1550
  insert: (input) => send("insert", input),
1550
1551
  upsert: (input) => send("upsert", input),
1551
1552
  insertMany: (input) => send("insertMany", input),
1553
+ upsertMany: (input) => send("upsertMany", input),
1552
1554
  transact: (input) => send("transact", input),
1553
1555
  update: (input) => send("update", input),
1554
1556
  delete: (input) => send("delete", input),