@camstack/system 1.2.443 → 1.2.444

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 (101) hide show
  1. package/assets/record-entries/icon.svg +8 -0
  2. package/dist/addon-runner.js +41 -11
  3. package/dist/addon-runner.mjs +42 -12
  4. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.js +1 -1
  5. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.mjs +1 -1
  6. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.js +1 -1
  7. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.mjs +1 -1
  8. package/dist/builtins/alerts/alerts.addon.d.ts +10 -2
  9. package/dist/builtins/alerts/alerts.addon.js +43 -10
  10. package/dist/builtins/alerts/alerts.addon.mjs +43 -10
  11. package/dist/builtins/autotrack/index.js +1 -1
  12. package/dist/builtins/autotrack/index.mjs +1 -1
  13. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.js +2 -2
  14. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.mjs +2 -2
  15. package/dist/builtins/calendars/calendars.addon.js +1 -1
  16. package/dist/builtins/calendars/calendars.addon.mjs +1 -1
  17. package/dist/builtins/camera-grid/index.js +1 -1
  18. package/dist/builtins/camera-grid/index.mjs +1 -1
  19. package/dist/builtins/composer/composer.addon.js +2 -2
  20. package/dist/builtins/composer/composer.addon.mjs +2 -2
  21. package/dist/builtins/console-logging/index.js +1 -1
  22. package/dist/builtins/console-logging/index.mjs +1 -1
  23. package/dist/builtins/core-blocks/core-blocks.addon.js +2 -2
  24. package/dist/builtins/core-blocks/core-blocks.addon.mjs +2 -2
  25. package/dist/builtins/device-manager/device-manager.addon.js +1 -1
  26. package/dist/builtins/device-manager/device-manager.addon.mjs +1 -1
  27. package/dist/builtins/hub-forwarder/index.js +1 -1
  28. package/dist/builtins/hub-forwarder/index.mjs +1 -1
  29. package/dist/builtins/keychain/index.js +3 -3
  30. package/dist/builtins/keychain/index.mjs +3 -3
  31. package/dist/builtins/liveness-monitor/liveness-monitor.addon.js +1 -1
  32. package/dist/builtins/liveness-monitor/liveness-monitor.addon.mjs +1 -1
  33. package/dist/builtins/local-auth/local-auth.addon.js +2 -2
  34. package/dist/builtins/local-auth/local-auth.addon.mjs +2 -2
  35. package/dist/builtins/local-network/local-network.addon.js +2 -2
  36. package/dist/builtins/local-network/local-network.addon.mjs +2 -2
  37. package/dist/builtins/loki-logging/index.js +1 -1
  38. package/dist/builtins/loki-logging/index.mjs +1 -1
  39. package/dist/builtins/native-metrics/native-metrics.addon.js +1 -1
  40. package/dist/builtins/native-metrics/native-metrics.addon.mjs +1 -1
  41. package/dist/builtins/platform-probe/index.js +1 -1
  42. package/dist/builtins/platform-probe/index.mjs +1 -1
  43. package/dist/builtins/record-entries/entries-service.d.ts +42 -0
  44. package/dist/builtins/record-entries/index.d.ts +1 -0
  45. package/dist/builtins/record-entries/index.js +142 -0
  46. package/dist/builtins/record-entries/index.mjs +136 -0
  47. package/dist/builtins/record-entries/record-entries.addon.d.ts +13 -0
  48. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.js +1 -1
  49. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.mjs +1 -1
  50. package/dist/builtins/snapshot/index.js +1 -1
  51. package/dist/builtins/snapshot/index.mjs +1 -1
  52. package/dist/builtins/sqlite-storage/file-host-client.d.ts +2 -0
  53. package/dist/builtins/sqlite-storage/file-host-handlers.d.ts +4 -0
  54. package/dist/builtins/sqlite-storage/file-host-protocol.d.ts +7 -1
  55. package/dist/builtins/sqlite-storage/filesystem-storage.addon.js +1 -1
  56. package/dist/builtins/sqlite-storage/filesystem-storage.addon.mjs +1 -1
  57. package/dist/builtins/sqlite-storage/index.js +1 -1
  58. package/dist/builtins/sqlite-storage/index.mjs +1 -1
  59. package/dist/builtins/sqlite-storage/owned-sqlite-file.d.ts +7 -0
  60. package/dist/builtins/sqlite-storage/placement-router.d.ts +5 -0
  61. package/dist/builtins/sqlite-storage/records/records-catalog.d.ts +29 -0
  62. package/dist/builtins/sqlite-storage/records/records-engine-types.d.ts +46 -0
  63. package/dist/builtins/sqlite-storage/records/records-engine.d.ts +90 -0
  64. package/dist/builtins/sqlite-storage/records/records-explain.d.ts +18 -0
  65. package/dist/builtins/sqlite-storage/records/records-plan.d.ts +90 -0
  66. package/dist/builtins/sqlite-storage/records/records-probe.d.ts +33 -0
  67. package/dist/builtins/sqlite-storage/records/records-provider.d.ts +59 -0
  68. package/dist/builtins/sqlite-storage/records/records-rows.d.ts +46 -0
  69. package/dist/builtins/sqlite-storage/records/records-sql.d.ts +41 -0
  70. package/dist/builtins/sqlite-storage/sqlite-settings-backend.d.ts +0 -10
  71. package/dist/builtins/sqlite-storage/sqlite-settings.addon.d.ts +8 -0
  72. package/dist/builtins/sqlite-storage/sqlite-settings.addon.js +337 -4
  73. package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +337 -4
  74. package/dist/builtins/sqlite-storage/storage-admin-provider.d.ts +7 -1
  75. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.js +1 -1
  76. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.mjs +1 -1
  77. package/dist/builtins/system-config/system-config.addon.js +2 -2
  78. package/dist/builtins/system-config/system-config.addon.mjs +2 -2
  79. package/dist/builtins/winston-logging/index.js +1 -1
  80. package/dist/builtins/winston-logging/index.mjs +1 -1
  81. package/dist/{composition-sources-C8tUG2uL.mjs → composition-sources-C3rKncIF.mjs} +1 -1
  82. package/dist/{composition-sources-BUXX1e8j.js → composition-sources-NAPZddic.js} +1 -1
  83. package/dist/{constants-CGwVaAV3.mjs → constants-BKX2QnIg.mjs} +1 -1
  84. package/dist/{constants-DWmm7_of.js → constants-BXiNEIeV.js} +1 -1
  85. package/dist/{device-ca-DGayRmC0.js → device-ca-DT9X2S6d.js} +1 -1
  86. package/dist/{device-ca-DBfiJ_SB.mjs → device-ca-jxjSL8Ov.mjs} +1 -1
  87. package/dist/{dist-D9qT3jHr.js → dist-D1vECb_6.js} +1172 -86
  88. package/dist/{dist-Bxt_S-9a.mjs → dist-DERK-gP5.mjs} +1011 -87
  89. package/dist/file-host-worker.js +11 -1
  90. package/dist/file-host-worker.mjs +11 -1
  91. package/dist/{hub-mdns-ciao-DpPcQDg-.mjs → hub-mdns-ciao-CRaC97V0.mjs} +1 -1
  92. package/dist/{hub-mdns-ciao-B-wk9nzx.js → hub-mdns-ciao-Cah9GCIg.js} +1 -1
  93. package/dist/index.js +50 -18
  94. package/dist/index.mjs +51 -19
  95. package/dist/kernel/moleculer/records-api-transport.d.ts +9 -0
  96. package/dist/kernel/settings-view-types.d.ts +0 -7
  97. package/dist/{restore-archive-BMNXwWWD.js → restore-archive-Dy-qKTdF.js} +1 -1
  98. package/dist/{restore-archive-9CGgeBzD.mjs → restore-archive-daKyE24F.mjs} +1 -1
  99. package/dist/{wal-maintenance-cRHR4rUY.js → wal-maintenance-dAv4IdzC.js} +0 -0
  100. package/dist/{wal-maintenance-sIB32ONp.mjs → wal-maintenance-zRc24mOC.mjs} +0 -0
  101. package/package.json +22 -1
