@camstack/system 1.2.29 → 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/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.js +1 -1
- package/dist/builtins/alerts/alerts.addon.mjs +1 -1
- 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 +43 -0
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.js +115 -61
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +115 -61
- package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.js +3 -1
- package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.mjs +3 -1
- 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-B7hOpr5i.mjs → dist-Dr_P0DOB.mjs} +81 -0
- package/dist/{dist-CPmFYLqq.js → dist-VIwdvws5.js} +81 -0
- package/dist/index.js +45 -1
- package/dist/index.mjs +45 -1
- package/dist/kernel/addon-installer.d.ts +38 -0
- package/package.json +1 -1
|
@@ -1,6 +1,62 @@
|
|
|
1
|
-
import { Dt as parseJsonUnknown, at as errMsg, c as RUNTIME_DEFAULTS, gt as asJsonObject, ot as BaseAddon, x as dataStoreProviderCapability } from "../../dist-
|
|
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)) ?? {};
|
|
@@ -183,6 +239,41 @@ var SqliteSettingsBackend = class SqliteSettingsBackend {
|
|
|
183
239
|
const decl = this.requireDeclared(scoped);
|
|
184
240
|
await this.tableDelete(scoped, { [decl.primaryKey]: key });
|
|
185
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
|
+
}
|
|
186
277
|
async count({ namespace, collection, filter }) {
|
|
187
278
|
const scoped = this.scopedName(namespace, collection);
|
|
188
279
|
const decl = this.requireDeclared(scoped);
|
|
@@ -210,30 +301,10 @@ var SqliteSettingsBackend = class SqliteSettingsBackend {
|
|
|
210
301
|
async histogram({ namespace, collection, field, bucketSize, origin, filter }) {
|
|
211
302
|
const scoped = this.scopedName(namespace, collection);
|
|
212
303
|
const decl = this.requireDeclared(scoped);
|
|
213
|
-
const
|
|
214
|
-
const
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
if (isKvShape) return `json_extract("data", '$.${f}')`;
|
|
218
|
-
return "";
|
|
219
|
-
};
|
|
220
|
-
const col = fieldExpr(field);
|
|
221
|
-
if (!col) return [];
|
|
222
|
-
const params = [];
|
|
223
|
-
const clauses = [];
|
|
224
|
-
if (filter?.where) for (const [f, value] of Object.entries(filter.where)) {
|
|
225
|
-
const expr = fieldExpr(f);
|
|
226
|
-
if (!expr) continue;
|
|
227
|
-
clauses.push(`${expr} = ?`);
|
|
228
|
-
params.push(this.serializeColumnValue(value));
|
|
229
|
-
}
|
|
230
|
-
if (filter?.whereBetween) for (const [f, [lo, hi]] of Object.entries(filter.whereBetween)) {
|
|
231
|
-
const expr = fieldExpr(f);
|
|
232
|
-
if (!expr) continue;
|
|
233
|
-
clauses.push(`${expr} BETWEEN ? AND ?`);
|
|
234
|
-
params.push(this.serializeColumnValue(lo), this.serializeColumnValue(hi));
|
|
235
|
-
}
|
|
236
|
-
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));
|
|
237
308
|
const sql = `SELECT ${`CAST((${col} - ?) / ? AS INTEGER)`} AS bucket, COUNT(*) AS count FROM "${scoped}"${where} GROUP BY bucket ORDER BY bucket`;
|
|
238
309
|
return this.getDb().prepare(sql).all(origin, bucketSize, ...params).map((r) => ({
|
|
239
310
|
bucket: r.bucket,
|
|
@@ -249,43 +320,13 @@ var SqliteSettingsBackend = class SqliteSettingsBackend {
|
|
|
249
320
|
const isKvShape = decl.columns.size === 1 && decl.columns.has("data");
|
|
250
321
|
let sql = `SELECT ${[`"${decl.primaryKey}"`, ...[...decl.columns].map((c) => `"${c}"`)].join(", ")} FROM "${table}"`;
|
|
251
322
|
const params = [];
|
|
252
|
-
const
|
|
253
|
-
const
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
* - real column → `"field"`
|
|
257
|
-
* - KV blob field (e.g. `username` inside `data`) → `json_extract`
|
|
258
|
-
* - structured table + non-column → empty string (filter dropped,
|
|
259
|
-
* matches legacy structured-table behaviour)
|
|
260
|
-
*/
|
|
261
|
-
const fieldExpr = (f) => {
|
|
262
|
-
if (isColumn(f)) return `"${f}"`;
|
|
263
|
-
if (isKvShape) return `json_extract("data", '$.${f}')`;
|
|
264
|
-
return "";
|
|
265
|
-
};
|
|
266
|
-
if (filter?.where) for (const [field, value] of Object.entries(filter.where)) {
|
|
267
|
-
const expr = fieldExpr(field);
|
|
268
|
-
if (!expr) continue;
|
|
269
|
-
whereClauses.push(`${expr} = ?`);
|
|
270
|
-
params.push(this.serializeColumnValue(value));
|
|
271
|
-
}
|
|
272
|
-
if (filter?.whereIn) for (const [field, values] of Object.entries(filter.whereIn)) {
|
|
273
|
-
const expr = fieldExpr(field);
|
|
274
|
-
if (!expr) continue;
|
|
275
|
-
const placeholders = values.map(() => "?").join(", ");
|
|
276
|
-
whereClauses.push(`${expr} IN (${placeholders})`);
|
|
277
|
-
for (const v of values) params.push(this.serializeColumnValue(v));
|
|
278
|
-
}
|
|
279
|
-
if (filter?.whereBetween) for (const [field, [low, high]] of Object.entries(filter.whereBetween)) {
|
|
280
|
-
const expr = fieldExpr(field);
|
|
281
|
-
if (!expr) continue;
|
|
282
|
-
whereClauses.push(`${expr} BETWEEN ? AND ?`);
|
|
283
|
-
params.push(this.serializeColumnValue(low), this.serializeColumnValue(high));
|
|
284
|
-
}
|
|
285
|
-
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);
|
|
286
327
|
if (filter?.orderBy) {
|
|
287
|
-
const expr =
|
|
288
|
-
if (expr) {
|
|
328
|
+
const expr = fieldExprFor(filter.orderBy.field, shape);
|
|
329
|
+
if (expr !== null) {
|
|
289
330
|
const dir = filter.orderBy.direction === "desc" ? "DESC" : "ASC";
|
|
290
331
|
sql += ` ORDER BY ${expr} ${dir}`;
|
|
291
332
|
}
|
|
@@ -461,6 +502,19 @@ var SqliteSettingsBackend = class SqliteSettingsBackend {
|
|
|
461
502
|
scopedName(namespace, collection) {
|
|
462
503
|
return namespace ? `${namespace}:${collection}` : collection;
|
|
463
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
|
+
}
|
|
464
518
|
async declareCollection(input) {
|
|
465
519
|
const table = this.scopedName(input.namespace, input.collection);
|
|
466
520
|
if (this.declaredCollections.has(table)) return;
|
|
@@ -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-
|
|
6
|
+
const require_dist = require("../../dist-VIwdvws5.js");
|
|
7
7
|
let node_path = require("node:path");
|
|
8
8
|
node_path = require_chunk.__toESM(node_path);
|
|
9
9
|
let node_fs_promises = require("node:fs/promises");
|
|
@@ -770,6 +770,8 @@ function createDataStoreDispatch(getEngines) {
|
|
|
770
770
|
insert: async (input) => (await engine()).insert(input),
|
|
771
771
|
update: async (input) => (await engine()).update(input),
|
|
772
772
|
delete: async (input) => (await engine()).delete(input),
|
|
773
|
+
deleteWhere: async (input) => (await engine()).deleteWhere(input),
|
|
774
|
+
updateWhere: async (input) => (await engine()).updateWhere(input),
|
|
773
775
|
count: async (input) => (await engine()).count(input),
|
|
774
776
|
histogram: async (input) => (await engine()).histogram(input),
|
|
775
777
|
isEmpty: async (input) => (await engine()).isEmpty(input),
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { $ as storageCapability, Et as parseJsonObject, Z as settingsStoreCapability, d as StorageLocationTypeSchema, ot as BaseAddon } from "../../dist-
|
|
1
|
+
import { $ as storageCapability, Et as parseJsonObject, Z as settingsStoreCapability, d as StorageLocationTypeSchema, ot as BaseAddon } from "../../dist-Dr_P0DOB.mjs";
|
|
2
2
|
import * as path$1 from "node:path";
|
|
3
3
|
import * as fs from "node:fs/promises";
|
|
4
4
|
import { buildStorageLocationRegistry } from "@camstack/system";
|
|
@@ -763,6 +763,8 @@ function createDataStoreDispatch(getEngines) {
|
|
|
763
763
|
insert: async (input) => (await engine()).insert(input),
|
|
764
764
|
update: async (input) => (await engine()).update(input),
|
|
765
765
|
delete: async (input) => (await engine()).delete(input),
|
|
766
|
+
deleteWhere: async (input) => (await engine()).deleteWhere(input),
|
|
767
|
+
updateWhere: async (input) => (await engine()).updateWhere(input),
|
|
766
768
|
count: async (input) => (await engine()).count(input),
|
|
767
769
|
histogram: async (input) => (await engine()).histogram(input),
|
|
768
770
|
isEmpty: async (input) => (await engine()).isEmpty(input),
|
|
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
|
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
5
|
require("../../chunk-Cek0wNdY.js");
|
|
6
|
-
const require_dist = require("../../dist-
|
|
6
|
+
const require_dist = require("../../dist-VIwdvws5.js");
|
|
7
7
|
//#region src/builtins/system-config/system-config.addon.ts
|
|
8
8
|
/**
|
|
9
9
|
* Built-in `system-config` addon — Phase 4 of the settings redesign.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Ct as hydrateSchema, at as errMsg, ot as BaseAddon } from "../../dist-
|
|
1
|
+
import { Ct as hydrateSchema, at as errMsg, ot as BaseAddon } from "../../dist-Dr_P0DOB.mjs";
|
|
2
2
|
//#region src/builtins/system-config/system-config.addon.ts
|
|
3
3
|
/**
|
|
4
4
|
* Built-in `system-config` addon — Phase 4 of the settings redesign.
|
|
@@ -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-
|
|
6
|
+
const require_dist = require("../../dist-VIwdvws5.js");
|
|
7
7
|
const require_formatter = require("../../formatter-DqAKDlvN.js");
|
|
8
8
|
let node_path = require("node:path");
|
|
9
9
|
node_path = require_chunk.__toESM(node_path);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { B as logDestinationCapability, ot as BaseAddon } from "../../dist-
|
|
1
|
+
import { B as logDestinationCapability, ot as BaseAddon } from "../../dist-Dr_P0DOB.mjs";
|
|
2
2
|
import { t as formatLogLine } from "../../formatter-B7qW8bPJ.mjs";
|
|
3
3
|
import * as path$1 from "node:path";
|
|
4
4
|
import path from "node:path";
|
|
@@ -14544,6 +14544,20 @@ var QueryFilterSchema = z.object({
|
|
|
14544
14544
|
limit: z.number().optional(),
|
|
14545
14545
|
offset: z.number().optional()
|
|
14546
14546
|
});
|
|
14547
|
+
/**
|
|
14548
|
+
* The predicate half of a filter, for BULK MUTATIONS.
|
|
14549
|
+
*
|
|
14550
|
+
* Deliberately not `QueryFilterSchema`: `orderBy` / `limit` / `offset` have no
|
|
14551
|
+
* meaning for a statement that rewrites a set, and accepting them would invite
|
|
14552
|
+
* a caller to believe `limit` bounds the damage. Every field is optional here
|
|
14553
|
+
* only so the shape stays composable — the implementation REJECTS a filter
|
|
14554
|
+
* that compiles to no predicate, because that is the whole collection.
|
|
14555
|
+
*/
|
|
14556
|
+
var MutationFilterSchema = z.object({
|
|
14557
|
+
where: z.record(z.string(), z.unknown()).optional(),
|
|
14558
|
+
whereIn: z.record(z.string(), z.array(z.unknown())).optional(),
|
|
14559
|
+
whereBetween: z.record(z.string(), z.tuple([z.unknown(), z.unknown()])).optional()
|
|
14560
|
+
});
|
|
14547
14561
|
/** A single stored record: `{ id, data }`. */
|
|
14548
14562
|
var SettingsRecordSchema = z.object({
|
|
14549
14563
|
id: z.string(),
|
|
@@ -14649,6 +14663,36 @@ var settingsStoreCapability = {
|
|
|
14649
14663
|
collection: z.string(),
|
|
14650
14664
|
key: z.string()
|
|
14651
14665
|
}), z.void(), { kind: "mutation" }),
|
|
14666
|
+
/**
|
|
14667
|
+
* Delete every record matching `filter`, in ONE statement, returning how
|
|
14668
|
+
* many rows went. This exists because its absence made every retention
|
|
14669
|
+
* path in the system an N+1 drain loop: `delete` takes a key, so a sweep
|
|
14670
|
+
* had to SELECT a page of full rows — every column, including the fat
|
|
14671
|
+
* ones — purely to learn their ids, then issue one call per row.
|
|
14672
|
+
*
|
|
14673
|
+
* **The filter is required and must resolve.** A predicate naming
|
|
14674
|
+
* something the collection cannot express is an ERROR here, not a
|
|
14675
|
+
* widening as it is on `query`, and a filter with no predicates is an
|
|
14676
|
+
* error rather than "every row". Deleting a whole collection is a
|
|
14677
|
+
* legitimate intent, but it must be asked for by name — not reached by
|
|
14678
|
+
* an empty object.
|
|
14679
|
+
*/
|
|
14680
|
+
deleteWhere: method(z.object({
|
|
14681
|
+
namespace: z.string().optional(),
|
|
14682
|
+
collection: z.string(),
|
|
14683
|
+
filter: MutationFilterSchema
|
|
14684
|
+
}), z.object({ deleted: z.number().int() }), { kind: "mutation" }),
|
|
14685
|
+
/**
|
|
14686
|
+
* Apply `data` to every record matching `filter`, in one statement,
|
|
14687
|
+
* returning how many rows changed. Same filter contract as
|
|
14688
|
+
* {@link deleteWhere} — an unresolvable predicate is an error.
|
|
14689
|
+
*/
|
|
14690
|
+
updateWhere: method(z.object({
|
|
14691
|
+
namespace: z.string().optional(),
|
|
14692
|
+
collection: z.string(),
|
|
14693
|
+
filter: MutationFilterSchema,
|
|
14694
|
+
data: z.record(z.string(), z.unknown())
|
|
14695
|
+
}), z.object({ updated: z.number().int() }), { kind: "mutation" }),
|
|
14652
14696
|
/** Count entries in a collection, optionally filtered. */
|
|
14653
14697
|
count: method(z.object({
|
|
14654
14698
|
namespace: z.string().optional(),
|
|
@@ -14784,6 +14828,19 @@ var dataStoreProviderCapability = {
|
|
|
14784
14828
|
collection: z.string(),
|
|
14785
14829
|
key: z.string()
|
|
14786
14830
|
}), z.void(), { kind: "mutation" }),
|
|
14831
|
+
/** Delete every record matching `filter`, in one statement. */
|
|
14832
|
+
deleteWhere: method(z.object({
|
|
14833
|
+
namespace: z.string().optional(),
|
|
14834
|
+
collection: z.string(),
|
|
14835
|
+
filter: MutationFilterSchema
|
|
14836
|
+
}), z.object({ deleted: z.number().int() }), { kind: "mutation" }),
|
|
14837
|
+
/** Apply `data` to every record matching `filter`, in one statement. */
|
|
14838
|
+
updateWhere: method(z.object({
|
|
14839
|
+
namespace: z.string().optional(),
|
|
14840
|
+
collection: z.string(),
|
|
14841
|
+
filter: MutationFilterSchema,
|
|
14842
|
+
data: z.record(z.string(), z.unknown())
|
|
14843
|
+
}), z.object({ updated: z.number().int() }), { kind: "mutation" }),
|
|
14787
14844
|
/** Count entries in a collection, optionally filtered. */
|
|
14788
14845
|
count: method(z.object({
|
|
14789
14846
|
namespace: z.string().optional(),
|
|
@@ -24963,6 +25020,12 @@ var METHOD_ACCESS_MAP = Object.freeze({
|
|
|
24963
25020
|
addonId: null,
|
|
24964
25021
|
access: "delete"
|
|
24965
25022
|
},
|
|
25023
|
+
"dataStoreProvider.deleteWhere": {
|
|
25024
|
+
capName: "data-store-provider",
|
|
25025
|
+
capScope: "system",
|
|
25026
|
+
addonId: null,
|
|
25027
|
+
access: "delete"
|
|
25028
|
+
},
|
|
24966
25029
|
"dataStoreProvider.get": {
|
|
24967
25030
|
capName: "data-store-provider",
|
|
24968
25031
|
capScope: "system",
|
|
@@ -25011,6 +25074,12 @@ var METHOD_ACCESS_MAP = Object.freeze({
|
|
|
25011
25074
|
addonId: null,
|
|
25012
25075
|
access: "create"
|
|
25013
25076
|
},
|
|
25077
|
+
"dataStoreProvider.updateWhere": {
|
|
25078
|
+
capName: "data-store-provider",
|
|
25079
|
+
capScope: "system",
|
|
25080
|
+
addonId: null,
|
|
25081
|
+
access: "create"
|
|
25082
|
+
},
|
|
25014
25083
|
"dayNight.getOptions": {
|
|
25015
25084
|
capName: "day-night",
|
|
25016
25085
|
capScope: "device",
|
|
@@ -28089,6 +28158,12 @@ var METHOD_ACCESS_MAP = Object.freeze({
|
|
|
28089
28158
|
addonId: null,
|
|
28090
28159
|
access: "delete"
|
|
28091
28160
|
},
|
|
28161
|
+
"settingsStore.deleteWhere": {
|
|
28162
|
+
capName: "settings-store",
|
|
28163
|
+
capScope: "system",
|
|
28164
|
+
addonId: null,
|
|
28165
|
+
access: "delete"
|
|
28166
|
+
},
|
|
28092
28167
|
"settingsStore.get": {
|
|
28093
28168
|
capName: "settings-store",
|
|
28094
28169
|
capScope: "system",
|
|
@@ -28131,6 +28206,12 @@ var METHOD_ACCESS_MAP = Object.freeze({
|
|
|
28131
28206
|
addonId: null,
|
|
28132
28207
|
access: "create"
|
|
28133
28208
|
},
|
|
28209
|
+
"settingsStore.updateWhere": {
|
|
28210
|
+
capName: "settings-store",
|
|
28211
|
+
capScope: "system",
|
|
28212
|
+
addonId: null,
|
|
28213
|
+
access: "create"
|
|
28214
|
+
},
|
|
28134
28215
|
"smtpProvider.getStatus": {
|
|
28135
28216
|
capName: "smtp-provider",
|
|
28136
28217
|
capScope: "system",
|
|
@@ -14544,6 +14544,20 @@ var QueryFilterSchema = zod.z.object({
|
|
|
14544
14544
|
limit: zod.z.number().optional(),
|
|
14545
14545
|
offset: zod.z.number().optional()
|
|
14546
14546
|
});
|
|
14547
|
+
/**
|
|
14548
|
+
* The predicate half of a filter, for BULK MUTATIONS.
|
|
14549
|
+
*
|
|
14550
|
+
* Deliberately not `QueryFilterSchema`: `orderBy` / `limit` / `offset` have no
|
|
14551
|
+
* meaning for a statement that rewrites a set, and accepting them would invite
|
|
14552
|
+
* a caller to believe `limit` bounds the damage. Every field is optional here
|
|
14553
|
+
* only so the shape stays composable — the implementation REJECTS a filter
|
|
14554
|
+
* that compiles to no predicate, because that is the whole collection.
|
|
14555
|
+
*/
|
|
14556
|
+
var MutationFilterSchema = zod.z.object({
|
|
14557
|
+
where: zod.z.record(zod.z.string(), zod.z.unknown()).optional(),
|
|
14558
|
+
whereIn: zod.z.record(zod.z.string(), zod.z.array(zod.z.unknown())).optional(),
|
|
14559
|
+
whereBetween: zod.z.record(zod.z.string(), zod.z.tuple([zod.z.unknown(), zod.z.unknown()])).optional()
|
|
14560
|
+
});
|
|
14547
14561
|
/** A single stored record: `{ id, data }`. */
|
|
14548
14562
|
var SettingsRecordSchema = zod.z.object({
|
|
14549
14563
|
id: zod.z.string(),
|
|
@@ -14649,6 +14663,36 @@ var settingsStoreCapability = {
|
|
|
14649
14663
|
collection: zod.z.string(),
|
|
14650
14664
|
key: zod.z.string()
|
|
14651
14665
|
}), zod.z.void(), { kind: "mutation" }),
|
|
14666
|
+
/**
|
|
14667
|
+
* Delete every record matching `filter`, in ONE statement, returning how
|
|
14668
|
+
* many rows went. This exists because its absence made every retention
|
|
14669
|
+
* path in the system an N+1 drain loop: `delete` takes a key, so a sweep
|
|
14670
|
+
* had to SELECT a page of full rows — every column, including the fat
|
|
14671
|
+
* ones — purely to learn their ids, then issue one call per row.
|
|
14672
|
+
*
|
|
14673
|
+
* **The filter is required and must resolve.** A predicate naming
|
|
14674
|
+
* something the collection cannot express is an ERROR here, not a
|
|
14675
|
+
* widening as it is on `query`, and a filter with no predicates is an
|
|
14676
|
+
* error rather than "every row". Deleting a whole collection is a
|
|
14677
|
+
* legitimate intent, but it must be asked for by name — not reached by
|
|
14678
|
+
* an empty object.
|
|
14679
|
+
*/
|
|
14680
|
+
deleteWhere: method(zod.z.object({
|
|
14681
|
+
namespace: zod.z.string().optional(),
|
|
14682
|
+
collection: zod.z.string(),
|
|
14683
|
+
filter: MutationFilterSchema
|
|
14684
|
+
}), zod.z.object({ deleted: zod.z.number().int() }), { kind: "mutation" }),
|
|
14685
|
+
/**
|
|
14686
|
+
* Apply `data` to every record matching `filter`, in one statement,
|
|
14687
|
+
* returning how many rows changed. Same filter contract as
|
|
14688
|
+
* {@link deleteWhere} — an unresolvable predicate is an error.
|
|
14689
|
+
*/
|
|
14690
|
+
updateWhere: method(zod.z.object({
|
|
14691
|
+
namespace: zod.z.string().optional(),
|
|
14692
|
+
collection: zod.z.string(),
|
|
14693
|
+
filter: MutationFilterSchema,
|
|
14694
|
+
data: zod.z.record(zod.z.string(), zod.z.unknown())
|
|
14695
|
+
}), zod.z.object({ updated: zod.z.number().int() }), { kind: "mutation" }),
|
|
14652
14696
|
/** Count entries in a collection, optionally filtered. */
|
|
14653
14697
|
count: method(zod.z.object({
|
|
14654
14698
|
namespace: zod.z.string().optional(),
|
|
@@ -14784,6 +14828,19 @@ var dataStoreProviderCapability = {
|
|
|
14784
14828
|
collection: zod.z.string(),
|
|
14785
14829
|
key: zod.z.string()
|
|
14786
14830
|
}), zod.z.void(), { kind: "mutation" }),
|
|
14831
|
+
/** Delete every record matching `filter`, in one statement. */
|
|
14832
|
+
deleteWhere: method(zod.z.object({
|
|
14833
|
+
namespace: zod.z.string().optional(),
|
|
14834
|
+
collection: zod.z.string(),
|
|
14835
|
+
filter: MutationFilterSchema
|
|
14836
|
+
}), zod.z.object({ deleted: zod.z.number().int() }), { kind: "mutation" }),
|
|
14837
|
+
/** Apply `data` to every record matching `filter`, in one statement. */
|
|
14838
|
+
updateWhere: method(zod.z.object({
|
|
14839
|
+
namespace: zod.z.string().optional(),
|
|
14840
|
+
collection: zod.z.string(),
|
|
14841
|
+
filter: MutationFilterSchema,
|
|
14842
|
+
data: zod.z.record(zod.z.string(), zod.z.unknown())
|
|
14843
|
+
}), zod.z.object({ updated: zod.z.number().int() }), { kind: "mutation" }),
|
|
14787
14844
|
/** Count entries in a collection, optionally filtered. */
|
|
14788
14845
|
count: method(zod.z.object({
|
|
14789
14846
|
namespace: zod.z.string().optional(),
|
|
@@ -24963,6 +25020,12 @@ var METHOD_ACCESS_MAP = Object.freeze({
|
|
|
24963
25020
|
addonId: null,
|
|
24964
25021
|
access: "delete"
|
|
24965
25022
|
},
|
|
25023
|
+
"dataStoreProvider.deleteWhere": {
|
|
25024
|
+
capName: "data-store-provider",
|
|
25025
|
+
capScope: "system",
|
|
25026
|
+
addonId: null,
|
|
25027
|
+
access: "delete"
|
|
25028
|
+
},
|
|
24966
25029
|
"dataStoreProvider.get": {
|
|
24967
25030
|
capName: "data-store-provider",
|
|
24968
25031
|
capScope: "system",
|
|
@@ -25011,6 +25074,12 @@ var METHOD_ACCESS_MAP = Object.freeze({
|
|
|
25011
25074
|
addonId: null,
|
|
25012
25075
|
access: "create"
|
|
25013
25076
|
},
|
|
25077
|
+
"dataStoreProvider.updateWhere": {
|
|
25078
|
+
capName: "data-store-provider",
|
|
25079
|
+
capScope: "system",
|
|
25080
|
+
addonId: null,
|
|
25081
|
+
access: "create"
|
|
25082
|
+
},
|
|
25014
25083
|
"dayNight.getOptions": {
|
|
25015
25084
|
capName: "day-night",
|
|
25016
25085
|
capScope: "device",
|
|
@@ -28089,6 +28158,12 @@ var METHOD_ACCESS_MAP = Object.freeze({
|
|
|
28089
28158
|
addonId: null,
|
|
28090
28159
|
access: "delete"
|
|
28091
28160
|
},
|
|
28161
|
+
"settingsStore.deleteWhere": {
|
|
28162
|
+
capName: "settings-store",
|
|
28163
|
+
capScope: "system",
|
|
28164
|
+
addonId: null,
|
|
28165
|
+
access: "delete"
|
|
28166
|
+
},
|
|
28092
28167
|
"settingsStore.get": {
|
|
28093
28168
|
capName: "settings-store",
|
|
28094
28169
|
capScope: "system",
|
|
@@ -28131,6 +28206,12 @@ var METHOD_ACCESS_MAP = Object.freeze({
|
|
|
28131
28206
|
addonId: null,
|
|
28132
28207
|
access: "create"
|
|
28133
28208
|
},
|
|
28209
|
+
"settingsStore.updateWhere": {
|
|
28210
|
+
capName: "settings-store",
|
|
28211
|
+
capScope: "system",
|
|
28212
|
+
addonId: null,
|
|
28213
|
+
access: "create"
|
|
28214
|
+
},
|
|
28134
28215
|
"smtpProvider.getStatus": {
|
|
28135
28216
|
capName: "smtp-provider",
|
|
28136
28217
|
capScope: "system",
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
2
|
const require_chunk = require("./chunk-Cek0wNdY.js");
|
|
3
|
-
const require_dist = require("./dist-
|
|
3
|
+
const require_dist = require("./dist-VIwdvws5.js");
|
|
4
4
|
const require_model_download_service = require("./model-download-service-hf0ookyy.js");
|
|
5
5
|
const require_manifest_python_deps = require("./manifest-python-deps-BqE5j0-O.js");
|
|
6
6
|
const require_resource_monitor = require("./resource-monitor-DNNomR-i.js");
|
|
@@ -3574,6 +3574,25 @@ function readPackageJson(pkgJsonPath) {
|
|
|
3574
3574
|
camstackAddons: Array.isArray(addonsField) ? addonsField : null
|
|
3575
3575
|
};
|
|
3576
3576
|
}
|
|
3577
|
+
/**
|
|
3578
|
+
* Should the bootstrap SKIP seeding `packageName` into /data/addons?
|
|
3579
|
+
*
|
|
3580
|
+
* True only for the package the closure provides, and only when it actually
|
|
3581
|
+
* resolves. Both halves are load-bearing:
|
|
3582
|
+
*
|
|
3583
|
+
* - seeding a copy the closure already provides creates one that WINS over
|
|
3584
|
+
* the closure and is never refreshed (bootstrap is seed-only, and
|
|
3585
|
+
* `camstack deploy` of a framework package is rejected on an image hub), so
|
|
3586
|
+
* the node runs first-boot builtins forever while reporting the new
|
|
3587
|
+
* version;
|
|
3588
|
+
* - skipping it when it does NOT resolve is how a fresh deployment was once
|
|
3589
|
+
* made unbootable ("No addon provides required infrastructure capability
|
|
3590
|
+
* storage-provider"). That is why the guard is resolvability rather than a
|
|
3591
|
+
* blanket removal from the bootstrap list.
|
|
3592
|
+
*/
|
|
3593
|
+
function shouldSkipClosureProvidedSeed(packageName, isResolvable) {
|
|
3594
|
+
return packageName === "@camstack/system" && isResolvable;
|
|
3595
|
+
}
|
|
3577
3596
|
/** Minimal no-op logger for default parameter */
|
|
3578
3597
|
var noopLogger = {
|
|
3579
3598
|
debug() {},
|
|
@@ -3593,6 +3612,7 @@ var AddonInstaller = class AddonInstaller {
|
|
|
3593
3612
|
workspaceDir;
|
|
3594
3613
|
installSource;
|
|
3595
3614
|
logger;
|
|
3615
|
+
isPackageResolvable;
|
|
3596
3616
|
/**
|
|
3597
3617
|
* Central manifest of installed addons. Tracks version + source +
|
|
3598
3618
|
* timestamps + last-backup pointer per package, written atomically
|
|
@@ -3608,6 +3628,7 @@ var AddonInstaller = class AddonInstaller {
|
|
|
3608
3628
|
this.workspaceDir = config.workspacePackagesDir;
|
|
3609
3629
|
this.logger = logger;
|
|
3610
3630
|
this.manifest = new AddonManifest(config.addonsDir);
|
|
3631
|
+
this.isPackageResolvable = config.isPackageResolvable;
|
|
3611
3632
|
if (config.installSource) this.installSource = config.installSource;
|
|
3612
3633
|
else this.installSource = "npm";
|
|
3613
3634
|
}
|
|
@@ -3636,6 +3657,25 @@ var AddonInstaller = class AddonInstaller {
|
|
|
3636
3657
|
* hardcoded lists — see design spec
|
|
3637
3658
|
* docs/superpowers/specs/2026-05-20-docker-install-from-npm-design.md.
|
|
3638
3659
|
*/
|
|
3660
|
+
/**
|
|
3661
|
+
* Is `packageName` reachable through normal resolution — i.e. does the
|
|
3662
|
+
* running closure already provide it?
|
|
3663
|
+
*
|
|
3664
|
+
* Overridable via `AddonInstallerConfig.isPackageResolvable` so a spec can
|
|
3665
|
+
* exercise BOTH sides. That matters more than it looks: the branch that
|
|
3666
|
+
* still seeds is the one protecting a fresh deployment from an unbootable
|
|
3667
|
+
* node, and it is unreachable in a test process where `@camstack/system`
|
|
3668
|
+
* always resolves.
|
|
3669
|
+
*/
|
|
3670
|
+
isResolvableFromClosure(packageName) {
|
|
3671
|
+
if (this.isPackageResolvable) return this.isPackageResolvable(packageName);
|
|
3672
|
+
try {
|
|
3673
|
+
require.resolve(`${packageName}/package.json`);
|
|
3674
|
+
return true;
|
|
3675
|
+
} catch {
|
|
3676
|
+
return false;
|
|
3677
|
+
}
|
|
3678
|
+
}
|
|
3639
3679
|
static deriveBootstrapList(metaPackageName) {
|
|
3640
3680
|
const pkgJsonId = `${metaPackageName}/package.json`;
|
|
3641
3681
|
let pkgPath;
|
|
@@ -3718,6 +3758,10 @@ var AddonInstaller = class AddonInstaller {
|
|
|
3718
3758
|
this.logger.debug(`${packageName} — already installed, skipping`);
|
|
3719
3759
|
continue;
|
|
3720
3760
|
}
|
|
3761
|
+
if (shouldSkipClosureProvidedSeed(packageName, this.isResolvableFromClosure(packageName))) {
|
|
3762
|
+
this.logger.info(`${packageName} — provided by the server closure, not seeding a copy that would shadow it`);
|
|
3763
|
+
continue;
|
|
3764
|
+
}
|
|
3721
3765
|
try {
|
|
3722
3766
|
if (isLocal) {
|
|
3723
3767
|
const pkgDir = this.findLocalPackage(packageName);
|