@smarthome.exchange/integrations 0.1.0 → 0.2.0

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
@@ -1,5 +1,14 @@
1
1
  # Changelog
2
2
 
3
+ ## 2026-08-10 - 0.2.0
4
+
5
+ ### Features
6
+
7
+ - **Breaking:** fail closed on retained runtime cleanup before setup (runtime-manager)
8
+ - Retry pending cleanup for the requested domain before creating or replacing an integration runtime.
9
+ - Reject overlapping same-domain setup and destroyAll calls while setup is in progress instead of queuing them.
10
+ - Retain failed replacement and mismatched-runtime cleanup under the requested domain, with retryPendingCleanup(domain) and hasPendingCleanup(domain) exposing the cleanup gate.
11
+
3
12
  ## 2026-08-10 - 0.1.0
4
13
 
5
14
  ### Features
@@ -3,7 +3,7 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@smarthome.exchange/integrations',
6
- version: '0.1.0',
6
+ version: '0.2.0',
7
7
  description: 'TypeScript-native device integrations for smarthome.exchange.'
8
8
  };
9
9
  //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiMDBfY29tbWl0aW5mb19kYXRhLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdHMvMDBfY29tbWl0aW5mb19kYXRhLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOztHQUVHO0FBQ0gsTUFBTSxDQUFDLE1BQU0sVUFBVSxHQUFHO0lBQ3hCLElBQUksRUFBRSxrQ0FBa0M7SUFDeEMsT0FBTyxFQUFFLE9BQU87SUFDaEIsV0FBVyxFQUFFLCtEQUErRDtDQUM3RSxDQUFBIn0=
