camstack 1.2.89 → 1.2.90

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/cli.js CHANGED
@@ -38,7 +38,7 @@ async function runServe(args) {
38
38
  ...typeof values.data === "string" ? { data: values.data } : {}
39
39
  };
40
40
  Object.assign(process.env, buildServeEnv(opts));
41
- await import("./launcher-POOJGOAQ.js");
41
+ await import("./launcher-7KH64TCV.js");
42
42
  }
43
43
 
44
44
  // src/commands/agent.ts
@@ -83,7 +83,7 @@ async function runAgent(args) {
83
83
  ...typeof values.port === "string" ? { port: values.port } : {}
84
84
  };
85
85
  Object.assign(process.env, buildAgentEnv(opts));
86
- await import("./launcher-POOJGOAQ.js");
86
+ await import("./launcher-7KH64TCV.js");
87
87
  }
88
88
 
89
89
  // src/commands/setup.ts
@@ -65799,6 +65799,68 @@ var require_device_manager_addon = __commonJS({
65799
65799
  return linkedIds.filter((id) => picked.has(id));
65800
65800
  }
65801
65801
  var LINK_CANDIDATE_PROJECTION = { projection: "slim" };
65802
+ function findLocationInvisibleContainers(params) {
65803
+ const { selfId, selfLocation, resolvedIds, candidates } = params;
65804
+ if (selfLocation === null || selfLocation.length === 0) return [];
65805
+ const byParent = /* @__PURE__ */ new Map();
65806
+ for (const c of candidates) {
65807
+ if (c.parentDeviceId === null) continue;
65808
+ const bucket = byParent.get(c.parentDeviceId);
65809
+ if (bucket) bucket.push(c);
65810
+ else byParent.set(c.parentDeviceId, [c]);
65811
+ }
65812
+ const out = [];
65813
+ for (const c of candidates) {
65814
+ if (c.type !== require_dist10.DeviceType.Container) continue;
65815
+ if (c.id === selfId) continue;
65816
+ if (c.location !== selfLocation) continue;
65817
+ let anyResolved = false;
65818
+ let anyNullLocation = false;
65819
+ let descendantCount = 0;
65820
+ const visited = /* @__PURE__ */ new Set([c.id]);
65821
+ let frontier = [c.id];
65822
+ while (frontier.length > 0) {
65823
+ const next = [];
65824
+ for (const parentId of frontier) for (const child of byParent.get(parentId) ?? []) {
65825
+ if (visited.has(child.id)) continue;
65826
+ visited.add(child.id);
65827
+ descendantCount += 1;
65828
+ if (resolvedIds.has(child.id)) anyResolved = true;
65829
+ if (child.location === null) anyNullLocation = true;
65830
+ next.push(child.id);
65831
+ }
65832
+ frontier = next;
65833
+ }
65834
+ if (descendantCount > 0 && !anyResolved && anyNullLocation) out.push(c.id);
65835
+ }
65836
+ return out;
65837
+ }
65838
+ var INVISIBLE_CONTAINER_WARN_CAP = 4096;
65839
+ var warnedInvisibleContainers = /* @__PURE__ */ new Set();
65840
+ function warnLocationInvisibleContainers(pctx, cameraId, byId, all, resolved) {
65841
+ if (resolved.mode !== "auto") return;
65842
+ const selfLocation = byId.get(cameraId)?.location ?? null;
65843
+ if (selfLocation === null || selfLocation.length === 0) return;
65844
+ const invisible = findLocationInvisibleContainers({
65845
+ selfId: cameraId,
65846
+ selfLocation,
65847
+ resolvedIds: new Set(resolved.devices.map((d) => d.deviceId)),
65848
+ candidates: all
65849
+ });
65850
+ for (const containerId of invisible) {
65851
+ const key = `${cameraId}:${containerId}`;
65852
+ if (warnedInvisibleContainers.has(key)) continue;
65853
+ if (warnedInvisibleContainers.size >= INVISIBLE_CONTAINER_WARN_CAP) return;
65854
+ warnedInvisibleContainers.add(key);
65855
+ pctx.host.ctx.logger.warn("getLinkedDevices: container matches the camera location but is invisible to auto-link \u2014 its children carry no location; re-save the container location to realign", {
65856
+ tags: { deviceId: containerId },
65857
+ meta: {
65858
+ cameraId,
65859
+ location: selfLocation
65860
+ }
65861
+ });
65862
+ }
65863
+ }
65802
65864
  function resolveFor(deviceId, all, byId, blob) {
65803
65865
  const config = parseLinkedDevicesConfig(blob);
65804
65866
  const ids = resolveLinkedDeviceIds({
@@ -65828,7 +65890,10 @@ var require_device_manager_addon = __commonJS({
65828
65890
  async function getLinkedDevices(pctx, input) {
65829
65891
  const { deviceId } = input;
65830
65892
  const [all, blob] = await Promise.all([listAll(pctx, LINK_CANDIDATE_PROJECTION), pctx.settings.readDeviceStore(deviceId)]);
65831
- return resolveFor(deviceId, all, new Map(all.map((d) => [d.id, d])), blob);
65893
+ const byId = new Map(all.map((d) => [d.id, d]));
65894
+ const resolved = resolveFor(deviceId, all, byId, blob);
65895
+ warnLocationInvisibleContainers(pctx, deviceId, byId, all, resolved);
65896
+ return resolved;
65832
65897
  }
65833
65898
  async function getLinkedDevicesBatch(pctx, input) {
65834
65899
  const deviceIds = [...new Set(input.deviceIds)];
@@ -65841,10 +65906,14 @@ var require_device_manager_addon = __commonJS({
65841
65906
  return {};
65842
65907
  })))]);
65843
65908
  const byId = new Map(all.map((d) => [d.id, d]));
65844
- return deviceIds.map((deviceId, index) => ({
65845
- deviceId,
65846
- ...resolveFor(deviceId, all, byId, blobs[index] ?? {})
65847
- }));
65909
+ return deviceIds.map((deviceId, index) => {
65910
+ const resolved = resolveFor(deviceId, all, byId, blobs[index] ?? {});
65911
+ warnLocationInvisibleContainers(pctx, deviceId, byId, all, resolved);
65912
+ return {
65913
+ deviceId,
65914
+ ...resolved
65915
+ };
65916
+ });
65848
65917
  }
65849
65918
  async function buildLinkedDevicesContribution(pctx, deviceId) {
65850
65919
  const persisted = await pctx.metaStore.resolvePersistedById(deviceId);
@@ -66773,6 +66842,121 @@ var require_device_manager_addon = __commonJS({
66773
66842
  roleKeysKeptAtTarget: Object.keys(target.role).toSorted()
66774
66843
  };
66775
66844
  }
66845
+ var LOCATION_BACKFILL_MARKER_KEY = "locationBackfillVersion";
66846
+ function nearestLocatedAncestor(byId, startParentId) {
66847
+ const visited = /* @__PURE__ */ new Set();
66848
+ let cursor = startParentId;
66849
+ while (cursor !== null && !visited.has(cursor)) {
66850
+ visited.add(cursor);
66851
+ const ancestor = byId.get(cursor);
66852
+ if (!ancestor) return null;
66853
+ if (ancestor.location !== null) return ancestor;
66854
+ cursor = ancestor.parentDeviceId;
66855
+ }
66856
+ return null;
66857
+ }
66858
+ function planNullLocationFills(rows) {
66859
+ const byId = new Map(rows.map((r) => [r.id, r]));
66860
+ const plan = [];
66861
+ for (const row of rows) {
66862
+ if (row.location !== null) continue;
66863
+ if (row.parentDeviceId === null) continue;
66864
+ const hit = nearestLocatedAncestor(byId, row.parentDeviceId);
66865
+ if (hit === null) continue;
66866
+ if (hit.type !== require_dist10.DeviceType.Container) continue;
66867
+ if (hit.location === null) continue;
66868
+ plan.push({
66869
+ id: row.id,
66870
+ location: hit.location,
66871
+ ancestorId: hit.id
66872
+ });
66873
+ }
66874
+ return plan;
66875
+ }
66876
+ async function resolveInheritedLocation(rows, parentDeviceId) {
66877
+ const visited = /* @__PURE__ */ new Set();
66878
+ let cursor = parentDeviceId;
66879
+ while (cursor !== null && !visited.has(cursor)) {
66880
+ visited.add(cursor);
66881
+ const ancestor = await rows.get(cursor);
66882
+ if (ancestor === null) return null;
66883
+ if (ancestor.meta.location !== null) return ancestor.meta.type === require_dist10.DeviceType.Container ? ancestor.meta.location : null;
66884
+ cursor = ancestor.meta.parentDeviceId;
66885
+ }
66886
+ return null;
66887
+ }
66888
+ function groupByAncestor(fills) {
66889
+ const grouped = /* @__PURE__ */ new Map();
66890
+ for (const fill of fills) {
66891
+ const bucket = grouped.get(fill.ancestorId);
66892
+ if (bucket) bucket.push(fill);
66893
+ else grouped.set(fill.ancestorId, [fill]);
66894
+ }
66895
+ return grouped;
66896
+ }
66897
+ async function runLocationBackfillAtBoot(pctx, bootRows) {
66898
+ const logger = pctx.host.ctx.logger;
66899
+ const markerRaw = (await pctx.settings.readAddonStore())[LOCATION_BACKFILL_MARKER_KEY];
66900
+ const completedVersion = typeof markerRaw === "number" ? markerRaw : 0;
66901
+ let repaired = false;
66902
+ if (completedVersion < 1) {
66903
+ const patched = [];
66904
+ const failed = [];
66905
+ await pctx.metaStore.withMetaWriteLock(async () => {
66906
+ const plan = planNullLocationFills((await pctx.metaStore.rows.listAll()).map((r) => r.meta));
66907
+ for (const fill of plan) try {
66908
+ const current = await pctx.metaStore.rows.get(fill.id);
66909
+ if (current === null || current.meta.location !== null) continue;
66910
+ await pctx.metaStore.rows.patch(fill.id, { location: fill.location });
66911
+ patched.push(fill);
66912
+ } catch (err) {
66913
+ failed.push({
66914
+ id: fill.id,
66915
+ error: err
66916
+ });
66917
+ }
66918
+ });
66919
+ repaired = true;
66920
+ for (const [ancestorId, fills] of groupByAncestor(patched)) {
66921
+ pctx.host.ctx.eventBus.emit({
66922
+ id: (0, node_crypto.randomUUID)(),
66923
+ timestamp: /* @__PURE__ */ new Date(),
66924
+ source: {
66925
+ type: "device",
66926
+ id: ancestorId
66927
+ },
66928
+ category: require_dist10.EventCategory.DeviceMetaChanged,
66929
+ data: {
66930
+ deviceId: ancestorId,
66931
+ field: "location",
66932
+ value: fills[0]?.location ?? null
66933
+ }
66934
+ });
66935
+ logger.info("location backfill: aligned location-less descendants to their ancestor", {
66936
+ tags: { deviceId: ancestorId },
66937
+ meta: {
66938
+ location: fills[0]?.location,
66939
+ updatedIds: fills.map((f) => f.id)
66940
+ }
66941
+ });
66942
+ }
66943
+ if (failed.length > 0) logger.error("location backfill partially failed \u2014 retrying on the next boot", { meta: {
66944
+ failedIds: failed.map((f) => f.id),
66945
+ errors: failed.map((f) => f.error instanceof Error ? f.error.message : String(f.error))
66946
+ } });
66947
+ else await pctx.metaStore.withAddonStoreWriteLock(async () => {
66948
+ await pctx.settings.writeAddonStore({ [LOCATION_BACKFILL_MARKER_KEY]: 1 });
66949
+ });
66950
+ }
66951
+ const residual = planNullLocationFills((!repaired && bootRows !== void 0 ? bootRows : await pctx.metaStore.rows.listAll()).map((r) => r.meta));
66952
+ for (const [ancestorId, fills] of groupByAncestor(residual)) logger.warn("location cascade misaligned \u2014 location-less descendants under a located ancestor never match location auto-link; re-save the ancestor location to realign", {
66953
+ tags: { deviceId: ancestorId },
66954
+ meta: {
66955
+ location: fills[0]?.location,
66956
+ misalignedDescendantIds: fills.map((f) => f.id)
66957
+ }
66958
+ });
66959
+ }
66776
66960
  async function stampIntegrationId(metaStore, ctx, deviceId, integrationId) {
66777
66961
  await metaStore.withMetaWriteLock(async () => {
66778
66962
  if (!await metaStore.resolvePersistedById(deviceId)) throw new Error(`[device-manager] stampIntegrationId: unknown device id=${deviceId}`);
@@ -66926,7 +67110,7 @@ var require_device_manager_addon = __commonJS({
66926
67110
  async function registerDevice(pctx, input) {
66927
67111
  const { addonId, stableId, id, type, name, parentDeviceId, features, config } = input;
66928
67112
  const featuresArr = Array.isArray(features) ? [...features] : [];
66929
- const { isFirstRegistration, exportFingerprint, fingerprintChanged } = await pctx.metaStore.withMetaWriteLock(async () => {
67113
+ const { isFirstRegistration, exportFingerprint, fingerprintChanged, inheritedLocation, inheritedDescendantIds, inheritFailed } = await pctx.metaStore.withMetaWriteLock(async () => {
66930
67114
  const existingRow = await pctx.metaStore.rows.get(id);
66931
67115
  const existingMeta = existingRow?.meta;
66932
67116
  const wasRegistered = existingRow?.registered ?? false;
@@ -66938,6 +67122,10 @@ var require_device_manager_addon = __commonJS({
66938
67122
  deviceType: type,
66939
67123
  features: featuresArr
66940
67124
  });
67125
+ const priorLocation = existingMeta?.location ?? null;
67126
+ const resolvedParentId = parentDeviceId ?? null;
67127
+ const parentChanged = existingMeta !== void 0 && (existingMeta.parentDeviceId ?? null) !== resolvedParentId;
67128
+ const inherited = priorLocation === null && resolvedParentId !== null && (isFirst || parentChanged) ? await resolveInheritedLocation(pctx.metaStore.rows, resolvedParentId) : null;
66941
67129
  await pctx.metaStore.rows.upsertRegistration({
66942
67130
  deviceId: id,
66943
67131
  addonId,
@@ -66945,21 +67133,69 @@ var require_device_manager_addon = __commonJS({
66945
67133
  type,
66946
67134
  name: resolvedName,
66947
67135
  userNamed: wasUserNamed,
66948
- location: existingMeta?.location ?? null,
67136
+ location: priorLocation ?? inherited,
66949
67137
  disabled: existingMeta?.disabled ?? false,
66950
- parentDeviceId: parentDeviceId ?? null,
67138
+ parentDeviceId: resolvedParentId,
66951
67139
  registered: true,
66952
67140
  features: featuresArr,
66953
67141
  exportFingerprint: fingerprint
66954
67142
  });
67143
+ const descendantsFilled = [];
67144
+ const descendantsFailed = [];
67145
+ if (inherited !== null) for (const descendant of await collectDescendants(pctx.metaStore, id)) {
67146
+ if (descendant.location !== null) continue;
67147
+ try {
67148
+ await pctx.metaStore.rows.patch(descendant.id, { location: inherited });
67149
+ descendantsFilled.push(descendant.id);
67150
+ } catch (err) {
67151
+ descendantsFailed.push({
67152
+ id: descendant.id,
67153
+ error: err
67154
+ });
67155
+ }
67156
+ }
66955
67157
  return {
66956
67158
  isFirstRegistration: isFirst,
66957
67159
  exportFingerprint: fingerprint,
66958
- fingerprintChanged: existingMeta?.exportFingerprint !== fingerprint
67160
+ fingerprintChanged: existingMeta?.exportFingerprint !== fingerprint,
67161
+ inheritedLocation: inherited,
67162
+ inheritedDescendantIds: descendantsFilled,
67163
+ inheritFailed: descendantsFailed
66959
67164
  };
66960
67165
  });
66961
67166
  if (Object.keys(config).length > 0) await pctx.settings.writeDeviceStore(id, config);
66962
67167
  pctx.metaStore.idToAddonId.set(id, addonId);
67168
+ if (inheritedLocation !== null) {
67169
+ for (const changedId of [id, ...inheritedDescendantIds]) pctx.host.ctx.eventBus.emit({
67170
+ id: (0, node_crypto.randomUUID)(),
67171
+ timestamp: /* @__PURE__ */ new Date(),
67172
+ source: {
67173
+ type: "device",
67174
+ id: changedId
67175
+ },
67176
+ category: require_dist10.EventCategory.DeviceMetaChanged,
67177
+ data: {
67178
+ deviceId: changedId,
67179
+ field: "location",
67180
+ value: inheritedLocation
67181
+ }
67182
+ });
67183
+ pctx.host.ctx.logger.info("registerDevice: inherited located-ancestor location", {
67184
+ tags: { deviceId: id },
67185
+ meta: {
67186
+ location: inheritedLocation,
67187
+ descendantsFilled: inheritedDescendantIds.length
67188
+ }
67189
+ });
67190
+ if (inheritFailed.length > 0) pctx.host.ctx.logger.error("registerDevice: inherited-location fill partially failed \u2014 some descendants NOT aligned", {
67191
+ tags: { deviceId: id },
67192
+ meta: {
67193
+ location: inheritedLocation,
67194
+ failedIds: inheritFailed.map((f) => f.id),
67195
+ errors: inheritFailed.map((f) => f.error instanceof Error ? f.error.message : String(f.error))
67196
+ }
67197
+ });
67198
+ }
66963
67199
  if (isFirstRegistration) pctx.host.ctx.eventBus.emit({
66964
67200
  id: (0, node_crypto.randomUUID)(),
66965
67201
  timestamp: /* @__PURE__ */ new Date(),
@@ -69142,7 +69378,8 @@ var require_device_manager_addon = __commonJS({
69142
69378
  const stateMirror = this.stateMirrorImpl;
69143
69379
  const resolvePersistedById = metaStore.resolvePersistedById;
69144
69380
  const idToAddonId = metaStore.idToAddonId;
69145
- for (const row of await deviceRows.listAll()) idToAddonId.set(row.meta.id, row.meta.addonId);
69381
+ const bootRows = await deviceRows.listAll();
69382
+ for (const row of bootRows) idToAddonId.set(row.meta.id, row.meta.addonId);
69146
69383
  const stampIntegrationId$1 = (deviceId, integrationId) => stampIntegrationId(metaStore, this.ctx, deviceId, integrationId);
69147
69384
  const pctx = {
69148
69385
  host: this.providerHost,
@@ -69157,6 +69394,11 @@ var require_device_manager_addon = __commonJS({
69157
69394
  return provider;
69158
69395
  }
69159
69396
  };
69397
+ try {
69398
+ await runLocationBackfillAtBoot(pctx, bootRows);
69399
+ } catch (err) {
69400
+ this.ctx.logger.warn("location backfill failed \u2014 fleet boots without it", { meta: { error: require_dist10.errMsg(err) } });
69401
+ }
69160
69402
  const provider = {
69161
69403
  resolveDeviceOwnerSync: (deviceId) => resolveDeviceOwnerSync(pctx, deviceId),
69162
69404
  resolveNativeCapOwnerSync: (capName, deviceId) => resolveNativeCapOwnerSync(pctx, capName, deviceId),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "camstack",
3
- "version": "1.2.89",
3
+ "version": "1.2.90",
4
4
  "description": "CLI tool for managing and running CamStack server",
5
5
  "keywords": [
6
6
  "camstack",