@camstack/system 1.2.156 → 1.2.157

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.
@@ -425,8 +425,8 @@ var StorageMigrationCoordinator = class {
425
425
  finishedAt: null,
426
426
  error: null
427
427
  };
428
- this.active = job;
429
428
  await this.persist(job);
429
+ this.active = job;
430
430
  this.runPromise = this.run(job);
431
431
  this.runPromise;
432
432
  return job.jobId;
@@ -418,8 +418,8 @@ var StorageMigrationCoordinator = class {
418
418
  finishedAt: null,
419
419
  error: null
420
420
  };
421
- this.active = job;
422
421
  await this.persist(job);
422
+ this.active = job;
423
423
  this.runPromise = this.run(job);
424
424
  this.runPromise;
425
425
  return job.jobId;
package/dist/index.js CHANGED
@@ -9612,20 +9612,63 @@ var ConfigManager = class ConfigManager {
9612
9612
  if (this.settingsStore === null) return;
9613
9613
  this.settingsStore.clearDeviceRuntimeState(deviceId);
9614
9614
  }
9615
+ /**
9616
+ * The addon's `ctx.settings`.
9617
+ *
9618
+ * The backend is chosen on EVERY call, never frozen when the view is built.
9619
+ * That is not a nicety: `settings-store` is owned by `storage-orchestrator`,
9620
+ * so the door is wired by `registerProvider` STRICTLY AFTER that addon's
9621
+ * context — and therefore its settings view — already exists. A view bound at
9622
+ * construction handed the door's own owner the sync-store backend, whose
9623
+ * writes throw for the whole life of the process once `sqlite-settings` runs
9624
+ * isolated (D181: no sync `ISettingsStore` reaches hub-main, only the async
9625
+ * door). Reads were worse than the writes: they answered `{}` in silence.
9626
+ * That is what made `storageMigration.start` fail on its first durable write
9627
+ * while every other addon's persistence worked. See D294.
9628
+ */
9615
9629
  createSettingsView(addonId) {
9616
- if (this.settingsStore === null && this.settingsDoor !== null) {
9630
+ const storeBacked = this.createStoreSettingsView(addonId);
9631
+ let doorBacked = null;
9632
+ let doorBackedSource = null;
9633
+ const active = () => {
9634
+ if (this.settingsStore !== null) return storeBacked;
9617
9635
  const door = this.settingsDoor;
9618
- return createDoorSettingsView(addonId, door, {
9619
- getSection: (section) => this.getSection(section),
9620
- setSection: async (section, patch) => {
9621
- for (const [key, value] of Object.entries(patch)) await door.set({
9622
- collection: "system-settings",
9623
- key: `${section}.${key}`,
9624
- value
9625
- });
9626
- }
9627
- });
9628
- }
9636
+ if (door === null) return storeBacked;
9637
+ if (doorBacked === null || doorBackedSource !== door) {
9638
+ doorBackedSource = door;
9639
+ doorBacked = this.createDoorBackedSettingsView(addonId, door);
9640
+ }
9641
+ return doorBacked;
9642
+ };
9643
+ return {
9644
+ readAddonStore: () => active().readAddonStore(),
9645
+ writeAddonStore: (patch) => active().writeAddonStore(patch),
9646
+ readDeviceStore: (deviceId) => active().readDeviceStore(deviceId),
9647
+ readDeviceStoreBatch: (deviceIds) => active().readDeviceStoreBatch(deviceIds),
9648
+ writeDeviceStore: (deviceId, patch) => active().writeDeviceStore(deviceId, patch),
9649
+ clearDeviceStore: (deviceId) => active().clearDeviceStore(deviceId),
9650
+ getSection: (section) => active().getSection(section),
9651
+ setSection: (section, patch) => active().setSection(section, patch),
9652
+ readDeviceRuntimeState: (deviceId) => active().readDeviceRuntimeState(deviceId),
9653
+ writeDeviceRuntimeState: (deviceId, data) => active().writeDeviceRuntimeState(deviceId, data),
9654
+ clearDeviceRuntimeState: (deviceId) => active().clearDeviceRuntimeState(deviceId)
9655
+ };
9656
+ }
9657
+ /** The async-door backend of {@link createSettingsView}. */
9658
+ createDoorBackedSettingsView(addonId, door) {
9659
+ return createDoorSettingsView(addonId, door, {
9660
+ getSection: (section) => this.getSection(section),
9661
+ setSection: async (section, patch) => {
9662
+ for (const [key, value] of Object.entries(patch)) await door.set({
9663
+ collection: "system-settings",
9664
+ key: `${section}.${key}`,
9665
+ value
9666
+ });
9667
+ }
9668
+ });
9669
+ }
9670
+ /** The sync-`ISettingsStore` backend of {@link createSettingsView}. */
9671
+ createStoreSettingsView(addonId) {
9629
9672
  const cm = this;
9630
9673
  return {
9631
9674
  async readAddonStore() {
@@ -9641,6 +9684,15 @@ var ConfigManager = class ConfigManager {
9641
9684
  async readDeviceStore(deviceId) {
9642
9685
  return cm.getAddonDevice(addonId, String(deviceId));
9643
9686
  },
9687
+ async readDeviceStoreBatch(deviceIds) {
9688
+ const out = /* @__PURE__ */ new Map();
9689
+ for (const deviceId of deviceIds) try {
9690
+ out.set(deviceId, cm.getAddonDevice(addonId, String(deviceId)));
9691
+ } catch {
9692
+ out.set(deviceId, {});
9693
+ }
9694
+ return out;
9695
+ },
9644
9696
  async writeDeviceStore(deviceId, patch) {
9645
9697
  const key = String(deviceId);
9646
9698
  const existing = cm.getAddonDevice(addonId, key);
package/dist/index.mjs CHANGED
@@ -9605,20 +9605,63 @@ var ConfigManager = class ConfigManager {
9605
9605
  if (this.settingsStore === null) return;
9606
9606
  this.settingsStore.clearDeviceRuntimeState(deviceId);
9607
9607
  }
9608
+ /**
9609
+ * The addon's `ctx.settings`.
9610
+ *
9611
+ * The backend is chosen on EVERY call, never frozen when the view is built.
9612
+ * That is not a nicety: `settings-store` is owned by `storage-orchestrator`,
9613
+ * so the door is wired by `registerProvider` STRICTLY AFTER that addon's
9614
+ * context — and therefore its settings view — already exists. A view bound at
9615
+ * construction handed the door's own owner the sync-store backend, whose
9616
+ * writes throw for the whole life of the process once `sqlite-settings` runs
9617
+ * isolated (D181: no sync `ISettingsStore` reaches hub-main, only the async
9618
+ * door). Reads were worse than the writes: they answered `{}` in silence.
9619
+ * That is what made `storageMigration.start` fail on its first durable write
9620
+ * while every other addon's persistence worked. See D294.
9621
+ */
9608
9622
  createSettingsView(addonId) {
9609
- if (this.settingsStore === null && this.settingsDoor !== null) {
9623
+ const storeBacked = this.createStoreSettingsView(addonId);
9624
+ let doorBacked = null;
9625
+ let doorBackedSource = null;
9626
+ const active = () => {
9627
+ if (this.settingsStore !== null) return storeBacked;
9610
9628
  const door = this.settingsDoor;
9611
- return createDoorSettingsView(addonId, door, {
9612
- getSection: (section) => this.getSection(section),
9613
- setSection: async (section, patch) => {
9614
- for (const [key, value] of Object.entries(patch)) await door.set({
9615
- collection: "system-settings",
9616
- key: `${section}.${key}`,
9617
- value
9618
- });
9619
- }
9620
- });
9621
- }
9629
+ if (door === null) return storeBacked;
9630
+ if (doorBacked === null || doorBackedSource !== door) {
9631
+ doorBackedSource = door;
9632
+ doorBacked = this.createDoorBackedSettingsView(addonId, door);
9633
+ }
9634
+ return doorBacked;
9635
+ };
9636
+ return {
9637
+ readAddonStore: () => active().readAddonStore(),
9638
+ writeAddonStore: (patch) => active().writeAddonStore(patch),
9639
+ readDeviceStore: (deviceId) => active().readDeviceStore(deviceId),
9640
+ readDeviceStoreBatch: (deviceIds) => active().readDeviceStoreBatch(deviceIds),
9641
+ writeDeviceStore: (deviceId, patch) => active().writeDeviceStore(deviceId, patch),
9642
+ clearDeviceStore: (deviceId) => active().clearDeviceStore(deviceId),
9643
+ getSection: (section) => active().getSection(section),
9644
+ setSection: (section, patch) => active().setSection(section, patch),
9645
+ readDeviceRuntimeState: (deviceId) => active().readDeviceRuntimeState(deviceId),
9646
+ writeDeviceRuntimeState: (deviceId, data) => active().writeDeviceRuntimeState(deviceId, data),
9647
+ clearDeviceRuntimeState: (deviceId) => active().clearDeviceRuntimeState(deviceId)
9648
+ };
9649
+ }
9650
+ /** The async-door backend of {@link createSettingsView}. */
9651
+ createDoorBackedSettingsView(addonId, door) {
9652
+ return createDoorSettingsView(addonId, door, {
9653
+ getSection: (section) => this.getSection(section),
9654
+ setSection: async (section, patch) => {
9655
+ for (const [key, value] of Object.entries(patch)) await door.set({
9656
+ collection: "system-settings",
9657
+ key: `${section}.${key}`,
9658
+ value
9659
+ });
9660
+ }
9661
+ });
9662
+ }
9663
+ /** The sync-`ISettingsStore` backend of {@link createSettingsView}. */
9664
+ createStoreSettingsView(addonId) {
9622
9665
  const cm = this;
9623
9666
  return {
9624
9667
  async readAddonStore() {
@@ -9634,6 +9677,15 @@ var ConfigManager = class ConfigManager {
9634
9677
  async readDeviceStore(deviceId) {
9635
9678
  return cm.getAddonDevice(addonId, String(deviceId));
9636
9679
  },
9680
+ async readDeviceStoreBatch(deviceIds) {
9681
+ const out = /* @__PURE__ */ new Map();
9682
+ for (const deviceId of deviceIds) try {
9683
+ out.set(deviceId, cm.getAddonDevice(addonId, String(deviceId)));
9684
+ } catch {
9685
+ out.set(deviceId, {});
9686
+ }
9687
+ return out;
9688
+ },
9637
9689
  async writeDeviceStore(deviceId, patch) {
9638
9690
  const key = String(deviceId);
9639
9691
  const existing = cm.getAddonDevice(addonId, key);
@@ -31,6 +31,19 @@ export interface AddonSettingsView {
31
31
  writeDeviceRuntimeState(deviceId: number, data: Record<string, unknown>): Promise<void>;
32
32
  clearDeviceRuntimeState(deviceId: number): Promise<void>;
33
33
  }
34
+ /**
35
+ * A CONCRETE backend behind {@link ConfigManager.createSettingsView} — the
36
+ * sync-`ISettingsStore` one or the async-door one.
37
+ *
38
+ * `readDeviceStoreBatch` is required here and optional on
39
+ * {@link AddonSettingsView} because both of these can answer it, so the view
40
+ * the kernel hands an addon never has to decide whether the method exists.
41
+ * The optionality on the public interface stays for the implementation that
42
+ * genuinely cannot batch: a forked runner's UDS-backed view.
43
+ */
44
+ export interface SettingsViewBackend extends AddonSettingsView {
45
+ readDeviceStoreBatch(deviceIds: readonly number[]): Promise<ReadonlyMap<number, Record<string, unknown>>>;
46
+ }
34
47
  export interface ISettingsStore {
35
48
  getSystem(key: string): unknown;
36
49
  setSystem(key: string, value: unknown): void;
@@ -181,7 +194,25 @@ export declare class ConfigManager {
181
194
  setDeviceRuntimeState(deviceId: string, blob: Record<string, unknown>): void;
182
195
  /** Forget the device's runtime state. No-op before the store is wired. */
183
196
  clearDeviceRuntimeState(deviceId: string): void;
197
+ /**
198
+ * The addon's `ctx.settings`.
199
+ *
200
+ * The backend is chosen on EVERY call, never frozen when the view is built.
201
+ * That is not a nicety: `settings-store` is owned by `storage-orchestrator`,
202
+ * so the door is wired by `registerProvider` STRICTLY AFTER that addon's
203
+ * context — and therefore its settings view — already exists. A view bound at
204
+ * construction handed the door's own owner the sync-store backend, whose
205
+ * writes throw for the whole life of the process once `sqlite-settings` runs
206
+ * isolated (D181: no sync `ISettingsStore` reaches hub-main, only the async
207
+ * door). Reads were worse than the writes: they answered `{}` in silence.
208
+ * That is what made `storageMigration.start` fail on its first durable write
209
+ * while every other addon's persistence worked. See D294.
210
+ */
184
211
  createSettingsView(addonId: string): AddonSettingsView;
212
+ /** The async-door backend of {@link createSettingsView}. */
213
+ private createDoorBackedSettingsView;
214
+ /** The sync-`ISettingsStore` backend of {@link createSettingsView}. */
215
+ private createStoreSettingsView;
185
216
  /** Get all system-wide activation overrides. */
186
217
  getSystemActivation(): Readonly<Record<string, boolean>>;
187
218
  /** Set system-wide activation for a capability/addon pair. */
@@ -1,7 +1,7 @@
1
1
  import { ISettingsStoreProvider } from '@camstack/types';
2
- import { AddonSettingsView } from './config-manager.js';
2
+ import { SettingsViewBackend } from './config-manager.js';
3
3
  export interface DoorSectionAccess {
4
4
  getSection(section: string): Record<string, unknown>;
5
5
  setSection(section: string, patch: Record<string, unknown>): void | Promise<void>;
6
6
  }
7
- export declare function createDoorSettingsView(addonId: string, door: ISettingsStoreProvider, sections: DoorSectionAccess): AddonSettingsView;
7
+ export declare function createDoorSettingsView(addonId: string, door: ISettingsStoreProvider, sections: DoorSectionAccess): SettingsViewBackend;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/system",
3
- "version": "1.2.156",
3
+ "version": "1.2.157",
4
4
  "description": "Core addon for CamStack — builtins, pipeline, process management, auth, logging, events",
5
5
  "keywords": [
6
6
  "camstack",