@camstack/addon-import-alexa 0.2.184 → 0.2.185

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/CHANGELOG.md CHANGED
@@ -7,6 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.2.185] - 2026-11-04
11
+
12
+ ### Changed
13
+
14
+ - Alexa starts without waiting for the first appliance list and state poll, so a large account no longer exceeds the runner init budget during an update
15
+ - Alexa broker migrations no longer read as 'skipped-newer' (and no false 'Database is newer' alert) after the addon boots
16
+
10
17
  ## [0.2.184] - 2026-11-04
11
18
 
12
19
  ### Changed
package/dist/addon.js CHANGED
@@ -6044,7 +6044,7 @@ function number(params) {
6044
6044
  return /* @__PURE__ */ _coercedNumber(ZodNumber, params);
6045
6045
  }
6046
6046
  //#endregion
6047
- //#region ../types/dist/sleep-BaWhXZFm.mjs
6047
+ //#region ../types/dist/sleep-Cv4Zmie9.mjs
6048
6048
  /**
6049
6049
  * The audio chunk plane's byte format, and the ONE expansion from a coded
6050
6050
  * window to float samples (D455).
@@ -6644,7 +6644,7 @@ var NOT_RUN = {
6644
6644
  * throws: an unreachable or refusing engine is logged and reported `declared: false`,
6645
6645
  * because the hub (and this addon's boot) must not hang on a migration.
6646
6646
  */
