@camstack/system 1.2.135 → 1.2.136

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.
@@ -1315,7 +1315,10 @@ async function listAll(pctx, input) {
1315
1315
  const idFilter = input.deviceIds === void 0 ? null : new Set(input.deviceIds);
1316
1316
  const results = [];
1317
1317
  const seen = /* @__PURE__ */ new Set();
1318
- const fleet = addonId ? await pctx.metaStore.rows.listByAddon(addonId) : await pctx.metaStore.rows.listAll();
1318
+ const fleet = addonId ? await pctx.metaStore.rows.listByAddon(addonId) : await pctx.metaStore.rows.listFleet({
1319
+ ...input.deviceIds !== void 0 ? { deviceIds: input.deviceIds } : {},
1320
+ slim
1321
+ });
1319
1322
  const rowById = /* @__PURE__ */ new Map();
1320
1323
  for (const row of fleet) rowById.set(row.meta.id, row);
1321
1324
  if (pctx.registry) {
@@ -4051,6 +4054,36 @@ var DEVICE_ROWS_COLUMNS = [
4051
4054
  type: "JSON"
4052
4055
  }
4053
4056
  ];
4057
+ /**
4058
+ * The columns a SLIM fleet read selects: every declared column except the
4059
+ * primary key (always returned) and `metadata`.
4060
+ *
4061
+ * ## Why this is a DENYLIST and must stay one
4062
+ *
4063
+ * `decodeDeviceRow` returns `null` when `deviceId`, `addonId`, `stableId`,
4064
+ * `type` or `name` is missing, and `list()` then SKIPS the row with a warn. A
4065
+ * hand-written allowlist that forgot one of the five would not make the fleet
4066
+ * slower — it would make devices vanish from every listing, quietly, and the
4067
+ * only trace would be a log line nobody is reading at the time. Deriving the
4068
+ * list by subtraction means a column added tomorrow is carried by default and
4069
+ * the only way to lose one is to name it here.
4070
+ *
4071
+ * `metadata` is the one slim provably discards: `device-queries.ts#listAll`
4072
+ * sets `metadata: null` on every slim row. It is also the largest JSON column
4073
+ * on the row, which is why this is worth doing at all.
4074
+ *
4075
+ * ## What is NOT here, and will not be
4076
+ *
4077
+ * There is no `where: { type: 'camera' }` companion to this. `isCameraDevice`
4078
+ * is a method-presence check (`getStreamSources`), not the persisted `type`, so
4079
+ * a doorbell with a camera answers YES while its row says `doorbell`. Filtering
4080
+ * the rows by type would strip exactly those devices of their row — and
4081
+ * `toDeviceInfo` reads `name` / `location` / `disabled` from the ROW precisely
4082
+ * because the live device's copies are stale by construction. The fleet would
4083
+ * keep working and start reporting pre-rename names on doorbells. The
4084
+ * `isCamera` filter stays in JS.
4085
+ */
4086
+ var DEVICE_ROWS_SLIM_COLUMNS = DEVICE_ROWS_COLUMNS.map((c) => c.name).filter((name) => name !== "id" && name !== "metadata");
4054
4087
  var DEVICE_ROWS_INDEXES = [
4055
4088
  {
4056
4089
  name: "idx_dm_devices_addon_stable",
@@ -4269,6 +4302,33 @@ var DeviceRowStore = class {
4269
4302
  });
4270
4303
  }
4271
4304
  /**
4305
+ * The fleet, read as narrowly as the question allows.
4306
+ *
4307
+ * `listAll` is a full-table `SELECT … ORDER BY "deviceId"` — ~1 017 rows a
4308
+ * call on the live hub, 14–54 calls a minute — and its callers then filtered
4309
+ * the result in JS. The two filters that CAN be pushed down are here:
4310
+ *
4311
+ * - `deviceIds` becomes a `whereIn`. The viewer's linked-devices panel asks
4312
+ * about ~8 named ids; it was answered by reading the whole ledger. An
4313
+ * EMPTY set reads nothing at all rather than degrading to "everything" —
4314
+ * a request for no devices is a request, not an omission.
4315
+ * - `slim` becomes a column projection, {@link DEVICE_ROWS_SLIM_COLUMNS}.
4316
+ *
4317
+ * `isCamera` is deliberately NOT one of them — see that constant's note and
4318
+ * `device-row-store-filtered.spec.ts`.
4319
+ */
4320
+ async listFleet(opts) {
4321
+ if (opts.deviceIds !== void 0 && opts.deviceIds.length === 0) return [];
4322
+ return this.list({
4323
+ ...opts.deviceIds !== void 0 ? { whereIn: { deviceId: [...new Set(opts.deviceIds)] } } : {},
4324
+ orderBy: {
4325
+ field: "deviceId",
4326
+ direction: "asc"
4327
+ },
4328
+ limit: DEVICE_ROWS_FLEET_LIMIT
4329
+ }, opts.slim === true ? DEVICE_ROWS_SLIM_COLUMNS : void 0);
4330
+ }
4331
+ /**
4272
4332
  * The device an addon knows as `stableId`, or `null`.
4273
4333
  *
4274
4334
  * `(addonId, stableId)` is the addon-facing identity — unique by
@@ -4339,11 +4399,12 @@ var DeviceRowStore = class {
4339
4399
  await this.declare();
4340
4400
  return this.backend.count({ collection: DEVICE_ROWS_COLLECTION });
4341
4401
  }
4342
- async list(filter) {
4402
+ async list(filter, columns) {
4343
4403
  await this.declare();
4344
4404
  const records = await this.backend.query({
4345
4405
  collection: DEVICE_ROWS_COLLECTION,
4346
- filter
4406
+ filter,
4407
+ ...columns === void 0 ? {} : { columns }
4347
4408
  });
4348
4409
  const out = [];
4349
4410
  for (const record of records) {
@@ -1310,7 +1310,10 @@ async function listAll(pctx, input) {
1310
1310
  const idFilter = input.deviceIds === void 0 ? null : new Set(input.deviceIds);
1311
1311
  const results = [];
1312
1312
  const seen = /* @__PURE__ */ new Set();
1313
- const fleet = addonId ? await pctx.metaStore.rows.listByAddon(addonId) : await pctx.metaStore.rows.listAll();
1313
+ const fleet = addonId ? await pctx.metaStore.rows.listByAddon(addonId) : await pctx.metaStore.rows.listFleet({
1314
+ ...input.deviceIds !== void 0 ? { deviceIds: input.deviceIds } : {},
1315
+ slim
1316
+ });
1314
1317
  const rowById = /* @__PURE__ */ new Map();
1315
1318
  for (const row of fleet) rowById.set(row.meta.id, row);
1316
1319
  if (pctx.registry) {
@@ -4046,6 +4049,36 @@ var DEVICE_ROWS_COLUMNS = [
4046
4049
  type: "JSON"
4047
4050
  }
4048
4051
  ];
4052
+ /**
4053
+ * The columns a SLIM fleet read selects: every declared column except the
4054
+ * primary key (always returned) and `metadata`.
4055
+ *
4056
+ * ## Why this is a DENYLIST and must stay one
4057
+ *
4058
+ * `decodeDeviceRow` returns `null` when `deviceId`, `addonId`, `stableId`,
4059
+ * `type` or `name` is missing, and `list()` then SKIPS the row with a warn. A
4060
+ * hand-written allowlist that forgot one of the five would not make the fleet
4061
+ * slower — it would make devices vanish from every listing, quietly, and the
4062
+ * only trace would be a log line nobody is reading at the time. Deriving the
4063
+ * list by subtraction means a column added tomorrow is carried by default and
4064
+ * the only way to lose one is to name it here.
4065
+ *
4066
+ * `metadata` is the one slim provably discards: `device-queries.ts#listAll`
4067
+ * sets `metadata: null` on every slim row. It is also the largest JSON column
4068
+ * on the row, which is why this is worth doing at all.
4069
+ *
4070
+ * ## What is NOT here, and will not be
4071
+ *
4072
+ * There is no `where: { type: 'camera' }` companion to this. `isCameraDevice`
4073
+ * is a method-presence check (`getStreamSources`), not the persisted `type`, so
4074
+ * a doorbell with a camera answers YES while its row says `doorbell`. Filtering
4075
+ * the rows by type would strip exactly those devices of their row — and
4076
+ * `toDeviceInfo` reads `name` / `location` / `disabled` from the ROW precisely
4077
+ * because the live device's copies are stale by construction. The fleet would
4078
+ * keep working and start reporting pre-rename names on doorbells. The
4079
+ * `isCamera` filter stays in JS.
4080
+ */
4081
+ var DEVICE_ROWS_SLIM_COLUMNS = DEVICE_ROWS_COLUMNS.map((c) => c.name).filter((name) => name !== "id" && name !== "metadata");
4049
4082
  var DEVICE_ROWS_INDEXES = [
4050
4083
  {
4051
4084
  name: "idx_dm_devices_addon_stable",
@@ -4264,6 +4297,33 @@ var DeviceRowStore = class {
4264
4297
  });
4265
4298
  }
4266
4299
  /**
4300
+ * The fleet, read as narrowly as the question allows.
4301
+ *
4302
+ * `listAll` is a full-table `SELECT … ORDER BY "deviceId"` — ~1 017 rows a
4303
+ * call on the live hub, 14–54 calls a minute — and its callers then filtered
4304
+ * the result in JS. The two filters that CAN be pushed down are here:
4305
+ *
4306
+ * - `deviceIds` becomes a `whereIn`. The viewer's linked-devices panel asks
4307
+ * about ~8 named ids; it was answered by reading the whole ledger. An
4308
+ * EMPTY set reads nothing at all rather than degrading to "everything" —
4309
+ * a request for no devices is a request, not an omission.
4310
+ * - `slim` becomes a column projection, {@link DEVICE_ROWS_SLIM_COLUMNS}.
4311
+ *
4312
+ * `isCamera` is deliberately NOT one of them — see that constant's note and
4313
+ * `device-row-store-filtered.spec.ts`.
4314
+ */
4315
+ async listFleet(opts) {
4316
+ if (opts.deviceIds !== void 0 && opts.deviceIds.length === 0) return [];
4317
+ return this.list({
4318
+ ...opts.deviceIds !== void 0 ? { whereIn: { deviceId: [...new Set(opts.deviceIds)] } } : {},
4319
+ orderBy: {
4320
+ field: "deviceId",
4321
+ direction: "asc"
4322
+ },
4323
+ limit: DEVICE_ROWS_FLEET_LIMIT
4324
+ }, opts.slim === true ? DEVICE_ROWS_SLIM_COLUMNS : void 0);
4325
+ }
4326
+ /**
4267
4327
  * The device an addon knows as `stableId`, or `null`.
4268
4328
  *
4269
4329
  * `(addonId, stableId)` is the addon-facing identity — unique by
@@ -4334,11 +4394,12 @@ var DeviceRowStore = class {
4334
4394
  await this.declare();
4335
4395
  return this.backend.count({ collection: DEVICE_ROWS_COLLECTION });
4336
4396
  }
4337
- async list(filter) {
4397
+ async list(filter, columns) {
4338
4398
  await this.declare();
4339
4399
  const records = await this.backend.query({
4340
4400
  collection: DEVICE_ROWS_COLLECTION,
4341
- filter
4401
+ filter,
4402
+ ...columns === void 0 ? {} : { columns }
4342
4403
  });
4343
4404
  const out = [];
4344
4405
  for (const record of records) {
@@ -43,6 +43,36 @@ export declare const DEVICE_ROWS_COLLECTION = "device-manager:devices";
43
43
  */
44
44
  export declare const DEVICE_ROWS_FLEET_LIMIT = 20000;
45
45
  export declare const DEVICE_ROWS_COLUMNS: readonly CollectionColumn[];
46
+ /**
47
+ * The columns a SLIM fleet read selects: every declared column except the
48
+ * primary key (always returned) and `metadata`.
49
+ *
50
+ * ## Why this is a DENYLIST and must stay one
51
+ *
52
+ * `decodeDeviceRow` returns `null` when `deviceId`, `addonId`, `stableId`,
53
+ * `type` or `name` is missing, and `list()` then SKIPS the row with a warn. A
54
+ * hand-written allowlist that forgot one of the five would not make the fleet
55
+ * slower — it would make devices vanish from every listing, quietly, and the
56
+ * only trace would be a log line nobody is reading at the time. Deriving the
57
+ * list by subtraction means a column added tomorrow is carried by default and
58
+ * the only way to lose one is to name it here.
59
+ *
60
+ * `metadata` is the one slim provably discards: `device-queries.ts#listAll`
61
+ * sets `metadata: null` on every slim row. It is also the largest JSON column
62
+ * on the row, which is why this is worth doing at all.
63
+ *
64
+ * ## What is NOT here, and will not be
65
+ *
66
+ * There is no `where: { type: 'camera' }` companion to this. `isCameraDevice`
67
+ * is a method-presence check (`getStreamSources`), not the persisted `type`, so
68
+ * a doorbell with a camera answers YES while its row says `doorbell`. Filtering
69
+ * the rows by type would strip exactly those devices of their row — and
70
+ * `toDeviceInfo` reads `name` / `location` / `disabled` from the ROW precisely
71
+ * because the live device's copies are stale by construction. The fleet would
72
+ * keep working and start reporting pre-rename names on doorbells. The
73
+ * `isCamera` filter stays in JS.
74
+ */
75
+ export declare const DEVICE_ROWS_SLIM_COLUMNS: readonly string[];
46
76
  export declare const DEVICE_ROWS_INDEXES: readonly CollectionIndex[];
47
77
  /**
48
78
  * The settings-store door this store needs, as a plain object surface.
@@ -193,6 +223,26 @@ export declare class DeviceRowStore {
193
223
  get(deviceId: number): Promise<DeviceRow | null>;
194
224
  /** Every device, ordered by numeric id. */
195
225
  listAll(): Promise<readonly DeviceRow[]>;
226
+ /**
227
+ * The fleet, read as narrowly as the question allows.
228
+ *
229
+ * `listAll` is a full-table `SELECT … ORDER BY "deviceId"` — ~1 017 rows a
230
+ * call on the live hub, 14–54 calls a minute — and its callers then filtered
231
+ * the result in JS. The two filters that CAN be pushed down are here:
232
+ *
233
+ * - `deviceIds` becomes a `whereIn`. The viewer's linked-devices panel asks
234
+ * about ~8 named ids; it was answered by reading the whole ledger. An
235
+ * EMPTY set reads nothing at all rather than degrading to "everything" —
236
+ * a request for no devices is a request, not an omission.
237
+ * - `slim` becomes a column projection, {@link DEVICE_ROWS_SLIM_COLUMNS}.
238
+ *
239
+ * `isCamera` is deliberately NOT one of them — see that constant's note and
240
+ * `device-row-store-filtered.spec.ts`.
241
+ */
242
+ listFleet(opts: {
243
+ readonly deviceIds?: readonly number[];
244
+ readonly slim?: boolean;
245
+ }): Promise<readonly DeviceRow[]>;
196
246
  /**
197
247
  * The device an addon knows as `stableId`, or `null`.
198
248
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/system",
3
- "version": "1.2.135",
3
+ "version": "1.2.136",
4
4
  "description": "Core addon for CamStack — builtins, pipeline, process management, auth, logging, events",
5
5
  "keywords": [
6
6
  "camstack",