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