@@ -1,5 +1,5 @@
1
1
  import { FilesystemStorageAddon, t as FilesystemStorageProvider } from "./filesystem-storage.addon.mjs";
2
- import { A as SQLITE_PAGE_CACHE_FLOOR_KIB, D as SQLITE_MMAP_CEILING_BYTES, E as SQLITE_JOURNAL_SIZE_LIMIT_BYTES, F as SQLITE_PROFILE_REPORT_INTERVAL_MS, I as SQLITE_PROFILE_TOP_N, L as SQLITE_SLOW_CALL_MS, M as sqlitePageCacheKibFor, N as SQLITE_ENGINE_STORM_WALL_SHARE, O as SQLITE_MMAP_FLOOR_BYTES, P as SQLITE_PROFILE_REPORT_FLOOR_MS, R as SqliteOpProfiler, a as WalMaintenance, c as SqliteSettingsBackend, i as WAL_MAINTENANCE_INTERVAL_MS, j as sqliteMmapSizeFor, k as SQLITE_PAGE_CACHE_CEILING_KIB, n as WAL_HIGH_WATER_BYTES, r as WAL_IDLE_QUIET_MS, t as WAL_CHECKPOINT_BUSY_TIMEOUT_MS, z as sqliteEngineStorm } from "../../wal-maintenance-sIB32ONp.mjs";
2
+ import { A as SQLITE_MMAP_FLOOR_BYTES, B as SqliteOpProfiler, F as SQLITE_ENGINE_STORM_WALL_SHARE, I as SQLITE_PROFILE_REPORT_FLOOR_MS, L as SQLITE_PROFILE_REPORT_INTERVAL_MS, M as SQLITE_PAGE_CACHE_FLOOR_KIB, N as sqliteMmapSizeFor, O as SQLITE_JOURNAL_SIZE_LIMIT_BYTES, P as sqlitePageCacheKibFor, R as SQLITE_PROFILE_TOP_N, V as sqliteEngineStorm, a as WalMaintenance, c as SqliteSettingsBackend, i as WAL_MAINTENANCE_INTERVAL_MS, j as SQLITE_PAGE_CACHE_CEILING_KIB, k as SQLITE_MMAP_CEILING_BYTES, n as WAL_HIGH_WATER_BYTES, r as WAL_IDLE_QUIET_MS, t as WAL_CHECKPOINT_BUSY_TIMEOUT_MS, z as SQLITE_SLOW_CALL_MS } from "../../wal-maintenance-zRc24mOC.mjs";
3
3
  import { SqliteSettingsAddon } from "./sqlite-settings.addon.mjs";
4
4
  //#region src/builtins/sqlite-storage/config-store.ts
