alchemy-effect 0.0.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 (101) hide show
  1. package/lib/app.d.ts +23 -0
  2. package/lib/app.d.ts.map +1 -0
  3. package/lib/app.js +26 -0
  4. package/lib/app.js.map +1 -0
  5. package/lib/apply.d.ts +22 -0
  6. package/lib/apply.d.ts.map +1 -0
  7. package/lib/apply.js +191 -0
  8. package/lib/apply.js.map +1 -0
  9. package/lib/approve.d.ts +15 -0
  10. package/lib/approve.d.ts.map +1 -0
  11. package/lib/approve.js +7 -0
  12. package/lib/approve.js.map +1 -0
  13. package/lib/binding.d.ts +58 -0
  14. package/lib/binding.d.ts.map +1 -0
  15. package/lib/binding.js +27 -0
  16. package/lib/binding.js.map +1 -0
  17. package/lib/capability.d.ts +11 -0
  18. package/lib/capability.d.ts.map +1 -0
  19. package/lib/capability.js +1 -0
  20. package/lib/capability.js.map +1 -0
  21. package/lib/destroy.d.ts +4 -0
  22. package/lib/destroy.d.ts.map +1 -0
  23. package/lib/destroy.js +1 -0
  24. package/lib/destroy.js.map +1 -0
  25. package/lib/dot-alchemy.d.ts +9 -0
  26. package/lib/dot-alchemy.d.ts.map +1 -0
  27. package/lib/dot-alchemy.js +14 -0
  28. package/lib/dot-alchemy.js.map +1 -0
  29. package/lib/env.d.ts +4 -0
  30. package/lib/env.d.ts.map +1 -0
  31. package/lib/env.js +33 -0
  32. package/lib/env.js.map +1 -0
  33. package/lib/event.d.ts +16 -0
  34. package/lib/event.d.ts.map +1 -0
  35. package/lib/event.js +1 -0
  36. package/lib/event.js.map +1 -0
  37. package/lib/index.d.ts +18 -0
  38. package/lib/index.d.ts.map +1 -0
  39. package/lib/index.js +18 -0
  40. package/lib/index.js.map +1 -0
  41. package/lib/phase.d.ts +2 -0
  42. package/lib/phase.d.ts.map +1 -0
  43. package/lib/phase.js +1 -0
  44. package/lib/phase.js.map +1 -0
  45. package/lib/physical-name.d.ts +4 -0
  46. package/lib/physical-name.d.ts.map +1 -0
  47. package/lib/physical-name.js +7 -0
  48. package/lib/physical-name.js.map +1 -0
  49. package/lib/plan.d.ts +98 -0
  50. package/lib/plan.d.ts.map +1 -0
  51. package/lib/plan.js +228 -0
  52. package/lib/plan.js.map +1 -0
  53. package/lib/policy.d.ts +37 -0
  54. package/lib/policy.d.ts.map +1 -0
  55. package/lib/policy.js +16 -0
  56. package/lib/policy.js.map +1 -0
  57. package/lib/provider.d.ts +57 -0
  58. package/lib/provider.d.ts.map +1 -0
  59. package/lib/provider.js +2 -0
  60. package/lib/provider.js.map +1 -0
  61. package/lib/reporter.d.ts +8 -0
  62. package/lib/reporter.d.ts.map +1 -0
  63. package/lib/reporter.js +4 -0
  64. package/lib/reporter.js.map +1 -0
  65. package/lib/resource.d.ts +38 -0
  66. package/lib/resource.d.ts.map +1 -0
  67. package/lib/resource.js +25 -0
  68. package/lib/resource.js.map +1 -0
  69. package/lib/runtime.d.ts +39 -0
  70. package/lib/runtime.d.ts.map +1 -0
  71. package/lib/runtime.js +54 -0
  72. package/lib/runtime.js.map +1 -0
  73. package/lib/service.d.ts +33 -0
  74. package/lib/service.d.ts.map +1 -0
  75. package/lib/service.js +4 -0
  76. package/lib/service.js.map +1 -0
  77. package/lib/state.d.ts +38 -0
  78. package/lib/state.d.ts.map +1 -0
  79. package/lib/state.js +66 -0
  80. package/lib/state.js.map +1 -0
  81. package/package.json +49 -0
  82. package/src/app.ts +43 -0
  83. package/src/apply.ts +318 -0
  84. package/src/approve.ts +13 -0
  85. package/src/binding.ts +144 -0
  86. package/src/capability.ts +19 -0
  87. package/src/destroy.ts +4 -0
  88. package/src/dot-alchemy.ts +17 -0
  89. package/src/env.ts +51 -0
  90. package/src/event.ts +27 -0
  91. package/src/index.ts +17 -0
  92. package/src/phase.ts +1 -0
  93. package/src/physical-name.ts +7 -0
  94. package/src/plan.ts +470 -0
  95. package/src/policy.ts +77 -0
  96. package/src/provider.ts +74 -0
  97. package/src/reporter.ts +8 -0
  98. package/src/resource.ts +68 -0
  99. package/src/runtime.ts +145 -0
  100. package/src/service.ts +55 -0
  101. package/src/state.ts +196 -0
