@camstack/system 1.2.28 → 1.2.30
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/addon-runner.js +3 -3
- package/dist/addon-runner.mjs +2 -2
- package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.js +1 -1
- package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.mjs +1 -1
- package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.js +1 -1
- package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.mjs +1 -1
- package/dist/builtins/alerts/alerts.addon.d.ts +0 -7
- package/dist/builtins/alerts/alerts.addon.js +47 -25
- package/dist/builtins/alerts/alerts.addon.mjs +47 -25
- package/dist/builtins/alerts/prune-policy.d.ts +44 -0
- package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.js +1 -1
- package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.mjs +1 -1
- package/dist/builtins/console-logging/index.js +1 -1
- package/dist/builtins/console-logging/index.mjs +1 -1
- package/dist/builtins/device-manager/device-manager.addon.js +1 -1
- package/dist/builtins/device-manager/device-manager.addon.mjs +1 -1
- package/dist/builtins/doorbell/virtual-doorbell.addon.js +1 -1
- package/dist/builtins/doorbell/virtual-doorbell.addon.mjs +1 -1
- package/dist/builtins/hub-forwarder/index.js +1 -1
- package/dist/builtins/hub-forwarder/index.mjs +1 -1
- package/dist/builtins/local-auth/local-auth.addon.js +1 -1
- package/dist/builtins/local-auth/local-auth.addon.mjs +1 -1
- package/dist/builtins/local-network/local-network.addon.js +1 -1
- package/dist/builtins/local-network/local-network.addon.mjs +1 -1
- package/dist/builtins/loki-logging/index.js +1 -1
- package/dist/builtins/loki-logging/index.mjs +1 -1
- package/dist/builtins/native-metrics/native-metrics.addon.js +1 -1
- package/dist/builtins/native-metrics/native-metrics.addon.mjs +1 -1
- package/dist/builtins/platform-probe/index.js +1 -1
- package/dist/builtins/platform-probe/index.mjs +1 -1
- package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.js +1 -1
- package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.mjs +1 -1
- package/dist/builtins/snapshot/index.js +1 -1
- package/dist/builtins/snapshot/index.mjs +1 -1
- package/dist/builtins/sqlite-storage/filesystem-storage.addon.js +1 -1
- package/dist/builtins/sqlite-storage/filesystem-storage.addon.mjs +1 -1
- package/dist/builtins/sqlite-storage/filter-compiler.d.ts +61 -0
- package/dist/builtins/sqlite-storage/sqlite-settings-backend.d.ts +50 -1
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.d.ts +8 -2
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.js +136 -64
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +136 -64
- package/dist/builtins/storage-orchestrator/data-store-dispatch.d.ts +12 -0
- package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.d.ts +18 -7
- package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.js +80 -11
- package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.mjs +80 -11
- package/dist/builtins/system-config/system-config.addon.js +1 -1
- package/dist/builtins/system-config/system-config.addon.mjs +1 -1
- package/dist/builtins/winston-logging/index.js +1 -1
- package/dist/builtins/winston-logging/index.mjs +1 -1
- package/dist/{dist-BXmFvn3h.mjs → dist-Dr_P0DOB.mjs} +492 -166
- package/dist/{dist-BxtIzexq.js → dist-VIwdvws5.js} +497 -165
- package/dist/index.d.ts +2 -0
- package/dist/index.js +150 -2
- package/dist/index.mjs +146 -3
- package/dist/kernel/addon-installer.d.ts +38 -0
- package/dist/kernel/heap-watch.d.ts +36 -0
- package/dist/kernel/transport/uds-event-bus.d.ts +4 -1
- package/dist/{manifest-python-deps-KZ00cbfP.js → manifest-python-deps-BqE5j0-O.js} +25 -4
- package/dist/{manifest-python-deps-BNOApgK0.mjs → manifest-python-deps-Ck4-9K9m.mjs} +25 -4
- package/package.json +7 -3
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One filter compiler for both the read and the bulk-mutation paths.
|
|
3
|
+
*
|
|
4
|
+
* It exists because those two paths must NOT behave the same way, and until
|
|
5
|
+
* now the only compiler lived inline in `query` — where being forgiving is
|
|
6
|
+
* correct. Reusing it verbatim for `deleteWhere` would have shipped two
|
|
7
|
+
* data-loss shapes:
|
|
8
|
+
*
|
|
9
|
+
* - **A dropped predicate.** A field that is not a column of a structured
|
|
10
|
+
* table is skipped, so `{ deviceId: 615, kind: 'motion' }` can compile to
|
|
11
|
+
* `WHERE "deviceId" = ?` — on a SELECT that returns extra rows, on a DELETE
|
|
12
|
+
* it removes rows the caller meant to keep.
|
|
13
|
+
* - **No predicate at all.** An empty filter compiles to an empty WHERE,
|
|
14
|
+
* which on a DELETE is the entire collection.
|
|
15
|
+
*
|
|
16
|
+
* `select` mode keeps the historical behaviour, because every existing caller
|
|
17
|
+
* was written against it. `mutate` mode refuses both, before any SQL exists.
|
|
18
|
+
* Emptying a collection is a legitimate thing to want, and it should be a
|
|
19
|
+
* differently-named operation rather than a filter that happens to be empty.
|
|
20
|
+
*/
|
|
21
|
+
/** The part of a declared collection this compiler needs. */
|
|
22
|
+
export interface CollectionShape {
|
|
23
|
+
readonly primaryKey: string;
|
|
24
|
+
readonly columns: ReadonlySet<string>;
|
|
25
|
+
/**
|
|
26
|
+
* The single JSON blob column for a KV-shaped collection (fields inside it
|
|
27
|
+
* are reachable via `json_extract`), or `null` for a structured table whose
|
|
28
|
+
* fields are real columns.
|
|
29
|
+
*/
|
|
30
|
+
readonly kvBlobColumn: string | null;
|
|
31
|
+
}
|
|
32
|
+
export type FilterMode = 'select' | 'mutate';
|
|
33
|
+
/** Marshals a JS value to what the driver should bind. */
|
|
34
|
+
export type SerializeValue = (value: unknown) => unknown;
|
|
35
|
+
export interface QueryFilterInput {
|
|
36
|
+
readonly where?: Readonly<Record<string, unknown>>;
|
|
37
|
+
readonly whereIn?: Readonly<Record<string, readonly unknown[]>>;
|
|
38
|
+
readonly whereBetween?: Readonly<Record<string, readonly [unknown, unknown]>>;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* The predicate-only filter a bulk mutation accepts — no ordering, no limit.
|
|
42
|
+
* Structurally the mutation half of {@link QueryFilterInput}.
|
|
43
|
+
*/
|
|
44
|
+
export type MutationFilterInput = QueryFilterInput;
|
|
45
|
+
export interface CompiledFilter {
|
|
46
|
+
/** Either `''` or a leading-space ` WHERE …` clause, ready to concatenate. */
|
|
47
|
+
readonly whereSql: string;
|
|
48
|
+
readonly params: readonly unknown[];
|
|
49
|
+
}
|
|
50
|
+
/** Thrown by `mutate` mode. Distinct type so a caller can map it to a 400. */
|
|
51
|
+
export declare class UnsafeFilterError extends Error {
|
|
52
|
+
constructor(message: string);
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* SQL expression for `field` on this collection, or `null` when it cannot be
|
|
56
|
+
* expressed: a real column → `"field"`; a KV blob field → `json_extract`;
|
|
57
|
+
* anything else on a structured table → unresolvable. Exported because
|
|
58
|
+
* `histogram` needs the same resolution for its bucketed column.
|
|
59
|
+
*/
|
|
60
|
+
export declare function fieldExprFor(field: string, shape: CollectionShape): string | null;
|
|
61
|
+
export declare function compileFilter(filter: QueryFilterInput | undefined, shape: CollectionShape, mode: FilterMode, serialize: SerializeValue): CompiledFilter;
|
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
import { default as Database } from 'better-sqlite3';
|
|
2
|
-
import { ISettingsBackend, SettingsRecord, TableSchema, TableQueryOptions, SettingsGetInput, SettingsSetInput, SettingsQueryInput, SettingsInsertInput, SettingsUpdateInput, SettingsDeleteInput, SettingsCountInput, SettingsIsEmptyInput, SettingsHistogramInput, HistogramBucket, CollectionColumn, CollectionIndex } from '@camstack/types';
|
|
2
|
+
import { DataStoreEngineInfo, ISettingsBackend, SettingsRecord, TableSchema, TableQueryOptions, SettingsGetInput, SettingsSetInput, SettingsQueryInput, SettingsInsertInput, SettingsUpdateInput, SettingsDeleteInput, SettingsCountInput, SettingsIsEmptyInput, SettingsHistogramInput, HistogramBucket, CollectionColumn, CollectionIndex } from '@camstack/types';
|
|
3
|
+
import { MutationFilterInput } from './filter-compiler.js';
|
|
4
|
+
/** Input for {@link SqliteSettingsBackend.deleteWhere}. */
|
|
5
|
+
interface SettingsDeleteWhereInput {
|
|
6
|
+
readonly namespace?: string;
|
|
7
|
+
readonly collection: string;
|
|
8
|
+
readonly filter: MutationFilterInput;
|
|
9
|
+
}
|
|
10
|
+
interface SettingsDeleteWhereResult {
|
|
11
|
+
readonly deleted: number;
|
|
12
|
+
}
|
|
13
|
+
/** Input for {@link SqliteSettingsBackend.updateWhere}. */
|
|
14
|
+
interface SettingsUpdateWhereInput {
|
|
15
|
+
readonly namespace?: string;
|
|
16
|
+
readonly collection: string;
|
|
17
|
+
readonly filter: MutationFilterInput;
|
|
18
|
+
readonly data: Readonly<Record<string, unknown>>;
|
|
19
|
+
}
|
|
20
|
+
interface SettingsUpdateWhereResult {
|
|
21
|
+
readonly updated: number;
|
|
22
|
+
}
|
|
3
23
|
/**
|
|
4
24
|
* SQLite implementation of ISettingsBackend.
|
|
5
25
|
*
|
|
@@ -39,12 +59,35 @@ export declare class SqliteSettingsBackend implements ISettingsBackend {
|
|
|
39
59
|
*/
|
|
40
60
|
private decodeColumnValue;
|
|
41
61
|
shutdown(): Promise<void>;
|
|
62
|
+
/**
|
|
63
|
+
* `data-store-provider` self-description — how the orchestrator names
|
|
64
|
+
* this engine in a log line, and (once collections declare an engine)
|
|
65
|
+
* how it picks between registrants.
|
|
66
|
+
*/
|
|
67
|
+
getEngineInfo(): Promise<DataStoreEngineInfo>;
|
|
42
68
|
get({ namespace, collection, key }: SettingsGetInput): Promise<unknown>;
|
|
43
69
|
set({ namespace, collection, key, value }: SettingsSetInput): Promise<void>;
|
|
44
70
|
query<T extends object = Record<string, unknown>>({ namespace, collection, filter, }: SettingsQueryInput): Promise<readonly SettingsRecord<T>[]>;
|
|
45
71
|
insert<T extends object = Record<string, unknown>>({ namespace, collection, record, }: SettingsInsertInput<T>): Promise<void>;
|
|
46
72
|
update({ namespace, collection, id, data }: SettingsUpdateInput): Promise<void>;
|
|
47
73
|
delete({ namespace, collection, key }: SettingsDeleteInput): Promise<void>;
|
|
74
|
+
/**
|
|
75
|
+
* Bulk delete. One statement, whatever the row count — this is what every
|
|
76
|
+
* retention path in the system was hand-rolling as a select-then-delete
|
|
77
|
+
* loop. The filter is compiled in `mutate` mode, so an unresolvable
|
|
78
|
+
* predicate or an empty one throws before any SQL exists.
|
|
79
|
+
*/
|
|
80
|
+
deleteWhere({ namespace, collection, filter, }: SettingsDeleteWhereInput): Promise<SettingsDeleteWhereResult>;
|
|
81
|
+
/**
|
|
82
|
+
* Bulk update. Same filter contract as {@link deleteWhere}.
|
|
83
|
+
*
|
|
84
|
+
* Every key of `data` must be a REAL column. A KV-shaped collection keeps
|
|
85
|
+
* its fields inside a JSON blob, and setting one would need `json_set` with
|
|
86
|
+
* read-modify-write semantics this method does not have — so it refuses
|
|
87
|
+
* rather than writing a column that does not exist, which is what the
|
|
88
|
+
* uncapped `tableUpdate` would have done.
|
|
89
|
+
*/
|
|
90
|
+
updateWhere({ namespace, collection, filter, data, }: SettingsUpdateWhereInput): Promise<SettingsUpdateWhereResult>;
|
|
48
91
|
count({ namespace, collection, filter }: SettingsCountInput): Promise<number>;
|
|
49
92
|
histogram({ namespace, collection, field, bucketSize, origin, filter, }: SettingsHistogramInput): Promise<readonly HistogramBucket[]>;
|
|
50
93
|
isEmpty({ namespace, collection }: SettingsIsEmptyInput): Promise<boolean>;
|
|
@@ -82,6 +125,12 @@ export declare class SqliteSettingsBackend implements ISettingsBackend {
|
|
|
82
125
|
private getAllScoped;
|
|
83
126
|
private setScopedKey;
|
|
84
127
|
private scopedName;
|
|
128
|
+
/**
|
|
129
|
+
* Project a declared collection onto what {@link compileFilter} needs. A
|
|
130
|
+
* collection whose only column is `data` is KV-shaped: its fields live
|
|
131
|
+
* inside that blob and are reached with `json_extract`.
|
|
132
|
+
*/
|
|
133
|
+
private shapeOf;
|
|
85
134
|
declareCollection(input: {
|
|
86
135
|
namespace?: string;
|
|
87
136
|
collection: string;
|
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
import { ProviderRegistration, BaseAddon } from '@camstack/types';
|
|
2
2
|
import { SqliteSettingsBackend } from './sqlite-settings-backend.js';
|
|
3
3
|
/**
|
|
4
|
-
* SQLite Settings addon —
|
|
5
|
-
*
|
|
4
|
+
* SQLite Settings addon — the relational ENGINE behind the data door.
|
|
5
|
+
*
|
|
6
|
+
* Capability: `data-store-provider` (collection, internal). It does not
|
|
7
|
+
* serve `settings-store`: that is the public door, owned by
|
|
8
|
+
* storage-orchestrator, which dispatches here
|
|
9
|
+
* ([D44](../../../../../docs/decisions/adr-0044.md)). Callers reach this
|
|
10
|
+
* engine only through the door — which is the point, because it is what
|
|
11
|
+
* makes the engine replaceable.
|
|
6
12
|
*/
|
|
7
13
|
export declare class SqliteSettingsAddon extends BaseAddon {
|
|
8
14
|
private backend;
|
|
@@ -3,10 +3,66 @@ 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-
|
|
6
|
+
const require_dist = require("../../dist-VIwdvws5.js");
|
|
7
7
|
let node_crypto = require("node:crypto");
|
|
8
8
|
let better_sqlite3 = require("better-sqlite3");
|
|
9
9
|
better_sqlite3 = require_chunk.__toESM(better_sqlite3);
|
|
10
|
+
//#region src/builtins/sqlite-storage/filter-compiler.ts
|
|
11
|
+
/** Thrown by `mutate` mode. Distinct type so a caller can map it to a 400. */
|
|
12
|
+
var UnsafeFilterError = class extends Error {
|
|
13
|
+
constructor(message) {
|
|
14
|
+
super(message);
|
|
15
|
+
this.name = "UnsafeFilterError";
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* SQL expression for `field` on this collection, or `null` when it cannot be
|
|
20
|
+
* expressed: a real column → `"field"`; a KV blob field → `json_extract`;
|
|
21
|
+
* anything else on a structured table → unresolvable. Exported because
|
|
22
|
+
* `histogram` needs the same resolution for its bucketed column.
|
|
23
|
+
*/
|
|
24
|
+
function fieldExprFor(field, shape) {
|
|
25
|
+
if (field === shape.primaryKey || shape.columns.has(field)) return `"${field}"`;
|
|
26
|
+
if (shape.kvBlobColumn !== null) return `json_extract("${shape.kvBlobColumn}", '$.${field}')`;
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
function compileFilter(filter, shape, mode, serialize) {
|
|
30
|
+
const clauses = [];
|
|
31
|
+
const params = [];
|
|
32
|
+
const resolve = (field) => {
|
|
33
|
+
const expr = fieldExprFor(field, shape);
|
|
34
|
+
if (expr === null && mode === "mutate") throw new UnsafeFilterError(`filter refers to "${field}", which this collection cannot express — refusing to run a bulk mutation with a dropped predicate`);
|
|
35
|
+
return expr;
|
|
36
|
+
};
|
|
37
|
+
for (const [field, value] of Object.entries(filter?.where ?? {})) {
|
|
38
|
+
const expr = resolve(field);
|
|
39
|
+
if (expr === null) continue;
|
|
40
|
+
clauses.push(`${expr} = ?`);
|
|
41
|
+
params.push(serialize(value));
|
|
42
|
+
}
|
|
43
|
+
for (const [field, values] of Object.entries(filter?.whereIn ?? {})) {
|
|
44
|
+
const expr = resolve(field);
|
|
45
|
+
if (expr === null) continue;
|
|
46
|
+
if (values.length === 0) {
|
|
47
|
+
if (mode === "mutate") throw new UnsafeFilterError(`filter has an empty whereIn list for "${field}" — it matches nothing, which is more likely a caller bug than an intent`);
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
clauses.push(`${expr} IN (${values.map(() => "?").join(", ")})`);
|
|
51
|
+
for (const v of values) params.push(serialize(v));
|
|
52
|
+
}
|
|
53
|
+
for (const [field, [low, high]] of Object.entries(filter?.whereBetween ?? {})) {
|
|
54
|
+
const expr = resolve(field);
|
|
55
|
+
if (expr === null) continue;
|
|
56
|
+
clauses.push(`${expr} BETWEEN ? AND ?`);
|
|
57
|
+
params.push(serialize(low), serialize(high));
|
|
58
|
+
}
|
|
59
|
+
if (clauses.length === 0 && mode === "mutate") throw new UnsafeFilterError("filter compiled to no predicate — that would affect every row in the collection; use the explicit clear operation if that is the intent");
|
|
60
|
+
return {
|
|
61
|
+
whereSql: clauses.length > 0 ? ` WHERE ${clauses.join(" AND ")}` : "",
|
|
62
|
+
params
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
//#endregion
|
|
10
66
|
//#region src/builtins/sqlite-storage/sqlite-settings-backend.ts
|
|
11
67
|
function parseRowData(raw) {
|
|
12
68
|
return require_dist.asJsonObject(require_dist.parseJsonUnknown(raw)) ?? {};
|
|
@@ -119,6 +175,18 @@ var SqliteSettingsBackend = class SqliteSettingsBackend {
|
|
|
119
175
|
this.db?.close();
|
|
120
176
|
this.db = null;
|
|
121
177
|
}
|
|
178
|
+
/**
|
|
179
|
+
* `data-store-provider` self-description — how the orchestrator names
|
|
180
|
+
* this engine in a log line, and (once collections declare an engine)
|
|
181
|
+
* how it picks between registrants.
|
|
182
|
+
*/
|
|
183
|
+
async getEngineInfo() {
|
|
184
|
+
return {
|
|
185
|
+
engineId: "sqlite",
|
|
186
|
+
kind: "relational",
|
|
187
|
+
displayName: "SQLite (WAL)"
|
|
188
|
+
};
|
|
189
|
+
}
|
|
122
190
|
async get({ namespace, collection, key }) {
|
|
123
191
|
const scoped = this.scopedName(namespace, collection);
|
|
124
192
|
const decl = this.requireDeclared(scoped);
|
|
@@ -177,6 +245,41 @@ var SqliteSettingsBackend = class SqliteSettingsBackend {
|
|
|
177
245
|
const decl = this.requireDeclared(scoped);
|
|
178
246
|
await this.tableDelete(scoped, { [decl.primaryKey]: key });
|
|
179
247
|
}
|
|
248
|
+
/**
|
|
249
|
+
* Bulk delete. One statement, whatever the row count — this is what every
|
|
250
|
+
* retention path in the system was hand-rolling as a select-then-delete
|
|
251
|
+
* loop. The filter is compiled in `mutate` mode, so an unresolvable
|
|
252
|
+
* predicate or an empty one throws before any SQL exists.
|
|
253
|
+
*/
|
|
254
|
+
async deleteWhere({ namespace, collection, filter }) {
|
|
255
|
+
const scoped = this.scopedName(namespace, collection);
|
|
256
|
+
const decl = this.requireDeclared(scoped);
|
|
257
|
+
const { whereSql, params } = compileFilter(filter, this.shapeOf(decl), "mutate", (v) => this.serializeColumnValue(v));
|
|
258
|
+
return { deleted: this.getDb().prepare(`DELETE FROM "${scoped}"${whereSql}`).run(...params).changes };
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* Bulk update. Same filter contract as {@link deleteWhere}.
|
|
262
|
+
*
|
|
263
|
+
* Every key of `data` must be a REAL column. A KV-shaped collection keeps
|
|
264
|
+
* its fields inside a JSON blob, and setting one would need `json_set` with
|
|
265
|
+
* read-modify-write semantics this method does not have — so it refuses
|
|
266
|
+
* rather than writing a column that does not exist, which is what the
|
|
267
|
+
* uncapped `tableUpdate` would have done.
|
|
268
|
+
*/
|
|
269
|
+
async updateWhere({ namespace, collection, filter, data }) {
|
|
270
|
+
const scoped = this.scopedName(namespace, collection);
|
|
271
|
+
const decl = this.requireDeclared(scoped);
|
|
272
|
+
const setClauses = [];
|
|
273
|
+
const setValues = [];
|
|
274
|
+
for (const [key, value] of Object.entries(data)) {
|
|
275
|
+
if (key !== decl.primaryKey && !decl.columns.has(key)) throw new UnsafeFilterError(`updateWhere cannot set "${key}" on "${scoped}" — it is not a column of this collection`);
|
|
276
|
+
setClauses.push(`"${key}" = ?`);
|
|
277
|
+
setValues.push(this.serializeColumnValue(value));
|
|
278
|
+
}
|
|
279
|
+
if (setClauses.length === 0) throw new UnsafeFilterError("updateWhere was given no fields to set");
|
|
280
|
+
const { whereSql, params } = compileFilter(filter, this.shapeOf(decl), "mutate", (v) => this.serializeColumnValue(v));
|
|
281
|
+
return { updated: this.getDb().prepare(`UPDATE "${scoped}" SET ${setClauses.join(", ")}${whereSql}`).run(...setValues, ...params).changes };
|
|
282
|
+
}
|
|
180
283
|
async count({ namespace, collection, filter }) {
|
|
181
284
|
const scoped = this.scopedName(namespace, collection);
|
|
182
285
|
const decl = this.requireDeclared(scoped);
|
|
@@ -204,30 +307,10 @@ var SqliteSettingsBackend = class SqliteSettingsBackend {
|
|
|
204
307
|
async histogram({ namespace, collection, field, bucketSize, origin, filter }) {
|
|
205
308
|
const scoped = this.scopedName(namespace, collection);
|
|
206
309
|
const decl = this.requireDeclared(scoped);
|
|
207
|
-
const
|
|
208
|
-
const
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
if (isKvShape) return `json_extract("data", '$.${f}')`;
|
|
212
|
-
return "";
|
|
213
|
-
};
|
|
214
|
-
const col = fieldExpr(field);
|
|
215
|
-
if (!col) return [];
|
|
216
|
-
const params = [];
|
|
217
|
-
const clauses = [];
|
|
218
|
-
if (filter?.where) for (const [f, value] of Object.entries(filter.where)) {
|
|
219
|
-
const expr = fieldExpr(f);
|
|
220
|
-
if (!expr) continue;
|
|
221
|
-
clauses.push(`${expr} = ?`);
|
|
222
|
-
params.push(this.serializeColumnValue(value));
|
|
223
|
-
}
|
|
224
|
-
if (filter?.whereBetween) for (const [f, [lo, hi]] of Object.entries(filter.whereBetween)) {
|
|
225
|
-
const expr = fieldExpr(f);
|
|
226
|
-
if (!expr) continue;
|
|
227
|
-
clauses.push(`${expr} BETWEEN ? AND ?`);
|
|
228
|
-
params.push(this.serializeColumnValue(lo), this.serializeColumnValue(hi));
|
|
229
|
-
}
|
|
230
|
-
const where = clauses.length > 0 ? ` WHERE ${clauses.join(" AND ")}` : "";
|
|
310
|
+
const shape = this.shapeOf(decl);
|
|
311
|
+
const col = fieldExprFor(field, shape);
|
|
312
|
+
if (col === null) return [];
|
|
313
|
+
const { whereSql: where, params } = compileFilter(filter, shape, "select", (v) => this.serializeColumnValue(v));
|
|
231
314
|
const sql = `SELECT ${`CAST((${col} - ?) / ? AS INTEGER)`} AS bucket, COUNT(*) AS count FROM "${scoped}"${where} GROUP BY bucket ORDER BY bucket`;
|
|
232
315
|
return this.getDb().prepare(sql).all(origin, bucketSize, ...params).map((r) => ({
|
|
233
316
|
bucket: r.bucket,
|
|
@@ -243,43 +326,13 @@ var SqliteSettingsBackend = class SqliteSettingsBackend {
|
|
|
243
326
|
const isKvShape = decl.columns.size === 1 && decl.columns.has("data");
|
|
244
327
|
let sql = `SELECT ${[`"${decl.primaryKey}"`, ...[...decl.columns].map((c) => `"${c}"`)].join(", ")} FROM "${table}"`;
|
|
245
328
|
const params = [];
|
|
246
|
-
const
|
|
247
|
-
const
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
* - real column → `"field"`
|
|
251
|
-
* - KV blob field (e.g. `username` inside `data`) → `json_extract`
|
|
252
|
-
* - structured table + non-column → empty string (filter dropped,
|
|
253
|
-
* matches legacy structured-table behaviour)
|
|
254
|
-
*/
|
|
255
|
-
const fieldExpr = (f) => {
|
|
256
|
-
if (isColumn(f)) return `"${f}"`;
|
|
257
|
-
if (isKvShape) return `json_extract("data", '$.${f}')`;
|
|
258
|
-
return "";
|
|
259
|
-
};
|
|
260
|
-
if (filter?.where) for (const [field, value] of Object.entries(filter.where)) {
|
|
261
|
-
const expr = fieldExpr(field);
|
|
262
|
-
if (!expr) continue;
|
|
263
|
-
whereClauses.push(`${expr} = ?`);
|
|
264
|
-
params.push(this.serializeColumnValue(value));
|
|
265
|
-
}
|
|
266
|
-
if (filter?.whereIn) for (const [field, values] of Object.entries(filter.whereIn)) {
|
|
267
|
-
const expr = fieldExpr(field);
|
|
268
|
-
if (!expr) continue;
|
|
269
|
-
const placeholders = values.map(() => "?").join(", ");
|
|
270
|
-
whereClauses.push(`${expr} IN (${placeholders})`);
|
|
271
|
-
for (const v of values) params.push(this.serializeColumnValue(v));
|
|
272
|
-
}
|
|
273
|
-
if (filter?.whereBetween) for (const [field, [low, high]] of Object.entries(filter.whereBetween)) {
|
|
274
|
-
const expr = fieldExpr(field);
|
|
275
|
-
if (!expr) continue;
|
|
276
|
-
whereClauses.push(`${expr} BETWEEN ? AND ?`);
|
|
277
|
-
params.push(this.serializeColumnValue(low), this.serializeColumnValue(high));
|
|
278
|
-
}
|
|
279
|
-
if (whereClauses.length > 0) sql += ` WHERE ${whereClauses.join(" AND ")}`;
|
|
329
|
+
const shape = this.shapeOf(decl);
|
|
330
|
+
const compiled = compileFilter(filter, shape, "select", (v) => this.serializeColumnValue(v));
|
|
331
|
+
sql += compiled.whereSql;
|
|
332
|
+
params.push(...compiled.params);
|
|
280
333
|
if (filter?.orderBy) {
|
|
281
|
-
const expr =
|
|
282
|
-
if (expr) {
|
|
334
|
+
const expr = fieldExprFor(filter.orderBy.field, shape);
|
|
335
|
+
if (expr !== null) {
|
|
283
336
|
const dir = filter.orderBy.direction === "desc" ? "DESC" : "ASC";
|
|
284
337
|
sql += ` ORDER BY ${expr} ${dir}`;
|
|
285
338
|
}
|
|
@@ -455,6 +508,19 @@ var SqliteSettingsBackend = class SqliteSettingsBackend {
|
|
|
455
508
|
scopedName(namespace, collection) {
|
|
456
509
|
return namespace ? `${namespace}:${collection}` : collection;
|
|
457
510
|
}
|
|
511
|
+
/**
|
|
512
|
+
* Project a declared collection onto what {@link compileFilter} needs. A
|
|
513
|
+
* collection whose only column is `data` is KV-shaped: its fields live
|
|
514
|
+
* inside that blob and are reached with `json_extract`.
|
|
515
|
+
*/
|
|
516
|
+
shapeOf(decl) {
|
|
517
|
+
const isKvShape = decl.columns.size === 1 && decl.columns.has("data");
|
|
518
|
+
return {
|
|
519
|
+
primaryKey: decl.primaryKey,
|
|
520
|
+
columns: decl.columns,
|
|
521
|
+
kvBlobColumn: isKvShape ? "data" : null
|
|
522
|
+
};
|
|
523
|
+
}
|
|
458
524
|
async declareCollection(input) {
|
|
459
525
|
const table = this.scopedName(input.namespace, input.collection);
|
|
460
526
|
if (this.declaredCollections.has(table)) return;
|
|
@@ -605,8 +671,14 @@ var SqliteSettingsBackend = class SqliteSettingsBackend {
|
|
|
605
671
|
//#endregion
|
|
606
672
|
//#region src/builtins/sqlite-storage/sqlite-settings.addon.ts
|
|
607
673
|
/**
|
|
608
|
-
* SQLite Settings addon —
|
|
609
|
-
*
|
|
674
|
+
* SQLite Settings addon — the relational ENGINE behind the data door.
|
|
675
|
+
*
|
|
676
|
+
* Capability: `data-store-provider` (collection, internal). It does not
|
|
677
|
+
* serve `settings-store`: that is the public door, owned by
|
|
678
|
+
* storage-orchestrator, which dispatches here
|
|
679
|
+
* ([D44](../../../../../docs/decisions/adr-0044.md)). Callers reach this
|
|
680
|
+
* engine only through the door — which is the point, because it is what
|
|
681
|
+
* makes the engine replaceable.
|
|
610
682
|
*/
|
|
611
683
|
var SqliteSettingsAddon = class extends require_dist.BaseAddon {
|
|
612
684
|
backend = null;
|
|
@@ -635,7 +707,7 @@ var SqliteSettingsAddon = class extends require_dist.BaseAddon {
|
|
|
635
707
|
await this.backend.initialize();
|
|
636
708
|
this.ctx.logger.info("Initialized successfully");
|
|
637
709
|
return [{
|
|
638
|
-
capability: require_dist.
|
|
710
|
+
capability: require_dist.dataStoreProviderCapability,
|
|
639
711
|
provider: this.backend
|
|
640
712
|
}];
|
|
641
713
|
}
|
|
@@ -1,6 +1,62 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Dt as parseJsonUnknown, at as errMsg, c as RUNTIME_DEFAULTS, gt as asJsonObject, ot as BaseAddon, x as dataStoreProviderCapability } from "../../dist-Dr_P0DOB.mjs";
|
|
2
2
|
import { randomUUID } from "node:crypto";
|
|
3
3
|
import Database from "better-sqlite3";
|
|
4
|
+
//#region src/builtins/sqlite-storage/filter-compiler.ts
|
|
5
|
+
/** Thrown by `mutate` mode. Distinct type so a caller can map it to a 400. */
|
|
6
|
+
var UnsafeFilterError = class extends Error {
|
|
7
|
+
constructor(message) {
|
|
8
|
+
super(message);
|
|
9
|
+
this.name = "UnsafeFilterError";
|
|
10
|
+
}
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* SQL expression for `field` on this collection, or `null` when it cannot be
|
|
14
|
+
* expressed: a real column → `"field"`; a KV blob field → `json_extract`;
|
|
15
|
+
* anything else on a structured table → unresolvable. Exported because
|
|
16
|
+
* `histogram` needs the same resolution for its bucketed column.
|
|
17
|
+
*/
|
|
18
|
+
function fieldExprFor(field, shape) {
|
|
19
|
+
if (field === shape.primaryKey || shape.columns.has(field)) return `"${field}"`;
|
|
20
|
+
if (shape.kvBlobColumn !== null) return `json_extract("${shape.kvBlobColumn}", '$.${field}')`;
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
function compileFilter(filter, shape, mode, serialize) {
|
|
24
|
+
const clauses = [];
|
|
25
|
+
const params = [];
|
|
26
|
+
const resolve = (field) => {
|
|
27
|
+
const expr = fieldExprFor(field, shape);
|
|
28
|
+
if (expr === null && mode === "mutate") throw new UnsafeFilterError(`filter refers to "${field}", which this collection cannot express — refusing to run a bulk mutation with a dropped predicate`);
|
|
29
|
+
return expr;
|
|
30
|
+
};
|
|
31
|
+
for (const [field, value] of Object.entries(filter?.where ?? {})) {
|
|
32
|
+
const expr = resolve(field);
|
|
33
|
+
if (expr === null) continue;
|
|
34
|
+
clauses.push(`${expr} = ?`);
|
|
35
|
+
params.push(serialize(value));
|
|
36
|
+
}
|
|
37
|
+
for (const [field, values] of Object.entries(filter?.whereIn ?? {})) {
|
|
38
|
+
const expr = resolve(field);
|
|
39
|
+
if (expr === null) continue;
|
|
40
|
+
if (values.length === 0) {
|
|
41
|
+
if (mode === "mutate") throw new UnsafeFilterError(`filter has an empty whereIn list for "${field}" — it matches nothing, which is more likely a caller bug than an intent`);
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
clauses.push(`${expr} IN (${values.map(() => "?").join(", ")})`);
|
|
45
|
+
for (const v of values) params.push(serialize(v));
|
|
46
|
+
}
|
|
47
|
+
for (const [field, [low, high]] of Object.entries(filter?.whereBetween ?? {})) {
|
|
48
|
+
const expr = resolve(field);
|
|
49
|
+
if (expr === null) continue;
|
|
50
|
+
clauses.push(`${expr} BETWEEN ? AND ?`);
|
|
51
|
+
params.push(serialize(low), serialize(high));
|
|
52
|
+
}
|
|
53
|
+
if (clauses.length === 0 && mode === "mutate") throw new UnsafeFilterError("filter compiled to no predicate — that would affect every row in the collection; use the explicit clear operation if that is the intent");
|
|
54
|
+
return {
|
|
55
|
+
whereSql: clauses.length > 0 ? ` WHERE ${clauses.join(" AND ")}` : "",
|
|
56
|
+
params
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
//#endregion
|
|
4
60
|
//#region src/builtins/sqlite-storage/sqlite-settings-backend.ts
|
|
5
61
|
function parseRowData(raw) {
|
|
6
62
|
return asJsonObject(parseJsonUnknown(raw)) ?? {};
|
|
@@ -113,6 +169,18 @@ var SqliteSettingsBackend = class SqliteSettingsBackend {
|
|
|
113
169
|
this.db?.close();
|
|
114
170
|
this.db = null;
|
|
115
171
|
}
|
|
172
|
+
/**
|
|
173
|
+
* `data-store-provider` self-description — how the orchestrator names
|
|
174
|
+
* this engine in a log line, and (once collections declare an engine)
|
|
175
|
+
* how it picks between registrants.
|
|
176
|
+
*/
|
|
177
|
+
async getEngineInfo() {
|
|
178
|
+
return {
|
|
179
|
+
engineId: "sqlite",
|
|
180
|
+
kind: "relational",
|
|
181
|
+
displayName: "SQLite (WAL)"
|
|
182
|
+
};
|
|
183
|
+
}
|
|
116
184
|
async get({ namespace, collection, key }) {
|
|
117
185
|
const scoped = this.scopedName(namespace, collection);
|
|
118
186
|
const decl = this.requireDeclared(scoped);
|
|
@@ -171,6 +239,41 @@ var SqliteSettingsBackend = class SqliteSettingsBackend {
|
|
|
171
239
|
const decl = this.requireDeclared(scoped);
|
|
172
240
|
await this.tableDelete(scoped, { [decl.primaryKey]: key });
|
|
173
241
|
}
|
|
242
|
+
/**
|
|
243
|
+
* Bulk delete. One statement, whatever the row count — this is what every
|
|
244
|
+
* retention path in the system was hand-rolling as a select-then-delete
|
|
245
|
+
* loop. The filter is compiled in `mutate` mode, so an unresolvable
|
|
246
|
+
* predicate or an empty one throws before any SQL exists.
|
|
247
|
+
*/
|
|
248
|
+
async deleteWhere({ namespace, collection, filter }) {
|
|
249
|
+
const scoped = this.scopedName(namespace, collection);
|
|
250
|
+
const decl = this.requireDeclared(scoped);
|
|
251
|
+
const { whereSql, params } = compileFilter(filter, this.shapeOf(decl), "mutate", (v) => this.serializeColumnValue(v));
|
|
252
|
+
return { deleted: this.getDb().prepare(`DELETE FROM "${scoped}"${whereSql}`).run(...params).changes };
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* Bulk update. Same filter contract as {@link deleteWhere}.
|
|
256
|
+
*
|
|
257
|
+
* Every key of `data` must be a REAL column. A KV-shaped collection keeps
|
|
258
|
+
* its fields inside a JSON blob, and setting one would need `json_set` with
|
|
259
|
+
* read-modify-write semantics this method does not have — so it refuses
|
|
260
|
+
* rather than writing a column that does not exist, which is what the
|
|
261
|
+
* uncapped `tableUpdate` would have done.
|
|
262
|
+
*/
|
|
263
|
+
async updateWhere({ namespace, collection, filter, data }) {
|
|
264
|
+
const scoped = this.scopedName(namespace, collection);
|
|
265
|
+
const decl = this.requireDeclared(scoped);
|
|
266
|
+
const setClauses = [];
|
|
267
|
+
const setValues = [];
|
|
268
|
+
for (const [key, value] of Object.entries(data)) {
|
|
269
|
+
if (key !== decl.primaryKey && !decl.columns.has(key)) throw new UnsafeFilterError(`updateWhere cannot set "${key}" on "${scoped}" — it is not a column of this collection`);
|
|
270
|
+
setClauses.push(`"${key}" = ?`);
|
|
271
|
+
setValues.push(this.serializeColumnValue(value));
|
|
272
|
+
}
|
|
273
|
+
if (setClauses.length === 0) throw new UnsafeFilterError("updateWhere was given no fields to set");
|
|
274
|
+
const { whereSql, params } = compileFilter(filter, this.shapeOf(decl), "mutate", (v) => this.serializeColumnValue(v));
|
|
275
|
+
return { updated: this.getDb().prepare(`UPDATE "${scoped}" SET ${setClauses.join(", ")}${whereSql}`).run(...setValues, ...params).changes };
|
|
276
|
+
}
|
|
174
277
|
async count({ namespace, collection, filter }) {
|
|
175
278
|
const scoped = this.scopedName(namespace, collection);
|
|
176
279
|
const decl = this.requireDeclared(scoped);
|
|
@@ -198,30 +301,10 @@ var SqliteSettingsBackend = class SqliteSettingsBackend {
|
|
|
198
301
|
async histogram({ namespace, collection, field, bucketSize, origin, filter }) {
|
|
199
302
|
const scoped = this.scopedName(namespace, collection);
|
|
200
303
|
const decl = this.requireDeclared(scoped);
|
|
201
|
-
const
|
|
202
|
-
const
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
if (isKvShape) return `json_extract("data", '$.${f}')`;
|
|
206
|
-
return "";
|
|
207
|
-
};
|
|
208
|
-
const col = fieldExpr(field);
|
|
209
|
-
if (!col) return [];
|
|
210
|
-
const params = [];
|
|
211
|
-
const clauses = [];
|
|
212
|
-
if (filter?.where) for (const [f, value] of Object.entries(filter.where)) {
|
|
213
|
-
const expr = fieldExpr(f);
|
|
214
|
-
if (!expr) continue;
|
|
215
|
-
clauses.push(`${expr} = ?`);
|
|
216
|
-
params.push(this.serializeColumnValue(value));
|
|
217
|
-
}
|
|
218
|
-
if (filter?.whereBetween) for (const [f, [lo, hi]] of Object.entries(filter.whereBetween)) {
|
|
219
|
-
const expr = fieldExpr(f);
|
|
220
|
-
if (!expr) continue;
|
|
221
|
-
clauses.push(`${expr} BETWEEN ? AND ?`);
|
|
222
|
-
params.push(this.serializeColumnValue(lo), this.serializeColumnValue(hi));
|
|
223
|
-
}
|
|
224
|
-
const where = clauses.length > 0 ? ` WHERE ${clauses.join(" AND ")}` : "";
|
|
304
|
+
const shape = this.shapeOf(decl);
|
|
305
|
+
const col = fieldExprFor(field, shape);
|
|
306
|
+
if (col === null) return [];
|
|
307
|
+
const { whereSql: where, params } = compileFilter(filter, shape, "select", (v) => this.serializeColumnValue(v));
|
|
225
308
|
const sql = `SELECT ${`CAST((${col} - ?) / ? AS INTEGER)`} AS bucket, COUNT(*) AS count FROM "${scoped}"${where} GROUP BY bucket ORDER BY bucket`;
|
|
226
309
|
return this.getDb().prepare(sql).all(origin, bucketSize, ...params).map((r) => ({
|
|
227
310
|
bucket: r.bucket,
|
|
@@ -237,43 +320,13 @@ var SqliteSettingsBackend = class SqliteSettingsBackend {
|
|
|
237
320
|
const isKvShape = decl.columns.size === 1 && decl.columns.has("data");
|
|
238
321
|
let sql = `SELECT ${[`"${decl.primaryKey}"`, ...[...decl.columns].map((c) => `"${c}"`)].join(", ")} FROM "${table}"`;
|
|
239
322
|
const params = [];
|
|
240
|
-
const
|
|
241
|
-
const
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
* - real column → `"field"`
|
|
245
|
-
* - KV blob field (e.g. `username` inside `data`) → `json_extract`
|
|
246
|
-
* - structured table + non-column → empty string (filter dropped,
|
|
247
|
-
* matches legacy structured-table behaviour)
|
|
248
|
-
*/
|
|
249
|
-
const fieldExpr = (f) => {
|
|
250
|
-
if (isColumn(f)) return `"${f}"`;
|
|
251
|
-
if (isKvShape) return `json_extract("data", '$.${f}')`;
|
|
252
|
-
return "";
|
|
253
|
-
};
|
|
254
|
-
if (filter?.where) for (const [field, value] of Object.entries(filter.where)) {
|
|
255
|
-
const expr = fieldExpr(field);
|
|
256
|
-
if (!expr) continue;
|
|
257
|
-
whereClauses.push(`${expr} = ?`);
|
|
258
|
-
params.push(this.serializeColumnValue(value));
|
|
259
|
-
}
|
|
260
|
-
if (filter?.whereIn) for (const [field, values] of Object.entries(filter.whereIn)) {
|
|
261
|
-
const expr = fieldExpr(field);
|
|
262
|
-
if (!expr) continue;
|
|
263
|
-
const placeholders = values.map(() => "?").join(", ");
|
|
264
|
-
whereClauses.push(`${expr} IN (${placeholders})`);
|
|
265
|
-
for (const v of values) params.push(this.serializeColumnValue(v));
|
|
266
|
-
}
|
|
267
|
-
if (filter?.whereBetween) for (const [field, [low, high]] of Object.entries(filter.whereBetween)) {
|
|
268
|
-
const expr = fieldExpr(field);
|
|
269
|
-
if (!expr) continue;
|
|
270
|
-
whereClauses.push(`${expr} BETWEEN ? AND ?`);
|
|
271
|
-
params.push(this.serializeColumnValue(low), this.serializeColumnValue(high));
|
|
272
|
-
}
|
|
273
|
-
if (whereClauses.length > 0) sql += ` WHERE ${whereClauses.join(" AND ")}`;
|
|
323
|
+
const shape = this.shapeOf(decl);
|
|
324
|
+
const compiled = compileFilter(filter, shape, "select", (v) => this.serializeColumnValue(v));
|
|
325
|
+
sql += compiled.whereSql;
|
|
326
|
+
params.push(...compiled.params);
|
|
274
327
|
if (filter?.orderBy) {
|
|
275
|
-
const expr =
|
|
276
|
-
if (expr) {
|
|
328
|
+
const expr = fieldExprFor(filter.orderBy.field, shape);
|
|
329
|
+
if (expr !== null) {
|
|
277
330
|
const dir = filter.orderBy.direction === "desc" ? "DESC" : "ASC";
|
|
278
331
|
sql += ` ORDER BY ${expr} ${dir}`;
|
|
279
332
|
}
|
|
@@ -449,6 +502,19 @@ var SqliteSettingsBackend = class SqliteSettingsBackend {
|
|
|
449
502
|
scopedName(namespace, collection) {
|
|
450
503
|
return namespace ? `${namespace}:${collection}` : collection;
|
|
451
504
|
}
|
|
505
|
+
/**
|
|
506
|
+
* Project a declared collection onto what {@link compileFilter} needs. A
|
|
507
|
+
* collection whose only column is `data` is KV-shaped: its fields live
|
|
508
|
+
* inside that blob and are reached with `json_extract`.
|
|
509
|
+
*/
|
|
510
|
+
shapeOf(decl) {
|
|
511
|
+
const isKvShape = decl.columns.size === 1 && decl.columns.has("data");
|
|
512
|
+
return {
|
|
513
|
+
primaryKey: decl.primaryKey,
|
|
514
|
+
columns: decl.columns,
|
|
515
|
+
kvBlobColumn: isKvShape ? "data" : null
|
|
516
|
+
};
|
|
517
|
+
}
|
|
452
518
|
async declareCollection(input) {
|
|
453
519
|
const table = this.scopedName(input.namespace, input.collection);
|
|
454
520
|
if (this.declaredCollections.has(table)) return;
|
|
@@ -599,8 +665,14 @@ var SqliteSettingsBackend = class SqliteSettingsBackend {
|
|
|
599
665
|
//#endregion
|
|
600
666
|
//#region src/builtins/sqlite-storage/sqlite-settings.addon.ts
|
|
601
667
|
/**
|
|
602
|
-
* SQLite Settings addon —
|
|
603
|
-
*
|
|
668
|
+
* SQLite Settings addon — the relational ENGINE behind the data door.
|
|
669
|
+
*
|
|
670
|
+
* Capability: `data-store-provider` (collection, internal). It does not
|
|
671
|
+
* serve `settings-store`: that is the public door, owned by
|
|
672
|
+
* storage-orchestrator, which dispatches here
|
|
673
|
+
* ([D44](../../../../../docs/decisions/adr-0044.md)). Callers reach this
|
|
674
|
+
* engine only through the door — which is the point, because it is what
|
|
675
|
+
* makes the engine replaceable.
|
|
604
676
|
*/
|
|
605
677
|
var SqliteSettingsAddon = class extends BaseAddon {
|
|
606
678
|
backend = null;
|
|
@@ -629,7 +701,7 @@ var SqliteSettingsAddon = class extends BaseAddon {
|
|
|
629
701
|
await this.backend.initialize();
|
|
630
702
|
this.ctx.logger.info("Initialized successfully");
|
|
631
703
|
return [{
|
|
632
|
-
capability:
|
|
704
|
+
capability: dataStoreProviderCapability,
|
|
633
705
|
provider: this.backend
|
|
634
706
|
}];
|
|
635
707
|
}
|