@camstack/addon-pipeline 1.2.373 → 1.2.375

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 (37) hide show
  1. package/dist/audio-analyzer/index.js +3 -3
  2. package/dist/audio-analyzer/index.mjs +3 -3
  3. package/dist/{custom-model-catalog-UaaDgBow.mjs → custom-model-catalog-DF5qs8u4.mjs} +2 -2
  4. package/dist/{custom-model-catalog-M-ECFeNJ.js → custom-model-catalog-gbLoUFki.js} +2 -2
  5. package/dist/detection-pipeline/index.js +4 -4
  6. package/dist/detection-pipeline/index.mjs +4 -4
  7. package/dist/{dist-QBVFmPYQ.js → dist-C0Mg8e3I.js} +924 -50
  8. package/dist/{dist-BT3uSeR7.mjs → dist-QyeZXkAO.mjs} +919 -51
  9. package/dist/{model-catalogs-C42Mo0jo.js → model-catalogs-BTuX-P0k.js} +1 -1
  10. package/dist/{model-catalogs-ChW0i2P6.mjs → model-catalogs-CIE4Dz4x.mjs} +1 -1
  11. package/dist/motion-wasm/index.js +1 -1
  12. package/dist/motion-wasm/index.mjs +1 -1
  13. package/dist/{node-B6ev-Hqa.js → node-CifGKjsd.js} +1 -1
  14. package/dist/{node-BVRFl1hZ.mjs → node-ndjp9DQO.mjs} +1 -1
  15. package/dist/pipeline-runner/index.js +3 -3
  16. package/dist/pipeline-runner/index.mjs +3 -3
  17. package/dist/{process-memory-Cdf2A2qg.js → process-memory-BMfSP5-i.js} +1 -1
  18. package/dist/{process-memory-DdeedEgU.mjs → process-memory-CaMI-Ry2.mjs} +1 -1
  19. package/dist/recorder/index.js +265 -94
  20. package/dist/recorder/index.mjs +263 -92
  21. package/dist/{segment-demux-js-8CfbEFeD.mjs → segment-demux-js-CLXBgUTo.mjs} +1 -1
  22. package/dist/{segment-demux-js-Dp0OI-2n.js → segment-demux-js-LhwVw18z.js} +1 -1
  23. package/dist/stream-broker/_stub.js +2 -2
  24. package/dist/stream-broker/{_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-Dl3wrJnM.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-B4MD_B4k.mjs} +2 -2
  25. package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-BvMrdOCD.mjs +26 -0
  26. package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-JVFs9LgI.mjs +26 -0
  27. package/dist/stream-broker/demux-worker-child.js +1 -1
  28. package/dist/stream-broker/demux-worker-child.mjs +1 -1
  29. package/dist/stream-broker/{hostInit-Bq36nWMP.mjs → hostInit-DBDp5YHD.mjs} +2 -2
  30. package/dist/stream-broker/index.js +1229 -288
  31. package/dist/stream-broker/index.mjs +1229 -288
  32. package/dist/stream-broker/remoteEntry.js +1 -1
  33. package/package.json +5 -1
  34. package/dist/retire-root-keys-Csb8z2f3.js +0 -338
  35. package/dist/retire-root-keys-DZvlso2l.mjs +0 -327
  36. package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-C3ssSNKh.mjs +0 -26
  37. package/dist/stream-broker/_virtual_mf___mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-BFRtKBOe.mjs +0 -26
@@ -30,7 +30,7 @@ async function d(e) {
30
30
  }
31
31
  }
