effect-pulumi 0.1.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.
@@ -0,0 +1,366 @@
1
+ import * as effect_Cause from 'effect/Cause';
2
+ import * as effect_Types from 'effect/Types';
3
+ import * as pulumi from '@pulumi/pulumi';
4
+ import { Effect } from 'effect';
5
+ import { PulumiFn, LocalWorkspaceOptions, ConfigMap, UpOptions, PreviewOptions, Stack, UpResult, PreviewResult, DestroyOptions, DestroyResult, RefreshOptions, RefreshResult, RemoveOptions, OutputMap } from '@pulumi/pulumi/automation/index.js';
6
+
7
+ declare const PulumiError_base: new <A extends Record<string, any> = {}>(args: effect_Types.VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => effect_Cause.YieldableError & {
8
+ readonly _tag: "PulumiError";
9
+ } & Readonly<A>;
10
+ /**
11
+ * Any synchronous failure constructing a resource (bad args, provider
12
+ * validation, etc.), or a failure resolving an Output's promise.
13
+ *
14
+ * Tagged `"PulumiError"`, so it can be matched by tag rather than by
15
+ * `instanceof`:
16
+ *
17
+ * @example
18
+ * ```ts
19
+ * program.pipe(
20
+ * Effect.catchTag("PulumiError", (e) => Effect.logError(e.message))
21
+ * );
22
+ * ```
23
+ */
24
+ declare class PulumiError extends PulumiError_base<{
25
+ /** The original thrown value or rejection reason, unwrapped and unmodified.
26
+ * Not necessarily an `Error`. */
27
+ readonly cause: unknown;
28
+ }> {
29
+ /** Derived so anything reading `.message` — plain logging, test failure
30
+ * output, non-Effect error handling — sees the underlying failure instead
31
+ * of an empty string. */
32
+ get message(): string;
33
+ }
34
+ declare const AutomationError_base: new <A extends Record<string, any> = {}>(args: effect_Types.VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => effect_Cause.YieldableError & {
35
+ readonly _tag: "AutomationError";
36
+ } & Readonly<A>;
37
+ /**
38
+ * Failure from an Automation API lifecycle call (up/preview/destroy/select).
39
+ *
40
+ * `stage` says which call failed, which matters most in the composite
41
+ * operations: a failed `deploy` could have died selecting the stack, applying
42
+ * config, or running the update, and the three want different responses.
43
+ *
44
+ * @example
45
+ * ```ts
46
+ * deploy(opts).pipe(
47
+ * Effect.catchTag("AutomationError", (e) =>
48
+ * e.stage === "createOrSelectStack"
49
+ * ? Effect.fail(new BackendUnreachable())
50
+ * : Effect.logError(e.message)
51
+ * )
52
+ * );
53
+ * ```
54
+ */
55
+ declare class AutomationError extends AutomationError_base<{
56
+ /** Which Automation API call failed. Named for the wrapper that raised it,
57
+ * so `deploy`'s failures still report the underlying stage. */
58
+ readonly stage: "createOrSelectStack" | "setConfig" | "up" | "preview" | "refresh" | "outputs" | "destroy" | "removeStack";
59
+ /** The rejection reason from the Automation API. For a failed update this is
60
+ * usually a `CommandError` carrying the CLI's stdout and stderr. */
61
+ readonly cause: unknown;
62
+ }> {
63
+ /** `"<stage> failed: <cause>"` — the stage is included because the cause
64
+ * alone rarely says which operation produced it. */
65
+ get message(): string;
66
+ }
67
+
68
+ /**
69
+ * Lift a single Output into an Effect.
70
+ *
71
+ * `withUnknowns: true` matters during `pulumi preview`, where a not-yet-created
72
+ * resource's outputs have no value: it resolves to Pulumi's unknown sentinel
73
+ * instead of throwing, so a program that reads outputs still previews cleanly.
74
+ *
75
+ * @param output - The Output to resolve.
76
+ * @returns An Effect yielding the settled value, failing with
77
+ * {@link PulumiError} if the underlying promise rejects.
78
+ *
79
+ * @example
80
+ * ```ts
81
+ * const bucket = yield* eaws.s3.Bucket("assets", {});
82
+ * const id = yield* fromOutput(bucket.id); // string
83
+ * ```
84
+ *
85
+ * @remarks Reading an Output only makes sense inside a running Pulumi program.
86
+ * During `preview` the value may be the unknown sentinel rather than real data,
87
+ * so don't branch on it to decide what to create.
88
+ */
89
+ declare const fromOutput: <T>(output: pulumi.Output<T>) => Effect.Effect<T, PulumiError>;
90
+ /**
91
+ * Lift a record of Outputs into a single Effect of the resolved record — use
92
+ * this right after constructing a resource to grab several fields at once.
93
+ *
94
+ * Resolves them together via `pulumi.all`, so it costs one await rather than
95
+ * one per field.
96
+ *
97
+ * @param outputs - A record whose values are all Outputs. Interfaces work as
98
+ * well as object literals; the constraint is self-referential rather than
99
+ * `Record<string, Output<any>>` precisely so interface-typed bags are accepted.
100
+ * @returns An Effect yielding the same record with each value unwrapped.
101
+ *
102
+ * @example
103
+ * ```ts
104
+ * const bucket = yield* eaws.s3.Bucket("assets", {});
105
+ * const { id, arn } = yield* fromOutputs({ id: bucket.id, arn: bucket.arn });
106
+ * ```
107
+ */
108
+ declare const fromOutputs: <T extends { [K in keyof T]: pulumi.Output<any>; }>(outputs: T) => Effect.Effect<{ [K in keyof T]: pulumi.Unwrap<T[K]>; }, PulumiError>;
109
+
110
+ /**
111
+ * effectify — auto-wrap a Pulumi provider package (e.g. @pulumi/aws,
112
+ * @pulumi/cloudflare) so every resource constructor becomes an
113
+ * Effect-returning factory, without the caller ever writing `Effect.sync`.
114
+ *
115
+ * Usage:
116
+ *
117
+ * import * as aws from "@pulumi/aws";
118
+ * const eaws = effectify(aws);
119
+ *
120
+ * const program = Effect.gen(function* () {
121
+ * const bucket = yield* eaws.s3.Bucket("my-bucket", { forceDestroy: true });
122
+ * // ^ Effect<aws.s3.Bucket, PulumiError> — no manual wrapping
123
+ * });
124
+ *
125
+ * How it works:
126
+ * - Every Pulumi resource class extends `pulumi.Resource` under the hood
127
+ * (via CustomResource / ComponentResource). That's the runtime marker
128
+ * used to tell "this export is a resource constructor" apart from "this
129
+ * export is a namespace object" (e.g. `aws.s3`) or "this export is an
130
+ * invoke function" (e.g. `aws.s3.getBucket`).
131
+ * - Namespace objects get recursively proxied (lazily, memoized).
132
+ * - CustomResource constructors get wrapped so any field in their args
133
+ * object may *additionally* be an Effect — resolved (concurrently, they
134
+ * are independent by construction) before construction. This is safe
135
+ * because codegen guarantees CustomResource args are always
136
+ * Record<string, Input<T>>.
137
+ * - Wrapped constructors keep their static members: `Bucket.get(...)`,
138
+ * `Bucket.isInstance(...)` and friends forward to the original class, so
139
+ * the wrapped package can be the only import a program needs.
140
+ * - ComponentResource constructors get wrapped with no arg-lifting — args
141
+ * pass through exactly as declared, since component args aren't
142
+ * guaranteed to be Input<T>-shaped (hand-authored, may do synchronous
143
+ * work on a bare primitive inside the constructor).
144
+ * - Invoke functions (`aws.s3.getBucket`) return an Effect instead of a
145
+ * Promise. There is no runtime marker for "this function is async", so
146
+ * the wrapper calls the function and inspects the result: a thenable
147
+ * becomes an Effect, anything else is returned as-is. That means the
148
+ * invoke *starts* at the call site (see the caveat on `wrapInvokeLike`);
149
+ * `*Output` invoke variants return an Output, which is not thenable, so
150
+ * they pass through untouched — matching the type-level mapping, which
151
+ * only rewrites Promise-returning signatures.
152
+ * - Everything else (enums, plain values, non-resource classes) passes
153
+ * through untouched.
154
+ *
155
+ * Resource registration remains synchronous under the hood — Effect.try
156
+ * runs its thunk immediately. This only removes hand-written wrapper
157
+ * boilerplate, not Pulumi's execution model.
158
+ */
159
+
160
+ /** Allow any field of a CustomResource args object to *additionally* be an
161
+ * Effect. Homomorphic, so optional fields stay optional and plain
162
+ * `Input<T>`/`Output<T>` values keep working untouched. */
163
+ type LiftedArgs<A> = A extends object ? {
164
+ [K in keyof A]: A[K] | Effect.Effect<A[K], PulumiError>;
165
+ } : A;
166
+ /** Apply `LiftedArgs` to the second constructor parameter (the args object),
167
+ * leaving `name` and `opts` alone. Mapping over the parameter tuple
168
+ * homomorphically preserves labels and optionality, so constructors whose
169
+ * args are optional stay callable as `Bucket("name")`. */
170
+ type LiftArgsParam<P extends readonly unknown[]> = {
171
+ [K in keyof P]: K extends "1" ? LiftedArgs<P[K]> : P[K];
172
+ };
173
+ /** The class's static side, minus `prototype`: `keyof` on a constructor type
174
+ * yields exactly the statics (own and inherited, e.g. codegen'd `get` and
175
+ * `isInstance`), which the runtime wrapper forwards to the original class. */
176
+ type StaticMembers<T> = Omit<T, "prototype">;
177
+ /**
178
+ * The type of {@link effectify}'s result: `T` with every resource constructor
179
+ * and every `Promise`-returning invoke rewritten, and everything else left
180
+ * alone.
181
+ *
182
+ * The cases, in the order they are tried:
183
+ *
184
+ * | Input | Becomes |
185
+ * | --------------------------- | ---------------------------------------------- |
186
+ * | `ComponentResource` class | factory returning `Effect`, args **not** lifted |
187
+ * | `CustomResource` class | factory returning `Effect`, args lifted |
188
+ * | other class | unchanged |
189
+ * | `(...) => Promise<R>` | `(...) => Effect<R, PulumiError>` |
190
+ * | other function | unchanged (includes `*Output` invokes) |
191
+ * | object | mapped recursively |
192
+ * | anything else | unchanged |
193
+ *
194
+ * Statics survive on the wrapped constructors, so `Bucket.get` and
195
+ * `Bucket.isInstance` remain callable.
196
+ */
197
+ type Effectify<T> = T extends abstract new (...params: infer P) => infer R ? R extends pulumi.ComponentResource ? ((...params: P) => Effect.Effect<R, PulumiError>) & StaticMembers<T> : R extends pulumi.CustomResource ? ((...params: LiftArgsParam<P>) => Effect.Effect<R, PulumiError>) & StaticMembers<T> : T : T extends (...args: infer A) => Promise<infer R> ? (...args: A) => Effect.Effect<R, PulumiError> : T extends (...args: any[]) => any ? T : T extends object ? {
198
+ [K in keyof T]: Effectify<T[K]>;
199
+ } : T;
200
+ /**
201
+ * Wrap a provider package (or any namespace) once, turning every resource
202
+ * constructor into an `Effect`-returning factory and every `Promise`-returning
203
+ * invoke into an `Effect`-returning function.
204
+ *
205
+ * Call this once per package at module scope and export the result — the
206
+ * wrapper is cached, so repeated calls and repeated property reads hand back
207
+ * the same objects, but there is no reason to re-wrap.
208
+ *
209
+ * @param mod - The provider package, or any namespace within one. Not mutated;
210
+ * the result is a read-only proxy that forwards to it.
211
+ * @returns A same-shaped view of `mod`, typed by {@link Effectify}.
212
+ *
213
+ * @example Constructing a resource
214
+ * ```ts
215
+ * import * as aws from "@pulumi/aws";
216
+ * const eaws = effectify(aws);
217
+ *
218
+ * const program = Effect.gen(function* () {
219
+ * const bucket = yield* eaws.s3.Bucket("assets", { forceDestroy: true });
220
+ * return bucket.id;
221
+ * });
222
+ * ```
223
+ *
224
+ * @example Passing an Effect as an argument
225
+ * A `CustomResource`'s args may hold Effects, which are resolved concurrently
226
+ * before the resource is constructed:
227
+ * ```ts
228
+ * yield* eaws.s3.BucketObject("readme", {
229
+ * bucket: bucketIdEffect,
230
+ * content: "hello",
231
+ * });
232
+ * ```
233
+ *
234
+ * @throws Nothing. Failures surface in the returned Effect's error channel as
235
+ * {@link PulumiError} — including synchronous throws from the constructor.
236
+ *
237
+ * @see {@link Effectify} for the type-level mapping.
238
+ */
239
+ declare function effectify<T extends object>(mod: T): Effectify<T>;
240
+
241
+ /** Arguments for an inline program — the Pulumi program is a function in this
242
+ * process, with no `Pulumi.yaml` on disk. */
243
+ interface InlineStackOptions {
244
+ /** Stack to select, created if absent. */
245
+ readonly stackName: string;
246
+ /** Project name to register the stack under. Chosen freely here, since
247
+ * there is no `Pulumi.yaml` to take it from — but it is part of the stack's
248
+ * identity in the backend, so changing it later points at a different
249
+ * stack. */
250
+ readonly projectName: string;
251
+ /** The program itself. Runs in this process, so it needs no separate Node
252
+ * runtime and can close over values from the caller. */
253
+ readonly program: PulumiFn;
254
+ readonly workspaceOptions?: LocalWorkspaceOptions;
255
+ }
256
+ /** Arguments for a local program — an existing Pulumi project on disk. */
257
+ interface LocalStackOptions {
258
+ /** Stack to select, created if absent. */
259
+ readonly stackName: string;
260
+ /** Directory holding the project's `Pulumi.yaml`. Its `name:` supplies the
261
+ * project name, which is why there is no `projectName` here. */
262
+ readonly workDir: string;
263
+ readonly workspaceOptions?: LocalWorkspaceOptions;
264
+ }
265
+ /** Either flavour of stack. Discriminated at runtime by the presence of
266
+ * `workDir`, so the two are not interchangeable: an inline program needs
267
+ * `projectName`, a local one takes it from `Pulumi.yaml`. */
268
+ type StackOptions = InlineStackOptions | LocalStackOptions;
269
+ /** Select the stack, creating it if it does not exist, and return the handle
270
+ * every other operation here takes.
271
+ *
272
+ * Creating the workspace is itself work — it may write files and shell out to
273
+ * the CLI — so hold on to the returned `Stack` rather than re-selecting before
274
+ * each operation. */
275
+ declare const createOrSelectStack: (opts: StackOptions) => Effect.Effect<Stack, AutomationError>;
276
+ /** Apply the whole config map in one `setAllConfig` call — a single CLI
277
+ * round-trip, where per-key `setConfig` costs one `pulumi config set`
278
+ * invocation each. */
279
+ declare const setStackConfig: (stack: Stack, config: ConfigMap | undefined) => Effect.Effect<void, AutomationError>;
280
+ /** Compute the plan without applying it.
281
+ *
282
+ * A preview is a full engine run against the provider, not a cheap check — see
283
+ * {@link DeployOptions.preview} before pairing one with an `up`. */
284
+ declare const previewStack: (stack: Stack, opts?: PreviewOptions) => Effect.Effect<PreviewResult, AutomationError>;
285
+ /** Apply the program: create, update and delete resources to match it.
286
+ *
287
+ * The result carries the stack's outputs and a summary; pass `onOutput` to
288
+ * watch progress while it runs. */
289
+ declare const upStack: (stack: Stack, opts?: UpOptions) => Effect.Effect<UpResult, AutomationError>;
290
+ /** Refresh the stack's state from the actual cloud resources, without
291
+ * changing them — what to run when state may have drifted (manual console
292
+ * edits, a crashed update) before deciding what to do about it. */
293
+ declare const refreshStack: (stack: Stack, opts?: RefreshOptions) => Effect.Effect<RefreshResult, AutomationError>;
294
+ /** Read the stack's current outputs without running an update. */
295
+ declare const stackOutputs: (stack: Stack) => Effect.Effect<OutputMap, AutomationError>;
296
+ /** Destroy the stack's resources. The stack itself remains registered with
297
+ * the backend — see `removeStack` / `teardownStack` to delete it too. */
298
+ declare const destroyStack: (stack: Stack, opts?: DestroyOptions) => Effect.Effect<DestroyResult, AutomationError>;
299
+ /** Delete the stack and its configuration and history from the backend.
300
+ *
301
+ * This does not destroy resources — run `destroyStack` first, or use
302
+ * `teardownStack`. Pulumi refuses to remove a stack that still has resources
303
+ * unless `RemoveOptions.force` is set, and forcing it orphans them: they keep
304
+ * existing and billing with nothing tracking them. */
305
+ declare const removeStack: (stack: Stack, opts?: RemoveOptions) => Effect.Effect<void, AutomationError>;
306
+ /** Full teardown: destroy the resources, then delete the stack.
307
+ *
308
+ * `destroyStack` alone leaves an empty stack behind, so anything creating
309
+ * stacks per-run (ephemeral environments, tests naming stacks by timestamp)
310
+ * accumulates them in the backend. */
311
+ declare const teardownStack: (stack: Stack, opts?: {
312
+ readonly destroy?: DestroyOptions;
313
+ readonly remove?: RemoveOptions;
314
+ }) => Effect.Effect<DestroyResult, AutomationError>;
315
+ /** {@link deploy}'s arguments: the stack to target, plus what to do with it. */
316
+ type DeployOptions = StackOptions & {
317
+ /** Config to apply before the update, in one `setAllConfig` call. Keys are
318
+ * fully qualified (`"my-project:myKey"`). */
319
+ readonly config?: ConfigMap;
320
+ /** Options forwarded to the update — `onOutput` to stream progress,
321
+ * `parallel`, `target`, and so on. */
322
+ readonly up?: UpOptions;
323
+ /** Run `preview` before `up`, returning its result.
324
+ *
325
+ * Off by default: a preview is a full engine run against the provider, so
326
+ * previewing and then immediately upping does the work twice. `up` reports
327
+ * the same failures, so this earns its cost only when you want the plan
328
+ * itself. */
329
+ readonly preview?: PreviewOptions | boolean;
330
+ };
331
+ /** What {@link deploy} hands back. */
332
+ interface DeployResult {
333
+ /** The selected stack, so teardown needs no second `createOrSelectStack`. */
334
+ readonly stack: Stack;
335
+ /** The update's result — `outputs` and `summary` live here. */
336
+ readonly result: UpResult;
337
+ /** Present only when `preview` was requested. */
338
+ readonly preview?: PreviewResult;
339
+ }
340
+ /**
341
+ * Select or create the stack, apply config, optionally preview, then up.
342
+ *
343
+ * The common path, assembled from the primitives above. Anything more
344
+ * involved — refreshing first, inspecting the plan before deciding, retrying a
345
+ * stage — should compose those directly rather than grow options here.
346
+ *
347
+ * @param opts - Which stack, and what to do with it.
348
+ * @returns The stack handle alongside the results, so callers can tear down
349
+ * afterwards without re-selecting.
350
+ *
351
+ * @example Deploy, use the outputs, then always tear down
352
+ * ```ts
353
+ * Effect.scoped(
354
+ * Effect.gen(function* () {
355
+ * const { result } = yield* Effect.acquireRelease(
356
+ * deploy({ stackName, projectName, program, up: { onOutput } }),
357
+ * ({ stack }) => teardownStack(stack).pipe(Effect.ignore)
358
+ * );
359
+ * return result.outputs.bucketId?.value;
360
+ * })
361
+ * );
362
+ * ```
363
+ */
364
+ declare const deploy: (opts: DeployOptions) => Effect.Effect<DeployResult, AutomationError>;
365
+
366
+ export { AutomationError, type DeployOptions, type DeployResult, type Effectify, type InlineStackOptions, type LocalStackOptions, PulumiError, type StackOptions, createOrSelectStack, deploy, destroyStack, effectify, fromOutput, fromOutputs, previewStack, refreshStack, removeStack, setStackConfig, stackOutputs, teardownStack, upStack };