alchemy-effect 0.7.0 → 0.8.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/bin/alchemy-effect.js +9 -7
- package/bin/alchemy-effect.js.map +1 -1
- package/bin/alchemy-effect.ts +11 -0
- package/package.json +1 -1
- package/src/AWS/AutoScaling/LaunchTemplate.ts +5 -4
- package/src/AWS/EC2/Instance.ts +3 -4
- package/src/AWS/ECS/Task.ts +36 -34
- package/src/AWS/Lambda/Function.ts +68 -68
- package/src/Binding.ts +10 -3
- package/src/Cloudflare/Container.ts +68 -67
- package/src/Cloudflare/D1/D1Database.ts +32 -32
- package/src/Cloudflare/KV/Delete.ts +7 -5
- package/src/Cloudflare/KV/Get.ts +5 -5
- package/src/Cloudflare/KV/GetWithMetadata.ts +5 -5
- package/src/Cloudflare/KV/{Namespace.ts → KVNamespace.ts} +6 -6
- package/src/Cloudflare/KV/{NamespaceBinding.ts → KVNamespaceBinding.ts} +2 -2
- package/src/Cloudflare/KV/List.ts +5 -5
- package/src/Cloudflare/KV/Put.ts +5 -5
- package/src/Cloudflare/KV/index.ts +1 -1
- package/src/Cloudflare/Providers.ts +2 -8
- package/src/Cloudflare/R2/{Bucket.ts → R2Bucket.ts} +19 -19
- package/src/Cloudflare/R2/R2BucketBinding.ts +299 -0
- package/src/Cloudflare/R2/index.ts +2 -9
- package/src/Cloudflare/Workers/InferEnv.ts +23 -0
- package/src/Cloudflare/Workers/Worker.ts +185 -108
- package/src/Cloudflare/Workers/index.ts +1 -0
- package/src/Cloudflare/index.ts +2 -2
- package/src/Plan.ts +12 -0
- package/src/Platform.ts +39 -55
- package/src/Test/Vitest.ts +15 -2
- package/src/Util/effect.ts +24 -0
- package/src/Cloudflare/R2/BucketBinding.ts +0 -31
- package/src/Cloudflare/R2/CreateMultipartUpload.ts +0 -59
- package/src/Cloudflare/R2/DeleteObject.ts +0 -41
- package/src/Cloudflare/R2/GetObject.ts +0 -47
- package/src/Cloudflare/R2/HeadObject.ts +0 -41
- package/src/Cloudflare/R2/ListObjects.ts +0 -45
- package/src/Cloudflare/R2/MultipartUploadClient.ts +0 -40
- package/src/Cloudflare/R2/PutObject.ts +0 -55
- package/src/Cloudflare/R2/ResumeMultipartUpload.ts +0 -48
- package/src/Cloudflare/R2/UploadValue.ts +0 -10
|
@@ -24,7 +24,7 @@ import { findCwdForBundle } from "../../Bundle/TempRoot.ts";
|
|
|
24
24
|
import type { ScopedPlanStatusSession } from "../../Cli/Cli.ts";
|
|
25
25
|
import { isResolved } from "../../Diff.ts";
|
|
26
26
|
import type { HttpEffect } from "../../Http.ts";
|
|
27
|
-
import type { Input } from "../../Input.ts";
|
|
27
|
+
import type { Input, InputProps } from "../../Input.ts";
|
|
28
28
|
import * as Output from "../../Output.ts";
|
|
29
29
|
import { createPhysicalName } from "../../PhysicalName.ts";
|
|
30
30
|
import {
|
|
@@ -39,7 +39,9 @@ import { Self } from "../../Self.ts";
|
|
|
39
39
|
import * as Serverless from "../../Serverless/index.ts";
|
|
40
40
|
import { Stack } from "../../Stack.ts";
|
|
41
41
|
import { Account } from "../Account.ts";
|
|
42
|
+
import { D1Database } from "../D1/D1Database.ts";
|
|
42
43
|
import { CloudflareLogs } from "../Logs.ts";
|
|
44
|
+
import type { R2Bucket } from "../R2/R2Bucket.ts";
|
|
43
45
|
import type { AssetsConfig, AssetsProps } from "./Assets.ts";
|
|
44
46
|
import * as Assets from "./Assets.ts";
|
|
45
47
|
import cloudflare_workers from "./cloudflare_workers.ts";
|
|
@@ -142,7 +144,29 @@ export const ExportedHandlerMethods = [
|
|
|
142
144
|
"queue",
|
|
143
145
|
] as const satisfies (keyof cf.ExportedHandler)[];
|
|
144
146
|
|
|
145
|
-
export interface
|
|
147
|
+
export interface WorkerExecutionContext extends Serverless.FunctionContext {
|
|
148
|
+
export(name: string, value: any): Effect.Effect<void>;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export type WorkerServices = Worker | WorkerEnvironment | Request;
|
|
152
|
+
|
|
153
|
+
export type WorkerShape = Main<WorkerServices>;
|
|
154
|
+
|
|
155
|
+
export type WorkerBindingResource = R2Bucket | D1Database;
|
|
156
|
+
|
|
157
|
+
export type WorkerBindings = {
|
|
158
|
+
[bindingName in string]: WorkerBindingResource;
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
export type WorkerBindingProps = {
|
|
162
|
+
[bindingName in string]:
|
|
163
|
+
| WorkerBindingResource
|
|
164
|
+
| Effect.Effect<WorkerBindingResource, any, any>;
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
export interface WorkerProps<
|
|
168
|
+
Bindings extends WorkerBindingProps = any,
|
|
169
|
+
> extends PlatformProps {
|
|
146
170
|
/**
|
|
147
171
|
* Worker name override. If omitted, Alchemy derives a deterministic physical
|
|
148
172
|
* name from the stack, stage, and logical ID.
|
|
@@ -185,19 +209,12 @@ export interface WorkerProps extends PlatformProps {
|
|
|
185
209
|
placement?: WorkerPlacement;
|
|
186
210
|
env?: Record<string, any>;
|
|
187
211
|
exports?: string[];
|
|
212
|
+
bindings?: Bindings;
|
|
188
213
|
}
|
|
189
214
|
|
|
190
|
-
export
|
|
191
|
-
export(name: string, value: any): Effect.Effect<void>;
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
export type WorkerServices = Worker | WorkerEnvironment | Request;
|
|
195
|
-
|
|
196
|
-
export type WorkerShape = Main<WorkerServices>;
|
|
197
|
-
|
|
198
|
-
export interface Worker extends Resource<
|
|
215
|
+
export type Worker<Bindings extends WorkerBindings = any> = Resource<
|
|
199
216
|
WorkerTypeId,
|
|
200
|
-
WorkerProps
|
|
217
|
+
WorkerProps<Bindings>,
|
|
201
218
|
{
|
|
202
219
|
workerId: string;
|
|
203
220
|
workerName: string;
|
|
@@ -215,7 +232,7 @@ export interface Worker extends Resource<
|
|
|
215
232
|
bindings: WorkerBinding[];
|
|
216
233
|
containers?: { className: string }[];
|
|
217
234
|
}
|
|
218
|
-
|
|
235
|
+
>;
|
|
219
236
|
|
|
220
237
|
/**
|
|
221
238
|
* A Cloudflare Worker host with deploy-time binding support and runtime export
|
|
@@ -238,105 +255,165 @@ export const Worker: Platform<
|
|
|
238
255
|
WorkerServices,
|
|
239
256
|
WorkerShape,
|
|
240
257
|
WorkerExecutionContext
|
|
241
|
-
>
|
|
242
|
-
const
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
258
|
+
> & {
|
|
259
|
+
<const Bindings extends WorkerBindingProps>(
|
|
260
|
+
id: string,
|
|
261
|
+
props: InputProps<WorkerProps<Bindings>>,
|
|
262
|
+
): Effect.Effect<
|
|
263
|
+
Worker<{
|
|
264
|
+
[B in keyof Bindings]: Bindings[B] extends Effect.Effect<
|
|
265
|
+
infer T extends WorkerBindingResource,
|
|
266
|
+
any,
|
|
267
|
+
any
|
|
268
|
+
>
|
|
269
|
+
? T
|
|
270
|
+
: Extract<Bindings[B], WorkerBindingResource>;
|
|
271
|
+
}>
|
|
272
|
+
>;
|
|
273
|
+
} = Platform(WorkerTypeId, {
|
|
274
|
+
onCreate: Effect.fnUntraced(function* (
|
|
275
|
+
resource: Worker,
|
|
276
|
+
props: InputProps<WorkerProps<WorkerBindingProps>>,
|
|
277
|
+
) {
|
|
278
|
+
if (props.bindings) {
|
|
279
|
+
for (const bindingName in props.bindings) {
|
|
280
|
+
// @ts-expect-error
|
|
281
|
+
const bindingEff = props.bindings?.[bindingName] as
|
|
282
|
+
| WorkerBindingResource
|
|
283
|
+
| Effect.Effect<WorkerBindingResource>;
|
|
284
|
+
const binding = Effect.isEffect(bindingEff)
|
|
285
|
+
? yield* bindingEff
|
|
286
|
+
: bindingEff;
|
|
287
|
+
|
|
288
|
+
const bindingMeta: InputProps<WorkerBinding> | undefined =
|
|
289
|
+
binding.Type === "Cloudflare.D1Database"
|
|
290
|
+
? {
|
|
291
|
+
type: "d1",
|
|
292
|
+
id: binding.databaseId,
|
|
293
|
+
name: bindingName,
|
|
294
|
+
}
|
|
295
|
+
: binding.Type === "Cloudflare.R2Bucket"
|
|
296
|
+
? {
|
|
297
|
+
type: "r2_bucket",
|
|
298
|
+
name: bindingName,
|
|
299
|
+
bucketName: binding.bucketName,
|
|
300
|
+
jurisdiction: binding.jurisdiction.pipe(
|
|
301
|
+
Output.map((jurisdiction) =>
|
|
302
|
+
jurisdiction === "default" ? undefined : jurisdiction,
|
|
303
|
+
),
|
|
304
|
+
),
|
|
305
|
+
}
|
|
306
|
+
: // TODO(sam): handle others
|
|
307
|
+
undefined;
|
|
308
|
+
|
|
309
|
+
if (bindingMeta) {
|
|
310
|
+
yield* resource.bind`${bindingName}`({
|
|
311
|
+
bindings: [bindingMeta],
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
}),
|
|
317
|
+
createExecutionContext: (id: string): WorkerExecutionContext => {
|
|
318
|
+
const listeners: Effect.Effect<Serverless.FunctionListener>[] = [];
|
|
319
|
+
const exports: Record<string, any> = {};
|
|
320
|
+
const env: Record<string, any> = {};
|
|
321
|
+
|
|
322
|
+
const ctx = {
|
|
323
|
+
Type: WorkerTypeId,
|
|
324
|
+
id,
|
|
325
|
+
env,
|
|
326
|
+
get: (key: string) =>
|
|
327
|
+
Effect.serviceOption(WorkerEnvironment).pipe(
|
|
328
|
+
Effect.map(Option.getOrUndefined),
|
|
329
|
+
Effect.flatMap((env) =>
|
|
330
|
+
env
|
|
331
|
+
? Effect.succeed(env[key])
|
|
332
|
+
: Effect.die("WorkerEnvironment not found"),
|
|
333
|
+
),
|
|
334
|
+
Effect.flatMap((value) =>
|
|
335
|
+
value
|
|
336
|
+
? Effect.succeed(value)
|
|
337
|
+
: Effect.die(`Environment variable '${key}' not found`),
|
|
338
|
+
),
|
|
339
|
+
Effect.map((json) => {
|
|
340
|
+
try {
|
|
341
|
+
const value = JSON.parse(json);
|
|
342
|
+
if (!Redacted.isRedacted(value)) {
|
|
343
|
+
return Redacted.make(value.value);
|
|
344
|
+
}
|
|
345
|
+
return value;
|
|
346
|
+
} catch {
|
|
347
|
+
return json;
|
|
268
348
|
}
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
349
|
+
}),
|
|
350
|
+
) as any,
|
|
351
|
+
set: (id: string, output: Output.Output) =>
|
|
352
|
+
Effect.sync(() => {
|
|
353
|
+
const key = id.replaceAll(/[^a-zA-Z0-9]/g, "_");
|
|
354
|
+
env[key] = output.pipe(
|
|
355
|
+
Output.map((value) =>
|
|
356
|
+
Redacted.isRedacted(value)
|
|
357
|
+
? JSON.stringify({
|
|
358
|
+
_tag: "Redacted",
|
|
359
|
+
value: Redacted.value(value),
|
|
360
|
+
})
|
|
361
|
+
: JSON.stringify(value),
|
|
362
|
+
),
|
|
363
|
+
);
|
|
364
|
+
return key;
|
|
273
365
|
}),
|
|
274
|
-
)
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
)
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
(request: any, env: unknown, context: cf.ExecutionContext) => {
|
|
313
|
-
const event: WorkerEvent = {
|
|
314
|
-
kind: "Cloudflare.Workers.WorkerEvent",
|
|
315
|
-
type,
|
|
316
|
-
input: request,
|
|
317
|
-
env,
|
|
318
|
-
context,
|
|
319
|
-
};
|
|
320
|
-
for (const handler of handlers) {
|
|
321
|
-
const eff = handler(event);
|
|
322
|
-
if (Effect.isEffect(eff)) {
|
|
323
|
-
return eff.pipe(
|
|
324
|
-
Effect.provide(Layer.succeed(ExecutionContext, context)),
|
|
325
|
-
Effect.runPromise,
|
|
326
|
-
);
|
|
366
|
+
serve: <Req = never>(handler: HttpEffect<Req>) =>
|
|
367
|
+
ctx.listen(workersHttpHandler(handler)),
|
|
368
|
+
listen: ((
|
|
369
|
+
handler:
|
|
370
|
+
| Serverless.FunctionListener
|
|
371
|
+
| Effect.Effect<Serverless.FunctionListener>,
|
|
372
|
+
) =>
|
|
373
|
+
Effect.sync(() =>
|
|
374
|
+
Effect.isEffect(handler)
|
|
375
|
+
? listeners.push(handler)
|
|
376
|
+
: listeners.push(Effect.succeed(handler)),
|
|
377
|
+
)) as any as Serverless.FunctionContext["listen"],
|
|
378
|
+
export: (name: string, value: any) =>
|
|
379
|
+
Effect.sync(() => {
|
|
380
|
+
exports[name] = value;
|
|
381
|
+
}),
|
|
382
|
+
exports: Effect.gen(function* () {
|
|
383
|
+
const handlers = yield* Effect.all(listeners, {
|
|
384
|
+
concurrency: "unbounded",
|
|
385
|
+
});
|
|
386
|
+
const handle =
|
|
387
|
+
(type: WorkerEvent["type"]) =>
|
|
388
|
+
(request: any, env: unknown, context: cf.ExecutionContext) => {
|
|
389
|
+
const event: WorkerEvent = {
|
|
390
|
+
kind: "Cloudflare.Workers.WorkerEvent",
|
|
391
|
+
type,
|
|
392
|
+
input: request,
|
|
393
|
+
env,
|
|
394
|
+
context,
|
|
395
|
+
};
|
|
396
|
+
for (const handler of handlers) {
|
|
397
|
+
const eff = handler(event);
|
|
398
|
+
if (Effect.isEffect(eff)) {
|
|
399
|
+
return eff.pipe(
|
|
400
|
+
Effect.provide(Layer.succeed(ExecutionContext, context)),
|
|
401
|
+
Effect.runPromise,
|
|
402
|
+
);
|
|
403
|
+
}
|
|
327
404
|
}
|
|
328
|
-
|
|
329
|
-
|
|
405
|
+
return Promise.reject(new Error("No event handler found"));
|
|
406
|
+
};
|
|
407
|
+
return {
|
|
408
|
+
...exports,
|
|
409
|
+
default: Object.fromEntries(
|
|
410
|
+
ExportedHandlerMethods.map((method) => [method, handle(method)]),
|
|
411
|
+
),
|
|
330
412
|
};
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
),
|
|
336
|
-
};
|
|
337
|
-
}),
|
|
338
|
-
};
|
|
339
|
-
return ctx;
|
|
413
|
+
}),
|
|
414
|
+
};
|
|
415
|
+
return ctx;
|
|
416
|
+
},
|
|
340
417
|
});
|
|
341
418
|
|
|
342
419
|
export const bindWorker = Effect.fnUntraced(function* <Shape, Req = never>(
|
package/src/Cloudflare/index.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
export * from "./Container.ts";
|
|
2
2
|
export * from "./D1/index.ts";
|
|
3
|
-
export *
|
|
3
|
+
export * from "./KV/index.ts";
|
|
4
4
|
export * from "./Providers.ts";
|
|
5
|
-
export *
|
|
5
|
+
export * from "./R2/index.ts";
|
|
6
6
|
export * from "./StageConfig.ts";
|
|
7
7
|
export * from "./Website/index.ts";
|
|
8
8
|
export * from "./Workers/index.ts";
|
package/src/Plan.ts
CHANGED
|
@@ -148,8 +148,13 @@ export type Plan<Output = any> = {
|
|
|
148
148
|
output: Output;
|
|
149
149
|
};
|
|
150
150
|
|
|
151
|
+
export interface MakePlanOptions {
|
|
152
|
+
force?: boolean;
|
|
153
|
+
}
|
|
154
|
+
|
|
151
155
|
export const make = <A>(
|
|
152
156
|
stack: StackSpec<A>,
|
|
157
|
+
options: MakePlanOptions = {},
|
|
153
158
|
): Effect.Effect<Plan<A>, never, State> =>
|
|
154
159
|
// @ts-expect-error
|
|
155
160
|
ensureArtifactStore(
|
|
@@ -486,6 +491,13 @@ export const make = <A>(
|
|
|
486
491
|
: "noop",
|
|
487
492
|
} as UpdateDiff | NoopDiff),
|
|
488
493
|
),
|
|
494
|
+
Effect.map((diff) =>
|
|
495
|
+
options.force && diff.action === "noop"
|
|
496
|
+
? ({
|
|
497
|
+
action: "update",
|
|
498
|
+
} satisfies UpdateDiff)
|
|
499
|
+
: diff,
|
|
500
|
+
),
|
|
489
501
|
);
|
|
490
502
|
|
|
491
503
|
if (oldState.status === "creating") {
|
package/src/Platform.ts
CHANGED
|
@@ -23,7 +23,6 @@ import { Self } from "./Self.ts";
|
|
|
23
23
|
import type { Stack, StackServices } from "./Stack.ts";
|
|
24
24
|
import type { Stage } from "./Stage.ts";
|
|
25
25
|
import { effectClass } from "./Util/effect.ts";
|
|
26
|
-
import type { IsAny } from "./Util/types.ts";
|
|
27
26
|
|
|
28
27
|
export interface PlatformProps {
|
|
29
28
|
/**
|
|
@@ -85,7 +84,6 @@ export interface Platform<
|
|
|
85
84
|
| Exclude<PropsReq | InitReq, Services | PlatformServices>
|
|
86
85
|
>;
|
|
87
86
|
new (_: never): MakeShape<Shape, BaseShape>;
|
|
88
|
-
promise(): PlatformPromise<Self>;
|
|
89
87
|
of(shape: Shape & MainShape): MakeShape<Shape, BaseShape>;
|
|
90
88
|
};
|
|
91
89
|
};
|
|
@@ -108,7 +106,6 @@ export interface Platform<
|
|
|
108
106
|
| Exclude<InitReq, Services | PlatformServices>
|
|
109
107
|
> & {
|
|
110
108
|
new (_: never): MakeShape<Shape, BaseShape>;
|
|
111
|
-
promise(): PlatformPromise<Self>;
|
|
112
109
|
};
|
|
113
110
|
<Shape, PropsReq = never>(
|
|
114
111
|
id: string,
|
|
@@ -129,7 +126,6 @@ export interface Platform<
|
|
|
129
126
|
| Exclude<PropsReq | InitReq, Services | PlatformServices>
|
|
130
127
|
>;
|
|
131
128
|
new (_: never): MakeShape<Shape, BaseShape>;
|
|
132
|
-
promise(): PlatformPromise<Self>;
|
|
133
129
|
} & (<InitReq extends Services | PlatformServices = never>(
|
|
134
130
|
impl: Effect.Effect<Shape, never, InitReq>,
|
|
135
131
|
) => Effect.Effect<
|
|
@@ -140,18 +136,18 @@ export interface Platform<
|
|
|
140
136
|
| Exclude<InitReq, Services | PlatformServices>
|
|
141
137
|
>);
|
|
142
138
|
};
|
|
143
|
-
<PropsReq = never, InitReq extends Services | PlatformServices = never>(
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
): Effect.Effect<
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
>;
|
|
139
|
+
// <PropsReq = never, InitReq extends Services | PlatformServices = never>(
|
|
140
|
+
// id: string,
|
|
141
|
+
// props:
|
|
142
|
+
// | InputProps<Resource["Props"]>
|
|
143
|
+
// | Effect.Effect<InputProps<Resource["Props"]>, never, PropsReq>,
|
|
144
|
+
// ): Effect.Effect<
|
|
145
|
+
// Resource,
|
|
146
|
+
// never,
|
|
147
|
+
// | Provider<Resource>
|
|
148
|
+
// | PropsReq
|
|
149
|
+
// | Exclude<InitReq, Services | PlatformServices>
|
|
150
|
+
// >;
|
|
155
151
|
<
|
|
156
152
|
Shape extends MainShape,
|
|
157
153
|
PropsReq = never,
|
|
@@ -168,9 +164,7 @@ export interface Platform<
|
|
|
168
164
|
| Provider<Resource>
|
|
169
165
|
| PropsReq
|
|
170
166
|
| Exclude<InitReq, Services | PlatformServices>
|
|
171
|
-
|
|
172
|
-
promise(): PlatformPromise<Shape>;
|
|
173
|
-
};
|
|
167
|
+
>;
|
|
174
168
|
}
|
|
175
169
|
|
|
176
170
|
type MakeShape<Shape, BaseShape> = Shape extends never | undefined | void
|
|
@@ -188,7 +182,10 @@ export const Platform = <
|
|
|
188
182
|
>,
|
|
189
183
|
>(
|
|
190
184
|
type: R["Type"],
|
|
191
|
-
|
|
185
|
+
hooks: {
|
|
186
|
+
createExecutionContext: (id: string) => BaseExecutionContext;
|
|
187
|
+
onCreate?: (resource: R, props: any) => Effect.Effect<void>;
|
|
188
|
+
},
|
|
192
189
|
): any => {
|
|
193
190
|
type Props = any;
|
|
194
191
|
type Impl = Effect.Effect<any>;
|
|
@@ -213,7 +210,7 @@ export const Platform = <
|
|
|
213
210
|
} else if (!impl) {
|
|
214
211
|
const cls = makeClass(id, props);
|
|
215
212
|
const asEffect = () =>
|
|
216
|
-
!isTag
|
|
213
|
+
(!isTag
|
|
217
214
|
? // this is a non-tagged resource yielded without providing an implementation
|
|
218
215
|
// e.g.
|
|
219
216
|
// yield* Cloudflare.Worker("id", { main: "./src/worker.ts" })
|
|
@@ -236,7 +233,15 @@ export const Platform = <
|
|
|
236
233
|
onNone: () => resource(id, props),
|
|
237
234
|
onSome: Effect.succeed,
|
|
238
235
|
}),
|
|
239
|
-
)
|
|
236
|
+
)
|
|
237
|
+
).pipe(
|
|
238
|
+
Effect.flatMap(
|
|
239
|
+
(resource) =>
|
|
240
|
+
hooks
|
|
241
|
+
.onCreate?.(resource as R, props)
|
|
242
|
+
.pipe(Effect.map(() => resource)) ?? Effect.succeed(resource),
|
|
243
|
+
),
|
|
244
|
+
);
|
|
240
245
|
return Object.assign(
|
|
241
246
|
function (impl: Impl) {
|
|
242
247
|
return cls.asEffect().pipe(Effect.provide(cls.make(impl)));
|
|
@@ -249,6 +254,8 @@ export const Platform = <
|
|
|
249
254
|
cls,
|
|
250
255
|
{
|
|
251
256
|
asEffect,
|
|
257
|
+
// @ts-expect-error
|
|
258
|
+
pipe: (...args: any[]) => asEffect().pipe(...args),
|
|
252
259
|
[Symbol.iterator]: () => new SingleShotGen({ asEffect }),
|
|
253
260
|
},
|
|
254
261
|
);
|
|
@@ -289,7 +296,7 @@ export const Platform = <
|
|
|
289
296
|
Effect.flatMap(
|
|
290
297
|
Effect.all([
|
|
291
298
|
Effect.isEffect(props) ? props : Effect.succeed(props ?? {}),
|
|
292
|
-
Effect.sync(() => createExecutionContext(id)),
|
|
299
|
+
Effect.sync(() => hooks.createExecutionContext(id)),
|
|
293
300
|
Effect.services<never>(),
|
|
294
301
|
]),
|
|
295
302
|
Effect.fnUntraced(function* ([
|
|
@@ -298,7 +305,15 @@ export const Platform = <
|
|
|
298
305
|
outerServices,
|
|
299
306
|
]) {
|
|
300
307
|
const instance = Object.assign(
|
|
301
|
-
yield* resource(id, props as any)
|
|
308
|
+
yield* resource(id, props as any).pipe(
|
|
309
|
+
Effect.flatMap(
|
|
310
|
+
(resource) =>
|
|
311
|
+
hooks
|
|
312
|
+
.onCreate?.(resource, props)
|
|
313
|
+
.pipe(Effect.map(() => resource)) ??
|
|
314
|
+
Effect.succeed(resource),
|
|
315
|
+
),
|
|
316
|
+
),
|
|
302
317
|
executionContext,
|
|
303
318
|
);
|
|
304
319
|
|
|
@@ -365,34 +380,3 @@ export const Platform = <
|
|
|
365
380
|
}) as any;
|
|
366
381
|
return instance;
|
|
367
382
|
};
|
|
368
|
-
|
|
369
|
-
/**
|
|
370
|
-
* Bridge between the Effect and the Promise.
|
|
371
|
-
*
|
|
372
|
-
* Only map types if needed.
|
|
373
|
-
*
|
|
374
|
-
* TODO(sam): probably over engineering? Maybe just let the user run into the wall of Effect.runPromise and fix? Good friction is good!
|
|
375
|
-
*/
|
|
376
|
-
export type PlatformPromise<Shape> = [
|
|
377
|
-
HasRequirements<Extract<Shape[keyof Shape], Effect.Effect<any, any, any>>>,
|
|
378
|
-
Effect.Services<Extract<Shape[keyof Shape], Effect.Effect<any, any, any>>>,
|
|
379
|
-
] extends [true, never]
|
|
380
|
-
? Promise<Shape>
|
|
381
|
-
: Promise<{
|
|
382
|
-
[key in keyof Shape]: Shape[key] extends (
|
|
383
|
-
...args: infer Args
|
|
384
|
-
) => Effect.Effect<infer A, infer Err, any>
|
|
385
|
-
? (...args: Args) => Effect.Effect<A, Err, never>
|
|
386
|
-
: Shape[key] extends Effect.Effect<infer A, infer Err, infer Req>
|
|
387
|
-
? Req extends never
|
|
388
|
-
? Shape[key]
|
|
389
|
-
: Effect.Effect<A, Err, never>
|
|
390
|
-
: Shape[key];
|
|
391
|
-
}>;
|
|
392
|
-
|
|
393
|
-
type HasRequirements<E extends Effect.Effect<any, any, any>> =
|
|
394
|
-
IsAny<Effect.Services<E>> extends true
|
|
395
|
-
? true
|
|
396
|
-
: Effect.Services<E> extends never
|
|
397
|
-
? false
|
|
398
|
-
: true;
|
package/src/Test/Vitest.ts
CHANGED
|
@@ -148,7 +148,10 @@ const deriveStackName = (testPath: string, suffix: string) => {
|
|
|
148
148
|
const runWithContext = <A, Err>(
|
|
149
149
|
stackName: string,
|
|
150
150
|
effect: Effect.Effect<A, Err, Provided>,
|
|
151
|
-
options: {
|
|
151
|
+
options: {
|
|
152
|
+
state?: Layer.Layer<State.State, never, Stack.Stack>;
|
|
153
|
+
providers?: boolean;
|
|
154
|
+
} = {},
|
|
152
155
|
): Effect.Effect<
|
|
153
156
|
A,
|
|
154
157
|
aws.Credentials.CredentialsError | Config.ConfigError | Err,
|
|
@@ -194,7 +197,9 @@ const runWithContext = <A, Err>(
|
|
|
194
197
|
}).pipe(
|
|
195
198
|
Effect.provide(
|
|
196
199
|
Layer.provideMerge(
|
|
197
|
-
|
|
200
|
+
options.providers === false
|
|
201
|
+
? Layer.empty
|
|
202
|
+
: Layer.mergeAll(awsProviders, cfProviders),
|
|
198
203
|
Layer.provideMerge(alchemy, platform),
|
|
199
204
|
),
|
|
200
205
|
),
|
|
@@ -216,6 +221,7 @@ export function test(
|
|
|
216
221
|
options: {
|
|
217
222
|
timeout?: number;
|
|
218
223
|
state?: Layer.Layer<State.State, never, Stack.Stack>;
|
|
224
|
+
providers?: boolean;
|
|
219
225
|
},
|
|
220
226
|
testCase: Effect.Effect<void, any, Provided>,
|
|
221
227
|
): void;
|
|
@@ -232,6 +238,7 @@ export function test(
|
|
|
232
238
|
{
|
|
233
239
|
timeout?: number;
|
|
234
240
|
state?: Layer.Layer<State.State, never, Stack.Stack>;
|
|
241
|
+
providers?: boolean;
|
|
235
242
|
},
|
|
236
243
|
Effect.Effect<void, any, Provided>,
|
|
237
244
|
]
|
|
@@ -253,6 +260,7 @@ export namespace test {
|
|
|
253
260
|
options: {
|
|
254
261
|
timeout?: number;
|
|
255
262
|
state?: Layer.Layer<State.State, never, Stack.Stack>;
|
|
263
|
+
providers?: boolean;
|
|
256
264
|
},
|
|
257
265
|
testCase: Effect.Effect<void, any, Provided>,
|
|
258
266
|
): void;
|
|
@@ -269,6 +277,7 @@ export namespace test {
|
|
|
269
277
|
{
|
|
270
278
|
timeout?: number;
|
|
271
279
|
state?: Layer.Layer<State.State, never, Stack.Stack>;
|
|
280
|
+
providers?: boolean;
|
|
272
281
|
},
|
|
273
282
|
Effect.Effect<void, any, Provided>,
|
|
274
283
|
]
|
|
@@ -287,6 +296,7 @@ export namespace test {
|
|
|
287
296
|
{
|
|
288
297
|
timeout?: number;
|
|
289
298
|
state?: Layer.Layer<State.State, never, Stack.Stack>;
|
|
299
|
+
providers?: boolean;
|
|
290
300
|
},
|
|
291
301
|
Effect.Effect<void, any, Provided>,
|
|
292
302
|
]
|
|
@@ -436,6 +446,7 @@ export function skip(
|
|
|
436
446
|
options: {
|
|
437
447
|
timeout?: number;
|
|
438
448
|
state?: Layer.Layer<State.State, never, Stack.Stack>;
|
|
449
|
+
providers?: boolean;
|
|
439
450
|
},
|
|
440
451
|
testCase: Effect.Effect<void, any, Provided>,
|
|
441
452
|
): void;
|
|
@@ -452,6 +463,7 @@ export function skip(
|
|
|
452
463
|
{
|
|
453
464
|
timeout?: number;
|
|
454
465
|
state?: Layer.Layer<State.State, never, Stack.Stack>;
|
|
466
|
+
providers?: boolean;
|
|
455
467
|
},
|
|
456
468
|
Effect.Effect<void, any, Provided>,
|
|
457
469
|
]
|
|
@@ -470,6 +482,7 @@ export function skipIf(condition: boolean) {
|
|
|
470
482
|
{
|
|
471
483
|
timeout?: number;
|
|
472
484
|
state?: Layer.Layer<State.State, never, Stack.Stack>;
|
|
485
|
+
providers?: boolean;
|
|
473
486
|
},
|
|
474
487
|
Effect.Effect<void, any, Provided>,
|
|
475
488
|
]
|
package/src/Util/effect.ts
CHANGED
|
@@ -55,3 +55,27 @@ export const taggedFunction = <
|
|
|
55
55
|
},
|
|
56
56
|
toString: () => `${tag.toString()}.${fn.name}`,
|
|
57
57
|
});
|
|
58
|
+
|
|
59
|
+
export type UnwrapEffect<T> =
|
|
60
|
+
T extends Effect.Effect<infer A, any, any> ? A : T;
|
|
61
|
+
|
|
62
|
+
export type ToEffectInterface<T> = {
|
|
63
|
+
raw: T;
|
|
64
|
+
} & {
|
|
65
|
+
[K in keyof T]: T[K] extends (...args: any[]) => any
|
|
66
|
+
? (...args: Parameters<T[K]>) => Effect.Effect<Awaited<ReturnType<T[K]>>>
|
|
67
|
+
: T[K];
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
export const toEffectInterface = <T extends object>(raw: T) =>
|
|
71
|
+
({
|
|
72
|
+
raw,
|
|
73
|
+
...Object.fromEntries(
|
|
74
|
+
Object.entries(raw).map(([key, value]) => [
|
|
75
|
+
key,
|
|
76
|
+
typeof value === "function"
|
|
77
|
+
? (...args: any[]) => Effect.tryPromise(async () => value(...args))
|
|
78
|
+
: value,
|
|
79
|
+
]),
|
|
80
|
+
),
|
|
81
|
+
}) as ToEffectInterface<T>;
|