alchemy-effect 0.6.4 → 0.7.1

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/src/Stack.ts CHANGED
@@ -7,6 +7,7 @@ import type { Scope } from "effect/Scope";
7
7
  import * as ServiceMap from "effect/ServiceMap";
8
8
  import type { HttpClient } from "effect/unstable/http/HttpClient";
9
9
  import type { ChildProcessSpawner } from "effect/unstable/process/ChildProcessSpawner";
10
+ import type { Artifacts } from "./Artifacts.ts";
10
11
  import { DotAlchemy } from "./Config.ts";
11
12
  import type { ResourceBinding, ResourceLike } from "./Resource.ts";
12
13
  import { Stage } from "./Stage.ts";
@@ -19,7 +20,8 @@ export type StackServices =
19
20
  | Path
20
21
  | DotAlchemy
21
22
  | HttpClient
22
- | ChildProcessSpawner;
23
+ | ChildProcessSpawner
24
+ | Artifacts;
23
25
 
24
26
  export class Stack extends ServiceMap.Service<
25
27
  Stack,
@@ -39,6 +41,10 @@ export interface StackSpec<Output = any> {
39
41
  output: Output;
40
42
  }
41
43
 
44
+ export interface CompiledStack<Output, Services> extends StackSpec<Output> {
45
+ services: ServiceMap.ServiceMap<Services>;
46
+ }
47
+
42
48
  export const StackName = Stack.use((stack) => Effect.succeed(stack.name));
43
49
 
44
50
  export const make =
@@ -56,11 +62,17 @@ export const make =
56
62
  Stack.asEffect(),
57
63
  Effect.services<ROut | StackServices>(),