32
32
  async function f() {
33
- return l ||= d(() => import("./_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-Dl3wrJnM.mjs")).catch((e) => {
33
+ return l ||= d(() => import("./_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-B4MD_B4k.mjs")).catch((e) => {
34
34
  throw l = void 0, e;
35
35
  }), l;
36
36
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-pipeline",
3
- "version": "1.2.373",
3
+ "version": "1.2.375",
4
4
  "description": "Pipeline bundle — runner, detection, motion, audio + stream broker. Multi-entry npm package shipping pipeline addons under a single bundle.",
5
5
  "keywords": [
6
6
  "camstack",
@@ -187,6 +187,10 @@
187
187
  "initialOldSpaceMb": 512
188
188
  },
189
189
  "capabilities": [
190
+ {
191
+ "name": "device-removal-hook",
192
+ "optional": true
193
+ },
190
194
  {
191
195
  "name": "stream-broker"
192
196
  },
@@ -1,338 +0,0 @@
1
- var ROW_MAP_VALUE_COLUMN = "value";
2
- var RowMapStore = class RowMapStore {
3
- spec;
4
- store;
5
- logger;
6
- constructor(deps) {
7
- this.spec = deps.spec;
8
- this.store = deps.store;
9
- this.logger = deps.logger;
10
- }
11
- /** The collection this store owns — for the caller's own log lines. */
12
- get collection() {
13
- return this.spec.collection;
14
- }
15
- /**
16
- * Register the collection. MUST run at boot, before any read or write: the
17
- * SQLite backend answers 412 for an undeclared collection.
18
- */
19
- static declare(store, spec) {
20
- return store.declareCollection.mutate({
21
- collection: spec.collection,
22
- columns: [
23
- {
24
- name: "id",
25
- type: "TEXT",
26
- primaryKey: true,
27
- notNull: true
28
- },
29
- ...spec.columns,
30
- {
31
- name: ROW_MAP_VALUE_COLUMN,
32
- type: "JSON",
33
- notNull: true
34
- }
35
- ],
36
- ...spec.indexes !== void 0 ? { indexes: [...spec.indexes] } : {}
37
- });
38
- }
39
- declare() {
40
- return RowMapStore.declare(this.store, this.spec);
41
- }
42
- /** One entry, by key. `null` when absent OR unparseable (which is said aloud). */
43
- async read(key) {
44
- const raw = await this.store.get.query({
45
- collection: this.spec.collection,
46
- key
47
- });
48
- if (!isRecord$1(raw)) return null;
49
- const row = raw;
50
- const parsed = this.spec.schema.safeParse(row[ROW_MAP_VALUE_COLUMN]);
51
- if (!parsed.success) {
52
- this.logger.warn("row-map row skipped as unparseable", {
53
- ...this.deviceTags(row),
54
- meta: {
55
- collection: this.spec.collection,
56
- key,
57
- error: parsed.error.message
58
- }
59
- });
60
- return null;
61
- }
62
- return parsed.data;
63
- }
64
- /**
65
- * Every entry, keyed. Unparseable rows are skipped — one bad entry costs that
66
- * entry, never the map.
67
- */
68
- async readAll() {
69
- const records = await this.store.query.query({
70
- collection: this.spec.collection,
71
- filter: { limit: this.spec.loadLimit }
72
- });
73
- const out = /* @__PURE__ */ new Map();
74
- let skipped = 0;
75
- for (const record of records) {
76
- const parsed = this.spec.schema.safeParse(record.data[ROW_MAP_VALUE_COLUMN]);
77
- if (!parsed.success) {
78
- skipped += 1;
79
- continue;
80
- }
81
- out.set(record.id, parsed.data);
82
- }
83
- if (skipped > 0) this.logger.warn("row-map rows skipped as unparseable", { meta: {
84
- collection: this.spec.collection,
85
- skipped,
86
- kept: out.size
87
- } });
88
- if (records.length >= this.spec.loadLimit) this.logger.warn("row-map read hit its own bound — the map may be truncated", { meta: {
89
- collection: this.spec.collection,
90
- loadLimit: this.spec.loadLimit
91
- } });
92
- return out;
93
- }
94
- /**
95
- * Every entry as it was WRITTEN, unvalidated and unparsed — the read a
96
- * MIGRATION needs.
97
- *
98
- * {@link readAll} answers through the CURRENT schema, which is exactly what a
99
- * migration must not do: a retired key is stripped by a non-strict `z.object`
100
- * before the migration could see it, so the row it is there to rescue looks
101
- * like a row that never needed rescuing. Only a migration should call this;
102
- * everything else wants the validated read.
103
- */
104
- async readAllRaw() {
105
- const records = await this.store.query.query({
106
- collection: this.spec.collection,
107
- filter: { limit: this.spec.loadLimit }
108
- });
109
- const out = /* @__PURE__ */ new Map();
110
- for (const record of records) out.set(record.id, record.data[ROW_MAP_VALUE_COLUMN]);
111
- return out;
112
- }
113
- /** Insert or replace ONE entry. Validates at the boundary. */
114
- async write(key, value) {
115
- const validated = this.spec.schema.parse(value);
116
- const columns = this.spec.project(key, validated);
117
- await this.store.set.mutate({
118
- collection: this.spec.collection,
119
- key,
120
- value: {
121
- ...columns,
122
- [ROW_MAP_VALUE_COLUMN]: validated
123
- }
124
- });
125
- }
126
- /** Drop ONE entry. Idempotent. */
127
- async remove(key) {
128
- await this.store.delete.mutate({
129
- collection: this.spec.collection,
130
- key
131
- });
132
- }
133
- /**
134
- * Make the collection hold exactly `next` — the write path for an owner whose
135
- * in-memory map IS the authority (a persister handed the complete set).
136
- *
137
- * Returns how many rows were DELETED, and says so out loud when any were: a
138
- * replace that silently drops rows is indistinguishable from one that never
139
- * had them. Only an owner holding an authoritative set may call this — a
140
- * prune driven by a fallible read is work destroyed (D130, D49).
141
- */
142
- async replaceAll(next) {
143
- const held = await this.store.query.query({
144
- collection: this.spec.collection,
145
- filter: { limit: this.spec.loadLimit }
146
- });
147
- for (const [key, value] of next) await this.write(key, value);
148
- const doomed = held.filter((record) => !next.has(record.id));
149
- for (const record of doomed) await this.remove(record.id);
150
- if (doomed.length > 0) this.logger.warn("row-map replaceAll dropped rows", { meta: {
151
- collection: this.spec.collection,
152
- dropped: doomed.length,
153
- keys: doomed.slice(0, 20).map((r) => r.id),
154
- remaining: next.size
155
- } });
156
- return doomed.length;
157
- }
158
- /**
159
- * The entries matching a projected-column predicate, in the order asked for.
160
- *
161
- * This is what projecting columns BUYS: "every export for camera 617, newest
162
- * first, capped at 50" is an indexed `WHERE` + `ORDER BY` + `LIMIT`, not a
163
- * parse of the whole map followed by a filter in JS.
164
- *
165
- * A `limit` is REQUIRED — `settings-store.query` silently caps an unbounded
166
- * read at 2 000 rows and answers as if that were everything.
167
- */
168
- async readWhere(filter) {
169
- const records = await this.store.query.query({
170
- collection: this.spec.collection,
171
- filter: {
172
- ...filter.where !== void 0 ? { where: filter.where } : {},
173
- ...filter.whereIn !== void 0 ? { whereIn: filter.whereIn } : {},
174
- ...filter.whereBetween !== void 0 ? { whereBetween: filter.whereBetween } : {},
175
- ...filter.orderBy !== void 0 ? { orderBy: filter.orderBy } : {},
176
- ...filter.offset !== void 0 ? { offset: filter.offset } : {},
177
- limit: filter.limit ?? this.spec.loadLimit
178
- }
179
- });
180
- const out = [];
181
- let skipped = 0;
182
- for (const record of records) {
183
- const parsed = this.spec.schema.safeParse(record.data[ROW_MAP_VALUE_COLUMN]);
184
- if (!parsed.success) {
185
- skipped += 1;
186
- continue;
187
- }
188
- out.push({
189
- key: record.id,
190
- value: parsed.data
191
- });
192
- }
193
- if (skipped > 0) this.logger.warn("row-map rows skipped as unparseable", { meta: {
194
- collection: this.spec.collection,
195
- skipped,
196
- kept: out.length
197
- } });
198
- return out;
199
- }
200
- /** How many rows match, without materialising one. */
201
- async count(filter) {
202
- return this.store.count.query({
203
- collection: this.spec.collection,
204
- ...filter !== void 0 ? { filter: {
205
- ...filter.where !== void 0 ? { where: filter.where } : {},
206
- ...filter.whereIn !== void 0 ? { whereIn: filter.whereIn } : {},
207
- ...filter.whereBetween !== void 0 ? { whereBetween: filter.whereBetween } : {}
208
- } } : {}
209
- });
210
- }
211
- /**
212
- * Delete every row matching a predicate, in ONE statement. Returns how many
213
- * went, and says so — a retention trim that silently drops rows is
214
- * indistinguishable from one that had nothing to do.
215
- *
216
- * The predicate is REQUIRED and the engine REFUSES one that compiles to
217
- * nothing: deleting a whole collection is a legitimate intent, but it must be
218
- * asked for by name rather than reached through an empty object.
219
- */
220
- async deleteWhere(filter) {
221
- const { deleted } = await this.store.deleteWhere.mutate({
222
- collection: this.spec.collection,
223
- filter
224
- });
225
- if (deleted > 0) this.logger.warn("row-map deleteWhere dropped rows", { meta: {
226
- collection: this.spec.collection,
227
- deleted,
228
- filter
229
- } });
230
- return deleted;
231
- }
232
- /** `tags: { deviceId }` off the PROJECTED column, when the spec names one. */
233
- deviceTags(row) {
234
- const column = this.spec.deviceIdColumn;
235
- if (column === void 0) return {};
236
- const raw = row[column];
237
- return typeof raw === "number" && Number.isInteger(raw) ? { tags: { deviceId: raw } } : {};
238
- }
239
- };
240
- /** A store payload that is a plain object, or `null`. A type guard, not a cast. */
241
- function isRecord$1(value) {
242
- return value !== null && typeof value === "object" && !Array.isArray(value);
243
- }
244
- //#endregion
245
- //#region src/durable/retire-root-keys.ts
246
- /** The canonical row a forked addon's settings live in. */
247
- var ADDON_SETTINGS_COLLECTION = "addon-settings";
248
- var ADDON_SETTINGS_ROW = "root";
249
- /**
250
- * Drop every retired key whose successor is populated. Returns the keys that
251
- * actually went — empty when there was nothing to do.
252
- */
253
- async function retireRootKeys(deps) {
254
- let row;
255
- try {
256
- const raw = await deps.store.get.query({
257
- namespace: deps.addonId,
258
- collection: ADDON_SETTINGS_COLLECTION,
259
- key: ADDON_SETTINGS_ROW
260
- });
261
- if (!isRecord(raw)) return [];
262
- row = raw;
263
- } catch (err) {
264
- deps.logger.warn("retired root keys — read failed, the settings row is untouched", { meta: {
265
- addonId: deps.addonId,
266
- error: errText(err)
267
- } });
268
- return [];
269
- }
270
- const present = deps.specs.filter((spec) => spec.key in row);
271
- if (present.length === 0) return [];
272
- /** Evidence counts, memoised — several keys may share one successor. */
273
- const evidence = /* @__PURE__ */ new Map();
274
- const doomed = [];
275
- for (const spec of present) {
276
- let count = evidence.get(spec.successor);
277
- if (count === void 0) {
278
- try {
279
- count = await deps.store.count.query({ collection: spec.successor });
280
- } catch (err) {
281
- deps.logger.warn("retired root key kept — its successor is unreadable", { meta: {
282
- addonId: deps.addonId,
283
- key: spec.key,
284
- successor: spec.successor,
285
- error: errText(err)
286
- } });
287
- count = 0;
288
- }
289
- evidence.set(spec.successor, count);
290
- }
291
- if (count === 0) {
292
- deps.logger.warn("retired root key kept — its successor is EMPTY, migration has not run", { meta: {
293
- addonId: deps.addonId,
294
- key: spec.key,
295
- successor: spec.successor
296
- } });
297
- continue;
298
- }
299
- doomed.push(spec);
300
- }
301
- if (doomed.length === 0) return [];
302
- const dropped = new Set(doomed.map((spec) => spec.key));
303
- const next = Object.fromEntries(Object.entries(row).filter(([key]) => !dropped.has(key)));
304
- await deps.store.set.mutate({
305
- namespace: deps.addonId,
306
- collection: ADDON_SETTINGS_COLLECTION,
307
- key: ADDON_SETTINGS_ROW,
308
- value: next
309
- });
310
- const keys = doomed.map((spec) => spec.key);
311
- deps.logger.info(`purged ${keys.length} retired settings key(s) from the root blob`, { meta: {
312
- addonId: deps.addonId,
313
- keys,
314
- keptKeys: Object.keys(next),
315
- reasons: doomed.map((spec) => `${spec.key}: ${spec.reason}`)
316
- } });
317
- return keys;
318
- }
319
- /** A store payload that is a plain object. A type guard, not a cast. */
320
- function isRecord(value) {
321
- return value !== null && typeof value === "object" && !Array.isArray(value);
322
- }
323
- function errText(err) {
324
- return err instanceof Error ? err.message : String(err);
325
- }
326
- //#endregion
327
- Object.defineProperty(exports, "RowMapStore", {
328
- enumerable: true,
329
- get: function() {
330
- return RowMapStore;
331
- }
332
- });
333
- Object.defineProperty(exports, "retireRootKeys", {
334
- enumerable: true,
335
- get: function() {
336
- return retireRootKeys;
337
- }
338
- });
@@ -1,327 +0,0 @@
1
- var ROW_MAP_VALUE_COLUMN = "value";
2
- var RowMapStore = class RowMapStore {
3
- spec;
4
- store;
5
- logger;
6
- constructor(deps) {
7
- this.spec = deps.spec;
8
- this.store = deps.store;
9
- this.logger = deps.logger;
10
- }
11
- /** The collection this store owns — for the caller's own log lines. */
12
- get collection() {
13
- return this.spec.collection;
14
- }
15
- /**
16
- * Register the collection. MUST run at boot, before any read or write: the
17
- * SQLite backend answers 412 for an undeclared collection.
18
- */
19
- static declare(store, spec) {
20
- return store.declareCollection.mutate({
21
- collection: spec.collection,
22
- columns: [
23
- {
24
- name: "id",
25
- type: "TEXT",
26
- primaryKey: true,
27
- notNull: true
28
- },
29
- ...spec.columns,
30
- {
31
- name: ROW_MAP_VALUE_COLUMN,
32
- type: "JSON",
33
- notNull: true
34
- }
35
- ],
36
- ...spec.indexes !== void 0 ? { indexes: [...spec.indexes] } : {}
37
- });
38
- }
39
- declare() {
40
- return RowMapStore.declare(this.store, this.spec);
41
- }
42
- /** One entry, by key. `null` when absent OR unparseable (which is said aloud). */
43
- async read(key) {
44
- const raw = await this.store.get.query({
45
- collection: this.spec.collection,
46
- key
47
- });
48
- if (!isRecord$1(raw)) return null;
49
- const row = raw;
50
- const parsed = this.spec.schema.safeParse(row[ROW_MAP_VALUE_COLUMN]);
51
- if (!parsed.success) {
52
- this.logger.warn("row-map row skipped as unparseable", {
53
- ...this.deviceTags(row),
54
- meta: {
55
- collection: this.spec.collection,
56
- key,
57
- error: parsed.error.message
58
- }
59
- });
60
- return null;
61
- }
62
- return parsed.data;
63
- }
64
- /**
65
- * Every entry, keyed. Unparseable rows are skipped — one bad entry costs that
66
- * entry, never the map.
67
- */
68
- async readAll() {
69
- const records = await this.store.query.query({
70
- collection: this.spec.collection,
71
- filter: { limit: this.spec.loadLimit }
72
- });
73
- const out = /* @__PURE__ */ new Map();
74
- let skipped = 0;
75
- for (const record of records) {
76
- const parsed = this.spec.schema.safeParse(record.data[ROW_MAP_VALUE_COLUMN]);
77
- if (!parsed.success) {
78
- skipped += 1;
79
- continue;
80
- }
81
- out.set(record.id, parsed.data);
82
- }
83
- if (skipped > 0) this.logger.warn("row-map rows skipped as unparseable", { meta: {
84
- collection: this.spec.collection,
85
- skipped,
86
- kept: out.size
87
- } });
88
- if (records.length >= this.spec.loadLimit) this.logger.warn("row-map read hit its own bound — the map may be truncated", { meta: {
89
- collection: this.spec.collection,
90
- loadLimit: this.spec.loadLimit
91
- } });
92
- return out;
93
- }
94
- /**
95
- * Every entry as it was WRITTEN, unvalidated and unparsed — the read a
96
- * MIGRATION needs.
97
- *
98
- * {@link readAll} answers through the CURRENT schema, which is exactly what a
99
- * migration must not do: a retired key is stripped by a non-strict `z.object`
100
- * before the migration could see it, so the row it is there to rescue looks
101
- * like a row that never needed rescuing. Only a migration should call this;
102
- * everything else wants the validated read.
103
- */
104
- async readAllRaw() {
105
- const records = await this.store.query.query({
106
- collection: this.spec.collection,
107
- filter: { limit: this.spec.loadLimit }
108
- });
109
- const out = /* @__PURE__ */ new Map();
110
- for (const record of records) out.set(record.id, record.data[ROW_MAP_VALUE_COLUMN]);
111
- return out;
112
- }
113
- /** Insert or replace ONE entry. Validates at the boundary. */
114
- async write(key, value) {
115
- const validated = this.spec.schema.parse(value);
116
- const columns = this.spec.project(key, validated);
117
- await this.store.set.mutate({
118
- collection: this.spec.collection,
119
- key,
120
- value: {
121
- ...columns,
122
- [ROW_MAP_VALUE_COLUMN]: validated
123
- }
124
- });
125
- }
126
- /** Drop ONE entry. Idempotent. */
127
- async remove(key) {
128
- await this.store.delete.mutate({
129
- collection: this.spec.collection,
130
- key
131
- });
132
- }
133
- /**
134
- * Make the collection hold exactly `next` — the write path for an owner whose
135
- * in-memory map IS the authority (a persister handed the complete set).
136
- *
137
- * Returns how many rows were DELETED, and says so out loud when any were: a
138
- * replace that silently drops rows is indistinguishable from one that never
139
- * had them. Only an owner holding an authoritative set may call this — a
140
- * prune driven by a fallible read is work destroyed (D130, D49).
141
- */
142
- async replaceAll(next) {
143
- const held = await this.store.query.query({
144
- collection: this.spec.collection,
145
- filter: { limit: this.spec.loadLimit }
146
- });
147
- for (const [key, value] of next) await this.write(key, value);
148
- const doomed = held.filter((record) => !next.has(record.id));
149
- for (const record of doomed) await this.remove(record.id);
150
- if (doomed.length > 0) this.logger.warn("row-map replaceAll dropped rows", { meta: {
151
- collection: this.spec.collection,
152
- dropped: doomed.length,
153
- keys: doomed.slice(0, 20).map((r) => r.id),
154
- remaining: next.size
155
- } });
156
- return doomed.length;
157
- }
158
- /**
159
- * The entries matching a projected-column predicate, in the order asked for.
160
- *
161
- * This is what projecting columns BUYS: "every export for camera 617, newest
162
- * first, capped at 50" is an indexed `WHERE` + `ORDER BY` + `LIMIT`, not a
163
- * parse of the whole map followed by a filter in JS.
164
- *
165
- * A `limit` is REQUIRED — `settings-store.query` silently caps an unbounded
166
- * read at 2 000 rows and answers as if that were everything.
167
- */
168
- async readWhere(filter) {
169
- const records = await this.store.query.query({
170
- collection: this.spec.collection,
171
- filter: {
172
- ...filter.where !== void 0 ? { where: filter.where } : {},
173
- ...filter.whereIn !== void 0 ? { whereIn: filter.whereIn } : {},
174
- ...filter.whereBetween !== void 0 ? { whereBetween: filter.whereBetween } : {},
175
- ...filter.orderBy !== void 0 ? { orderBy: filter.orderBy } : {},
176
- ...filter.offset !== void 0 ? { offset: filter.offset } : {},
177
- limit: filter.limit ?? this.spec.loadLimit
178
- }
179
- });
180
- const out = [];
181
- let skipped = 0;
182
- for (const record of records) {
183
- const parsed = this.spec.schema.safeParse(record.data[ROW_MAP_VALUE_COLUMN]);
184
- if (!parsed.success) {
185
- skipped += 1;
186
- continue;
187
- }
188
- out.push({
189
- key: record.id,
190
- value: parsed.data
191
- });
192
- }
193
- if (skipped > 0) this.logger.warn("row-map rows skipped as unparseable", { meta: {
194
- collection: this.spec.collection,
195
- skipped,
196
- kept: out.length
197
- } });
198
- return out;
199
- }
200
- /** How many rows match, without materialising one. */
201
- async count(filter) {
202
- return this.store.count.query({
203
- collection: this.spec.collection,
204
- ...filter !== void 0 ? { filter: {
205
- ...filter.where !== void 0 ? { where: filter.where } : {},
206
- ...filter.whereIn !== void 0 ? { whereIn: filter.whereIn } : {},
207
- ...filter.whereBetween !== void 0 ? { whereBetween: filter.whereBetween } : {}
208
- } } : {}
209
- });
210
- }
211
- /**
212
- * Delete every row matching a predicate, in ONE statement. Returns how many
213
- * went, and says so — a retention trim that silently drops rows is
214
- * indistinguishable from one that had nothing to do.
215
- *
216
- * The predicate is REQUIRED and the engine REFUSES one that compiles to
217
- * nothing: deleting a whole collection is a legitimate intent, but it must be
218
- * asked for by name rather than reached through an empty object.
219
- */
220
- async deleteWhere(filter) {
221
- const { deleted } = await this.store.deleteWhere.mutate({
222
- collection: this.spec.collection,
223
- filter
224
- });
225
- if (deleted > 0) this.logger.warn("row-map deleteWhere dropped rows", { meta: {
226
- collection: this.spec.collection,
227
- deleted,
228
- filter
229
- } });
230
- return deleted;
231
- }
232
- /** `tags: { deviceId }` off the PROJECTED column, when the spec names one. */
233
- deviceTags(row) {
234
- const column = this.spec.deviceIdColumn;
235
- if (column === void 0) return {};
236
- const raw = row[column];
237
- return typeof raw === "number" && Number.isInteger(raw) ? { tags: { deviceId: raw } } : {};
238
- }
239
- };
240
- /** A store payload that is a plain object, or `null`. A type guard, not a cast. */
241
- function isRecord$1(value) {
242
- return value !== null && typeof value === "object" && !Array.isArray(value);
243
- }
244
- //#endregion
245
- //#region src/durable/retire-root-keys.ts
246
- /** The canonical row a forked addon's settings live in. */
247
- var ADDON_SETTINGS_COLLECTION = "addon-settings";
248
- var ADDON_SETTINGS_ROW = "root";
249
- /**
250
- * Drop every retired key whose successor is populated. Returns the keys that
251
- * actually went — empty when there was nothing to do.
252
- */
253
- async function retireRootKeys(deps) {
254
- let row;
255
- try {
256
- const raw = await deps.store.get.query({
257
- namespace: deps.addonId,
258
- collection: ADDON_SETTINGS_COLLECTION,
259
- key: ADDON_SETTINGS_ROW
260
- });
261
- if (!isRecord(raw)) return [];
262
- row = raw;
263
- } catch (err) {
264
- deps.logger.warn("retired root keys — read failed, the settings row is untouched", { meta: {
265
- addonId: deps.addonId,
266
- error: errText(err)
267
- } });
268
- return [];
269
- }
270
- const present = deps.specs.filter((spec) => spec.key in row);
271
- if (present.length === 0) return [];
272
- /** Evidence counts, memoised — several keys may share one successor. */
273
- const evidence = /* @__PURE__ */ new Map();
274
- const doomed = [];
275
- for (const spec of present) {
276
- let count = evidence.get(spec.successor);
277
- if (count === void 0) {
278
- try {
279
- count = await deps.store.count.query({ collection: spec.successor });
280
- } catch (err) {
281
- deps.logger.warn("retired root key kept — its successor is unreadable", { meta: {
282
- addonId: deps.addonId,
283
- key: spec.key,
284
- successor: spec.successor,
285
- error: errText(err)
286
- } });
287
- count = 0;
288
- }
289
- evidence.set(spec.successor, count);
290
- }
291
- if (count === 0) {
292
- deps.logger.warn("retired root key kept — its successor is EMPTY, migration has not run", { meta: {
293
- addonId: deps.addonId,
294
- key: spec.key,
295
- successor: spec.successor
296
- } });
297
- continue;
298
- }
299
- doomed.push(spec);
300
- }
301
- if (doomed.length === 0) return [];
302
- const dropped = new Set(doomed.map((spec) => spec.key));
303
- const next = Object.fromEntries(Object.entries(row).filter(([key]) => !dropped.has(key)));
304
- await deps.store.set.mutate({
305
- namespace: deps.addonId,
306
- collection: ADDON_SETTINGS_COLLECTION,
307
- key: ADDON_SETTINGS_ROW,
308
- value: next
309
- });
310
- const keys = doomed.map((spec) => spec.key);
311
- deps.logger.info(`purged ${keys.length} retired settings key(s) from the root blob`, { meta: {
312
- addonId: deps.addonId,
313
- keys,
314
- keptKeys: Object.keys(next),
315
- reasons: doomed.map((spec) => `${spec.key}: ${spec.reason}`)
316
- } });
317
- return keys;
318
- }
319
- /** A store payload that is a plain object. A type guard, not a cast. */
320
- function isRecord(value) {
321
- return value !== null && typeof value === "object" && !Array.isArray(value);
322
- }
323
- function errText(err) {
324
- return err instanceof Error ? err.message : String(err);
325
- }
326
- //#endregion
327
- export { RowMapStore as n, retireRootKeys as t };