effect-pulumi 0.1.1 → 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/dist/index.js CHANGED
@@ -1,211 +1,470 @@
1
- import { Data, Effect } from 'effect';
2
- import * as pulumi2 from '@pulumi/pulumi';
3
- import { LocalWorkspace } from '@pulumi/pulumi/automation/index.js';
4
-
5
- // src/errors.ts
6
- var describeCause = (cause) => cause instanceof Error ? cause.message : String(cause);
1
+ import { Data, Effect } from "effect";
2
+ import * as pulumi from "@pulumi/pulumi";
3
+ import { LocalWorkspace } from "@pulumi/pulumi/automation/index.js";
4
+ //#region src/errors.ts
5
+ const describeCause = (cause) => cause instanceof Error ? cause.message : String(cause);
6
+ /**
7
+ * Any synchronous failure constructing a resource (bad args, provider
8
+ * validation, etc.), or a failure resolving an Output's promise.
9
+ *
10
+ * Tagged `"PulumiError"`, so it can be matched by tag rather than by
11
+ * `instanceof`:
12
+ *
13
+ * @example
14
+ * ```ts
15
+ * program.pipe(
16
+ * Effect.catchTag("PulumiError", (e) => Effect.logError(e.message))
17
+ * );
18
+ * ```
19
+ */
7
20
  var PulumiError = class extends Data.TaggedError("PulumiError") {
8
- /** Derived so anything reading `.message` - plain logging, test failure
9
- * output, non-Effect error handling - sees the underlying failure instead
10
- * of an empty string. */
11
- get message() {
12
- return describeCause(this.cause);
13
- }
21
+ /** Derived so anything reading `.message` - plain logging, test failure
22
+ * output, non-Effect error handling - sees the underlying failure instead
23
+ * of an empty string. */
24
+ get message() {
25
+ return describeCause(this.cause);
26
+ }
14
27
  };
28
+ /**
29
+ * Failure from an Automation API lifecycle call (up/preview/destroy/select).
30
+ *
31
+ * `stage` says which call failed, which matters most in the composite
32
+ * operations: a failed `deploy` could have died selecting the stack, applying
33
+ * config, or running the update, and the three want different responses.
34
+ *
35
+ * @example
36
+ * ```ts
37
+ * deploy(opts).pipe(
38
+ * Effect.catchTag("AutomationError", (e) =>
39
+ * e.stage === "createOrSelectStack"
40
+ * ? Effect.fail(new BackendUnreachable())
41
+ * : Effect.logError(e.message)
42
+ * )
43
+ * );
44
+ * ```
45
+ */
15
46
  var AutomationError = class extends Data.TaggedError("AutomationError") {
16
- /** `"<stage> failed: <cause>"` - the stage is included because the cause
17
- * alone rarely says which operation produced it. */
18
- get message() {
19
- return `${this.stage} failed: ${describeCause(this.cause)}`;
20
- }
47
+ /** `"<stage> failed: <cause>"` - the stage is included because the cause
48
+ * alone rarely says which operation produced it. */
49
+ get message() {
50
+ return `${this.stage} failed: ${describeCause(this.cause)}`;
51
+ }
21
52
  };