6647
- async function runAddonMigrations(host, migrations, logger) {
6647
+ async function runAddonMigrations(host, migrations, logger, options = {}) {
6648
6648
  const first = migrations[0];
6649
6649
  if (first === void 0) return NOT_RUN;
6650
6650
  const owner = first.meta.owner;
@@ -6673,7 +6673,9 @@ async function runAddonMigrations(host, migrations, logger) {
6673
6673
  else if (status === "blocked") blocked.push(id);
6674
6674
  else skipped.push(id);
6675
6675
  };
6676
+ const selected = options.only === void 0 ? null : new Set(options.only);
6676
6677
  for (const step of declaration.steps) {
6678
+ if (selected !== null && !selected.has(step.id)) continue;
6677
6679
  const bound = byId.get(step.id);
6678
6680
  if (bound === void 0 || step.action === "skip") {
6679
6681
  skipped.push(step.id);
@@ -7833,14 +7835,18 @@ var BaseAddon = class {
7833
7835
  * nothing; the boot continues. A migration that FAILED (or was blocked by one)
7834
7836
  * and declares `onFailure: 'block-owner'` makes this throw, so the addon does not
7835
7837
  * serve data whose shape it cannot trust; `tolerate` migrations never do.
7838
+ *
7839
+ * ALWAYS pass the whole catalogue; a phase that must run only some of it passes
7840
+ * `{ only: [ids] }` — never a filtered catalogue (the engine would judge the
7841
+ * omitted applied ids "newer").
7836
7842
  */
7837
- async runMigrations(migrations) {
7843
+ async runMigrations(migrations, options) {
7838
7844
  const api = this.ctx.api.migrations;
7839
7845
  const report = await runAddonMigrations({
7840
7846
  declareOwner: (input) => api.declareOwner.mutate(input),
7841
7847
  startMigration: (input) => api.startMigration.mutate(input),
7842
7848
  finishMigration: (input) => api.finishMigration.mutate(input)
7843
- }, migrations, this.ctx.logger);
7849
+ }, migrations, this.ctx.logger, options);
7844
7850
  if (report.blockOwner.length > 0) throw new AddonMigrationsBlockedError(report.blockOwner);
7845
7851
  return report;
7846
7852
  }
@@ -14672,7 +14678,13 @@ var MigrationLedgerEntrySchema = object({
14672
14678
  * joined by the engine from the catalogues it knows this boot. Absent when no
14673
14679
  * catalogue lists the id (a newer database).
14674
14680
  */
14675
- meta: MigrationMetaSchema.optional()
14681
+ meta: MigrationMetaSchema.optional(),
14682
+ /**
14683
+ * Set when this is an applied row of an addon owner whose catalogue the answering
14684
+ * process has not loaded (the owner has not declared yet): the row is applied, the
14685
+ * process just cannot judge it. Never `skipped-newer` — that needs the owner loaded.
14686
+ */
14687
+ ownerNotLoaded: boolean().optional()
14676
14688
  });
14677
14689
  /** A catalogued migration that is not applied, with where it stands. */
14678
14690
  var MigrationPendingEntrySchema = object({
@@ -141638,10 +141650,8 @@ var AlexaIntegrationManager = class {
141638
141650
  this.#status = "connected";
141639
141651
  this.#lastCheckedAt = Date.now();
141640
141652
  this.#onConnected(this.#id);
141641
- await this.refreshAppliances();
141642
- await this.poll();
141643
- this.#schedulePoll();
141644
141653
  this.#logger.info("AlexaIntegrationManager: started", { tags: { brokerId: this.#id } });
141654
+ this.#firstSync(client);
141645
141655
  } catch (err) {
141646
141656
  if (err instanceof AlexaAuthError) {
141647
141657
  this.#status = "auth-failed";
@@ -141662,6 +141672,24 @@ var AlexaIntegrationManager = class {
141662
141672
  });
141663
141673
  }
141664
141674
  }
141675
+ /** Background first refresh + poll, then the poll loop. Does nothing once `client` is stale. */
141676
+ async #firstSync(client) {
141677
+ let refreshed = false;
141678
+ try {
141679
+ await this.refreshAppliances();
141680
+ refreshed = true;
141681
+ } catch (err) {
141682
+ this.#error = errMsg(err);
141683
+ this.#logger.warn("AlexaIntegrationManager: first appliance refresh failed", {
141684
+ tags: { brokerId: this.#id },
141685
+ meta: { error: errMsg(err) }
141686
+ });
141687
+ }
141688
+ if (this.#client !== client) return;
141689
+ if (refreshed) await this.poll();
141690
+ if (this.#client !== client) return;
141691
+ this.#schedulePoll();
141692
+ }
141665
141693
  /** List the account's appliances + publish them to the resolver. */
141666
141694
  async refreshAppliances() {
141667
141695
  const client = this.#client;
@@ -145116,7 +145144,7 @@ var AlexaImportAddon = class extends BaseDeviceProvider {
145116
145144
  infoOf: (id) => this.registry?.get(id) ?? null,
145117
145145
  onChanged: (id) => this.syncBrokerRuntime(id)
145118
145146
  });
145119
- await this.runMigrations(defineAddonMigrations(ADDON_MIGRATIONS.filter((m) => m.meta.id === FLATTEN_BROKER_RECORDS_ID), this.migrationPorts(records)));
145147
+ await this.runMigrations(defineAddonMigrations(ADDON_MIGRATIONS, this.migrationPorts(records)), { only: [FLATTEN_BROKER_RECORDS_ID] });
145120
145148
  this.activeBrokers = await this.loadPersistedBrokers();
145121
145149
  this.registry = new AlexaBrokerRegistry(this.ctx.logger, {
145122
145150
  dataDir: this.ctx.dataDir,
@@ -145161,7 +145189,7 @@ var AlexaImportAddon = class extends BaseDeviceProvider {
145161
145189
  deviceCode: this.deviceCodeLogin,
145162
145190
  commitCookie: (input) => this.commitCapturedCookie(input)
145163
145191
  }), ...speakIconRoutes()];
145164
- await this.runMigrations(defineAddonMigrations(ADDON_MIGRATIONS.filter((m) => m.meta.id !== FLATTEN_BROKER_RECORDS_ID), this.migrationPorts(records)));
145192
+ await this.runMigrations(defineAddonMigrations(ADDON_MIGRATIONS, this.migrationPorts(records)), { only: ADDON_MIGRATIONS.map((m) => m.meta.id).filter((id) => id !== FLATTEN_BROKER_RECORDS_ID) });
145165
145193
  const speak = new AlexaSpeakController({
145166
145194
  logger: this.ctx.logger.child("speak"),
145167
145195
  readConfig: () => this.config,
package/dist/addon.mjs CHANGED
@@ -6042,7 +6042,7 @@ function number(params) {
6042
6042
  return /* @__PURE__ */ _coercedNumber(ZodNumber, params);
6043
6043
  }
6044
6044
  //#endregion
6045
- //#region ../types/dist/sleep-BaWhXZFm.mjs
6045
+ //#region ../types/dist/sleep-Cv4Zmie9.mjs
6046
6046
  /**
6047
6047
  * The audio chunk plane's byte format, and the ONE expansion from a coded
6048
6048
  * window to float samples (D455).
@@ -6642,7 +6642,7 @@ var NOT_RUN = {
6642
6642
  * throws: an unreachable or refusing engine is logged and reported `declared: false`,
6643
6643
  * because the hub (and this addon's boot) must not hang on a migration.
6644
6644
  */
6645
- async function runAddonMigrations(host, migrations, logger) {
6645
+ async function runAddonMigrations(host, migrations, logger, options = {}) {
6646
6646
  const first = migrations[0];
6647
6647
  if (first === void 0) return NOT_RUN;
6648
6648
  const owner = first.meta.owner;
@@ -6671,7 +6671,9 @@ async function runAddonMigrations(host, migrations, logger) {
6671
6671
  else if (status === "blocked") blocked.push(id);
6672
6672
  else skipped.push(id);
6673
6673
  };
6674
+ const selected = options.only === void 0 ? null : new Set(options.only);
6674
6675
  for (const step of declaration.steps) {
6676
+ if (selected !== null && !selected.has(step.id)) continue;
6675
6677
  const bound = byId.get(step.id);
6676
6678
  if (bound === void 0 || step.action === "skip") {
6677
6679
  skipped.push(step.id);
@@ -7831,14 +7833,18 @@ var BaseAddon = class {
7831
7833
  * nothing; the boot continues. A migration that FAILED (or was blocked by one)
7832
7834
  * and declares `onFailure: 'block-owner'` makes this throw, so the addon does not
7833
7835
  * serve data whose shape it cannot trust; `tolerate` migrations never do.
7836
+ *
7837
+ * ALWAYS pass the whole catalogue; a phase that must run only some of it passes
7838
+ * `{ only: [ids] }` — never a filtered catalogue (the engine would judge the
7839
+ * omitted applied ids "newer").
7834
7840
  */
7835
- async runMigrations(migrations) {
7841
+ async runMigrations(migrations, options) {
7836
7842
  const api = this.ctx.api.migrations;
7837
7843
  const report = await runAddonMigrations({
7838
7844
  declareOwner: (input) => api.declareOwner.mutate(input),
7839
7845
  startMigration: (input) => api.startMigration.mutate(input),
7840
7846
  finishMigration: (input) => api.finishMigration.mutate(input)
7841
- }, migrations, this.ctx.logger);
7847
+ }, migrations, this.ctx.logger, options);
7842
7848
  if (report.blockOwner.length > 0) throw new AddonMigrationsBlockedError(report.blockOwner);
7843
7849
  return report;
7844
7850
  }
@@ -14670,7 +14676,13 @@ var MigrationLedgerEntrySchema = object({
14670
14676
  * joined by the engine from the catalogues it knows this boot. Absent when no
14671
14677
  * catalogue lists the id (a newer database).
14672
14678
  */
14673
- meta: MigrationMetaSchema.optional()
14679
+ meta: MigrationMetaSchema.optional(),
14680
+ /**
14681
+ * Set when this is an applied row of an addon owner whose catalogue the answering
14682
+ * process has not loaded (the owner has not declared yet): the row is applied, the
14683
+ * process just cannot judge it. Never `skipped-newer` — that needs the owner loaded.
14684
+ */
14685
+ ownerNotLoaded: boolean().optional()
14674
14686
  });
14675
14687
  /** A catalogued migration that is not applied, with where it stands. */
14676
14688
  var MigrationPendingEntrySchema = object({
@@ -141636,10 +141648,8 @@ var AlexaIntegrationManager = class {
141636
141648
  this.#status = "connected";
141637
141649
  this.#lastCheckedAt = Date.now();
141638
141650
  this.#onConnected(this.#id);
141639
- await this.refreshAppliances();
141640
- await this.poll();
141641
- this.#schedulePoll();
141642
141651
  this.#logger.info("AlexaIntegrationManager: started", { tags: { brokerId: this.#id } });
141652
+ this.#firstSync(client);
141643
141653
  } catch (err) {
141644
141654
  if (err instanceof AlexaAuthError) {
141645
141655
  this.#status = "auth-failed";
@@ -141660,6 +141670,24 @@ var AlexaIntegrationManager = class {
141660
141670
  });
141661
141671
  }
141662
141672
  }
141673
+ /** Background first refresh + poll, then the poll loop. Does nothing once `client` is stale. */
141674
+ async #firstSync(client) {
141675
+ let refreshed = false;
141676
+ try {
141677
+ await this.refreshAppliances();
141678
+ refreshed = true;
141679
+ } catch (err) {
141680
+ this.#error = errMsg(err);
141681
+ this.#logger.warn("AlexaIntegrationManager: first appliance refresh failed", {
141682
+ tags: { brokerId: this.#id },
141683
+ meta: { error: errMsg(err) }
141684
+ });
141685
+ }
141686
+ if (this.#client !== client) return;
141687
+ if (refreshed) await this.poll();
141688
+ if (this.#client !== client) return;
141689
+ this.#schedulePoll();
141690
+ }
141663
141691
  /** List the account's appliances + publish them to the resolver. */
141664
141692
  async refreshAppliances() {
141665
141693
  const client = this.#client;
@@ -145114,7 +145142,7 @@ var AlexaImportAddon = class extends BaseDeviceProvider {
145114
145142
  infoOf: (id) => this.registry?.get(id) ?? null,
145115
145143
  onChanged: (id) => this.syncBrokerRuntime(id)
145116
145144
  });
145117
- await this.runMigrations(defineAddonMigrations(ADDON_MIGRATIONS.filter((m) => m.meta.id === FLATTEN_BROKER_RECORDS_ID), this.migrationPorts(records)));
145145
+ await this.runMigrations(defineAddonMigrations(ADDON_MIGRATIONS, this.migrationPorts(records)), { only: [FLATTEN_BROKER_RECORDS_ID] });
145118
145146
  this.activeBrokers = await this.loadPersistedBrokers();
145119
145147
  this.registry = new AlexaBrokerRegistry(this.ctx.logger, {
145120
145148
  dataDir: this.ctx.dataDir,
@@ -145159,7 +145187,7 @@ var AlexaImportAddon = class extends BaseDeviceProvider {
145159
145187
  deviceCode: this.deviceCodeLogin,
145160
145188
  commitCookie: (input) => this.commitCapturedCookie(input)
145161
145189
  }), ...speakIconRoutes()];
145162
- await this.runMigrations(defineAddonMigrations(ADDON_MIGRATIONS.filter((m) => m.meta.id !== FLATTEN_BROKER_RECORDS_ID), this.migrationPorts(records)));
145190
+ await this.runMigrations(defineAddonMigrations(ADDON_MIGRATIONS, this.migrationPorts(records)), { only: ADDON_MIGRATIONS.map((m) => m.meta.id).filter((id) => id !== FLATTEN_BROKER_RECORDS_ID) });
145163
145191
  const speak = new AlexaSpeakController({
145164
145192
  logger: this.ctx.logger.child("speak"),
145165
145193
  readConfig: () => this.config,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-import-alexa",
3
- "version": "0.2.184",
3
+ "version": "0.2.185",
4
4
  "description": "Alexa device-import provider for CamStack — imports the smart-home devices in a user's Alexa account via the unofficial alexa-remote2 cookie/token client (the inverse of the Alexa exporter)",
5
5
  "keywords": [
6
6
  "camstack",
@@ -72,7 +72,7 @@
72
72
  }
73
73
  ],
74
74
  "requires": {
75
- "server": ">=1.2.762"
75
+ "server": ">=1.2.763"
76
76
  }
77
77
  },
78
78
  "files": [