5
5
  var ConfigStore = class {
@@ -13,6 +13,7 @@
13
13
  * package knows a file path.
14
14
  */
15
15
  import type { DatabaseSnapshotResult, DataStoreDatabase, IScopedLogger } from "@camstack/types";
16
+ import type { RecordsInput, RecordsOp, RecordsOutput } from "./records/records-engine-types.js";
16
17
  import { type FileHostThreadFactory } from "./file-host-client.js";
17
18
  import type { DataStoreInput, DataStoreOp, DataStoreOutput, OwnedSqliteFileSpec, VectorInput, VectorOp, VectorOutput } from "./file-host-protocol.js";
18
19
  import { SqliteSettingsBackend } from "./sqlite-settings-backend.js";
@@ -26,6 +27,8 @@ export interface OwnedSqliteFile {
26
27
  readonly databaseId: DataStoreDatabase;
27
28
  readonly livePath: string;
28
29
  call<K extends DataStoreOp>(op: K, input: DataStoreInput<K>): Promise<DataStoreOutput<K>>;
30
+ /** `records` calls (D881). */
31
+ records<K extends RecordsOp>(op: K, input: RecordsInput<K>): Promise<RecordsOutput<K>>;
29
32
  /** `vector-store` calls; refused on a file that does not load sqlite-vec. */
30
33
  vector<K extends VectorOp>(op: K, input: VectorInput<K>): Promise<VectorOutput<K>>;
31
34
  /** The collections this file's own `_collection_declarations` names. */
@@ -85,6 +88,7 @@ declare abstract class OwnedFileBase implements OwnedSqliteFile {
85
88
  constructor(spec: OwnedSqliteFileSpec, deps: OwnedFileDeps);
86
89
  abstract call<K extends DataStoreOp>(op: K, input: DataStoreInput<K>): Promise<DataStoreOutput<K>>;
87
90
  abstract vector<K extends VectorOp>(op: K, input: VectorInput<K>): Promise<VectorOutput<K>>;
91
+ abstract records<K extends RecordsOp>(op: K, input: RecordsInput<K>): Promise<RecordsOutput<K>>;
88
92
  abstract declaredCollections(): ReadonlySet<string>;
89
93
  abstract hasTable(name: string): Promise<boolean>;
90
94
  abstract dropRetiredTable(name: string): Promise<DroppedRetiredTable | null>;
@@ -106,10 +110,12 @@ export declare class InlineOwnedFile extends OwnedFileBase {
106
110
  private vectorIndex;
107
111
  private vectorProvider;
108
112
  private maintenance;
113
+ private recordsEngine;
109
114
  constructor(spec: OwnedSqliteFileSpec, deps: OwnedFileDeps);
110
115
  open(): Promise<void>;
111
116
  getVectorIndex(): SqliteVecVectorIndex | null;
112
117
  call<K extends DataStoreOp>(op: K, input: DataStoreInput<K>): Promise<DataStoreOutput<K>>;
118
+ records<K extends RecordsOp>(op: K, input: RecordsInput<K>): Promise<RecordsOutput<K>>;
113
119
  vector<K extends VectorOp>(op: K, input: VectorInput<K>): Promise<VectorOutput<K>>;
114
120
  declaredCollections(): ReadonlySet<string>;
115
121
  hasTable(name: string): Promise<boolean>;
@@ -125,6 +131,7 @@ export declare class WorkerOwnedFile extends OwnedFileBase {
125
131
  open(): Promise<void>;
126
132
  call<K extends DataStoreOp>(op: K, input: DataStoreInput<K>): Promise<DataStoreOutput<K>>;
127
133
  vector<K extends VectorOp>(op: K, input: VectorInput<K>): Promise<VectorOutput<K>>;
134
+ records<K extends RecordsOp>(op: K, input: RecordsInput<K>): Promise<RecordsOutput<K>>;
128
135
  declaredCollections(): ReadonlySet<string>;
129
136
  hasTable(name: string): Promise<boolean>;
130
137
  dropRetiredTable(name: string): Promise<DroppedRetiredTable | null>;
@@ -55,6 +55,11 @@ export declare class PlacementRouter implements IDataStoreProvider {
55
55
  private fileOf;
56
56
  /** The database a table is served from. */
57
57
  databaseOf(table: string): DataStoreDatabase;
58
+ /**
59
+ * The file that hosts records of `databaseId`, created by the first
60
+ * declaration that names it (`events`). `history` never hosts records.
61
+ */
62
+ recordsFileFor(databaseId: DataStoreDatabase): Promise<OwnedSqliteFile>;
58
63
  /** The files currently open, settings first. */
59
64
  openFiles(): readonly OwnedSqliteFile[];
60
65
  private openFile;
@@ -0,0 +1,29 @@
1
+ import type Database from "better-sqlite3";
2
+ import { type DeclareKindResult, type RecordKindDeclaration } from "@camstack/types";
3
+ /** One kind as the engine holds it. */
4
+ export interface KindRow {
5
+ readonly kindId: number;
6
+ readonly owner: string;
7
+ readonly kind: string;
8
+ readonly database: "settings" | "events";
9
+ readonly hash: string;
10
+ readonly state: "declared" | "orphaned" | "dropped";
11
+ readonly declaration: RecordKindDeclaration;
12
+ /** When `adoptRecords` finished copying the legacy table into this kind; the boot guard reads it. */
13
+ readonly adoptedAt: number | null;
14
+ }
15
+ /** The hash a declaration is known by; two spellings of the same declaration agree. */
16
+ export declare function declarationHash(decl: RecordKindDeclaration): string;
17
+ /** Create the shared tables of a file. Idempotent. */
18
+ export declare function ensureRecordsSchema(db: Database.Database): void;
19
+ /** Every kind this file's catalog holds. */
20
+ export declare function loadKinds(db: Database.Database): readonly KindRow[];
21
+ export interface DeclareOutcome {
22
+ readonly row: KindRow;
23
+ readonly result: DeclareKindResult;
24
+ }
25
+ /**
26
+ * Reconcile one kind: insert its catalog row and create the declared indexes,
27
+ * or confirm it is unchanged, or apply an additive change. One transaction.
28
+ */
29
+ export declare function declareKind(db: Database.Database, owner: string, kind: string, declaration: RecordKindDeclaration, prior: KindRow | undefined): DeclareOutcome;
@@ -0,0 +1,46 @@
1
+ /**
2
+ * The ops one database file serves for `records` (D881): the `records` cap's
3
+ * methods with an explicit `owner` (the transport-stamped caller, decided by
4
+ * the router) plus the engine's own housekeeping. Pure types: the worker bundle
5
+ * and the main thread both import this.
6
+ */
7
+ import type { IRecordsProvider, StorageKind } from "@camstack/types";
8
+ /** A `records` cap method's input with the owner the router resolved. */
9
+ export type OwnedInput<M extends keyof IRecordsProvider> = Parameters<IRecordsProvider[M]>[0] & {
10
+ readonly owner: string;
11
+ };
12
+ type OwnedOps = { readonly [M in keyof IRecordsProvider]: (input: OwnedInput<M>) => Promise<Awaited<ReturnType<IRecordsProvider[M]>>> };
13
+ export interface RecordsEngineOps extends OwnedOps {
14
+ /** Every kind this file holds, with rows and a size estimate (System -> Data). */
15
+ listKinds(input: {
16
+ readonly owner?: string | undefined;
17
+ }): Promise<readonly StorageKind[]>;
18
+ /** Delete rows past `expires_at`, in batches. */
19
+ sweepExpired(input: {
20
+ readonly batch?: number;
21
+ }): Promise<{
22
+ readonly deleted: number;
23
+ }>;
24
+ /** Rows of a legacy table in THIS file; `null` when the table is not there. */
25
+ legacyRowCount(input: {
26
+ readonly table: string;
27
+ }): Promise<number | null>;
28
+ /** Has `adoptRecords` finished copying the legacy table into this kind? (the boot guard) */
29
+ adoptionState(input: {
30
+ readonly owner: string;
31
+ readonly kind: string;
32
+ }): Promise<{
33
+ readonly adoptedAt: number | null;
34
+ }>;
35
+ /** Record that the copy is complete. Idempotent. */
36
+ markAdopted(input: {
37
+ readonly owner: string;
38
+ readonly kind: string;
39
+ }): Promise<void>;
40
+ }
41
+ export type RecordsOp = keyof RecordsEngineOps;
42
+ export type RecordsInput<K extends RecordsOp> = Parameters<RecordsEngineOps[K]>[0];
43
+ export type RecordsOutput<K extends RecordsOp> = Awaited<ReturnType<RecordsEngineOps[K]>>;
44
+ export declare const RECORDS_OPS: readonly RecordsOp[];
45
+ export declare function isRecordsOp(op: string): op is RecordsOp;
46
+ export {};
@@ -0,0 +1,90 @@
1
+ /**
2
+ * The records engine of ONE database file (D881): the operations behind
3
+ * `ctx.records`, over the same connection the file's collections use.
4
+ *
5
+ * Everything here is synchronous SQLite inside a method that returns a promise,
6
+ * so a big delete can YIELD between batches and the serving thread is not held.
7
+ * One thread per file (D851) serialises every statement, so each op is atomic
8
+ * and a read-modify-write goes through `ifRev`.
9
+ *
10
+ * Every statement is compiled with `kind_id = <literal>` first (records-sql.ts),
11
+ * no op exists without an (owner, kind), and an owner reaches only its own
12
+ * kinds: another addon's kind is indistinguishable from one that does not exist.
13
+ */
14
+ import type Database from "better-sqlite3";
15
+ import { type DataStoreDatabase, type IScopedLogger, type StorageKind } from "@camstack/types";
16
+ import { type KindRow } from "./records-catalog.js";
17
+ import type { OwnedInput, RecordsEngineOps, RecordsOutput } from "./records-engine-types.js";
18
+ export interface RecordsEngineOptions {
19
+ readonly databaseId: DataStoreDatabase;
20
+ readonly logger: IScopedLogger;
21
+ readonly now?: () => number;
22
+ }
23
+ export declare class RecordsEngine implements RecordsEngineOps {
24
+ private readonly db;
25
+ private readonly options;
26
+ private readonly kinds;
27
+ private readonly now;
28
+ private readonly writesSinceTrim;
29
+ constructor(db: Database.Database, options: RecordsEngineOptions);
30
+ private schemaReady;
31
+ private kindOf;
32
+ declareKinds(input: OwnedInput<"declareKinds">): Promise<RecordsOutput<"declareKinds">>;
33
+ private timed;
34
+ private static readonly ROW_SELECT;
35
+ get(input: OwnedInput<"get">): Promise<RecordsOutput<"get">>;
36
+ getMany(input: OwnedInput<"getMany">): Promise<RecordsOutput<"getMany">>;
37
+ secretRefs(input: OwnedInput<"secretRefs">): Promise<RecordsOutput<"secretRefs">>;
38
+ query(input: OwnedInput<"query">): Promise<RecordsOutput<"query">>;
39
+ count(input: OwnedInput<"count">): Promise<RecordsOutput<"count">>;
40
+ histogram(input: OwnedInput<"histogram">): Promise<RecordsOutput<"histogram">>;
41
+ aggregate(input: OwnedInput<"aggregate">): Promise<RecordsOutput<"aggregate">>;
42
+ private storedRow;
43
+ private upsertSql;
44
+ private wrapWrite;
45
+ /** One row, inside the caller's transaction. */
46
+ private writeRecord;
47
+ put(input: OwnedInput<"put">): Promise<RecordsOutput<"put">>;
48
+ putMany(input: OwnedInput<"putMany">): Promise<RecordsOutput<"putMany">>;
49
+ patch(input: OwnedInput<"patch">): Promise<RecordsOutput<"patch">>;
50
+ private refsOfRows;
51
+ delete(input: OwnedInput<"delete">): Promise<RecordsOutput<"delete">>;
52
+ deleteMany(input: OwnedInput<"deleteMany">): Promise<RecordsOutput<"deleteMany">>;
53
+ /** Delete the rows `selectSql` names, in batches, yielding between them. */
54
+ private deleteBatched;
55
+ deleteWhere(input: OwnedInput<"deleteWhere">): Promise<RecordsOutput<"deleteWhere">>;
56
+ deleteDevice(input: OwnedInput<"deleteDevice">): Promise<RecordsOutput<"deleteDevice">>;
57
+ allocateId(input: OwnedInput<"allocateId">): Promise<RecordsOutput<"allocateId">>;
58
+ /**
59
+ * Trim a ring after a write. Counting a big ring on every write would be the
60
+ * cost the bound exists to avoid, so a ring of more than TRIM_SLACK rows is
61
+ * trimmed once per (bound / TRIM_SLACK) writes: the bound holds within 1 %.
62
+ */
63
+ private afterWrite;
64
+ private trimScope;
65
+ sweepExpired(input: {
66
+ readonly batch?: number;
67
+ }): Promise<{
68
+ readonly deleted: number;
69
+ }>;
70
+ listKinds(input: {
71
+ readonly owner?: string | undefined;
72
+ readonly counts?: boolean | undefined;
73
+ }): Promise<readonly StorageKind[]>;
74
+ private countRows;
75
+ /** The declarations this file holds, for the router's placement map and the entries service. */
76
+ declarations(): readonly KindRow[];
77
+ legacyRowCount(input: {
78
+ readonly table: string;
79
+ }): Promise<number | null>;
80
+ adoptionState(input: {
81
+ readonly owner: string;
82
+ readonly kind: string;
83
+ }): Promise<{
84
+ readonly adoptedAt: number | null;
85
+ }>;
86
+ markAdopted(input: {
87
+ readonly owner: string;
88
+ readonly kind: string;
89
+ }): Promise<void>;
90
+ }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Reading a SQLite plan for the D447 proof (D881).
3
+ *
4
+ * `EXPLAIN QUERY PLAN` prints `USING INDEX` whether or not the table row is
5
+ * read behind that index entry, so it cannot prove an aggregate is served from
6
+ * the index alone. The VDBE program can: a statement that never reads the
7
+ * table has no `Column` opcode on the table's cursor. Both readers live here
8
+ * because the specs and the `check-records-index-served` guard must agree.
9
+ */
10
+ import type Database from "better-sqlite3";
11
+ export interface ExplainedStatement {
12
+ readonly sql: string;
13
+ readonly params: readonly unknown[];
14
+ }
15
+ /** The `EXPLAIN QUERY PLAN` detail lines of a statement. */
16
+ export declare function planOf(db: Database.Database, s: ExplainedStatement): readonly string[];
17
+ /** How many `Column` opcodes read the `records` TABLE (not an index) in this statement. */
18
+ export declare function tableColumnReads(db: Database.Database, s: ExplainedStatement): number;
@@ -0,0 +1,90 @@
1
+ /**
2
+ * Statement planning for `records` (D881): turn a request into SQL that is
3
+ * served by an index BY CONSTRUCTION, or refuse it.
4
+ *
5
+ * - `where` accepts declared index fields only: the equality fields must be the
6
+ * LEADING fields of one declared index (any order), with at most one range on
7
+ * the field right after them. Anything else throws {@link UnindexedFilterError}
8
+ * - an unindexed filter does not compile.
9
+ * - Every index path is named with `INDEXED BY`: if SQLite cannot use that index
10
+ * for the statement it ERRORS instead of quietly scanning.
11
+ * - Pagination is keyset (`(key..., rid) > cursor`); there is no offset.
12
+ *
13
+ * Pure: no database in here, so the statements are testable (and EXPLAINed) on
14
+ * their own.
15
+ */
16
+ import { type RecordKindDeclaration, type RecordWhere } from "@camstack/types";
17
+ type Bindable = string | number | null;
18
+ /** A declared index and what a `where` leaves for ordering. */
19
+ export interface WherePlan {
20
+ readonly index: RecordKindDeclaration["indexes"][number];
21
+ readonly clauses: readonly string[];
22
+ readonly params: readonly Bindable[];
23
+ /** The index fields after the equality prefix: the order a `where` query returns in. */
24
+ readonly orderFields: readonly string[];
25
+ }
26
+ /**
27
+ * Find the declared index that serves `where` (plus any `extraFields` the
28
+ * statement reads: an aggregate's value and group columns), or throw.
29
+ */
30
+ export declare function planWhere(kind: string, decl: RecordKindDeclaration, where: RecordWhere | undefined, opts: {
31
+ readonly allowIn: boolean;
32
+ readonly extraFields?: readonly string[];
33
+ }): WherePlan | null;
34
+ /** The built-in filters shared by every statement. */
35
+ export interface WindowFilter {
36
+ readonly deviceId?: number | undefined;
37
+ readonly atFrom?: number | undefined;
38
+ readonly atTo?: number | undefined;
39
+ }
40
+ /** The built-in index that serves a device/time window. */
41
+ export declare function windowIndex(w: WindowFilter): string;
42
+ export interface PlannedStatement {
43
+ readonly sql: string;
44
+ readonly params: readonly Bindable[];
45
+ }
46
+ export interface PlannedQuery extends PlannedStatement {
47
+ /** Names the access path; a cursor is valid only for the plan that issued it. */
48
+ readonly tag: string;
49
+ /** How many `_k<i>` cursor-key columns the statement selects (before `rid`). */
50
+ readonly keyCount: number;
51
+ }
52
+ export interface QueryRequest extends WindowFilter {
53
+ readonly where?: RecordWhere | undefined;
54
+ readonly order?: "at" | "id" | undefined;
55
+ readonly desc?: boolean | undefined;
56
+ readonly limit: number;
57
+ /** A decoded cursor: the tag of the plan that issued it, and its key values. */
58
+ readonly cursor?: {
59
+ readonly tag: string;
60
+ readonly values: readonly Bindable[];
61
+ } | undefined;
62
+ }
63
+ export declare function planQuery(kind: string, kindId: number, decl: RecordKindDeclaration, q: QueryRequest): PlannedQuery;
64
+ export interface CountRequest extends WindowFilter {
65
+ readonly where?: RecordWhere | undefined;
66
+ }
67
+ export declare function planCount(kind: string, kindId: number, decl: RecordKindDeclaration, q: CountRequest): PlannedStatement;
68
+ export interface HistogramRequest {
69
+ readonly deviceId?: number | undefined;
70
+ readonly from: number;
71
+ readonly to: number;
72
+ readonly bucketMs: number;
73
+ }
74
+ /** Buckets one answer may carry: a histogram is a chart, not an export. */
75
+ export declare const HISTOGRAM_MAX_BUCKETS = 1e4;
76
+ export declare function planHistogram(kind: string, kindId: number, decl: RecordKindDeclaration, q: HistogramRequest): PlannedStatement;
77
+ export interface AggregateRequest {
78
+ readonly op: "sum" | "min" | "max" | "count";
79
+ readonly field?: string | undefined;
80
+ readonly groupBy?: string | undefined;
81
+ readonly where?: RecordWhere | undefined;
82
+ }
83
+ export declare function planAggregate(kind: string, kindId: number, decl: RecordKindDeclaration, q: AggregateRequest): PlannedStatement;
84
+ export interface DeleteWhereRequest extends WindowFilter {
85
+ readonly atBefore?: number | undefined;
86
+ readonly where?: RecordWhere | undefined;
87
+ }
88
+ /** The subselect that names up to `limit` rows to delete. */
89
+ export declare function planDeleteWhere(kind: string, kindId: number, decl: RecordKindDeclaration, q: DeleteWhereRequest, limit: number): PlannedStatement;
90
+ export {};
@@ -0,0 +1,33 @@
1
+ /**
2
+ * The index-served proof of a record kind (D881, D447), run on the statements
3
+ * the engine REALLY prepares.
4
+ *
5
+ * A kind is declared with its indexes; this builds an in-memory engine, declares
6
+ * the kind, runs every operation the kind's declaration promises (by device and
7
+ * time, by id, by each declared index at each prefix length, the counts, the
8
+ * histogram, the aggregates over indexed fields, the bounded deletes) with a
9
+ * spy on `prepare`, and then EXPLAINs each captured statement:
10
+ *
11
+ * - no plan may be a table SCAN, and none may sort (`TEMP B-TREE FOR ORDER BY`);
12
+ * - every statement names `kind_id` as a literal (the partial indexes need it);
13
+ * - a count and an aggregate must not read the table row - checked on the
14
+ * opcodes, because `EXPLAIN QUERY PLAN` says `USING INDEX` either way.
15
+ *
16
+ * It is what `scripts/check-records-index-served.ts` runs over every declared
17
+ * kind, and what its spec runs over a deliberately broken one.
18
+ */
19
+ import Database from "better-sqlite3";
20
+ import type { DefinedRecordKind } from "@camstack/types";
21
+ export interface ProbeProblem {
22
+ readonly kind: string;
23
+ readonly op: string;
24
+ readonly problem: string;
25
+ }
26
+ export interface Captured {
27
+ readonly sql: string;
28
+ readonly params: readonly unknown[];
29
+ }
30
+ /** Judge one prepared statement. Exported so a spec can plant a bad one and watch it fail. */
31
+ export declare function judgeStatement(db: Database.Database, kind: string, op: string, statement: Captured): readonly ProbeProblem[];
32
+ /** Probe one kind. An empty answer means every promised operation is served from an index. */
33
+ export declare function probeKind(def: DefinedRecordKind): Promise<readonly ProbeProblem[]>;
@@ -0,0 +1,59 @@
1
+ /**
2
+ * The `records` capability of the engine (D881): resolves WHO is calling and
3
+ * WHICH file a kind lives in, then hands the call to that file's records engine.
4
+ *
5
+ * - **Owner** = the transport-stamped `callerAddonId` (as device-history's
6
+ * `not-owner` and the keychain). It is never a field of any input; a call with
7
+ * no stamp is refused `records/no-caller`.
8
+ * - **Placement**: a kind's file is its declaration's `database`. The map is
9
+ * rebuilt from each open file's own `_record_kinds` at boot and updated on
10
+ * declaration. A kind declared for another file than the one it lives in is
11
+ * refused (the engine never moves data, D853).
12
+ * - **The boot guard**: after a declaration, a kind with `adoptsFrom` that is
13
+ * EMPTY while its legacy table still holds rows is reported `adoptionPending`;
14
+ * `ctx.records.declare` turns that into a refusal to start.
15
+ */
16
+ import { type IRecordsProvider, type IScopedLogger, type IStorageAdminProvider, type StorageKind } from "@camstack/types";
17
+ import type { PlacementRouter } from "../placement-router.js";
18
+ /** The caller the transport stamped, or a refusal. */
19
+ export declare function recordsCallerOf(input: unknown): string;
20
+ type AdminRecords = Pick<IStorageAdminProvider, "listKinds" | "readRecords" | "adoptRecords">;
21
+ export declare class RecordsProvider implements IRecordsProvider, AdminRecords {
22
+ private readonly router;
23
+ private readonly logger;
24
+ private readonly placement;
25
+ constructor(router: PlacementRouter, logger: IScopedLogger);
26
+ /** Rebuild the placement map from every open file's catalog. Call once, after the files are open. */
27
+ init(): Promise<void>;
28
+ private fileOf;
29
+ declareKinds(input: Parameters<IRecordsProvider["declareKinds"]>[0]): ReturnType<IRecordsProvider["declareKinds"]>;
30
+ /** The boot guard: empty kind + a legacy table that still has rows = adoption pending. */
31
+ private withAdoption;
32
+ get(input: Parameters<IRecordsProvider["get"]>[0]): ReturnType<IRecordsProvider["get"]>;
33
+ getMany(input: Parameters<IRecordsProvider["getMany"]>[0]): ReturnType<IRecordsProvider["getMany"]>;
34
+ secretRefs(input: Parameters<IRecordsProvider["secretRefs"]>[0]): ReturnType<IRecordsProvider["secretRefs"]>;
35
+ put(input: Parameters<IRecordsProvider["put"]>[0]): ReturnType<IRecordsProvider["put"]>;
36
+ putMany(input: Parameters<IRecordsProvider["putMany"]>[0]): ReturnType<IRecordsProvider["putMany"]>;
37
+ patch(input: Parameters<IRecordsProvider["patch"]>[0]): ReturnType<IRecordsProvider["patch"]>;
38
+ delete(input: Parameters<IRecordsProvider["delete"]>[0]): ReturnType<IRecordsProvider["delete"]>;
39
+ deleteMany(input: Parameters<IRecordsProvider["deleteMany"]>[0]): ReturnType<IRecordsProvider["deleteMany"]>;
40
+ query(input: Parameters<IRecordsProvider["query"]>[0]): ReturnType<IRecordsProvider["query"]>;
41
+ count(input: Parameters<IRecordsProvider["count"]>[0]): ReturnType<IRecordsProvider["count"]>;
42
+ histogram(input: Parameters<IRecordsProvider["histogram"]>[0]): ReturnType<IRecordsProvider["histogram"]>;
43
+ aggregate(input: Parameters<IRecordsProvider["aggregate"]>[0]): ReturnType<IRecordsProvider["aggregate"]>;
44
+ deleteWhere(input: Parameters<IRecordsProvider["deleteWhere"]>[0]): ReturnType<IRecordsProvider["deleteWhere"]>;
45
+ allocateId(input: Parameters<IRecordsProvider["allocateId"]>[0]): ReturnType<IRecordsProvider["allocateId"]>;
46
+ /** The D865 hook body: every file that holds kinds of this owner. */
47
+ deleteDevice(input: Parameters<IRecordsProvider["deleteDevice"]>[0]): ReturnType<IRecordsProvider["deleteDevice"]>;
48
+ listKinds(input: {
49
+ readonly owner?: string | undefined;
50
+ readonly counts?: boolean | undefined;
51
+ }): Promise<{
52
+ kinds: StorageKind[];
53
+ }>;
54
+ readRecords(input: Parameters<IStorageAdminProvider["readRecords"]>[0]): ReturnType<IStorageAdminProvider["readRecords"]>;
55
+ adoptRecords(input: Parameters<IStorageAdminProvider["adoptRecords"]>[0]): ReturnType<IStorageAdminProvider["adoptRecords"]>;
56
+ /** Housekeeping: delete rows past `expires_at` in every file that hosts kinds. */
57
+ sweepExpired(): Promise<number>;
58
+ }
59
+ export {};
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Row-level helpers of the records engine (D881): validating a write against
3
+ * its kind, enforcing the secret-field rules, and shaping what a read returns.
4
+ * Pure - no database here.
5
+ */
6
+ import { type RecordInput, type RecordKindDeclaration, type RecordView } from "@camstack/types";
7
+ /** A row as `SELECT` returns it (the `data` column is `json(data)`). */
8
+ export interface StoredRow {
9
+ readonly rid: number;
10
+ readonly id: string;
11
+ readonly deviceId: number | null;
12
+ readonly at: number | null;
13
+ readonly expiresAt: number | null;
14
+ readonly rev: number;
15
+ readonly updatedAt: number;
16
+ readonly data: string;
17
+ }
18
+ export declare function parseBody(text: string): Record<string, unknown>;
19
+ /** What a write is allowed to say about a row, after the kind's rules. */
20
+ export interface WriteHeader {
21
+ readonly id: string;
22
+ readonly deviceId: number | null;
23
+ readonly at: number | null;
24
+ readonly expiresAt: number | null;
25
+ }
26
+ export declare function validateHeader(kind: string, decl: RecordKindDeclaration, rec: Pick<RecordInput, "id" | "deviceId" | "at" | "expiresAt">, nowMs: number): WriteHeader;
27
+ /** Serialize the body and enforce `maxDataBytes`; `onTooLarge` is called (rate-limited) before throwing. */
28
+ export declare function serializeBody(kind: string, decl: RecordKindDeclaration, id: string, body: Record<string, unknown>, onTooLarge: (kind: string, bytes: number) => void, nowMs: number): string;
29
+ /** Markers found in a stored body, by concrete path. */
30
+ export declare function markersOf(decl: RecordKindDeclaration, body: unknown): ReadonlyMap<string, string>;
31
+ /** Show a stored body: every set secret field becomes the redaction token. */
32
+ export declare function redactBody(decl: RecordKindDeclaration, body: Record<string, unknown>): Record<string, unknown>;
33
+ /**
34
+ * The secret rule of a write (D878, once, in the store): a secret field may
35
+ * hold ONLY a keychain marker bound to this row's own ref. The redaction token
36
+ * keeps the stored marker (or leaves the field absent when nothing is stored);
37
+ * `null` removes; a plain string is refused - plaintext is never stored.
38
+ *
39
+ * `stored` is the row's current body (null for a new row). Returns the body to
40
+ * write and the refs the write RELEASES (markers that were stored and are gone).
41
+ */
42
+ export declare function enforceSecrets(owner: string, kind: string, decl: RecordKindDeclaration, id: string, body: Record<string, unknown>, stored: Record<string, unknown> | null, mode: "put" | "patch"): {
43
+ readonly body: Record<string, unknown>;
44
+ readonly releasedRefs: readonly string[];
45
+ };
46
+ export declare function toView(row: StoredRow, decl: RecordKindDeclaration): RecordView;
@@ -0,0 +1,41 @@
1
+ /**
2
+ * @durable class=ledger owner=records
3
+ * write="every ctx.records write of an owning addon: one row per record of any declared kind, keyed (kind_id, id)"
4
+ * retention="per kind, declared with the kind: an engine ttl, a row bound, the owner's own sweep, or none - the reason is shown in System -> Data"
5
+ */
6
+ export declare const RECORDS_TABLE = "records";
7
+ /**
8
+ * @durable class=config owner=records
9
+ * write="an owner declares its kinds when its addon boots (declareKinds): one row per (owner, kind)"
10
+ * retention="none - a kind row is never removed by this release; a kind no installed package declares is only marked orphaned"
11
+ */
12
+ export declare const RECORD_KINDS_TABLE = "_record_kinds";
13
+ /**
14
+ * @durable class=config owner=records
15
+ * write="allocateId on a kind: one counter row per kind"
16
+ * retention="none - the counter must never go back, so it is never pruned"
17
+ */
18
+ export declare const RECORD_SEQUENCES_TABLE = "_record_sequences";
19
+ /** The statements that create the shared tables. Idempotent. */
20
+ export declare const RECORDS_SCHEMA_SQL = "\nCREATE TABLE IF NOT EXISTS records (\n rid INTEGER PRIMARY KEY,\n kind_id INTEGER NOT NULL,\n id TEXT NOT NULL,\n device_id INTEGER,\n at INTEGER,\n expires_at INTEGER,\n rev INTEGER NOT NULL DEFAULT 1,\n updated_at INTEGER NOT NULL,\n data BLOB NOT NULL,\n UNIQUE (kind_id, id)\n);\nCREATE INDEX IF NOT EXISTS records_kind_device_at ON records(kind_id, device_id, at);\nCREATE INDEX IF NOT EXISTS records_kind_at ON records(kind_id, at);\nCREATE INDEX IF NOT EXISTS records_expires ON records(expires_at) WHERE expires_at IS NOT NULL;\nCREATE TABLE IF NOT EXISTS _record_kinds (\n kind_id INTEGER PRIMARY KEY AUTOINCREMENT,\n owner TEXT NOT NULL,\n kind TEXT NOT NULL,\n database TEXT NOT NULL,\n manifest_hash TEXT NOT NULL,\n declaration TEXT NOT NULL,\n state TEXT NOT NULL,\n orphaned_at INTEGER,\n adopted_at INTEGER,\n UNIQUE (owner, kind)\n);\nCREATE TABLE IF NOT EXISTS _record_sequences (\n kind_id INTEGER PRIMARY KEY,\n next INTEGER NOT NULL\n);\n";
21
+ /** The tables the records engine owns: never droppable through `dropCollection`. */
22
+ export declare const RECORDS_ENGINE_TABLES: readonly string[];
23
+ /** The built-in indexes. */
24
+ export declare const INDEX_KIND_DEVICE_AT = "records_kind_device_at";
25
+ export declare const INDEX_KIND_AT = "records_kind_at";
26
+ export declare const INDEX_EXPIRES = "records_expires";
27
+ /**
28
+ * The expression of a record field: `json_extract("data", '$.a.b')`. THE one
29
+ * renderer. Throws on a path outside the safe charset (it lands in DDL).
30
+ */
31
+ export declare function recordFieldExpr(field: string): string;
32
+ /** `kind_id = 17` as text; the argument must be a safe integer from the catalog. */
33
+ export declare function kindClause(kindId: number): string;
34
+ /** The physical name of a declared index. */
35
+ export declare function indexName(kindId: number, name: string): string;
36
+ /** The exact DDL of one declared index. Compared character for character when it already exists. */
37
+ export declare function indexDdl(kindId: number, index: {
38
+ readonly name: string;
39
+ readonly fields: readonly string[];
40
+ readonly unique?: boolean | undefined;
41
+ }): string;
@@ -403,14 +403,6 @@ export declare class SqliteSettingsBackend implements ISettingsBackend {
403
403
  getAllAddon(addonId: string): Record<string, unknown>;
404
404
  /** Bulk-set all settings for an addon */
405
405
  setAllAddon(addonId: string, config: Record<string, unknown>): void;
406
- /** Get all settings for a provider */
407
- getAllProvider(providerId: string): Record<string, unknown>;
408
- /** Set a provider setting */
409
- setProvider(providerId: string, key: string, value: unknown): void;
410
- /** Get all settings for a device */
411
- getAllDevice(deviceId: string): Record<string, unknown>;
412
- /** Set a device setting */
413
- setDevice(deviceId: string, key: string, value: unknown): void;
414
406
  /**
415
407
  * The device's whole cap-slice map. `{}` — never null — when the device has never persisted a slice,
416
408
  * because every caller treats the result as a cap map to iterate.
@@ -556,8 +548,6 @@ export declare class SqliteSettingsBackend implements ISettingsBackend {
556
548
  * function refuses degrades in speed and never in correctness.
557
549
  */
558
550
  private prefixWhere;
559
- private getAllScoped;
560
- private setScopedKey;
561
551
  private scopedName;
562
552
  /**
563
553
  * Project a declared collection onto what {@link compileFilter} needs. A
@@ -32,6 +32,8 @@ import type { SqliteVecVectorIndex } from "./vector-index-vec.js";
32
32
  export declare class SqliteSettingsAddon extends BaseAddon {
33
33
  private settingsFile;
34
34
  private router;
35
+ private sweepTimer;
36
+ private sweepStart;
35
37
  constructor();
36
38
  /**
37
39
  * The engine behind `settings-store` cannot read that door during
@@ -50,6 +52,12 @@ export declare class SqliteSettingsAddon extends BaseAddon {
50
52
  * open file).
51
53
  */
52
54
  private purgeBeforeServing;
55
+ /**
56
+ * The engine's TTL sweep (D881): every 10 minutes, off the boot path (the
57
+ * first run a minute after boot), batched and yielding. A failed pass is
58
+ * warned about and the next one retries; it never blocks anything.
59
+ */
60
+ private startRecordsSweep;
53
61
  protected onShutdown(): Promise<void>;
54
62
  getBackend(): SqliteSettingsBackend | null;
55
63
  /** The placement router; null before `onInitialize`. */