22
- var fromOutput = (output) => Effect.tryPromise({
23
- try: () => output.promise(true),
24
- catch: (cause) => new PulumiError({ cause })
53
+ //#endregion
54
+ //#region src/output-bridge.ts
55
+ /**
56
+ * Lift a single Output into an Effect.
57
+ *
58
+ * `withUnknowns: true` matters during `pulumi preview`, where a not-yet-created
59
+ * resource's outputs have no value: it resolves to Pulumi's unknown sentinel
60
+ * instead of throwing, so a program that reads outputs still previews cleanly.
61
+ *
62
+ * @param output - The Output to resolve.
63
+ * @returns An Effect yielding the settled value, failing with
64
+ * {@link PulumiError} if the underlying promise rejects.
65
+ *
66
+ * @example
67
+ * ```ts
68
+ * const bucket = yield* eaws.s3.Bucket("assets", {});
69
+ * const id = yield* fromOutput(bucket.id); // string
70
+ * ```
71
+ *
72
+ * @remarks Reading an Output only makes sense inside a running Pulumi program.
73
+ * During `preview` the value may be the unknown sentinel rather than real data,
74
+ * so don't branch on it to decide what to create.
75
+ */
76
+ const fromOutput = (output) => Effect.tryPromise({
77
+ try: () => output.promise(true),
78
+ catch: (cause) => new PulumiError({ cause })
25
79
  });
26
- var fromOutputs = (outputs) => fromOutput(pulumi2.all(outputs));
27
- var isResourceConstructor = (value) => typeof value === "function" && (value === pulumi2.Resource || value.prototype instanceof pulumi2.Resource);
28
- var isComponentResourceConstructor = (Ctor) => Ctor === pulumi2.ComponentResource || Ctor.prototype instanceof pulumi2.ComponentResource;
29
- var isNamespace = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
30
- var isArgsObject = (value) => isNamespace(value);
31
- var isPromiseLike = (value) => typeof value === "object" && value !== null && typeof value.then === "function";
32
- var proxyCache = /* @__PURE__ */ new WeakMap();
33
- var isEffect = (value) => Effect.isEffect(value);
80
+ /**
81
+ * Lift a record of Outputs into a single Effect of the resolved record - use
82
+ * this right after constructing a resource to grab several fields at once.
83
+ *
84
+ * Resolves them together via `pulumi.all`, so it costs one await rather than
85
+ * one per field.
86
+ *
87
+ * @param outputs - A record whose values are all Outputs. Interfaces work as
88
+ * well as object literals; the constraint is self-referential rather than
89
+ * `Record<string, Output<any>>` precisely so interface-typed bags are accepted.
90
+ * @returns An Effect yielding the same record with each value unwrapped.
91
+ *
92
+ * @example
93
+ * ```ts
94
+ * const bucket = yield* eaws.s3.Bucket("assets", {});
95
+ * const { id, arn } = yield* fromOutputs({ id: bucket.id, arn: bucket.arn });
96
+ * ```
97
+ */
98
+ const fromOutputs = (outputs) => fromOutput(pulumi.all(outputs));
99
+ //#endregion
100
+ //#region src/effectify.ts
101
+ /**
102
+ * effectify - auto-wrap a Pulumi provider package (e.g. @pulumi/aws,
103
+ * @pulumi/cloudflare) so every resource constructor becomes an
104
+ * Effect-returning factory, without the caller ever writing `Effect.sync`.
105
+ *
106
+ * Usage:
107
+ *
108
+ * import * as aws from "@pulumi/aws";
109
+ * const eaws = effectify(aws);
110
+ *
111
+ * const program = Effect.gen(function* () {
112
+ * const bucket = yield* eaws.s3.Bucket("my-bucket", { forceDestroy: true });
113
+ * // ^ Effect<aws.s3.Bucket, PulumiError> - no manual wrapping
114
+ * });
115
+ *
116
+ * How it works:
117
+ * - Every Pulumi resource class extends `pulumi.Resource` under the hood
118
+ * (via CustomResource / ComponentResource). That's the runtime marker
119
+ * used to tell "this export is a resource constructor" apart from "this
120
+ * export is a namespace object" (e.g. `aws.s3`) or "this export is an
121
+ * invoke function" (e.g. `aws.s3.getBucket`).
122
+ * - Namespace objects get recursively proxied (lazily, memoized).
123
+ * - CustomResource constructors get wrapped so any field in their args
124
+ * object may *additionally* be an Effect - resolved (concurrently, they
125
+ * are independent by construction) before construction. This is safe
126
+ * because codegen guarantees CustomResource args are always
127
+ * Record<string, Input<T>>.
128
+ * - Wrapped constructors keep their static members: `Bucket.get(...)`,
129
+ * `Bucket.isInstance(...)` and friends forward to the original class, so
130
+ * the wrapped package can be the only import a program needs.
131
+ * - ComponentResource constructors get wrapped with no arg-lifting - args
132
+ * pass through exactly as declared, since component args aren't
133
+ * guaranteed to be Input<T>-shaped (hand-authored, may do synchronous
134
+ * work on a bare primitive inside the constructor).
135
+ * - Invoke functions (`aws.s3.getBucket`) return an Effect instead of a
136
+ * Promise. There is no runtime marker for "this function is async", so
137
+ * the wrapper calls the function and inspects the result: a thenable
138
+ * becomes an Effect, anything else is returned as-is. That means the
139
+ * invoke *starts* at the call site (see the caveat on `wrapInvokeLike`);
140
+ * `*Output` invoke variants return an Output, which is not thenable, so
141
+ * they pass through untouched - matching the type-level mapping, which
142
+ * only rewrites Promise-returning signatures.
143
+ * - Everything else (enums, plain values, non-resource classes) passes
144
+ * through untouched.
145
+ *
146
+ * Resource registration remains synchronous under the hood - Effect.try
147
+ * runs its thunk immediately. This only removes hand-written wrapper
148
+ * boilerplate, not Pulumi's execution model.
149
+ */
150
+ const isResourceConstructor = (value) => typeof value === "function" && (value === pulumi.Resource || value.prototype instanceof pulumi.Resource);
151
+ const isComponentResourceConstructor = (Ctor) => Ctor === pulumi.ComponentResource || Ctor.prototype instanceof pulumi.ComponentResource;
152
+ /** Is this a namespace to recurse into? The narrowed type is only ever handed
153
+ * to `effectifyInner`, which takes `object` - so claim exactly that and no
154
+ * more. */
155
+ const isNamespace = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
156
+ /** Is this an args object whose entries we can scan for Effects? Same runtime
157
+ * check as `isNamespace`, but a different question, so it gets the narrowing
158
+ * that question needs: `object` would make `Object.entries` infer `any` values
159
+ * and silently drop the `isEffect` filter's type safety. */
160
+ const isArgsObject = (value) => isNamespace(value);
161
+ /** Note: `pulumi.Output` is deliberately not thenable, so `*Output` invoke
162
+ * variants fail this check and pass through unwrapped - keeping the runtime
163
+ * behaviour aligned with the type mapping, which only rewrites
164
+ * Promise-returning signatures. */
165
+ const isPromiseLike = (value) => typeof value === "object" && value !== null && typeof value.then === "function";
166
+ const proxyCache = /* @__PURE__ */ new WeakMap();
167
+ const isEffect = (value) => Effect.isEffect(value);
34
168
  function resolveLiftedArgs(args) {
35
- if (!isArgsObject(args)) {
36
- return Effect.succeed(args);
37
- }
38
- const effectEntries = Object.entries(args).filter(([, v]) => isEffect(v));
39
- if (effectEntries.length === 0) {
40
- return Effect.succeed(args);
41
- }
42
- return Effect.map(
43
- Effect.all(
44
- effectEntries.map(
45
- ([key, effectValue]) => Effect.map(
46
- effectValue,
47
- (resolved) => [key, resolved]
48
- )
49
- ),
50
- { concurrency: "unbounded" }
51
- ),
52
- (resolved) => ({ ...args, ...Object.fromEntries(resolved) })
53
- );
169
+ if (!isArgsObject(args)) return Effect.succeed(args);
170
+ const effectEntries = Object.entries(args).filter(([, v]) => isEffect(v));
171
+ if (effectEntries.length === 0) return Effect.succeed(args);
172
+ return Effect.map(Effect.all(effectEntries.map(([key, effectValue]) => Effect.map(effectValue, (resolved) => [key, resolved])), { concurrency: "unbounded" }), (resolved) => ({
173
+ ...args,
174
+ ...Object.fromEntries(resolved)
175
+ }));
54
176
  }
55
177
  function wrapCustomResourceCtor(Ctor) {
56
- return (name, args, opts) => Effect.gen(function* () {
57
- const resolvedArgs = yield* resolveLiftedArgs(args);
58
- return yield* Effect.try({
59
- try: () => new Ctor(name, resolvedArgs, opts),
60
- catch: (cause) => new PulumiError({ cause })
61
- });
62
- });
178
+ return (name, args, opts) => Effect.gen(function* () {
179
+ const resolvedArgs = yield* resolveLiftedArgs(args);
180
+ return yield* Effect.try({
181
+ try: () => new Ctor(name, resolvedArgs, opts),
182
+ catch: (cause) => new PulumiError({ cause })
183
+ });
184
+ });
63
185
  }
64
186
  function wrapComponentResourceCtor(Ctor) {
65
- return (name, args, opts) => Effect.try({
66
- try: () => new Ctor(name, args, opts),
67
- catch: (cause) => new PulumiError({ cause })
68
- });
187
+ return (name, args, opts) => Effect.try({
188
+ try: () => new Ctor(name, args, opts),
189
+ catch: (cause) => new PulumiError({ cause })
190
+ });
69
191
  }
70
- var withStatics = (factory, Ctor) => new Proxy(factory, {
71
- get: (target, prop, receiver) => Reflect.has(target, prop) ? Reflect.get(target, prop, receiver) : Reflect.get(Ctor, prop),
72
- has: (target, prop) => Reflect.has(target, prop) || Reflect.has(Ctor, prop)
192
+ /** Forward static members (codegen'd `get`, `isInstance`, …) from the class
193
+ * onto the factory. The factory's own and inherited properties (`name`,
194
+ * `length`, `call`, …) win; anything else falls through to the class, so new
195
+ * statics keep working without being enumerated here. A bonus of forwarding
196
+ * `prototype` is that `x instanceof wrapped` still works. */
197
+ const withStatics = (factory, Ctor) => new Proxy(factory, {
198
+ get: (target, prop, receiver) => Reflect.has(target, prop) ? Reflect.get(target, prop, receiver) : Reflect.get(Ctor, prop),
199
+ has: (target, prop) => Reflect.has(target, prop) || Reflect.has(Ctor, prop)
73
200
  });
74
201
  function wrapResourceCtor(Ctor) {
75
- const factory = isComponentResourceConstructor(Ctor) ? wrapComponentResourceCtor(Ctor) : wrapCustomResourceCtor(
76
- Ctor
77
- );
78
- return withStatics(factory, Ctor);
202
+ const factory = isComponentResourceConstructor(Ctor) ? wrapComponentResourceCtor(Ctor) : wrapCustomResourceCtor(Ctor);
203
+ return withStatics(factory, Ctor);
79
204
  }
205
+ /** Turn a Promise-returning invoke into an Effect-returning one, leaving
206
+ * synchronous functions' behaviour untouched.
207
+ *
208
+ * A Proxy over the original function (rather than a new function) so `name`,
209
+ * `length`, own properties and prototype all survive; only the call itself is
210
+ * intercepted.
211
+ *
212
+ * Caveat, stated openly: whether a function is async is only knowable by
213
+ * calling it, so the invoke *starts* when the factory is called - the Effect
214
+ * resolves an already-in-flight Promise rather than deferring the call. Two
215
+ * consequences:
216
+ * - `Effect.retry` re-awaits the same call instead of re-invoking. To
217
+ * re-invoke per attempt, wrap the call site: `Effect.suspend(() =>
218
+ * eaws.getAmi(args))`.
219
+ * - A discarded Effect must not surface as an unhandled rejection, so the
220
+ * rejection is pre-observed on a side branch before the Effect awaits it.
221
+ */
80
222
  function wrapInvokeLike(fn) {
81
- return new Proxy(fn, {
82
- apply(target, thisArg, args) {
83
- const result = Reflect.apply(target, thisArg, args);
84
- if (!isPromiseLike(result)) {
85
- return result;
86
- }
87
- result.then(void 0, () => {
88
- });
89
- return Effect.tryPromise({
90
- try: () => result,
91
- catch: (cause) => new PulumiError({ cause })
92
- });
93
- }
94
- });
223
+ return new Proxy(fn, { apply(target, thisArg, args) {
224
+ const result = Reflect.apply(target, thisArg, args);
225
+ if (!isPromiseLike(result)) return result;
226
+ result.then(void 0, () => {});
227
+ return Effect.tryPromise({
228
+ try: () => result,
229
+ catch: (cause) => new PulumiError({ cause })
230
+ });
231
+ } });
95
232
  }
96
- var wrapperCache = /* @__PURE__ */ new WeakMap();
233
+ /** Wrapped constructors are cached so repeated reads of the same property
234
+ * hand back the same function - identity checks like
235
+ * `eaws.s3.Bucket === eaws.s3.Bucket` hold, and the `get` and
236
+ * `getOwnPropertyDescriptor` traps agree on a property's value. */
237
+ const wrapperCache = /* @__PURE__ */ new WeakMap();
97
238
  function effectifyValue(value) {
98
- if (isResourceConstructor(value)) {
99
- const cached = wrapperCache.get(value);
100
- if (cached) return cached;
101
- const wrapped = wrapResourceCtor(value);
102
- wrapperCache.set(value, wrapped);
103
- return wrapped;
104
- }
105
- if (typeof value === "function") {
106
- const cached = wrapperCache.get(value);
107
- if (cached) return cached;
108
- const wrapped = wrapInvokeLike(value);
109
- wrapperCache.set(value, wrapped);
110
- return wrapped;
111
- }
112
- if (isNamespace(value)) {
113
- return effectifyInner(value);
114
- }
115
- return value;
239
+ if (isResourceConstructor(value)) {
240
+ const cached = wrapperCache.get(value);
241
+ if (cached) return cached;
242
+ const wrapped = wrapResourceCtor(value);
243
+ wrapperCache.set(value, wrapped);
244
+ return wrapped;
245
+ }
246
+ if (typeof value === "function") {
247
+ const cached = wrapperCache.get(value);
248
+ if (cached) return cached;
249
+ const wrapped = wrapInvokeLike(value);
250
+ wrapperCache.set(value, wrapped);
251
+ return wrapped;
252
+ }
253
+ if (isNamespace(value)) return effectifyInner(value);
254
+ return value;
116
255
  }
117
256
  function effectifyInner(mod) {
118
- const cached = proxyCache.get(mod);
119
- if (cached) return cached;
120
- const proxy = new Proxy(/* @__PURE__ */ Object.create(null), {
121
- get(_target, prop) {
122
- return effectifyValue(Reflect.get(mod, prop));
123
- },
124
- has(_target, prop) {
125
- return Reflect.has(mod, prop);
126
- },
127
- ownKeys() {
128
- return Reflect.ownKeys(mod);
129
- },
130
- getOwnPropertyDescriptor(_target, prop) {
131
- const descriptor = Reflect.getOwnPropertyDescriptor(mod, prop);
132
- if (!descriptor) return void 0;
133
- return {
134
- value: effectifyValue(Reflect.get(mod, prop)),
135
- enumerable: descriptor.enumerable,
136
- configurable: true,
137
- writable: true
138
- };
139
- },
140
- set() {
141
- return false;
142
- }
143
- });
144
- proxyCache.set(mod, proxy);
145
- return proxy;
257
+ const cached = proxyCache.get(mod);
258
+ if (cached) return cached;
259
+ const proxy = new Proxy(Object.create(null), {
260
+ get(_target, prop) {
261
+ return effectifyValue(Reflect.get(mod, prop));
262
+ },
263
+ has(_target, prop) {
264
+ return Reflect.has(mod, prop);
265
+ },
266
+ ownKeys() {
267
+ return Reflect.ownKeys(mod);
268
+ },
269
+ getOwnPropertyDescriptor(_target, prop) {
270
+ const descriptor = Reflect.getOwnPropertyDescriptor(mod, prop);
271
+ if (!descriptor) return void 0;
272
+ return {
273
+ value: effectifyValue(Reflect.get(mod, prop)),
274
+ enumerable: descriptor.enumerable,
275
+ configurable: true,
276
+ writable: true
277
+ };
278
+ },
279
+ set() {
280
+ return false;
281
+ }
282
+ });
283
+ proxyCache.set(mod, proxy);
284
+ return proxy;
146
285
  }
286
+ /**
287
+ * Wrap a provider package (or any namespace) once, turning every resource
288
+ * constructor into an `Effect`-returning factory and every `Promise`-returning
289
+ * invoke into an `Effect`-returning function.
290
+ *
291
+ * Call this once per package at module scope and export the result - the
292
+ * wrapper is cached, so repeated calls and repeated property reads hand back
293
+ * the same objects, but there is no reason to re-wrap.
294
+ *
295
+ * @param mod - The provider package, or any namespace within one. Not mutated;
296
+ * the result is a read-only proxy that forwards to it.
297
+ * @returns A same-shaped view of `mod`, typed by {@link Effectify}.
298
+ *
299
+ * @example Constructing a resource
300
+ * ```ts
301
+ * import * as aws from "@pulumi/aws";
302
+ * const eaws = effectify(aws);
303
+ *
304
+ * const program = Effect.gen(function* () {
305
+ * const bucket = yield* eaws.s3.Bucket("assets", { forceDestroy: true });
306
+ * return bucket.id;
307
+ * });
308
+ * ```
309
+ *
310
+ * @example Passing an Effect as an argument
311
+ * A `CustomResource`'s args may hold Effects, which are resolved concurrently
312
+ * before the resource is constructed:
313
+ * ```ts
314
+ * yield* eaws.s3.BucketObject("readme", {
315
+ * bucket: bucketIdEffect,
316
+ * content: "hello",
317
+ * });
318
+ * ```
319
+ *
320
+ * @throws Nothing. Failures surface in the returned Effect's error channel as
321
+ * {@link PulumiError} - including synchronous throws from the constructor.
322
+ *
323
+ * @see {@link Effectify} for the type-level mapping.
324
+ */
147
325
  function effectify(mod) {
148
- return effectifyInner(mod);
326
+ return effectifyInner(mod);
149
327
  }
150
- var isLocal = (opts) => "workDir" in opts;
151
- var createOrSelectStack = (opts) => Effect.tryPromise({
152
- try: () => isLocal(opts) ? LocalWorkspace.createOrSelectStack(
153
- { stackName: opts.stackName, workDir: opts.workDir },
154
- opts.workspaceOptions
155
- ) : LocalWorkspace.createOrSelectStack(
156
- {
157
- stackName: opts.stackName,
158
- projectName: opts.projectName,
159
- program: opts.program
160
- },
161
- opts.workspaceOptions
162
- ),
163
- catch: (cause) => new AutomationError({ stage: "createOrSelectStack", cause })
328
+ //#endregion
329
+ //#region src/automation.ts
330
+ const isLocal = (opts) => "workDir" in opts;
331
+ /** Select the stack, creating it if it does not exist, and return the handle
332
+ * every other operation here takes.
333
+ *
334
+ * Creating the workspace is itself work - it may write files and shell out to
335
+ * the CLI - so hold on to the returned `Stack` rather than re-selecting before
336
+ * each operation. */
337
+ const createOrSelectStack = (opts) => Effect.tryPromise({
338
+ try: () => isLocal(opts) ? LocalWorkspace.createOrSelectStack({
339
+ stackName: opts.stackName,
340
+ workDir: opts.workDir
341
+ }, opts.workspaceOptions) : LocalWorkspace.createOrSelectStack({
342
+ stackName: opts.stackName,
343
+ projectName: opts.projectName,
344
+ program: opts.program
345
+ }, opts.workspaceOptions),
346
+ catch: (cause) => new AutomationError({
347
+ stage: "createOrSelectStack",
348
+ cause
349
+ })
164
350
  });
165
- var setStackConfig = (stack, config) => !config || Object.keys(config).length === 0 ? Effect.void : Effect.tryPromise({
166
- try: () => stack.setAllConfig(config),
167
- catch: (cause) => new AutomationError({ stage: "setConfig", cause })
351
+ /** Apply the whole config map in one `setAllConfig` call - a single CLI
352
+ * round-trip, where per-key `setConfig` costs one `pulumi config set`
353
+ * invocation each. */
354
+ const setStackConfig = (stack, config) => !config || Object.keys(config).length === 0 ? Effect.void : Effect.tryPromise({
355
+ try: () => stack.setAllConfig(config),
356
+ catch: (cause) => new AutomationError({
357
+ stage: "setConfig",
358
+ cause
359
+ })
168
360
  });
169
- var previewStack = (stack, opts) => Effect.tryPromise({
170
- try: () => stack.preview(opts),
171
- catch: (cause) => new AutomationError({ stage: "preview", cause })
361
+ /** Compute the plan without applying it.
362
+ *
363
+ * A preview is a full engine run against the provider, not a cheap check - see
364
+ * {@link DeployOptions.preview} before pairing one with an `up`. */
365
+ const previewStack = (stack, opts) => Effect.tryPromise({
366
+ try: () => stack.preview(opts),
367
+ catch: (cause) => new AutomationError({
368
+ stage: "preview",
369
+ cause
370
+ })
172
371
  });
173
- var upStack = (stack, opts) => Effect.tryPromise({
174
- try: () => stack.up(opts),
175
- catch: (cause) => new AutomationError({ stage: "up", cause })
372
+ /** Apply the program: create, update and delete resources to match it.
373
+ *
374
+ * The result carries the stack's outputs and a summary; pass `onOutput` to
375
+ * watch progress while it runs. */
376
+ const upStack = (stack, opts) => Effect.tryPromise({
377
+ try: () => stack.up(opts),
378
+ catch: (cause) => new AutomationError({
379
+ stage: "up",
380
+ cause
381
+ })
176
382
  });
177
- var refreshStack = (stack, opts) => Effect.tryPromise({
178
- try: () => stack.refresh(opts),
179
- catch: (cause) => new AutomationError({ stage: "refresh", cause })
383
+ /** Refresh the stack's state from the actual cloud resources, without
384
+ * changing them - what to run when state may have drifted (manual console
385
+ * edits, a crashed update) before deciding what to do about it. */
386
+ const refreshStack = (stack, opts) => Effect.tryPromise({
387
+ try: () => stack.refresh(opts),
388
+ catch: (cause) => new AutomationError({
389
+ stage: "refresh",
390
+ cause
391
+ })
180
392
  });
181
- var stackOutputs = (stack) => Effect.tryPromise({
182
- try: () => stack.outputs(),
183
- catch: (cause) => new AutomationError({ stage: "outputs", cause })
393
+ /** Read the stack's current outputs without running an update. */
394
+ const stackOutputs = (stack) => Effect.tryPromise({
395
+ try: () => stack.outputs(),
396
+ catch: (cause) => new AutomationError({
397
+ stage: "outputs",
398
+ cause
399
+ })
184
400
  });
185
- var destroyStack = (stack, opts) => Effect.tryPromise({
186
- try: () => stack.destroy(opts),
187
- catch: (cause) => new AutomationError({ stage: "destroy", cause })
401
+ /** Destroy the stack's resources. The stack itself remains registered with
402
+ * the backend - see `removeStack` / `teardownStack` to delete it too. */
403
+ const destroyStack = (stack, opts) => Effect.tryPromise({
404
+ try: () => stack.destroy(opts),
405
+ catch: (cause) => new AutomationError({
406
+ stage: "destroy",
407
+ cause
408
+ })
188
409
  });
189
- var removeStack = (stack, opts) => Effect.tryPromise({
190
- try: () => stack.workspace.removeStack(stack.name, opts),
191
- catch: (cause) => new AutomationError({ stage: "removeStack", cause })
410
+ /** Delete the stack and its configuration and history from the backend.
411
+ *
412
+ * This does not destroy resources - run `destroyStack` first, or use
413
+ * `teardownStack`. Pulumi refuses to remove a stack that still has resources
414
+ * unless `RemoveOptions.force` is set, and forcing it orphans them: they keep
415
+ * existing and billing with nothing tracking them. */
416
+ const removeStack = (stack, opts) => Effect.tryPromise({
417
+ try: () => stack.workspace.removeStack(stack.name, opts),
418
+ catch: (cause) => new AutomationError({
419
+ stage: "removeStack",
420
+ cause
421
+ })
192
422
  });
193
- var teardownStack = (stack, opts) => Effect.gen(function* () {
194
- const result = yield* destroyStack(stack, opts?.destroy);
195
- yield* removeStack(stack, opts?.remove);
196
- return result;
423
+ /** Full teardown: destroy the resources, then delete the stack.
424
+ *
425
+ * `destroyStack` alone leaves an empty stack behind, so anything creating
426
+ * stacks per-run (ephemeral environments, tests naming stacks by timestamp)
427
+ * accumulates them in the backend. */
428
+ const teardownStack = (stack, opts) => Effect.gen(function* () {
429
+ const result = yield* destroyStack(stack, opts?.destroy);
430
+ yield* removeStack(stack, opts?.remove);
431
+ return result;
197
432
  });
198
- var deploy = (opts) => Effect.gen(function* () {
199
- const stack = yield* createOrSelectStack(opts);
200
- yield* setStackConfig(stack, opts.config);
201
- const preview = opts.preview ? yield* previewStack(
202
- stack,
203
- typeof opts.preview === "boolean" ? void 0 : opts.preview
204
- ) : void 0;
205
- const result = yield* upStack(stack, opts.up);
206
- return { stack, result, preview };
433
+ /**
434
+ * Select or create the stack, apply config, optionally preview, then up.
435
+ *
436
+ * The common path, assembled from the primitives above. Anything more
437
+ * involved - refreshing first, inspecting the plan before deciding, retrying a
438
+ * stage - should compose those directly rather than grow options here.
439
+ *
440
+ * @param opts - Which stack, and what to do with it.
441
+ * @returns The stack handle alongside the results, so callers can tear down
442
+ * afterwards without re-selecting.
443
+ *
444
+ * @example Deploy, use the outputs, then always tear down
445
+ * ```ts
446
+ * Effect.scoped(
447
+ * Effect.gen(function* () {
448
+ * const { result } = yield* Effect.acquireRelease(
449
+ * deploy({ stackName, projectName, program, up: { onOutput } }),
450
+ * ({ stack }) => teardownStack(stack).pipe(Effect.ignore)
451
+ * );
452
+ * return result.outputs.bucketId?.value;
453
+ * })
454
+ * );
455
+ * ```
456
+ */
457
+ const deploy = (opts) => Effect.gen(function* () {
458
+ const stack = yield* createOrSelectStack(opts);
459
+ yield* setStackConfig(stack, opts.config);
460
+ const preview = opts.preview ? yield* previewStack(stack, typeof opts.preview === "boolean" ? void 0 : opts.preview) : void 0;
461
+ return {
462
+ stack,
463
+ result: yield* upStack(stack, opts.up),
464
+ preview
465
+ };
207
466
  });
208
-
467
+ //#endregion
209
468
  export { AutomationError, PulumiError, createOrSelectStack, deploy, destroyStack, effectify, fromOutput, fromOutputs, previewStack, refreshStack, removeStack, setStackConfig, stackOutputs, teardownStack, upStack };
210
- //# sourceMappingURL=index.js.map
469
+
211
470
  //# sourceMappingURL=index.js.map