58
64
  ]).pipe(
59
- Effect.map(([output, stack, services]) => ({
60
- output,
61
- services,
62
- ...stack,
63
- })),
65
+ Effect.map(
66
+ ([output, stack, services]) =>
67
+ ({
68
+ output,
69
+ services,
70
+ ...stack,
71
+ }) satisfies CompiledStack<A, ROut | StackServices> as CompiledStack<
72
+ A,
73
+ ROut | StackServices
74
+ >,
75
+ ),
64
76
  Effect.provide(providers),
65
77
  Effect.provideServiceEffect(
66
78
  Stack,
@@ -23,6 +23,7 @@ import * as Cloudflare from "../Cloudflare/index.ts";
23
23
 
24
24
  import type { ChildProcessSpawner } from "effect/unstable/process/ChildProcessSpawner";
25
25
  import { apply } from "../Apply.ts";
26
+ import { Artifacts, provideFreshArtifactStore } from "../Artifacts.ts";
26
27
  import * as Credentials from "../AWS/Credentials.ts";
27
28
  import * as Region from "../AWS/Region.ts";
28
29
  import type { Command } from "../Build/Command.ts";
@@ -78,6 +79,7 @@ type Provided =
78
79
  | Server.ServerHost
79
80
  | Serverless.FunctionContext
80
81
  | AWS.StageConfig
82
+ | Artifacts
81
83
  | Provider<Command>
82
84
  | Layer.Success<ReturnType<typeof AWS.providers>>
83
85
  | Layer.Success<ReturnType<typeof Cloudflare.providers>>
@@ -146,7 +148,10 @@ const deriveStackName = (testPath: string, suffix: string) => {
146
148
  const runWithContext = <A, Err>(
147
149
  stackName: string,
148
150
  effect: Effect.Effect<A, Err, Provided>,
149
- options: { state?: Layer.Layer<State.State, never, Stack.Stack> } = {},
151
+ options: {
152
+ state?: Layer.Layer<State.State, never, Stack.Stack>;
153
+ providers?: boolean;
154
+ } = {},
150
155
  ): Effect.Effect<
151
156
  A,
152
157
  aws.Credentials.CredentialsError | Config.ConfigError | Err,
@@ -192,7 +197,9 @@ const runWithContext = <A, Err>(
192
197
  }).pipe(
193
198
  Effect.provide(
194
199
  Layer.provideMerge(
195
- Layer.mergeAll(awsProviders, cfProviders),
200
+ options.providers === false
201
+ ? Layer.empty
202
+ : Layer.mergeAll(awsProviders, cfProviders),
196
203
  Layer.provideMerge(alchemy, platform),
197
204
  ),
198
205
  ),
@@ -214,6 +221,7 @@ export function test(
214
221
  options: {
215
222
  timeout?: number;
216
223
  state?: Layer.Layer<State.State, never, Stack.Stack>;
224
+ providers?: boolean;
217
225
  },
218
226
  testCase: Effect.Effect<void, any, Provided>,
219
227
  ): void;
@@ -230,6 +238,7 @@ export function test(
230
238
  {
231
239
  timeout?: number;
232
240
  state?: Layer.Layer<State.State, never, Stack.Stack>;
241
+ providers?: boolean;
233
242
  },
234
243
  Effect.Effect<void, any, Provided>,
235
244
  ]
@@ -251,6 +260,7 @@ export namespace test {
251
260
  options: {
252
261
  timeout?: number;
253
262
  state?: Layer.Layer<State.State, never, Stack.Stack>;
263
+ providers?: boolean;
254
264
  },
255
265
  testCase: Effect.Effect<void, any, Provided>,
256
266
  ): void;
@@ -267,6 +277,7 @@ export namespace test {
267
277
  {
268
278
  timeout?: number;
269
279
  state?: Layer.Layer<State.State, never, Stack.Stack>;
280
+ providers?: boolean;
270
281
  },
271
282
  Effect.Effect<void, any, Provided>,
272
283
  ]
@@ -285,6 +296,7 @@ export namespace test {
285
296
  {
286
297
  timeout?: number;
287
298
  state?: Layer.Layer<State.State, never, Stack.Stack>;
299
+ providers?: boolean;
288
300
  },
289
301
  Effect.Effect<void, any, Provided>,
290
302
  ]
@@ -337,17 +349,24 @@ export namespace test {
337
349
  effect: Effect.Effect<A, Err, Req>,
338
350
  ): Effect.Effect<Input.Resolve<A>, Err, Req | Stack.Stack> =>
339
351
  Stack.Stack.use((stack) => {
340
- return effect.pipe(
341
- // Effect.tap(Effect.logInfo),
342
- // @ts-expect-error
343
- Stack.make(stack.name, Layer.effectServices(Effect.services<never>()), {
344
- ...stack,
345
- resources: {},
346
- bindings: {},
347
- }),
348
- Effect.flatMap(Plan.make),
349
- Effect.tap((plan) => Effect.logInfo(formatPlan(plan))),
350
- Effect.flatMap(apply),
352
+ return provideFreshArtifactStore(
353
+ effect.pipe(
354
+ // Effect.tap(Effect.logInfo),
355
+ // @ts-expect-error
356
+ Stack.make(
357
+ stack.name,
358
+ Layer.effectServices(Effect.services<never>()),
359
+ {
360
+ ...stack,
361
+ resources: {},
362
+ bindings: {},
363
+ output: {},
364
+ },
365
+ ),
366
+ Effect.flatMap(Plan.make),
367
+ Effect.tap((plan) => Effect.logInfo(formatPlan(plan))),
368
+ Effect.flatMap(apply),
369
+ ),
351
370
  );
352
371
  });
353
372
  }
@@ -427,6 +446,7 @@ export function skip(
427
446
  options: {
428
447
  timeout?: number;
429
448
  state?: Layer.Layer<State.State, never, Stack.Stack>;
449
+ providers?: boolean;
430
450
  },
431
451
  testCase: Effect.Effect<void, any, Provided>,
432
452
  ): void;
@@ -443,6 +463,7 @@ export function skip(
443
463
  {
444
464
  timeout?: number;
445
465
  state?: Layer.Layer<State.State, never, Stack.Stack>;
466
+ providers?: boolean;
446
467
  },
447
468
  Effect.Effect<void, any, Provided>,
448
469
  ]
@@ -461,6 +482,7 @@ export function skipIf(condition: boolean) {
461
482
  {
462
483
  timeout?: number;
463
484
  state?: Layer.Layer<State.State, never, Stack.Stack>;
485
+ providers?: boolean;
464
486
  },
465
487
  Effect.Effect<void, any, Provided>,
466
488
  ]
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Converts gitignore-style rules into glob patterns for tools like fast-glob's {@link https://github.com/mrmlnc/fast-glob#ignore `ignore`} option.
3
+ *
4
+ * Gitignore and glob differ (ordering, negation, escapes). This maps the common cases:
5
+ *
6
+ * - Rules with no `/` match at any depth → \`**\/name\`, \`**\/name\/\*\/`
7
+ * - A leading `/` anchors to the ignore file's directory (use the same `cwd` in fast-glob) → `name`, `name/**`
8
+ * - A `/` in the pattern (not only leading) uses path-aware matching → `a/b`, `a/b/**`
9
+ * - A trailing `/` restricts to directories → adds `/**` as needed
10
+ * - Lines starting with `!` (negation) are returned with a `!` prefix for use in **positive** glob
11
+ * lists; they are not valid in fast-glob's `ignore` array (filter them out if you only pass `ignore`)
12
+ *
13
+ * Does not implement full gitignore escaping (e.g. `\\ ` for trailing space) or `**` edge cases
14
+ * identical to git; escape handling can be added later if needed.
15
+ */
16
+ export function gitignoreRulesToGlobs(rules: ReadonlyArray<string>): string[] {
17
+ const out: string[] = [];
18
+ for (const raw of rules) {
19
+ const line = raw.trim();
20
+ if (line.length === 0 || line.startsWith("#")) {
21
+ continue;
22
+ }
23
+ let negated = false;
24
+ let rest = line;
25
+ if (rest.startsWith("!")) {
26
+ negated = true;
27
+ rest = rest.slice(1).trim();
28
+ if (rest.length === 0 || rest.startsWith("#")) {
29
+ continue;
30
+ }
31
+ }
32
+ let dirOnly = rest.endsWith("/");
33
+ if (dirOnly) {
34
+ rest = rest.slice(0, -1);
35
+ }
36
+ rest = rest.replace(/\\/g, "/");
37
+ if (rest.length === 0) {
38
+ continue;
39
+ }
40
+ const anchored = rest.startsWith("/");
41
+ if (anchored) {
42
+ rest = rest.slice(1);
43
+ }
44
+ if (rest.length === 0) {
45
+ continue;
46
+ }
47
+ const hasSlash = rest.includes("/");
48
+ const globs = ruleBodyToGlobs(rest, { anchored, hasSlash, dirOnly });
49
+ for (const g of globs) {
50
+ out.push(negated ? `!${g}` : g);
51
+ }
52
+ }
53
+ return out;
54
+ }
55
+
56
+ function ruleBodyToGlobs(
57
+ body: string,
58
+ opts: { anchored: boolean; hasSlash: boolean; dirOnly: boolean },
59
+ ): string[] {
60
+ const { anchored, hasSlash, dirOnly } = opts;
61
+ if (dirOnly) {
62
+ if (anchored) {
63
+ return [`${body}/**`];
64
+ }
65
+ if (hasSlash) {
66
+ return [`${body}/**`];
67
+ }
68
+ return [`**/${body}/**`];
69
+ }
70
+ if (anchored) {
71
+ if (hasSlash) {
72
+ return [body, `${body}/**`];
73
+ }
74
+ return [body, `${body}/**`];
75
+ }
76
+ if (hasSlash) {
77
+ return [body, `${body}/**`];
78
+ }
79
+ return [`**/${body}`, `**/${body}/**`];
80
+ }
@@ -1,14 +0,0 @@
1
- import * as Effect from "effect/Effect";
2
- import { Worker } from "../Workers/Worker.ts";
3
-
4
- export const TanstackStart: typeof Worker = ((...args: any[]) =>
5
- args.length === 0
6
- ? (...args: [string, Effect.Effect<any>]) => TanstackStart(...args)
7
- : Worker(
8
- args[0],
9
- {
10
- // TODO(sam): main entrypoint should be the Tanstack Start entrypoint (that is assumed to import and run this)
11
- main: import.meta.filename,
12
- },
13
- args[1],
14
- )) as any;