package/src/apply.ts ADDED
@@ -0,0 +1,318 @@
1
+ import * as Context from "effect/Context";
2
+ import * as Effect from "effect/Effect";
3
+ import * as Option from "effect/Option";
4
+ import type { Simplify } from "effect/Types";
5
+ import { PlanReviewer, type PlanRejected } from "./approve.ts";
6
+ import type { AnyBinding, BindingService } from "./binding.ts";
7
+ import type { ApplyEvent, ApplyStatus } from "./event.ts";
8
+ import { type BindNode, type CRUD, type Delete, type Plan } from "./plan.ts";
9
+ import type { Resource } from "./resource.ts";
10
+ import { State } from "./state.ts";
11
+
12
+ export interface PlanStatusSession {
13
+ emit: (event: ApplyEvent) => Effect.Effect<void>;
14
+ done: () => Effect.Effect<void>;
15
+ }
16
+
17
+ export interface ScopedPlanStatusSession extends PlanStatusSession {
18
+ note: (note: string) => Effect.Effect<void>;
19
+ }
20
+
21
+ export class PlanStatusReporter extends Context.Tag("PlanStatusReporter")<
22
+ PlanStatusReporter,
23
+ {
24
+ start(plan: Plan): Effect.Effect<PlanStatusSession, never>;
25
+ }
26
+ >() {}
27
+
28
+ export const apply = <P extends Plan, Err, Req>(
29
+ plan: Effect.Effect<P, Err, Req>,
30
+ ) =>
31
+ plan.pipe(
32
+ Effect.flatMap((plan) =>
33
+ Effect.gen(function* () {
34
+ const state = yield* State;
35
+ const outputs = {} as Record<string, Effect.Effect<any, any>>;
36
+ const reviewer = yield* Effect.serviceOption(PlanReviewer);
37
+
38
+ if (Option.isSome(reviewer)) {
39
+ yield* reviewer.value.approve(plan);
40
+ }
41
+
42
+ const events = yield* Effect.serviceOption(PlanStatusReporter);
43
+
44
+ const session = Option.isSome(events)
45
+ ? yield* events.value.start(plan)
46
+ : ({
47
+ emit: () => Effect.void,
48
+ done: () => Effect.void,
49
+ } satisfies PlanStatusSession);
50
+ const { emit, done } = session;
51
+
52
+ const applyBindings = Effect.fn(function* (
53
+ resource: Resource,
54
+ bindings: BindNode[],
55
+ target: {
56
+ id: string;
57
+ props: any;
58
+ attr: any;
59
+ },
60
+ ) {
61
+ return yield* Effect.all(
62
+ bindings.map(
63
+ Effect.fn(function* (node) {
64
+ const binding = node.binding as AnyBinding & {
65
+ // smuggled property (because it interacts poorly with inference)
66
+ Tag: Context.Tag<never, BindingService>;
67
+ };
68
+ const provider = yield* binding.Tag;
69
+
70
+ const resourceId: string = node.binding.capability.resource.id;
71
+ const upstreamNode = plan.resources[resourceId];
72
+ const upstreamAttr = resource
73
+ ? yield* apply(upstreamNode)
74
+ : yield* Effect.dieMessage(
75
+ `Resource ${resourceId} not found`,
76
+ );
77
+
78
+ const attach = provider.attach({
79
+ source: {
80
+ id: resourceId,
81
+ attr: upstreamAttr,
82
+ props: upstreamNode.resource.props,
83
+ },
84
+ props: node.binding.props,
85
+ target,
86
+ });
87
+ return Effect.isEffect(attach)
88
+ ? yield* attach as Effect.Effect<any, never, never>
89
+ : attach;
90
+ }),
91
+ ),
92
+ );
93
+ });
94
+
95
+ const apply: (node: CRUD) => Effect.Effect<any, never, never> = (
96
+ node,
97
+ ) =>
98
+ Effect.gen(function* () {
99
+ const checkpoint = <Out, Err>(
100
+ effect: Effect.Effect<Out, Err, never>,
101
+ ) =>
102
+ effect.pipe(
103
+ Effect.flatMap((output) =>
104
+ state
105
+ .set(node.resource.id, {
106
+ id: node.resource.id,
107
+ type: node.resource.type,
108
+ status: node.action === "create" ? "created" : "updated",
109
+ props: node.resource.props,
110
+ output,
111
+ bindings: node.bindings,
112
+ })
113
+ .pipe(Effect.map(() => output)),
114
+ ),
115
+ );
116
+
117
+ const id = node.resource.id;
118
+ const resource = node.resource;
119
+
120
+ const scopedSession = {
121
+ ...session,
122
+ note: (note: string) =>
123
+ session.emit({
124
+ id,
125
+ kind: "annotate",
126
+ message: note,
127
+ }),
128
+ } satisfies ScopedPlanStatusSession;
129
+
130
+ return yield* (outputs[id] ??= yield* Effect.cached(
131
+ Effect.gen(function* () {
132
+ const report = (status: ApplyStatus) =>
133
+ emit({
134
+ kind: "status-change",
135
+ id,
136
+ type: node.resource.type,
137
+ status,
138
+ });
139
+
140
+ if (node.action === "noop") {
141
+ return (yield* state.get(id))?.output;
142
+ } else if (node.action === "create") {
143
+ let attr: any;
144
+ if (node.provider.stub) {
145
+ // stub the resource prior to resolving upstream resources or bindings if a stub is available
146
+ attr = yield* node.provider.stub({
147
+ id,
148
+ news: node.news,
149
+ session: scopedSession,
150
+ });
151
+ }
152
+
153
+ const bindings = yield* applyBindings(
154
+ resource,
155
+ node.bindings,
156
+ {
157
+ id,
158
+ props: node.news,
159
+ attr,
160
+ },
161
+ );
162
+
163
+ yield* report("creating");
164
+
165
+ return yield* node.provider
166
+ .create({
167
+ id,
168
+ news: node.news,
169
+ bindings,
170
+ session: scopedSession,
171
+ })
172
+ .pipe(
173
+ checkpoint,
174
+ Effect.tap(() => report("created")),
175
+ );
176
+ } else if (node.action === "update") {
177
+ const bindings = yield* applyBindings(
178
+ resource,
179
+ node.bindings,
180
+ {
181
+ id,
182
+ props: node.news,
183
+ attr: node.attributes,
184
+ },
185
+ );
186
+
187
+ yield* report("updating");
188
+
189
+ // const bindings = yield* applyDependencies(node.bindings);
190
+
191
+ return yield* node.provider
192
+ .update({
193
+ id,
194
+ news: node.news,
195
+ olds: node.olds,
196
+ output: node.output,
197
+ bindings,
198
+ session: scopedSession,
199
+ })
200
+ .pipe(
201
+ checkpoint,
202
+ Effect.tap(() => report("updated")),
203
+ );
204
+ } else if (node.action === "delete") {
205
+ yield* Effect.all(
206
+ node.downstream.map((dep) =>
207
+ dep in plan.resources
208
+ ? apply(
209
+ plan.resources[
210
+ dep
211
+ ] as P["resources"][keyof P["resources"]],
212
+ )
213
+ : Effect.void,
214
+ ),
215
+ );
216
+ yield* report("deleting");
217
+
218
+ return yield* node.provider
219
+ .delete({
220
+ id,
221
+ olds: node.olds,
222
+ output: node.output,
223
+ session: scopedSession,
224
+ bindings: [],
225
+ })
226
+ .pipe(
227
+ Effect.flatMap(() => state.delete(id)),
228
+ Effect.tap(() => report("deleted")),
229
+ );
230
+ } else if (node.action === "replace") {
231
+ const destroy = Effect.gen(function* () {
232
+ yield* report("deleting");
233
+ return yield* node.provider.delete({
234
+ id,
235
+ olds: node.olds,
236
+ output: node.output,
237
+ session: scopedSession,
238
+ bindings: [],
239
+ });
240
+ });
241
+ const create = Effect.gen(function* () {
242
+ yield* report("creating");
243
+ return yield* (
244
+ node.provider
245
+ .create({
246
+ id,
247
+ news: node.news,
248
+ // TODO(sam): these need to only include attach actions
249
+ bindings: yield* applyBindings(
250
+ resource,
251
+ node.bindings,
252
+ {
253
+ id,
254
+ props: node.news,
255
+ attr: node.attributes,
256
+ },
257
+ ),
258
+ session: scopedSession,
259
+ })
260
+ // TODO(sam): delete and create will conflict here, we need to extend the state store for replace
261
+ .pipe(
262
+ checkpoint,
263
+ Effect.tap(() => report("created")),
264
+ )
265
+ );
266
+ });
267
+ if (!node.deleteFirst) {
268
+ const outputs = yield* create;
269
+ yield* destroy;
270
+ return outputs;
271
+ } else {
272
+ yield* destroy;
273
+ return yield* create;
274
+ }
275
+ }
276
+ }),
277
+ ));
278
+ }) as Effect.Effect<any, never, never>;
279
+
280
+ const nodes = [
281
+ ...Object.entries(plan.resources),
282
+ ...Object.entries(plan.deletions),
283
+ ];
284
+
285
+ const resources: any = Object.fromEntries(
286
+ yield* Effect.all(
287
+ nodes.map(
288
+ Effect.fn(function* ([id, node]) {
289
+ return [id, yield* apply(node as CRUD)];
290
+ }),
291
+ ),
292
+ ),
293
+ );
294
+ yield* done();
295
+ if (Object.keys(plan.resources).length === 0) {
296
+ // all resources are deleted, return undefined
297
+ return undefined;
298
+ }
299
+ return resources;
300
+ }),
301
+ ),
302
+ ) as Effect.Effect<
303
+ "update" extends P["phase"]
304
+ ?
305
+ | {
306
+ [id in keyof P["resources"]]: P["resources"][id] extends
307
+ | Delete<Resource>
308
+ | undefined
309
+ | never
310
+ ? never
311
+ : Simplify<P["resources"][id]["resource"]["attr"]>;
312
+ }
313
+ // union distribution isn't happening, so we gotta add this additional void here just in case
314
+ | ("destroy" extends P["phase"] ? void : never)
315
+ : void,
316
+ Err | PlanRejected,
317
+ Req
318
+ >;
package/src/approve.ts ADDED
@@ -0,0 +1,13 @@
1
+ import * as Context from "effect/Context";
2
+ import * as Data from "effect/Data";
3
+ import type * as Effect from "effect/Effect";
4
+ import type { Plan } from "./plan.ts";
5
+
6
+ export class PlanRejected extends Data.TaggedError("PlanRejected")<{}> {}
7
+
8
+ export class PlanReviewer extends Context.Tag("PlanReviewer")<
9
+ PlanReviewer,
10
+ {
11
+ approve: <P extends Plan>(plan: P) => Effect.Effect<void, PlanRejected>;
12
+ }
13
+ >() {}
package/src/binding.ts ADDED
@@ -0,0 +1,144 @@
1
+ import * as Context from "effect/Context";
2
+ import type { Effect } from "effect/Effect";
3
+ import * as Layer from "effect/Layer";
4
+ import type { Capability, ICapability } from "./capability.ts";
5
+ import type { Resource } from "./resource.ts";
6
+ import type { Runtime } from "./runtime.ts";
7
+
8
+ export interface BindingProps {
9
+ [key: string]: any;
10
+ }
11
+
12
+ export const isBinding = (b: any): b is AnyBinding =>
13
+ "runtime" in b && "capability" in b && "tag" in b && "output" in b;
14
+
15
+ export type AnyBinding<F extends Runtime = any> = Binding<
16
+ F,
17
+ any,
18
+ any,
19
+ string,
20
+ boolean
21
+ >;
22
+
23
+ export interface Binding<
24
+ Run extends Runtime,
25
+ Cap extends Capability = Capability,
26
+ Props = any,
27
+ Tag extends string = Cap["type"],
28
+ IsCustom extends boolean = Cap["type"] extends Tag ? false : true,
29
+ > {
30
+ runtime: Run;
31
+ capability: Cap;
32
+ tag: Tag;
33
+ props: Props;
34
+ isCustom: IsCustom;
35
+ }
36
+
37
+ /** Tag for a Service that can bind a Capability to a Runtime */
38
+ export interface Bind<
39
+ F extends Runtime,
40
+ Cap extends Capability,
41
+ Tag extends string,
42
+ > extends Context.Tag<
43
+ `${F["type"]}(${Cap["type"]}, ${Tag})`,
44
+ BindingService<
45
+ F,
46
+ Extract<Extract<Cap["resource"], Resource>["parent"], Resource>,
47
+ F["props"]
48
+ >
49
+ > {
50
+ /** @internal phantom */
51
+ name: Tag;
52
+ }
53
+
54
+ export const Binding: {
55
+ <F extends (resource: any, props?: any) => AnyBinding & { isCustom: true }>(
56
+ runtime: ReturnType<F>["runtime"],
57
+ // resource: new () => ReturnType<F>["capability"]["resource"],
58
+ type: ReturnType<F>["capability"]["type"],
59
+ tag: ReturnType<F>["tag"],
60
+ ): F & BindingDeclaration<ReturnType<F>["runtime"], F>;
61
+ <F extends (resource: any, props?: any) => AnyBinding & { isCustom: false }>(
62
+ runtime: ReturnType<F>["runtime"],
63
+ // resource: new () => ReturnType<F>["capability"]["resource"],
64
+ type: ReturnType<F>["capability"]["type"],
65
+ ): F & BindingDeclaration<ReturnType<F>["runtime"], F>;
66
+ } = (runtime: any, cap: string, tag?: string) => {
67
+ const Tag = Context.Tag(`${runtime.type}(${cap}, ${tag ?? cap})`)();
68
+ return Object.assign(
69
+ (resource: any, props?: any) =>
70
+ ({
71
+ runtime,
72
+ capability: {
73
+ type: cap,
74
+ resource,
75
+ constraint: undefined!,
76
+ sid: `${cap}${resource.id}`.replace(/[^a-zA-Z0-9]/g, ""),
77
+ label: `${cap}(${resource.id})`,
78
+ } satisfies ICapability,
79
+ props,
80
+ isCustom: false,
81
+ tag: tag ?? cap,
82
+ // @ts-expect-error - we smuggle this property because it interacts poorly with inference
83
+ Tag,
84
+ }) satisfies Binding<any, any, any, string, false>,
85
+ {
86
+ provider: {
87
+ effect: (eff) => Layer.effect(Tag, eff),
88
+ succeed: (service) => Layer.succeed(Tag, service),
89
+ },
90
+ } satisfies BindingDeclaration<Runtime, any>,
91
+ );
92
+ };
93
+
94
+ export interface BindingDeclaration<
95
+ Run extends Runtime,
96
+ F extends (target: any, props?: any) => AnyBinding<Run>,
97
+ Tag extends string = ReturnType<F>["tag"],
98
+ Cap extends Capability = ReturnType<F>["capability"],
99
+ > {
100
+ provider: {
101
+ effect<Err, Req>(
102
+ eff: Effect<
103
+ BindingService<Run, Parameters<F>[0], Parameters<F>[1]>,
104
+ Err,
105
+ Req
106
+ >,
107
+ ): Layer.Layer<Bind<Run, Cap, Tag>, Err, Req>;
108
+ succeed(
109
+ service: BindingService<Run, Parameters<F>[0], Parameters<F>[1]>,
110
+ ): Layer.Layer<Bind<Run, Cap, Tag>>;
111
+ };
112
+ }
113
+
114
+ export type BindingService<
115
+ Target extends Runtime = any,
116
+ Source extends Resource = Resource,
117
+ Props = any,
118
+ AttachReq = never,
119
+ DetachReq = never,
120
+ > = {
121
+ attach: (props: {
122
+ source: {
123
+ id: string;
124
+ attr: Source["attr"];
125
+ props: Source["props"];
126
+ };
127
+ props: Props;
128
+ target: {
129
+ id: string;
130
+ props: Target["props"];
131
+ attr: Target["attr"];
132
+ };
133
+ }) =>
134
+ | Effect<Partial<Target["binding"]> | void, never, AttachReq>
135
+ | Partial<Target["binding"]>;
136
+ detach?: (
137
+ resource: {
138
+ id: string;
139
+ attr: Source["attr"];
140
+ props: Source["props"];
141
+ },
142
+ from: Target["binding"],
143
+ ) => Effect<void, never, DetachReq> | void;
144
+ };
@@ -0,0 +1,19 @@
1
+ export interface ICapability<
2
+ Type extends string = string,
3
+ Resource = unknown,
4
+ Constraint = unknown,
5
+ > {
6
+ type: Type;
7
+ resource: Resource;
8
+ constraint: Constraint;
9
+ sid: string;
10
+ label: string;
11
+ }
12
+
13
+ export interface Capability<
14
+ Type extends string = string,
15
+ Resource = unknown,
16
+ Constraint = unknown,
17
+ > extends ICapability<Type, Resource, Constraint> {
18
+ new (): {};
19
+ }
package/src/destroy.ts ADDED
@@ -0,0 +1,4 @@
1
+ import type * as Effect from "effect/Effect";
2
+ import type { App } from "./app.ts";
3
+
4
+ export declare const destroy: () => Effect.Effect<void, never, App>;
@@ -0,0 +1,17 @@
1
+ import { FileSystem } from "@effect/platform";
2
+ import * as Context from "effect/Context";
3
+ import * as Effect from "effect/Effect";
4
+ import * as Layer from "effect/Layer";
5
+ import path from "node:path";
6
+
7
+ export class DotAlchemy extends Context.Tag(".alchemy")<DotAlchemy, string>() {}
8
+
9
+ export const dotAlchemy = Layer.effect(
10
+ DotAlchemy,
11
+ Effect.gen(function* () {
12
+ const fs = yield* FileSystem.FileSystem;
13
+ const dir = path.join(process.cwd(), ".alchemy");
14
+ yield* fs.makeDirectory(dir, { recursive: true });
15
+ return dir;
16
+ }),
17
+ );
package/src/env.ts ADDED
@@ -0,0 +1,51 @@
1
+ export const toEnvKey = <const ID extends string, const Suffix extends string>(
2
+ id: ID,
3
+ suffix: Suffix,
4
+ ) => `${replace(toUpper(id))}_${replace(toUpper(suffix))}` as const;
5
+
6
+ const toUpper = <const S extends string>(str: S) =>
7
+ str.toUpperCase() as string extends S ? S : Uppercase<S>;
8
+
9
+ const replace = <const S extends string>(str: S) =>
10
+ str.replace(/-/g, "_") as Replace<S>;
11
+
12
+ type Replace<S extends string, Accum extends string = ""> = string extends S
13
+ ? S
14
+ : S extends ""
15
+ ? Accum
16
+ : S extends `${infer S}${infer Rest}`
17
+ ? S extends "-"
18
+ ? Replace<Rest, `${Accum}_`>
19
+ : Replace<Rest, `${Accum}${S}`>
20
+ : Accum;
21
+
22
+ function _test_both_literals() {
23
+ const key = toEnvKey("my-id", "my-suffix");
24
+ const _: typeof key = "MY_ID_MY_SUFFIX";
25
+ // @ts-expect-error
26
+ const _err: typeof key = "MY_ID_MY_SUFFIX2";
27
+ }
28
+ function _test_replace_wide_string() {
29
+ const ___ = toUpper(undefined! as string);
30
+ const id: string = "my-id";
31
+ const key = toEnvKey(id, "my-suffix");
32
+ const _: typeof key = "MY_ID_MY_SUFFIX";
33
+ const _2: typeof key = `${id}_MY_SUFFIX` as const;
34
+ // @ts-expect-error
35
+ const _err: typeof key = "MY_ID_MY_SUFFIX2";
36
+ // @ts-expect-error
37
+ const _err2: typeof key = `${id}_MY_SUFFIX2` as const;
38
+ }
39
+
40
+ function _test_replace_wide_suffix() {
41
+ const ___ = toUpper(undefined! as string);
42
+ const id = "my-id";
43
+ const suffix = "my-suffix" as string;
44
+ const key = toEnvKey(id, suffix);
45
+ const _: typeof key = "MY_ID_MY_SUFFIX";
46
+ const _2: typeof key = `MY_ID_${suffix}` as const;
47
+ // @ts-expect-error
48
+ const _err: typeof key = "WRONG_PREFIX_MY_SUFFIX";
49
+ // @ts-expect-error
50
+ const _err2: typeof key = `WRONG_PREFIX_${suffix}`;
51
+ }
package/src/event.ts ADDED
@@ -0,0 +1,27 @@
1
+ export type ApplyStatus =
2
+ | "pending"
3
+ | "creating"
4
+ | "created"
5
+ | "updating"
6
+ | "updated"
7
+ | "deleting"
8
+ | "deleted"
9
+ | "success"
10
+ | "fail";
11
+
12
+ export type ApplyEvent = AnnotateEvent | StatusChangeEvent;
13
+
14
+ export interface AnnotateEvent {
15
+ kind: "annotate";
16
+ id: string;
17
+ message: string;
18
+ }
19
+
20
+ export interface StatusChangeEvent {
21
+ kind: "status-change";
22
+ id: string; // resource id (e.g. "messages", "api")
23
+ type: string; // resource type (e.g. "AWS::Lambda::Function", "Cloudflare::Worker")
24
+ status: ApplyStatus;
25
+ message?: string; // optional details
26
+ bindingId?: string; // if this event is for a binding
27
+ }
package/src/index.ts ADDED
@@ -0,0 +1,17 @@
1
+ export * from "./app.ts";
2
+ export * from "./apply.ts";
3
+ export * from "./approve.ts";
4
+ export * from "./binding.ts";
5
+ export * from "./capability.ts";
6
+ export * from "./destroy.ts";
7
+ export * from "./dot-alchemy.ts";
8
+ export * from "./env.ts";
9
+ export * from "./event.ts";
10
+ export * from "./phase.ts";
11
+ export * from "./plan.ts";
12
+ export * from "./policy.ts";
13
+ export * from "./provider.ts";
14
+ export * from "./resource.ts";
15
+ export * from "./runtime.ts";
16
+ export * from "./service.ts";
17
+ export * as State from "./state.ts";
package/src/phase.ts ADDED
@@ -0,0 +1 @@
1
+ export type Phase = "update" | "destroy";
@@ -0,0 +1,7 @@
1
+ import * as Effect from "effect/Effect";
2
+ import { App } from "./app.ts";
3
+
4
+ export const physicalName = Effect.fn(function* (id: string) {
5
+ const app = yield* App;
6
+ return `${app.name}-${id}-${app.stage}`;
7
+ });