@@ -5,7 +5,8 @@ export declare class IntegrationRuntimeManager {
5
5
  private readonly runtimesPendingCleanup;
6
6
  private readonly runtimeDestructionPromises;
7
7
  private readonly destroyedRuntimes;
8
- private operationTail;
8
+ private readonly setupDomainsInProgress;
9
+ private destroyAllPromise?;
9
10
  createRuntime<TConfig>(integrationArg: BaseIntegration<TConfig>, configArg: TConfig, contextArg?: IIntegrationSetupContext): Promise<IIntegrationRuntime>;
10
11
  setupIntegration<TConfig>(integrationArg: BaseIntegration<TConfig>, configArg: TConfig, contextArg?: IIntegrationSetupContext): Promise<IIntegrationRuntime>;
11
12
  activateRuntime(domainArg: string, runtimeArg: IIntegrationRuntime): IIntegrationRuntime | undefined;
@@ -15,8 +16,11 @@ export declare class IntegrationRuntimeManager {
15
16
  getActiveRuntime(domainArg: string): IIntegrationRuntime | undefined;
16
17
  getRuntime(domainArg: string): IIntegrationRuntime | undefined;
17
18
  listRuntimes(): IIntegrationRuntime[];
19
+ retryPendingCleanup(domainArg: string): Promise<void>;
20
+ hasPendingCleanup(domainArg: string): boolean;
18
21
  writeDeviceFeature(requestArg: IDeviceFeatureWriteRequest, expectedRuntimeArg?: IIntegrationRuntime, abortSignalArg?: AbortSignal): Promise<IServiceCallResult>;
19
22
  destroyAll(timeoutMsArg?: number): Promise<void>;
20
23
  private withTimeout;
21
- private runExclusive;
24
+ private performDestroyAll;
25
+ private markRuntimePendingCleanup;
22
26
  }
@@ -1,17 +1,20 @@
1
1
  import { createDescriptorOnlyIntegrationError } from './classes.descriptoronlyintegration.js';
2
2
  export class IntegrationRuntimeManager {
3
3
  runtimes = new Map();
4
- runtimesPendingCleanup = new Set();
4
+ runtimesPendingCleanup = new Map();
5
5
  runtimeDestructionPromises = new Map();
6
6
  destroyedRuntimes = new WeakSet();
7
- operationTail = Promise.resolve();
7
+ setupDomainsInProgress = new Set();
8
+ destroyAllPromise;
8
9
  async createRuntime(integrationArg, configArg, contextArg = {}) {
9
10
  if (integrationArg.status === 'descriptor-only') {
10
11
  throw createDescriptorOnlyIntegrationError(integrationArg.domain);
11
12
  }
13
+ await this.retryPendingCleanup(integrationArg.domain);
12
14
  const runtime = await integrationArg.setup(configArg, contextArg);
13
15
  if (runtime.domain !== integrationArg.domain) {
14
16
  const domainError = new Error(`Integration runtime domain mismatch: expected ${integrationArg.domain}, received ${runtime.domain}`);
17
+ this.markRuntimePendingCleanup(integrationArg.domain, runtime);
15
18
  try {
16
19
  await this.destroyRuntime(runtime);
17
20
  }
@@ -23,26 +26,26 @@ export class IntegrationRuntimeManager {
23
26
  return runtime;
24
27
  }
25
28
  async setupIntegration(integrationArg, configArg, contextArg = {}) {
26
- return this.runExclusive(async () => {
27
- const runtime = await this.createRuntime(integrationArg, configArg, contextArg);
29
+ if (this.destroyAllPromise) {
30
+ throw new Error('Cannot set up an integration while destroyAll is in progress.');
31
+ }
32
+ if (this.setupDomainsInProgress.has(integrationArg.domain)) {
33
+ throw new Error(`Integration setup is already in progress: ${integrationArg.domain}`);
34
+ }
35
+ this.setupDomainsInProgress.add(integrationArg.domain);
36
+ try {
37
+ await this.retryPendingCleanup(integrationArg.domain);
28
38
  const previousRuntime = this.retireRuntime(integrationArg.domain);
29
39
  if (previousRuntime) {
30
- try {
31
- await this.destroyRuntime(previousRuntime);
32
- }
33
- catch (destroyError) {
34
- try {
35
- await this.destroyRuntime(runtime);
36
- }
37
- catch (cleanupError) {
38
- throw new AggregateError([destroyError, cleanupError], `Failed to replace integration runtime for ${integrationArg.domain}`);
39
- }
40
- throw destroyError;
41
- }
40
+ await this.destroyRuntime(previousRuntime);
42
41
  }
42
+ const runtime = await this.createRuntime(integrationArg, configArg, contextArg);
43
43
  this.activateRuntime(integrationArg.domain, runtime);
44
44
  return runtime;
45
- });
45
+ }
46
+ finally {
47
+ this.setupDomainsInProgress.delete(integrationArg.domain);
48
+ }
46
49
  }
47
50
  activateRuntime(domainArg, runtimeArg) {
48
51
  if (runtimeArg.domain !== domainArg) {
@@ -64,16 +67,19 @@ export class IntegrationRuntimeManager {
64
67
  retireRuntime(domainArg, expectedRuntimeArg) {
65
68
  const runtime = this.removeRuntime(domainArg, expectedRuntimeArg);
66
69
  if (runtime) {
67
- this.runtimesPendingCleanup.add(runtime);
70
+ this.markRuntimePendingCleanup(domainArg, runtime);
68
71
  }
69
72
  return runtime;
70
73
  }
71
74
  destroyRuntime(runtimeArg, timeoutMsArg) {
72
75
  this.retireRuntime(runtimeArg.domain, runtimeArg);
73
76
  if (this.destroyedRuntimes.has(runtimeArg)) {
77
+ this.runtimesPendingCleanup.delete(runtimeArg);
74
78
  return Promise.resolve();
75
79
  }
76
- this.runtimesPendingCleanup.add(runtimeArg);
80
+ if (!this.runtimesPendingCleanup.has(runtimeArg)) {
81
+ this.markRuntimePendingCleanup(runtimeArg.domain, runtimeArg);
82
+ }
77
83
  let destructionPromise = this.runtimeDestructionPromises.get(runtimeArg);
78
84
  if (!destructionPromise) {
79
85
  const destroyAttempt = Promise.resolve().then(() => runtimeArg.destroy());
@@ -103,6 +109,24 @@ export class IntegrationRuntimeManager {
103
109
  listRuntimes() {
104
110
  return [...this.runtimes.values()];
105
111
  }
112
+ async retryPendingCleanup(domainArg) {
113
+ const runtimes = [...this.runtimesPendingCleanup]
114
+ .filter(([, cleanupDomainArg]) => cleanupDomainArg === domainArg)
115
+ .map(([runtimeArg]) => runtimeArg);
116
+ if (runtimes.length === 0) {
117
+ return;
118
+ }
119
+ const results = await Promise.allSettled(runtimes.map((runtimeArg) => this.destroyRuntime(runtimeArg)));
120
+ const errors = results
121
+ .filter((resultArg) => resultArg.status === 'rejected')
122
+ .map((resultArg) => resultArg.reason);
123
+ if (errors.length > 0) {
124
+ throw new AggregateError(errors, `Failed to clean up retained integration runtimes for ${domainArg}`);
125
+ }
126
+ }
127
+ hasPendingCleanup(domainArg) {
128
+ return [...this.runtimesPendingCleanup.values()].some((cleanupDomainArg) => cleanupDomainArg === domainArg);
129
+ }
106
130
  async writeDeviceFeature(requestArg, expectedRuntimeArg, abortSignalArg) {
107
131
  const integrationDomain = requestArg.device.integrationDomain;
108
132
  if (!integrationDomain) {
@@ -132,21 +156,20 @@ export class IntegrationRuntimeManager {
132
156
  }
133
157
  return runtime.writeFeature(requestArg, abortSignalArg);
134
158
  }
135
- async destroyAll(timeoutMsArg) {
136
- return this.runExclusive(async () => {
137
- for (const [domain, runtime] of this.runtimes) {
138
- this.runtimes.delete(domain);
139
- this.runtimesPendingCleanup.add(runtime);
140
- }
141
- const runtimes = [...this.runtimesPendingCleanup];
142
- const results = await Promise.allSettled(runtimes.map((runtimeArg) => this.destroyRuntime(runtimeArg, timeoutMsArg)));
143
- const errors = results
144
- .filter((resultArg) => resultArg.status === 'rejected')
145
- .map((resultArg) => resultArg.reason);
146
- if (errors.length > 0) {
147
- throw new AggregateError(errors, 'Failed to destroy all integration runtimes cleanly');
159
+ destroyAll(timeoutMsArg) {
160
+ if (this.destroyAllPromise) {
161
+ return this.destroyAllPromise;
162
+ }
163
+ if (this.setupDomainsInProgress.size > 0) {
164
+ return Promise.reject(new Error('Cannot destroy integration runtimes while setup is in progress.'));
165
+ }
166
+ const destroyAllPromise = this.performDestroyAll(timeoutMsArg).finally(() => {
167
+ if (this.destroyAllPromise === destroyAllPromise) {
168
+ this.destroyAllPromise = undefined;
148
169
  }
149
170
  });
171
+ this.destroyAllPromise = destroyAllPromise;
172
+ return destroyAllPromise;
150
173
  }
151
174
  async withTimeout(promiseArg, timeoutMsArg, messageArg) {
152
175
  let timeout;
@@ -162,12 +185,24 @@ export class IntegrationRuntimeManager {
162
185
  }
163
186
  }
164
187
  }
165
- runExclusive(operationArg) {
166
- const operationPromise = this.operationTail
167
- .catch(() => undefined)
168
- .then(operationArg);
169
- this.operationTail = operationPromise;
170
- return operationPromise;
188
+ async performDestroyAll(timeoutMsArg) {
189
+ for (const [domain, runtime] of this.runtimes) {
190
+ this.runtimes.delete(domain);
191
+ this.markRuntimePendingCleanup(domain, runtime);
192
+ }
193
+ const runtimes = [...this.runtimesPendingCleanup.keys()];
194
+ const results = await Promise.allSettled(runtimes.map((runtimeArg) => this.destroyRuntime(runtimeArg, timeoutMsArg)));
195
+ const errors = results
196
+ .filter((resultArg) => resultArg.status === 'rejected')
197
+ .map((resultArg) => resultArg.reason);
198
+ if (errors.length > 0) {
199
+ throw new AggregateError(errors, 'Failed to destroy all integration runtimes cleanly');
200
+ }
201
+ }
202
+ markRuntimePendingCleanup(domainArg, runtimeArg) {
203
+ if (!this.runtimesPendingCleanup.has(runtimeArg)) {
204
+ this.runtimesPendingCleanup.set(runtimeArg, domainArg);
205
+ }
171
206
  }
172
207
  }
173
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy5ydW50aW1lbWFuYWdlci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3RzL2NvcmUvY2xhc3Nlcy5ydW50aW1lbWFuYWdlci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFDQSxPQUFPLEVBQUUsb0NBQW9DLEVBQUUsTUFBTSx3Q0FBd0MsQ0FBQztBQVE5RixNQUFNLE9BQU8seUJBQXlCO0lBQ25CLFFBQVEsR0FBRyxJQUFJLEdBQUcsRUFBK0IsQ0FBQztJQUNsRCxzQkFBc0IsR0FBRyxJQUFJLEdBQUcsRUFBdUIsQ0FBQztJQUN4RCwwQkFBMEIsR0FBRyxJQUFJLEdBQUcsRUFBc0MsQ0FBQztJQUMzRSxpQkFBaUIsR0FBRyxJQUFJLE9BQU8sRUFBdUIsQ0FBQztJQUNoRSxhQUFhLEdBQXFCLE9BQU8sQ0FBQyxPQUFPLEVBQUUsQ0FBQztJQUVyRCxLQUFLLENBQUMsYUFBYSxDQUN4QixjQUF3QyxFQUN4QyxTQUFrQixFQUNsQixhQUF1QyxFQUFFO1FBRXpDLElBQUksY0FBYyxDQUFDLE1BQU0sS0FBSyxpQkFBaUIsRUFBRSxDQUFDO1lBQ2hELE1BQU0sb0NBQW9DLENBQUMsY0FBYyxDQUFDLE1BQU0sQ0FBQyxDQUFDO1FBQ3BFLENBQUM7UUFDRCxNQUFNLE9BQU8sR0FBRyxNQUFNLGNBQWMsQ0FBQyxLQUFLLENBQUMsU0FBUyxFQUFFLFVBQVUsQ0FBQyxDQUFDO1FBQ2xFLElBQUksT0FBTyxDQUFDLE1BQU0sS0FBSyxjQUFjLENBQUMsTUFBTSxFQUFFLENBQUM7WUFDN0MsTUFBTSxXQUFXLEdBQUcsSUFBSSxLQUFLLENBQzNCLGlEQUFpRCxjQUFjLENBQUMsTUFBTSxjQUFjLE9BQU8sQ0FBQyxNQUFNLEVBQUUsQ0FDckcsQ0FBQztZQUNGLElBQUksQ0FBQztnQkFDSCxNQUFNLElBQUksQ0FBQyxjQUFjLENBQUMsT0FBTyxDQUFDLENBQUM7WUFDckMsQ0FBQztZQUFDLE9BQU8sWUFBWSxFQUFFLENBQUM7Z0JBQ3RCLE1B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
208
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy5ydW50aW1lbWFuYWdlci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3RzL2NvcmUvY2xhc3Nlcy5ydW50aW1lbWFuYWdlci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFDQSxPQUFPLEVBQUUsb0NBQW9DLEVBQUUsTUFBTSx3Q0FBd0MsQ0FBQztBQVE5RixNQUFNLE9BQU8seUJBQXlCO0lBQ25CLFFBQVEsR0FBRyxJQUFJLEdBQUcsRUFBK0IsQ0FBQztJQUNsRCxzQkFBc0IsR0FBRyxJQUFJLEdBQUcsRUFBK0IsQ0FBQztJQUNoRSwwQkFBMEIsR0FBRyxJQUFJLEdBQUcsRUFBc0MsQ0FBQztJQUMzRSxpQkFBaUIsR0FBRyxJQUFJLE9BQU8sRUFBdUIsQ0FBQztJQUN2RCxzQkFBc0IsR0FBRyxJQUFJLEdBQUcsRUFBVSxDQUFDO0lBQ3BELGlCQUFpQixDQUFpQjtJQUVuQyxLQUFLLENBQUMsYUFBYSxDQUN4QixjQUF3QyxFQUN4QyxTQUFrQixFQUNsQixhQUF1QyxFQUFFO1FBRXpDLElBQUksY0FBYyxDQUFDLE1BQU0sS0FBSyxpQkFBaUIsRUFBRSxDQUFDO1lBQ2hELE1BQU0sb0NBQW9DLENBQUMsY0FBYyxDQUFDLE1BQU0sQ0FBQyxDQUFDO1FBQ3BFLENBQUM7UUFDRCxNQUFNLElBQUksQ0FBQyxtQkFBbUIsQ0FBQyxjQUFjLENBQUMsTUFBTSxDQUFDLENBQUM7UUFDdEQsTUFBTSxPQUFPLEdBQUcsTUFBTSxjQUFjLENBQUMsS0FBSyxDQUFDLFNBQVMsRUFBRSxVQUFVLENBQUMsQ0FBQztRQUNsRSxJQUFJLE9BQU8sQ0FBQyxNQUFNLEtBQUssY0FBYyxDQUFDLE1BQU0sRUFBRSxDQUFDO1lBQzdDLE1BQU0sV0FBVyxHQUFHLElBQUksS0FBSyxDQUMzQixpREFBaUQsY0FBYyxDQUFDLE1BQU0sY0FBYyxPQUFPLENBQUMsTUFBTSxFQUFFLENBQ3JHLENBQUM7WUFDRixJQUFJLENBQUMseUJBQXlCLENBQUMsY0FBYyxD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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@smarthome.exchange/integrations",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "private": false,
5
5
  "description": "TypeScript-native device integrations for smarthome.exchange.",
6
6
  "engines": {
package/readme.md CHANGED
@@ -65,7 +65,7 @@ const matches = await engine.validateCandidate({
65
65
  console.log(matches);
66
66
  ```
67
67
 
68
- The hub uses the same primitives through `discoverIntegrationCandidates()` and `setupIntegration(...)`, then reconciles each runtime's complete normalized device snapshot into the canonical registry. Devices absent from a later snapshot are removed.
68
+ The hub uses the same primitives through `discoverIntegrationCandidates()` and `setupIntegration(...)`, then reconciles each runtime's complete normalized device snapshot into the canonical registry. Devices absent from a later snapshot are removed. `IntegrationRuntimeManager.createRuntime()` and `setupIntegration()` retry retained cleanup for the requested domain before invoking integration setup and fail closed with an `AggregateError` while cleanup still fails. Replacement setup destroys the previous runtime before constructing its successor, and overlapping same-domain setup calls are rejected. Failed replacement and mismatched-runtime cleanup remains retained under the requested domain; `retryPendingCleanup(domain)` is the explicit no-timeout retry entry point, while `hasPendingCleanup(domain)` exposes whether that gate remains closed.
69
69
 
70
70
  Runtime setup receives an optional abort signal. Device snapshots, subscriptions, and feature writes can also accept abort signals, allowing the hub to bound setup, synchronization, and writes. A control runtime may implement `writeFeature(...)`, `callService(...)`, or both; authoritative readable state still comes from a later `devices()` snapshot rather than an optimistic write. Every runtime implements `destroy()` for explicit cleanup.
71
71
 
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@smarthome.exchange/integrations',
6
- version: '0.1.0',
6
+ version: '0.2.0',
7
7
  description: 'TypeScript-native device integrations for smarthome.exchange.'
8
8
  }
@@ -9,10 +9,11 @@ import type {
9
9
 
10
10
  export class IntegrationRuntimeManager {
11
11
  private readonly runtimes = new Map<string, IIntegrationRuntime>();
12
- private readonly runtimesPendingCleanup = new Set<IIntegrationRuntime>();
12
+ private readonly runtimesPendingCleanup = new Map<IIntegrationRuntime, string>();
13
13
  private readonly runtimeDestructionPromises = new Map<IIntegrationRuntime, Promise<void>>();
14
14
  private readonly destroyedRuntimes = new WeakSet<IIntegrationRuntime>();
15
- private operationTail: Promise<unknown> = Promise.resolve();
15
+ private readonly setupDomainsInProgress = new Set<string>();
16
+ private destroyAllPromise?: Promise<void>;
16
17
 
17
18
  public async createRuntime<TConfig>(
18
19
  integrationArg: BaseIntegration<TConfig>,
@@ -22,11 +23,13 @@ export class IntegrationRuntimeManager {
22
23
  if (integrationArg.status === 'descriptor-only') {
23
24
  throw createDescriptorOnlyIntegrationError(integrationArg.domain);
24
25
  }
26
+ await this.retryPendingCleanup(integrationArg.domain);
25
27
  const runtime = await integrationArg.setup(configArg, contextArg);
26
28
  if (runtime.domain !== integrationArg.domain) {
27
29
  const domainError = new Error(
28
30
  `Integration runtime domain mismatch: expected ${integrationArg.domain}, received ${runtime.domain}`
29
31
  );
32
+ this.markRuntimePendingCleanup(integrationArg.domain, runtime);
30
33
  try {
31
34
  await this.destroyRuntime(runtime);
32
35
  } catch (destroyError) {
@@ -45,29 +48,25 @@ export class IntegrationRuntimeManager {
45
48
  configArg: TConfig,
46
49
  contextArg: IIntegrationSetupContext = {}
47
50
  ): Promise<IIntegrationRuntime> {
48
- return this.runExclusive(async () => {
49
- const runtime = await this.createRuntime(integrationArg, configArg, contextArg);
51
+ if (this.destroyAllPromise) {
52
+ throw new Error('Cannot set up an integration while destroyAll is in progress.');
53
+ }
54
+ if (this.setupDomainsInProgress.has(integrationArg.domain)) {
55
+ throw new Error(`Integration setup is already in progress: ${integrationArg.domain}`);
56
+ }
57
+ this.setupDomainsInProgress.add(integrationArg.domain);
58
+ try {
59
+ await this.retryPendingCleanup(integrationArg.domain);
50
60
  const previousRuntime = this.retireRuntime(integrationArg.domain);
51
-
52
61
  if (previousRuntime) {
53
- try {
54
- await this.destroyRuntime(previousRuntime);
55
- } catch (destroyError) {
56
- try {
57
- await this.destroyRuntime(runtime);
58
- } catch (cleanupError) {
59
- throw new AggregateError(
60
- [destroyError, cleanupError],
61
- `Failed to replace integration runtime for ${integrationArg.domain}`
62
- );
63
- }
64
- throw destroyError;
65
- }
62
+ await this.destroyRuntime(previousRuntime);
66
63
  }
67
-
64
+ const runtime = await this.createRuntime(integrationArg, configArg, contextArg);
68
65
  this.activateRuntime(integrationArg.domain, runtime);
69
66
  return runtime;
70
- });
67
+ } finally {
68
+ this.setupDomainsInProgress.delete(integrationArg.domain);
69
+ }
71
70
  }
72
71
 
73
72
  public activateRuntime(domainArg: string, runtimeArg: IIntegrationRuntime): IIntegrationRuntime | undefined {
@@ -94,7 +93,7 @@ export class IntegrationRuntimeManager {
94
93
  public retireRuntime(domainArg: string, expectedRuntimeArg?: IIntegrationRuntime): IIntegrationRuntime | undefined {
95
94
  const runtime = this.removeRuntime(domainArg, expectedRuntimeArg);
96
95
  if (runtime) {
97
- this.runtimesPendingCleanup.add(runtime);
96
+ this.markRuntimePendingCleanup(domainArg, runtime);
98
97
  }
99
98
  return runtime;
100
99
  }
@@ -105,9 +104,12 @@ export class IntegrationRuntimeManager {
105
104
  ): Promise<void> {
106
105
  this.retireRuntime(runtimeArg.domain, runtimeArg);
107
106
  if (this.destroyedRuntimes.has(runtimeArg)) {
107
+ this.runtimesPendingCleanup.delete(runtimeArg);
108
108
  return Promise.resolve();
109
109
  }
110
- this.runtimesPendingCleanup.add(runtimeArg);
110
+ if (!this.runtimesPendingCleanup.has(runtimeArg)) {
111
+ this.markRuntimePendingCleanup(runtimeArg.domain, runtimeArg);
112
+ }
111
113
  let destructionPromise = this.runtimeDestructionPromises.get(runtimeArg);
112
114
  if (!destructionPromise) {
113
115
  const destroyAttempt = Promise.resolve().then(() => runtimeArg.destroy());
@@ -145,6 +147,33 @@ export class IntegrationRuntimeManager {
145
147
  return [...this.runtimes.values()];
146
148
  }
147
149
 
150
+ public async retryPendingCleanup(domainArg: string): Promise<void> {
151
+ const runtimes = [...this.runtimesPendingCleanup]
152
+ .filter(([, cleanupDomainArg]) => cleanupDomainArg === domainArg)
153
+ .map(([runtimeArg]) => runtimeArg);
154
+ if (runtimes.length === 0) {
155
+ return;
156
+ }
157
+ const results = await Promise.allSettled(
158
+ runtimes.map((runtimeArg) => this.destroyRuntime(runtimeArg))
159
+ );
160
+ const errors = results
161
+ .filter((resultArg): resultArg is PromiseRejectedResult => resultArg.status === 'rejected')
162
+ .map((resultArg) => resultArg.reason);
163
+ if (errors.length > 0) {
164
+ throw new AggregateError(
165
+ errors,
166
+ `Failed to clean up retained integration runtimes for ${domainArg}`
167
+ );
168
+ }
169
+ }
170
+
171
+ public hasPendingCleanup(domainArg: string): boolean {
172
+ return [...this.runtimesPendingCleanup.values()].some(
173
+ (cleanupDomainArg) => cleanupDomainArg === domainArg
174
+ );
175
+ }
176
+
148
177
  public async writeDeviceFeature(
149
178
  requestArg: IDeviceFeatureWriteRequest,
150
179
  expectedRuntimeArg?: IIntegrationRuntime,
@@ -181,24 +210,20 @@ export class IntegrationRuntimeManager {
181
210
  return runtime.writeFeature(requestArg, abortSignalArg);
182
211
  }
183
212
 
184
- public async destroyAll(timeoutMsArg?: number): Promise<void> {
185
- return this.runExclusive(async () => {
186
- for (const [domain, runtime] of this.runtimes) {
187
- this.runtimes.delete(domain);
188
- this.runtimesPendingCleanup.add(runtime);
189
- }
190
-
191
- const runtimes = [...this.runtimesPendingCleanup];
192
- const results = await Promise.allSettled(
193
- runtimes.map((runtimeArg) => this.destroyRuntime(runtimeArg, timeoutMsArg))
194
- );
195
- const errors = results
196
- .filter((resultArg): resultArg is PromiseRejectedResult => resultArg.status === 'rejected')
197
- .map((resultArg) => resultArg.reason);
198
- if (errors.length > 0) {
199
- throw new AggregateError(errors, 'Failed to destroy all integration runtimes cleanly');
213
+ public destroyAll(timeoutMsArg?: number): Promise<void> {
214
+ if (this.destroyAllPromise) {
215
+ return this.destroyAllPromise;
216
+ }
217
+ if (this.setupDomainsInProgress.size > 0) {
218
+ return Promise.reject(new Error('Cannot destroy integration runtimes while setup is in progress.'));
219
+ }
220
+ const destroyAllPromise = this.performDestroyAll(timeoutMsArg).finally(() => {
221
+ if (this.destroyAllPromise === destroyAllPromise) {
222
+ this.destroyAllPromise = undefined;
200
223
  }
201
224
  });
225
+ this.destroyAllPromise = destroyAllPromise;
226
+ return destroyAllPromise;
202
227
  }
203
228
 
204
229
  private async withTimeout<TResult>(
@@ -219,11 +244,30 @@ export class IntegrationRuntimeManager {
219
244
  }
220
245
  }
221
246
 
222
- private runExclusive<TResult>(operationArg: () => Promise<TResult>): Promise<TResult> {
223
- const operationPromise = this.operationTail
224
- .catch(() => undefined)
225
- .then(operationArg);
226
- this.operationTail = operationPromise;
227
- return operationPromise;
247
+ private async performDestroyAll(timeoutMsArg?: number): Promise<void> {
248
+ for (const [domain, runtime] of this.runtimes) {
249
+ this.runtimes.delete(domain);
250
+ this.markRuntimePendingCleanup(domain, runtime);
251
+ }
252
+
253
+ const runtimes = [...this.runtimesPendingCleanup.keys()];
254
+ const results = await Promise.allSettled(
255
+ runtimes.map((runtimeArg) => this.destroyRuntime(runtimeArg, timeoutMsArg))
256
+ );
257
+ const errors = results
258
+ .filter((resultArg): resultArg is PromiseRejectedResult => resultArg.status === 'rejected')
259
+ .map((resultArg) => resultArg.reason);
260
+ if (errors.length > 0) {
261
+ throw new AggregateError(errors, 'Failed to destroy all integration runtimes cleanly');
262
+ }
263
+ }
264
+
265
+ private markRuntimePendingCleanup(
266
+ domainArg: string,
267
+ runtimeArg: IIntegrationRuntime
268
+ ): void {
269
+ if (!this.runtimesPendingCleanup.has(runtimeArg)) {
270
+ this.runtimesPendingCleanup.set(runtimeArg, domainArg);
271
+ }
228
272
  }
229
273
  }