alchemy 0.63.1 → 0.64.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.
Files changed (157) hide show
  1. package/bin/alchemy.js +96 -86
  2. package/lib/alchemy.d.ts +4 -0
  3. package/lib/alchemy.d.ts.map +1 -1
  4. package/lib/alchemy.js +10 -4
  5. package/lib/alchemy.js.map +1 -1
  6. package/lib/apply.d.ts +3 -0
  7. package/lib/apply.d.ts.map +1 -1
  8. package/lib/apply.js +11 -1
  9. package/lib/apply.js.map +1 -1
  10. package/lib/cloudflare/bindings.d.ts +3 -1
  11. package/lib/cloudflare/bindings.d.ts.map +1 -1
  12. package/lib/cloudflare/bucket.d.ts +2 -1
  13. package/lib/cloudflare/bucket.d.ts.map +1 -1
  14. package/lib/cloudflare/bucket.js.map +1 -1
  15. package/lib/cloudflare/bundle/cloudflare-internal.d.ts +6 -0
  16. package/lib/cloudflare/bundle/cloudflare-internal.d.ts.map +1 -0
  17. package/lib/cloudflare/bundle/cloudflare-internal.js +39 -0
  18. package/lib/cloudflare/bundle/cloudflare-internal.js.map +1 -0
  19. package/lib/cloudflare/bundle/nodejs-builtin-modules.d.ts +2 -0
  20. package/lib/cloudflare/bundle/nodejs-builtin-modules.d.ts.map +1 -0
  21. package/lib/cloudflare/bundle/nodejs-builtin-modules.js +9 -0
  22. package/lib/cloudflare/bundle/nodejs-builtin-modules.js.map +1 -0
  23. package/lib/cloudflare/bundle/plugin-als-external.d.ts +6 -0
  24. package/lib/cloudflare/bundle/plugin-als-external.d.ts.map +1 -0
  25. package/lib/cloudflare/bundle/plugin-als-external.js +12 -0
  26. package/lib/cloudflare/bundle/plugin-als-external.js.map +1 -0
  27. package/lib/cloudflare/bundle/plugin-hybrid-node-compat.d.ts +4 -1
  28. package/lib/cloudflare/bundle/plugin-hybrid-node-compat.d.ts.map +1 -1
  29. package/lib/cloudflare/bundle/plugin-hybrid-node-compat.js +23 -23
  30. package/lib/cloudflare/bundle/plugin-hybrid-node-compat.js.map +1 -1
  31. package/lib/cloudflare/bundle/plugin-node-compat.d.ts +11 -0
  32. package/lib/cloudflare/bundle/plugin-node-compat.d.ts.map +1 -0
  33. package/lib/cloudflare/bundle/plugin-node-compat.js +93 -0
  34. package/lib/cloudflare/bundle/plugin-node-compat.js.map +1 -0
  35. package/lib/cloudflare/d1-local-migrations.js.map +1 -1
  36. package/lib/cloudflare/durable-object-namespace.d.ts +1 -0
  37. package/lib/cloudflare/durable-object-namespace.d.ts.map +1 -1
  38. package/lib/cloudflare/durable-object-namespace.js.map +1 -1
  39. package/lib/cloudflare/website.d.ts.map +1 -1
  40. package/lib/cloudflare/website.js +80 -82
  41. package/lib/cloudflare/website.js.map +1 -1
  42. package/lib/cloudflare/worker-bundle.d.ts +6 -0
  43. package/lib/cloudflare/worker-bundle.d.ts.map +1 -1
  44. package/lib/cloudflare/worker-bundle.js +34 -10
  45. package/lib/cloudflare/worker-bundle.js.map +1 -1
  46. package/lib/cloudflare/worker-metadata.d.ts.map +1 -1
  47. package/lib/cloudflare/worker-metadata.js +1 -0
  48. package/lib/cloudflare/worker-metadata.js.map +1 -1
  49. package/lib/cloudflare/worker.d.ts.map +1 -1
  50. package/lib/cloudflare/worker.js +24 -6
  51. package/lib/cloudflare/worker.js.map +1 -1
  52. package/lib/cloudflare/wrangler.json.d.ts +2 -4
  53. package/lib/cloudflare/wrangler.json.d.ts.map +1 -1
  54. package/lib/cloudflare/wrangler.json.js +16 -14
  55. package/lib/cloudflare/wrangler.json.js.map +1 -1
  56. package/lib/context.d.ts +3 -1
  57. package/lib/context.d.ts.map +1 -1
  58. package/lib/context.js +2 -2
  59. package/lib/context.js.map +1 -1
  60. package/lib/destroy.d.ts +8 -0
  61. package/lib/destroy.d.ts.map +1 -1
  62. package/lib/destroy.js +20 -4
  63. package/lib/destroy.js.map +1 -1
  64. package/lib/planetscale/api/client.gen.d.ts +1750 -0
  65. package/lib/planetscale/api/client.gen.d.ts.map +1 -0
  66. package/lib/planetscale/api/client.gen.js +2401 -0
  67. package/lib/planetscale/api/client.gen.js.map +1 -0
  68. package/lib/planetscale/api/index.d.ts +3 -0
  69. package/lib/planetscale/api/index.d.ts.map +1 -0
  70. package/lib/planetscale/api/index.js +4 -0
  71. package/lib/planetscale/api/index.js.map +1 -0
  72. package/lib/planetscale/api/types.gen.d.ts +12156 -0
  73. package/lib/planetscale/api/types.gen.d.ts.map +1 -0
  74. package/lib/planetscale/api/types.gen.js +2 -0
  75. package/lib/planetscale/api/types.gen.js.map +1 -0
  76. package/lib/planetscale/branch.d.ts +5 -8
  77. package/lib/planetscale/branch.d.ts.map +1 -1
  78. package/lib/planetscale/branch.js +111 -97
  79. package/lib/planetscale/branch.js.map +1 -1
  80. package/lib/planetscale/database.d.ts +2 -6
  81. package/lib/planetscale/database.d.ts.map +1 -1
  82. package/lib/planetscale/database.js +157 -118
  83. package/lib/planetscale/database.js.map +1 -1
  84. package/lib/planetscale/index.d.ts +1 -1
  85. package/lib/planetscale/index.d.ts.map +1 -1
  86. package/lib/planetscale/index.js +1 -1
  87. package/lib/planetscale/index.js.map +1 -1
  88. package/lib/planetscale/password.d.ts +4 -7
  89. package/lib/planetscale/password.d.ts.map +1 -1
  90. package/lib/planetscale/password.js +71 -56
  91. package/lib/planetscale/password.js.map +1 -1
  92. package/lib/planetscale/utils.d.ts +8 -6
  93. package/lib/planetscale/utils.d.ts.map +1 -1
  94. package/lib/planetscale/utils.js +85 -70
  95. package/lib/planetscale/utils.js.map +1 -1
  96. package/lib/scope.d.ts +5 -1
  97. package/lib/scope.d.ts.map +1 -1
  98. package/lib/scope.js +11 -3
  99. package/lib/scope.js.map +1 -1
  100. package/lib/util/dedent.d.ts +3 -14
  101. package/lib/util/dedent.d.ts.map +1 -1
  102. package/lib/util/dedent.js +28 -52
  103. package/lib/util/dedent.js.map +1 -1
  104. package/package.json +4 -3
  105. package/src/alchemy.ts +14 -6
  106. package/src/apply.ts +13 -1
  107. package/src/cloudflare/bindings.ts +3 -1
  108. package/src/cloudflare/bucket.ts +3 -1
  109. package/src/cloudflare/bundle/cloudflare-internal.ts +42 -0
  110. package/src/cloudflare/bundle/nodejs-builtin-modules.ts +14 -0
  111. package/src/cloudflare/bundle/plugin-als-external.ts +13 -0
  112. package/src/cloudflare/bundle/plugin-hybrid-node-compat.ts +29 -28
  113. package/src/cloudflare/bundle/plugin-node-compat.ts +114 -0
  114. package/src/cloudflare/d1-local-migrations.ts +2 -2
  115. package/src/cloudflare/durable-object-namespace.ts +1 -0
  116. package/src/cloudflare/website.ts +88 -89
  117. package/src/cloudflare/worker-bundle.ts +46 -9
  118. package/src/cloudflare/worker-metadata.ts +2 -0
  119. package/src/cloudflare/worker.ts +35 -7
  120. package/src/cloudflare/wrangler.json.ts +108 -110
  121. package/src/context.ts +5 -3
  122. package/src/destroy.ts +26 -8
  123. package/src/planetscale/api/client.gen.ts +2882 -0
  124. package/src/planetscale/api/index.ts +4 -0
  125. package/src/planetscale/api/types.gen.ts +13041 -0
  126. package/src/planetscale/branch.ts +152 -169
  127. package/src/planetscale/database.ts +195 -204
  128. package/src/planetscale/index.ts +1 -1
  129. package/src/planetscale/password.ts +86 -85
  130. package/src/planetscale/utils.ts +141 -122
  131. package/src/scope.ts +12 -3
  132. package/src/util/dedent.ts +33 -61
  133. package/templates/astro/alchemy.run.ts +1 -3
  134. package/templates/hono/alchemy.run.ts +0 -1
  135. package/templates/nuxt/alchemy.run.ts +1 -3
  136. package/templates/react-router/alchemy.run.ts +1 -3
  137. package/templates/rwsdk/alchemy.run.ts +0 -2
  138. package/templates/sveltekit/alchemy.run.ts +1 -3
  139. package/templates/tanstack-start/alchemy.run.ts +1 -3
  140. package/templates/typescript/alchemy.run.ts +0 -1
  141. package/templates/vite/alchemy.run.ts +0 -1
  142. package/workers/cloudflare-state-store.js +58 -58
  143. package/lib/cloudflare/bundle/externals.d.ts +0 -3
  144. package/lib/cloudflare/bundle/externals.d.ts.map +0 -1
  145. package/lib/cloudflare/bundle/externals.js +0 -35
  146. package/lib/cloudflare/bundle/externals.js.map +0 -1
  147. package/lib/cloudflare/bundle/plugin-compat-warning.d.ts +0 -10
  148. package/lib/cloudflare/bundle/plugin-compat-warning.d.ts.map +0 -1
  149. package/lib/cloudflare/bundle/plugin-compat-warning.js +0 -67
  150. package/lib/cloudflare/bundle/plugin-compat-warning.js.map +0 -1
  151. package/lib/planetscale/api.d.ts +0 -53
  152. package/lib/planetscale/api.d.ts.map +0 -1
  153. package/lib/planetscale/api.js +0 -84
  154. package/lib/planetscale/api.js.map +0 -1
  155. package/src/cloudflare/bundle/externals.ts +0 -36
  156. package/src/cloudflare/bundle/plugin-compat-warning.ts +0 -94
  157. package/src/planetscale/api.ts +0 -115
