@camstack/types 1.2.59 → 1.2.60
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/capabilities/snapshot.cap.d.ts +18 -14
- package/dist/device/declared-device.d.ts +12 -0
- package/dist/index.js +48 -16
- package/dist/index.mjs +48 -16
- package/package.json +1 -1
|
@@ -90,24 +90,28 @@ export declare const snapshotCapability: {
|
|
|
90
90
|
*
|
|
91
91
|
* `getSnapshotOverview` is cache-only by contract: it answers from whatever
|
|
92
92
|
* the wrapper happens to hold and never captures. Under D93 the client
|
|
93
|
-
* versions its image URL on that answer, and an image REQUEST
|
|
94
|
-
*
|
|
95
|
-
*
|
|
96
|
-
*
|
|
97
|
-
*
|
|
98
|
-
*
|
|
99
|
-
*
|
|
100
|
-
*
|
|
93
|
+
* versions its image URL on that answer, and an image REQUEST was the only
|
|
94
|
+
* demand signal. Both of those are satisfiable by the client's own image
|
|
95
|
+
* cache — `expo-image` is URL-keyed and never revalidates — so a URL painted
|
|
96
|
+
* in a previous session comes off disk with no network, no demand, and no
|
|
97
|
+
* capture. Measured on the live hub: reopening after two minutes idle
|
|
98
|
+
* painted 15 of 16 tiles at **168 s old** with zero HTTP requests, and the
|
|
99
|
+
* fleet only recovered because a later poll happened to observe a different
|
|
100
|
+
* identity.
|
|
101
101
|
*
|
|
102
102
|
* ## The two properties that fix it
|
|
103
103
|
*
|
|
104
104
|
* **It is an RPC, so no client cache can answer it.** The demand signal
|
|
105
|
-
* always reaches the wrapper. This method therefore
|
|
106
|
-
*
|
|
107
|
-
*
|
|
108
|
-
*
|
|
109
|
-
*
|
|
110
|
-
*
|
|
105
|
+
* always reaches the wrapper. This method therefore CAPTURES, where
|
|
106
|
+
* `getSnapshotOverview` must never (D93) — the distinction is not "one is
|
|
107
|
+
* newer" but that the overview poll is app-wide (a capturing overview would
|
|
108
|
+
* dial every camera on the install) while this is called by a rendered
|
|
109
|
+
* surface naming the tiles it is actually painting, at the width it is
|
|
110
|
+
* painting them.
|
|
111
|
+
*
|
|
112
|
+
* Since 2026-08-11 this is the ONLY thing that refreshes a snapshot: the
|
|
113
|
+
* server-side keep-warm loop was removed (operator directive — on-demand,
|
|
114
|
+
* always), so a camera nobody is looking at costs nothing at all.
|
|
111
115
|
*
|
|
112
116
|
* **It waits, briefly and boundedly, for the capture it triggered.** The
|
|
113
117
|
* returned `capturedAt` is the frame the link will serve, not the frame the
|
|
@@ -193,6 +193,18 @@ export declare class DeclaredDevices {
|
|
|
193
193
|
*/
|
|
194
194
|
private ensureIntegration;
|
|
195
195
|
private readIndex;
|
|
196
|
+
/**
|
|
197
|
+
* Devices this kernel already has CONSTRUCTED, by stableId.
|
|
198
|
+
*
|
|
199
|
+
* Distinct from {@link readIndex}, and the distinction is the bug: the index
|
|
200
|
+
* is persisted rows, this is live objects. A row without an object must be
|
|
201
|
+
* adopted; an object must be left exactly as it is.
|
|
202
|
+
*
|
|
203
|
+
* Failure is non-fatal and deliberately so — an empty map degrades to the
|
|
204
|
+
* previous behaviour (attempt the adopt) rather than skipping a device that
|
|
205
|
+
* genuinely needs bringing up.
|
|
206
|
+
*/
|
|
207
|
+
private readLiveByStableId;
|
|
196
208
|
/**
|
|
197
209
|
* One declaration: adopt what exists, create what does not.
|
|
198
210
|
*
|
package/dist/index.js
CHANGED
|
@@ -17815,24 +17815,28 @@ var snapshotCapability = {
|
|
|
17815
17815
|
*
|
|
17816
17816
|
* `getSnapshotOverview` is cache-only by contract: it answers from whatever
|
|
17817
17817
|
* the wrapper happens to hold and never captures. Under D93 the client
|
|
17818
|
-
* versions its image URL on that answer, and an image REQUEST
|
|
17819
|
-
*
|
|
17820
|
-
*
|
|
17821
|
-
*
|
|
17822
|
-
*
|
|
17823
|
-
*
|
|
17824
|
-
*
|
|
17825
|
-
*
|
|
17818
|
+
* versions its image URL on that answer, and an image REQUEST was the only
|
|
17819
|
+
* demand signal. Both of those are satisfiable by the client's own image
|
|
17820
|
+
* cache — `expo-image` is URL-keyed and never revalidates — so a URL painted
|
|
17821
|
+
* in a previous session comes off disk with no network, no demand, and no
|
|
17822
|
+
* capture. Measured on the live hub: reopening after two minutes idle
|
|
17823
|
+
* painted 15 of 16 tiles at **168 s old** with zero HTTP requests, and the
|
|
17824
|
+
* fleet only recovered because a later poll happened to observe a different
|
|
17825
|
+
* identity.
|
|
17826
17826
|
*
|
|
17827
17827
|
* ## The two properties that fix it
|
|
17828
17828
|
*
|
|
17829
17829
|
* **It is an RPC, so no client cache can answer it.** The demand signal
|
|
17830
|
-
* always reaches the wrapper. This method therefore
|
|
17831
|
-
*
|
|
17832
|
-
*
|
|
17833
|
-
*
|
|
17834
|
-
*
|
|
17835
|
-
*
|
|
17830
|
+
* always reaches the wrapper. This method therefore CAPTURES, where
|
|
17831
|
+
* `getSnapshotOverview` must never (D93) — the distinction is not "one is
|
|
17832
|
+
* newer" but that the overview poll is app-wide (a capturing overview would
|
|
17833
|
+
* dial every camera on the install) while this is called by a rendered
|
|
17834
|
+
* surface naming the tiles it is actually painting, at the width it is
|
|
17835
|
+
* painting them.
|
|
17836
|
+
*
|
|
17837
|
+
* Since 2026-08-11 this is the ONLY thing that refreshes a snapshot: the
|
|
17838
|
+
* server-side keep-warm loop was removed (operator directive — on-demand,
|
|
17839
|
+
* always), so a camera nobody is looking at costs nothing at all.
|
|
17836
17840
|
*
|
|
17837
17841
|
* **It waits, briefly and boundedly, for the capture it triggered.** The
|
|
17838
17842
|
* returned `capturedAt` is the frame the link will serve, not the frame the
|
|
@@ -29148,9 +29152,10 @@ var DeclaredDevices = class {
|
|
|
29148
29152
|
}
|
|
29149
29153
|
const integrationId = spec.integrationId ?? await this.ensureIntegration(spec.integrationName);
|
|
29150
29154
|
const index = await this.readIndex();
|
|
29155
|
+
const live = await this.readLiveByStableId();
|
|
29151
29156
|
const outcomes = [];
|
|
29152
29157
|
for (const declaration of spec.devices) {
|
|
29153
|
-
const outcome = await this.applyDeclaration(declaration, integrationId, index);
|
|
29158
|
+
const outcome = await this.applyDeclaration(declaration, integrationId, index, live);
|
|
29154
29159
|
if (outcome !== null) outcomes.push(outcome);
|
|
29155
29160
|
}
|
|
29156
29161
|
return {
|
|
@@ -29196,6 +29201,26 @@ var DeclaredDevices = class {
|
|
|
29196
29201
|
return new Map(rows.map((row) => [row.stableId, row]));
|
|
29197
29202
|
}
|
|
29198
29203
|
/**
|
|
29204
|
+
* Devices this kernel already has CONSTRUCTED, by stableId.
|
|
29205
|
+
*
|
|
29206
|
+
* Distinct from {@link readIndex}, and the distinction is the bug: the index
|
|
29207
|
+
* is persisted rows, this is live objects. A row without an object must be
|
|
29208
|
+
* adopted; an object must be left exactly as it is.
|
|
29209
|
+
*
|
|
29210
|
+
* Failure is non-fatal and deliberately so — an empty map degrades to the
|
|
29211
|
+
* previous behaviour (attempt the adopt) rather than skipping a device that
|
|
29212
|
+
* genuinely needs bringing up.
|
|
29213
|
+
*/
|
|
29214
|
+
async readLiveByStableId() {
|
|
29215
|
+
try {
|
|
29216
|
+
const devices = await this.ports.devices.getAll();
|
|
29217
|
+
return new Map(devices.map((device) => [device.stableId, device]));
|
|
29218
|
+
} catch (err) {
|
|
29219
|
+
this.ports.logger.warn("could not read live devices — falling back to adopt-by-row", { meta: { error: err instanceof Error ? err.message : String(err) } });
|
|
29220
|
+
return /* @__PURE__ */ new Map();
|
|
29221
|
+
}
|
|
29222
|
+
}
|
|
29223
|
+
/**
|
|
29199
29224
|
* One declaration: adopt what exists, create what does not.
|
|
29200
29225
|
*
|
|
29201
29226
|
* The create branch is the destructive one — it seeds `initialMeta`, and
|
|
@@ -29204,8 +29229,15 @@ var DeclaredDevices = class {
|
|
|
29204
29229
|
* the declared name over the operator's rename. D49: that branch needs a
|
|
29205
29230
|
* second read to agree.
|
|
29206
29231
|
*/
|
|
29207
|
-
async applyDeclaration(declaration, integrationId, index) {
|
|
29232
|
+
async applyDeclaration(declaration, integrationId, index, live) {
|
|
29208
29233
|
try {
|
|
29234
|
+
const alreadyLive = live.get(declaration.stableId);
|
|
29235
|
+
if (alreadyLive !== void 0) return {
|
|
29236
|
+
stableId: declaration.stableId,
|
|
29237
|
+
deviceId: alreadyLive.id,
|
|
29238
|
+
device: alreadyLive,
|
|
29239
|
+
created: false
|
|
29240
|
+
};
|
|
29209
29241
|
let existing = index.get(declaration.stableId);
|
|
29210
29242
|
if (existing === void 0) {
|
|
29211
29243
|
existing = (await this.readIndex()).get(declaration.stableId);
|
package/dist/index.mjs
CHANGED
|
@@ -17814,24 +17814,28 @@ var snapshotCapability = {
|
|
|
17814
17814
|
*
|
|
17815
17815
|
* `getSnapshotOverview` is cache-only by contract: it answers from whatever
|
|
17816
17816
|
* the wrapper happens to hold and never captures. Under D93 the client
|
|
17817
|
-
* versions its image URL on that answer, and an image REQUEST
|
|
17818
|
-
*
|
|
17819
|
-
*
|
|
17820
|
-
*
|
|
17821
|
-
*
|
|
17822
|
-
*
|
|
17823
|
-
*
|
|
17824
|
-
*
|
|
17817
|
+
* versions its image URL on that answer, and an image REQUEST was the only
|
|
17818
|
+
* demand signal. Both of those are satisfiable by the client's own image
|
|
17819
|
+
* cache — `expo-image` is URL-keyed and never revalidates — so a URL painted
|
|
17820
|
+
* in a previous session comes off disk with no network, no demand, and no
|
|
17821
|
+
* capture. Measured on the live hub: reopening after two minutes idle
|
|
17822
|
+
* painted 15 of 16 tiles at **168 s old** with zero HTTP requests, and the
|
|
17823
|
+
* fleet only recovered because a later poll happened to observe a different
|
|
17824
|
+
* identity.
|
|
17825
17825
|
*
|
|
17826
17826
|
* ## The two properties that fix it
|
|
17827
17827
|
*
|
|
17828
17828
|
* **It is an RPC, so no client cache can answer it.** The demand signal
|
|
17829
|
-
* always reaches the wrapper. This method therefore
|
|
17830
|
-
*
|
|
17831
|
-
*
|
|
17832
|
-
*
|
|
17833
|
-
*
|
|
17834
|
-
*
|
|
17829
|
+
* always reaches the wrapper. This method therefore CAPTURES, where
|
|
17830
|
+
* `getSnapshotOverview` must never (D93) — the distinction is not "one is
|
|
17831
|
+
* newer" but that the overview poll is app-wide (a capturing overview would
|
|
17832
|
+
* dial every camera on the install) while this is called by a rendered
|
|
17833
|
+
* surface naming the tiles it is actually painting, at the width it is
|
|
17834
|
+
* painting them.
|
|
17835
|
+
*
|
|
17836
|
+
* Since 2026-08-11 this is the ONLY thing that refreshes a snapshot: the
|
|
17837
|
+
* server-side keep-warm loop was removed (operator directive — on-demand,
|
|
17838
|
+
* always), so a camera nobody is looking at costs nothing at all.
|
|
17835
17839
|
*
|
|
17836
17840
|
* **It waits, briefly and boundedly, for the capture it triggered.** The
|
|
17837
17841
|
* returned `capturedAt` is the frame the link will serve, not the frame the
|
|
@@ -29147,9 +29151,10 @@ var DeclaredDevices = class {
|
|
|
29147
29151
|
}
|
|
29148
29152
|
const integrationId = spec.integrationId ?? await this.ensureIntegration(spec.integrationName);
|
|
29149
29153
|
const index = await this.readIndex();
|
|
29154
|
+
const live = await this.readLiveByStableId();
|
|
29150
29155
|
const outcomes = [];
|
|
29151
29156
|
for (const declaration of spec.devices) {
|
|
29152
|
-
const outcome = await this.applyDeclaration(declaration, integrationId, index);
|
|
29157
|
+
const outcome = await this.applyDeclaration(declaration, integrationId, index, live);
|
|
29153
29158
|
if (outcome !== null) outcomes.push(outcome);
|
|
29154
29159
|
}
|
|
29155
29160
|
return {
|
|
@@ -29195,6 +29200,26 @@ var DeclaredDevices = class {
|
|
|
29195
29200
|
return new Map(rows.map((row) => [row.stableId, row]));
|
|
29196
29201
|
}
|
|
29197
29202
|
/**
|
|
29203
|
+
* Devices this kernel already has CONSTRUCTED, by stableId.
|
|
29204
|
+
*
|
|
29205
|
+
* Distinct from {@link readIndex}, and the distinction is the bug: the index
|
|
29206
|
+
* is persisted rows, this is live objects. A row without an object must be
|
|
29207
|
+
* adopted; an object must be left exactly as it is.
|
|
29208
|
+
*
|
|
29209
|
+
* Failure is non-fatal and deliberately so — an empty map degrades to the
|
|
29210
|
+
* previous behaviour (attempt the adopt) rather than skipping a device that
|
|
29211
|
+
* genuinely needs bringing up.
|
|
29212
|
+
*/
|
|
29213
|
+
async readLiveByStableId() {
|
|
29214
|
+
try {
|
|
29215
|
+
const devices = await this.ports.devices.getAll();
|
|
29216
|
+
return new Map(devices.map((device) => [device.stableId, device]));
|
|
29217
|
+
} catch (err) {
|
|
29218
|
+
this.ports.logger.warn("could not read live devices — falling back to adopt-by-row", { meta: { error: err instanceof Error ? err.message : String(err) } });
|
|
29219
|
+
return /* @__PURE__ */ new Map();
|
|
29220
|
+
}
|
|
29221
|
+
}
|
|
29222
|
+
/**
|
|
29198
29223
|
* One declaration: adopt what exists, create what does not.
|
|
29199
29224
|
*
|
|
29200
29225
|
* The create branch is the destructive one — it seeds `initialMeta`, and
|
|
@@ -29203,8 +29228,15 @@ var DeclaredDevices = class {
|
|
|
29203
29228
|
* the declared name over the operator's rename. D49: that branch needs a
|
|
29204
29229
|
* second read to agree.
|
|
29205
29230
|
*/
|
|
29206
|
-
async applyDeclaration(declaration, integrationId, index) {
|
|
29231
|
+
async applyDeclaration(declaration, integrationId, index, live) {
|
|
29207
29232
|
try {
|
|
29233
|
+
const alreadyLive = live.get(declaration.stableId);
|
|
29234
|
+
if (alreadyLive !== void 0) return {
|
|
29235
|
+
stableId: declaration.stableId,
|
|
29236
|
+
deviceId: alreadyLive.id,
|
|
29237
|
+
device: alreadyLive,
|
|
29238
|
+
created: false
|
|
29239
|
+
};
|
|
29208
29240
|
let existing = index.get(declaration.stableId);
|
|
29209
29241
|
if (existing === void 0) {
|
|
29210
29242
|
existing = (await this.readIndex()).get(declaration.stableId);
|