@@ -2,6 +2,7 @@ import fs from "node:fs/promises";
2
2
  import path from "node:path";
3
3
  import type { Context } from "../context.ts";
4
4
  import { Resource } from "../resource.ts";
5
+ import { Scope } from "../scope.ts";
5
6
  import { isSecret } from "../secret.ts";
6
7
  import { assertNever } from "../util/assert-never.ts";
7
8
  import {
@@ -9,6 +10,7 @@ import {
9
10
  type Bindings,
10
11
  type WorkerBindingRateLimit,
11
12
  } from "./bindings.ts";
13
+ import type { R2BucketJurisdiction } from "./bucket.ts";
12
14
  import type { DurableObjectNamespace } from "./durable-object-namespace.ts";
13
15
  import type { EventSource } from "./event-source.ts";
14
16
  import { isQueueEventSource } from "./event-source.ts";
@@ -86,9 +88,7 @@ export interface WranglerJsonProps {
86
88
  /**
87
89
  * Output returned after WranglerJson creation/update
88
90
  */
89
- export interface WranglerJson
90
- extends Resource<"cloudflare::WranglerJson">,
91
- WranglerJsonProps {
91
+ export interface WranglerJson extends WranglerJsonProps {
92
92
  /**
93
93
  * Time at which the file was created
94
94
  */
@@ -110,128 +110,123 @@ export interface WranglerJson
110
110
  spec: WranglerJsonSpec;
111
111
  }
112
112
 
113
+ // we are deprecating the WranglerJson resource (it is now just a funciton)
114
+ // but, a user may still have a resource that depends on it, so we register a no-op dummy resource so that it can be cleanly delted
115
+ Resource("cloudflare::WranglerJson", async function (this: Context<any>) {
116
+ if (this.phase === "delete") {
117
+ return this.destroy();
118
+ }
119
+
120
+ throw new Error("Not implemented");
121
+ });
122
+
113
123
  /**
114
124
  * Resource for managing wrangler.json configuration files
115
125
  */
116
- export const WranglerJson = Resource(
117
- "cloudflare::WranglerJson",
118
- {
119
- alwaysUpdate: true,
120
- },
121
- async function (
122
- this: Context<WranglerJson>,
123
- _id: string,
124
- props: WranglerJsonProps,
125
- ): Promise<WranglerJson> {
126
- if (this.phase === "delete") {
127
- return this.destroy();
128
- }
126
+ export async function WranglerJson(
127
+ props: WranglerJsonProps,
128
+ ): Promise<WranglerJson> {
129
+ const cwd = props.worker.cwd ? path.resolve(props.worker.cwd) : process.cwd();
129
130
 
130
- const cwd = props.worker.cwd
131
- ? path.resolve(props.worker.cwd)
132
- : process.cwd();
133
-
134
- const toAbsolute = <T extends string | undefined>(input: T): T => {
135
- return (input ? path.resolve(cwd, input) : undefined) as T;
136
- };
131
+ const toAbsolute = <T extends string | undefined>(input: T): T => {
132
+ return (input ? path.resolve(cwd, input) : undefined) as T;
133
+ };
137
134
 
138
- const main = toAbsolute(props.main ?? props.worker.entrypoint);
139
- let filePath = toAbsolute(props.path ?? cwd);
140
- if (!path.basename(filePath).match(".json")) {
141
- filePath = path.join(filePath, props.name ?? "wrangler.jsonc");
142
- }
135
+ const main = toAbsolute(props.main ?? props.worker.entrypoint);
136
+ let filePath = toAbsolute(props.path ?? cwd);
137
+ if (!path.basename(filePath).match(".json")) {
138
+ filePath = path.join(filePath, props.name ?? "wrangler.jsonc");
139
+ }
143
140
 
144
- const dirname = path.dirname(filePath);
141
+ const dirname = path.dirname(filePath);
145
142
 
146
- if (!main) {
147
- throw new Error(
148
- "Worker must have an entrypoint to generate a wrangler.json",
149
- );
150
- }
143
+ if (!main) {
144
+ throw new Error(
145
+ "Worker must have an entrypoint to generate a wrangler.json",
146
+ );
147
+ }
151
148
 
152
- const worker = props.worker;
153
-
154
- const spec: WranglerJsonSpec = {
155
- name: worker.name,
156
- // Use entrypoint as main if it exists
157
- main: path.relative(dirname, main),
158
- // see: https://developers.cloudflare.com/workers/configuration/compatibility-dates/
159
- compatibility_date: worker.compatibilityDate,
160
- compatibility_flags: props.worker.compatibilityFlags,
161
- assets: props.assets
162
- ? {
163
- directory: toAbsolute(props.assets.directory),
164
- binding: props.assets.binding,
165
- not_found_handling: props.worker.assets?.not_found_handling,
166
- html_handling: props.worker.assets?.html_handling,
167
- run_worker_first: props.worker.assets?.run_worker_first,
168
- }
169
- : undefined,
170
- placement: worker.placement,
171
- limits: worker.limits,
172
- };
149
+ const worker = props.worker;
150
+
151
+ const spec: WranglerJsonSpec = {
152
+ name: worker.name,
153
+ // Use entrypoint as main if it exists
154
+ main: path.relative(dirname, main),
155
+ // see: https://developers.cloudflare.com/workers/configuration/compatibility-dates/
156
+ compatibility_date: worker.compatibilityDate,
157
+ compatibility_flags: props.worker.compatibilityFlags,
158
+ assets: props.assets
159
+ ? {
160
+ directory: toAbsolute(props.assets.directory),
161
+ binding: props.assets.binding,
162
+ not_found_handling: props.worker.assets?.not_found_handling,
163
+ html_handling: props.worker.assets?.html_handling,
164
+ run_worker_first: props.worker.assets?.run_worker_first,
165
+ }
166
+ : undefined,
167
+ placement: worker.placement,
168
+ limits: worker.limits,
169
+ };
173
170
 
174
- // Process bindings if they exist
175
- if (worker.bindings) {
176
- processBindings(
177
- spec,
178
- worker.bindings,
179
- worker.eventSources,
180
- worker.name,
181
- cwd,
182
- props.secrets ?? false,
183
- this.scope.local && !props.worker.dev?.remote,
184
- );
185
- }
171
+ // Process bindings if they exist
172
+ if (worker.bindings) {
173
+ processBindings(
174
+ spec,
175
+ worker.bindings,
176
+ worker.eventSources,
177
+ worker.name,
178
+ cwd,
179
+ props.secrets ?? false,
180
+ Scope.current.local && !props.worker.dev?.remote,
181
+ );
182
+ }
186
183
 
187
- // Add environment variables as vars
188
- if (worker.env) {
189
- spec.vars = { ...worker.env };
190
- }
184
+ // Add environment variables as vars
185
+ if (worker.env) {
186
+ spec.vars = { ...worker.env };
187
+ }
191
188
 
192
- if (worker.crons && worker.crons.length > 0) {
193
- spec.triggers = { crons: worker.crons };
194
- }
189
+ if (worker.crons && worker.crons.length > 0) {
190
+ spec.triggers = { crons: worker.crons };
191
+ }
195
192
 
196
- if (spec.assets) {
197
- spec.assets.directory = path.relative(dirname, spec.assets.directory);
198
- }
193
+ if (spec.assets) {
194
+ spec.assets.directory = path.relative(dirname, spec.assets.directory);
195
+ }
199
196
 
200
- // Apply the wrangler configuration hook as the final transformation
201
- const finalSpec = props.transform?.wrangler
202
- ? await props.transform.wrangler(spec)
203
- : spec;
204
-
205
- await fs.mkdir(dirname, { recursive: true });
206
- if (props.secrets) {
207
- // If secrets are enabled, decrypt them in the wrangler.json file,
208
- // but do not modify `finalSpec` so that way secrets aren't written to state unencrypted.
209
- const withSecretsUnwrapped = {
210
- ...finalSpec,
211
- vars: {
212
- ...finalSpec.vars,
213
- ...Object.fromEntries(
214
- Object.entries(finalSpec.vars ?? {}).map(([key, value]) =>
215
- isSecret(value) ? [key, value.unencrypted] : [key, value],
216
- ),
197
+ // Apply the wrangler configuration hook as the final transformation
198
+ const finalSpec = props.transform?.wrangler
199
+ ? await props.transform.wrangler(spec)
200
+ : spec;
201
+
202
+ await fs.mkdir(dirname, { recursive: true });
203
+ if (props.secrets) {
204
+ // If secrets are enabled, decrypt them in the wrangler.json file,
205
+ // but do not modify `finalSpec` so that way secrets aren't written to state unencrypted.
206
+ const withSecretsUnwrapped = {
207
+ ...finalSpec,
208
+ vars: {
209
+ ...finalSpec.vars,
210
+ ...Object.fromEntries(
211
+ Object.entries(finalSpec.vars ?? {}).map(([key, value]) =>
212
+ isSecret(value) ? [key, value.unencrypted] : [key, value],
217
213
  ),
218
- },
219
- };
220
- await writeJSON(filePath, withSecretsUnwrapped);
221
- } else {
222
- await writeJSON(filePath, finalSpec);
223
- }
214
+ ),
215
+ },
216
+ };
217
+ await writeJSON(filePath, withSecretsUnwrapped);
218
+ } else {
219
+ await writeJSON(filePath, finalSpec);
220
+ }
224
221
 
225
- // Return the resource
226
- return this({
227
- ...props,
228
- path: path.relative(cwd, filePath),
229
- spec: finalSpec,
230
- createdAt: Date.now(),
231
- updatedAt: Date.now(),
232
- });
233
- },
234
- );
222
+ return {
223
+ ...props,
224
+ path: path.relative(cwd, filePath),
225
+ spec: finalSpec,
226
+ createdAt: Date.now(),
227
+ updatedAt: Date.now(),
228
+ };
229
+ }
235
230
 
236
231
  const writeJSON = async (filePath: string, content: any) => {
237
232
  await fs.writeFile(filePath, `${JSON.stringify(content, null, 2)}\n`);
@@ -537,6 +532,7 @@ function processBindings(
537
532
  binding: string;
538
533
  bucket_name: string;
539
534
  preview_bucket_name: string;
535
+ jurisdiction?: R2BucketJurisdiction;
540
536
  }[] = [];
541
537
  const services: { binding: string; service: string; environment?: string }[] =
542
538
  [];
@@ -683,6 +679,8 @@ function processBindings(
683
679
  binding: bindingName,
684
680
  bucket_name: name,
685
681
  preview_bucket_name: name,
682
+ jurisdiction:
683
+ binding.jurisdiction === "default" ? undefined : binding.jurisdiction,
686
684
  ...(binding.dev?.remote ? { experimental_remote: true } : {}),
687
685
  });
688
686
  } else if (binding.type === "secret") {
package/src/context.ts CHANGED
@@ -65,8 +65,10 @@ export interface BaseContext<Out extends Resource> {
65
65
  *
66
66
  * It is so that the resource lifecycle handler can "return never" instead of
67
67
  * "return undefined" so that `await MyResource()` always returns a value.
68
+ *
69
+ * @param retainChildren - Whether to retain the children of the resource.
68
70
  */
69
- destroy(): never;
71
+ destroy(retainChildren?: boolean): never;
70
72
  /**
71
73
  * Register a cleanup function that will be called when the process exits.
72
74
  *
@@ -161,8 +163,8 @@ export function context<
161
163
  return value;
162
164
  },
163
165
  quiet: scope.quiet,
164
- destroy: () => {
165
- throw new DestroyedSignal();
166
+ destroy: (retainChildren = false) => {
167
+ throw new DestroyedSignal(retainChildren);
166
168
  },
167
169
  onCleanup: (fn: () => void | Promise<void>) => {
168
170
  // make the function idempotent so repeated calls don't cause the process to hang
package/src/destroy.ts CHANGED
@@ -15,7 +15,16 @@ import type { State } from "./state.ts";
15
15
  import { formatFQN } from "./util/cli.ts";
16
16
  import { logger } from "./util/logger.ts";
17
17
 
18
- export class DestroyedSignal extends Error {}
18
+ export function isDestroyedSignal(error: any): error is DestroyedSignal {
19
+ return error instanceof Error && (error as any).kind === "DestroyedSignal";
20
+ }
21
+
22
+ export class DestroyedSignal extends Error {
23
+ readonly kind = "DestroyedSignal";
24
+ constructor(public readonly noop: boolean) {
25
+ super();
26
+ }
27
+ }
19
28
 
20
29
  export type DestroyStrategy = "sequential" | "parallel";
21
30
 
@@ -28,6 +37,10 @@ export interface DestroyOptions {
28
37
  props?: ResourceProps | undefined;
29
38
  output?: Resource<string>;
30
39
  };
40
+ /**
41
+ * If true, children of the resource will not be destroyed (but their state will be deleted).
42
+ */
43
+ noop?: boolean;
31
44
  }
32
45
 
33
46
  function isScopeArgs(a: any): a is [scope: Scope, options?: DestroyOptions] {
@@ -98,7 +111,7 @@ export async function destroy<Type extends string>(
98
111
  const quiet = options?.quiet ?? scope.quiet;
99
112
 
100
113
  try {
101
- if (!quiet) {
114
+ if (!quiet && !options?.noop) {
102
115
  logger.task(instance[ResourceFQN], {
103
116
  prefix: options?.replace ? "cleanup" : "deleting",
104
117
  prefixColor: options?.replace ? "magenta" : "redBright",
@@ -149,6 +162,8 @@ export async function destroy<Type extends string>(
149
162
  });
150
163
 
151
164
  let nestedScope: Scope | undefined;
165
+ let noop = options?.noop ?? false;
166
+
152
167
  try {
153
168
  // BUG: this does not restore persisted scope
154
169
  await alchemy.run(
@@ -158,17 +173,19 @@ export async function destroy<Type extends string>(
158
173
  isResource: instance[ResourceKind] !== "alchemy::Scope",
159
174
  parent: scope,
160
175
  destroyStrategy: instance[DestroyStrategy] ?? "sequential",
176
+ noop,
161
177
  },
162
178
  async (scope) => {
163
179
  nestedScope = options?.replace?.props == null ? scope : undefined;
164
- return await Provider.handler.bind(ctx)(
165
- instance[ResourceID],
166
- ctx.props,
167
- );
180
+ if (noop) {
181
+ return ctx.destroy(noop);
182
+ }
183
+ return Provider.handler.bind(ctx)(instance[ResourceID], ctx.props);
168
184
  },
169
185
  );
170
186
  } catch (err) {
171
- if (err instanceof DestroyedSignal) {
187
+ if (isDestroyedSignal(err)) {
188
+ noop = noop || err.noop;
172
189
  // TODO: should we fail if the DestroyedSignal is not thrown?
173
190
  } else {
174
191
  throw err;
@@ -178,6 +195,7 @@ export async function destroy<Type extends string>(
178
195
  if (nestedScope) {
179
196
  await destroy(nestedScope, {
180
197
  ...options,
198
+ noop,
181
199
  strategy: instance[DestroyStrategy] ?? "sequential",
182
200
  });
183
201
  }
@@ -195,7 +213,7 @@ export async function destroy<Type extends string>(
195
213
  await scope.set("pendingDeletions", pendingDeletions);
196
214
  }
197
215
 
198
- if (!quiet) {
216
+ if (!quiet && !options?.noop) {
199
217
  logger.task(instance[ResourceFQN], {
200
218
  prefix: options?.replace ? "cleaned" : "deleted",
201
219
  prefixColor: "greenBright",