alchemy 2.0.0-beta.35 → 2.0.0-beta.37
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/exec.js +176 -26
- package/bin/exec.js.map +1 -1
- package/lib/AWS/Lambda/Function.d.ts.map +1 -1
- package/lib/AWS/Lambda/Function.js +22 -2
- package/lib/AWS/Lambda/Function.js.map +1 -1
- package/lib/Apply.d.ts.map +1 -1
- package/lib/Apply.js +6 -1
- package/lib/Apply.js.map +1 -1
- package/lib/Cli/main.d.ts.map +1 -1
- package/lib/Cli/main.js +2 -0
- package/lib/Cli/main.js.map +1 -1
- package/lib/Cloudflare/AnalyticsEngine/AnalyticsEngineDataset.d.ts +54 -0
- package/lib/Cloudflare/AnalyticsEngine/AnalyticsEngineDataset.d.ts.map +1 -0
- package/lib/Cloudflare/AnalyticsEngine/AnalyticsEngineDataset.js +17 -0
- package/lib/Cloudflare/AnalyticsEngine/AnalyticsEngineDataset.js.map +1 -0
- package/lib/Cloudflare/AnalyticsEngine/AnalyticsEngineDatasetBinding.d.ts +37 -0
- package/lib/Cloudflare/AnalyticsEngine/AnalyticsEngineDatasetBinding.d.ts.map +1 -0
- package/lib/Cloudflare/AnalyticsEngine/AnalyticsEngineDatasetBinding.js +45 -0
- package/lib/Cloudflare/AnalyticsEngine/AnalyticsEngineDatasetBinding.js.map +1 -0
- package/lib/Cloudflare/AnalyticsEngine/index.d.ts +3 -0
- package/lib/Cloudflare/AnalyticsEngine/index.d.ts.map +1 -0
- package/lib/Cloudflare/AnalyticsEngine/index.js +3 -0
- package/lib/Cloudflare/AnalyticsEngine/index.js.map +1 -0
- package/lib/Cloudflare/D1/D1Connection.d.ts +47 -3
- package/lib/Cloudflare/D1/D1Connection.d.ts.map +1 -1
- package/lib/Cloudflare/D1/D1Connection.js +62 -5
- package/lib/Cloudflare/D1/D1Connection.js.map +1 -1
- package/lib/Cloudflare/Images/Images.d.ts +103 -0
- package/lib/Cloudflare/Images/Images.d.ts.map +1 -0
- package/lib/Cloudflare/Images/Images.js +82 -0
- package/lib/Cloudflare/Images/Images.js.map +1 -0
- package/lib/Cloudflare/Images/ImagesBinding.d.ts +73 -0
- package/lib/Cloudflare/Images/ImagesBinding.d.ts.map +1 -0
- package/lib/Cloudflare/Images/ImagesBinding.js +83 -0
- package/lib/Cloudflare/Images/ImagesBinding.js.map +1 -0
- package/lib/Cloudflare/Images/index.d.ts +3 -0
- package/lib/Cloudflare/Images/index.d.ts.map +1 -0
- package/lib/Cloudflare/Images/index.js +3 -0
- package/lib/Cloudflare/Images/index.js.map +1 -0
- package/lib/Cloudflare/Local/SidecarHandlers.js +9 -1
- package/lib/Cloudflare/Local/SidecarHandlers.js.map +1 -1
- package/lib/Cloudflare/Providers.d.ts.map +1 -1
- package/lib/Cloudflare/Providers.js +6 -1
- package/lib/Cloudflare/Providers.js.map +1 -1
- package/lib/Cloudflare/R2/R2Bucket.d.ts +135 -0
- package/lib/Cloudflare/R2/R2Bucket.d.ts.map +1 -1
- package/lib/Cloudflare/R2/R2Bucket.js +146 -7
- package/lib/Cloudflare/R2/R2Bucket.js.map +1 -1
- package/lib/Cloudflare/StateStore/Api.d.ts +6 -1
- package/lib/Cloudflare/StateStore/Api.d.ts.map +1 -1
- package/lib/Cloudflare/StateStore/Api.js +23 -1
- package/lib/Cloudflare/StateStore/Api.js.map +1 -1
- package/lib/Cloudflare/StateStore/Store.d.ts +16 -1
- package/lib/Cloudflare/StateStore/Store.d.ts.map +1 -1
- package/lib/Cloudflare/StateStore/Store.js +22 -5
- package/lib/Cloudflare/StateStore/Store.js.map +1 -1
- package/lib/Cloudflare/Workers/CronEventSource.d.ts +35 -0
- package/lib/Cloudflare/Workers/CronEventSource.d.ts.map +1 -0
- package/lib/Cloudflare/Workers/CronEventSource.js +55 -0
- package/lib/Cloudflare/Workers/CronEventSource.js.map +1 -0
- package/lib/Cloudflare/Workers/InferEnv.d.ts +37 -2
- package/lib/Cloudflare/Workers/InferEnv.d.ts.map +1 -1
- package/lib/Cloudflare/Workers/LocalWorkerProvider.d.ts.map +1 -1
- package/lib/Cloudflare/Workers/LocalWorkerProvider.js +2 -1
- package/lib/Cloudflare/Workers/LocalWorkerProvider.js.map +1 -1
- package/lib/Cloudflare/Workers/Rpc.d.ts +76 -3
- package/lib/Cloudflare/Workers/Rpc.d.ts.map +1 -1
- package/lib/Cloudflare/Workers/Rpc.js +197 -13
- package/lib/Cloudflare/Workers/Rpc.js.map +1 -1
- package/lib/Cloudflare/Workers/Worker.d.ts +26 -6
- package/lib/Cloudflare/Workers/Worker.d.ts.map +1 -1
- package/lib/Cloudflare/Workers/Worker.js +183 -41
- package/lib/Cloudflare/Workers/Worker.js.map +1 -1
- package/lib/Cloudflare/Workers/WorkerBundle.d.ts.map +1 -1
- package/lib/Cloudflare/Workers/WorkerBundle.js +15 -5
- package/lib/Cloudflare/Workers/WorkerBundle.js.map +1 -1
- package/lib/Cloudflare/Workers/Workflow.d.ts +50 -47
- package/lib/Cloudflare/Workers/Workflow.d.ts.map +1 -1
- package/lib/Cloudflare/Workers/Workflow.js +41 -35
- package/lib/Cloudflare/Workers/Workflow.js.map +1 -1
- package/lib/Cloudflare/Workers/index.d.ts +1 -0
- package/lib/Cloudflare/Workers/index.d.ts.map +1 -1
- package/lib/Cloudflare/Workers/index.js +1 -0
- package/lib/Cloudflare/Workers/index.js.map +1 -1
- package/lib/Cloudflare/index.d.ts +2 -0
- package/lib/Cloudflare/index.d.ts.map +1 -1
- package/lib/Cloudflare/index.js +2 -0
- package/lib/Cloudflare/index.js.map +1 -1
- package/lib/Deploy.d.ts +4 -1
- package/lib/Deploy.d.ts.map +1 -1
- package/lib/Deploy.js +2 -2
- package/lib/Deploy.js.map +1 -1
- package/lib/Destroy.d.ts +4 -1
- package/lib/Destroy.d.ts.map +1 -1
- package/lib/Destroy.js +2 -2
- package/lib/Destroy.js.map +1 -1
- package/lib/Output.d.ts +45 -5
- package/lib/Output.d.ts.map +1 -1
- package/lib/Output.js +100 -4
- package/lib/Output.js.map +1 -1
- package/lib/Plan.d.ts.map +1 -1
- package/lib/Plan.js +93 -12
- package/lib/Plan.js.map +1 -1
- package/lib/Platform.d.ts.map +1 -1
- package/lib/Platform.js +16 -3
- package/lib/Platform.js.map +1 -1
- package/lib/Ref.d.ts +4 -1
- package/lib/Ref.d.ts.map +1 -1
- package/lib/Ref.js +1 -1
- package/lib/Ref.js.map +1 -1
- package/lib/Resource.d.ts +8 -0
- package/lib/Resource.d.ts.map +1 -1
- package/lib/Resource.js +10 -0
- package/lib/Resource.js.map +1 -1
- package/lib/RuntimeContext.d.ts +3 -1
- package/lib/RuntimeContext.d.ts.map +1 -1
- package/lib/RuntimeContext.js.map +1 -1
- package/lib/Secret.d.ts +50 -0
- package/lib/Secret.d.ts.map +1 -0
- package/lib/Secret.js +23 -0
- package/lib/Secret.js.map +1 -0
- package/lib/Serverless/Function.d.ts +9 -1
- package/lib/Serverless/Function.d.ts.map +1 -1
- package/lib/Stack.d.ts +14 -5
- package/lib/Stack.d.ts.map +1 -1
- package/lib/Stack.js +31 -19
- package/lib/Stack.js.map +1 -1
- package/lib/State/HttpStateApi.d.ts +15 -1
- package/lib/State/HttpStateApi.d.ts.map +1 -1
- package/lib/State/HttpStateApi.js +11 -0
- package/lib/State/HttpStateApi.js.map +1 -1
- package/lib/State/HttpStateStore.d.ts.map +1 -1
- package/lib/State/HttpStateStore.js +11 -0
- package/lib/State/HttpStateStore.js.map +1 -1
- package/lib/State/InMemoryState.d.ts +2 -2
- package/lib/State/InMemoryState.d.ts.map +1 -1
- package/lib/State/InMemoryState.js +12 -6
- package/lib/State/InMemoryState.js.map +1 -1
- package/lib/State/LocalState.d.ts.map +1 -1
- package/lib/State/LocalState.js +11 -2
- package/lib/State/LocalState.js.map +1 -1
- package/lib/State/State.d.ts +21 -0
- package/lib/State/State.d.ts.map +1 -1
- package/lib/Test/Bun.d.ts.map +1 -1
- package/lib/Test/Bun.js +30 -7
- package/lib/Test/Bun.js.map +1 -1
- package/lib/Test/Core.d.ts +17 -2
- package/lib/Test/Core.d.ts.map +1 -1
- package/lib/Test/Core.js +29 -11
- package/lib/Test/Core.js.map +1 -1
- package/lib/Test/Vitest.d.ts.map +1 -1
- package/lib/Test/Vitest.js +26 -5
- package/lib/Test/Vitest.js.map +1 -1
- package/lib/Util/LocalhostDns.d.ts +23 -0
- package/lib/Util/LocalhostDns.d.ts.map +1 -0
- package/lib/Util/LocalhostDns.js +46 -0
- package/lib/Util/LocalhostDns.js.map +1 -0
- package/lib/Util/PlatformServices.d.ts.map +1 -1
- package/lib/Util/PlatformServices.js +2 -0
- package/lib/Util/PlatformServices.js.map +1 -1
- package/lib/Util/effect.d.ts +7 -0
- package/lib/Util/effect.d.ts.map +1 -1
- package/lib/Util/effect.js +6 -0
- package/lib/Util/effect.js.map +1 -1
- package/lib/Variable.d.ts +29 -0
- package/lib/Variable.d.ts.map +1 -0
- package/lib/Variable.js +9 -0
- package/lib/Variable.js.map +1 -0
- package/lib/index.d.ts +3 -0
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +3 -0
- package/lib/index.js.map +1 -1
- package/lib/tsconfig.test.tsbuildinfo +1 -1
- package/package.json +7 -7
- package/src/AWS/Lambda/Function.ts +30 -2
- package/src/Apply.ts +8 -1
- package/src/Cli/main.ts +3 -0
- package/src/Cloudflare/AnalyticsEngine/AnalyticsEngineDataset.ts +82 -0
- package/src/Cloudflare/AnalyticsEngine/AnalyticsEngineDatasetBinding.ts +106 -0
- package/src/Cloudflare/AnalyticsEngine/index.ts +2 -0
- package/src/Cloudflare/D1/D1Connection.ts +95 -12
- package/src/Cloudflare/Images/Images.ts +121 -0
- package/src/Cloudflare/Images/ImagesBinding.ts +192 -0
- package/src/Cloudflare/Images/index.ts +2 -0
- package/src/Cloudflare/Local/SidecarHandlers.ts +9 -1
- package/src/Cloudflare/Providers.ts +8 -0
- package/src/Cloudflare/R2/R2Bucket.ts +271 -7
- package/src/Cloudflare/StateStore/Api.ts +34 -1
- package/src/Cloudflare/StateStore/Store.ts +40 -10
- package/src/Cloudflare/Workers/CronEventSource.ts +89 -0
- package/src/Cloudflare/Workers/InferEnv.ts +94 -16
- package/src/Cloudflare/Workers/LocalWorkerProvider.ts +9 -1
- package/src/Cloudflare/Workers/Rpc.ts +227 -18
- package/src/Cloudflare/Workers/Worker.ts +295 -62
- package/src/Cloudflare/Workers/WorkerBundle.ts +15 -5
- package/src/Cloudflare/Workers/Workflow.ts +74 -70
- package/src/Cloudflare/Workers/index.ts +1 -0
- package/src/Cloudflare/index.ts +2 -0
- package/src/Deploy.ts +5 -1
- package/src/Destroy.ts +5 -1
- package/src/Output.ts +121 -12
- package/src/Plan.ts +101 -12
- package/src/Platform.ts +19 -4
- package/src/Ref.ts +10 -5
- package/src/Resource.ts +22 -0
- package/src/RuntimeContext.ts +1 -0
- package/src/Secret.ts +87 -0
- package/src/Serverless/Function.ts +10 -1
- package/src/Stack.ts +52 -29
- package/src/State/HttpStateApi.ts +21 -0
- package/src/State/HttpStateStore.ts +21 -0
- package/src/State/InMemoryState.ts +29 -8
- package/src/State/LocalState.ts +28 -4
- package/src/State/State.ts +21 -0
- package/src/Test/Bun.ts +43 -7
- package/src/Test/Core.ts +29 -8
- package/src/Test/Vitest.ts +36 -4
- package/src/Util/LocalhostDns.ts +52 -0
- package/src/Util/PlatformServices.ts +2 -0
- package/src/Util/effect.ts +25 -0
- package/src/Variable.ts +44 -0
- package/src/index.ts +3 -0
package/src/Plan.ts
CHANGED
|
@@ -349,8 +349,53 @@ export const make = <A>(
|
|
|
349
349
|
});
|
|
350
350
|
} else if (Output.isLiteralExpr(expr)) {
|
|
351
351
|
return expr.value;
|
|
352
|
+
} else if (Output.isRefExpr(expr)) {
|
|
353
|
+
const refStack = expr.stack ?? stackName;
|
|
354
|
+
const refStage = expr.stage ?? stage;
|
|
355
|
+
const refState = yield* state
|
|
356
|
+
.get({
|
|
357
|
+
stack: refStack,
|
|
358
|
+
stage: refStage,
|
|
359
|
+
fqn: expr.resourceId,
|
|
360
|
+
})
|
|
361
|
+
.pipe(Effect.orDie);
|
|
362
|
+
if (!refState) {
|
|
363
|
+
return yield* Effect.die(
|
|
364
|
+
new Output.InvalidReferenceError({
|
|
365
|
+
message: `Reference to '${expr.resourceId}' in stack '${refStack}' and stage '${refStage}' not found. Have you deployed '${refStage}' of '${refStack}'?`,
|
|
366
|
+
stack: refStack,
|
|
367
|
+
stage: refStage,
|
|
368
|
+
resourceId: expr.resourceId,
|
|
369
|
+
}),
|
|
370
|
+
);
|
|
371
|
+
}
|
|
372
|
+
return refState.attr;
|
|
373
|
+
} else if (Output.isStackRefExpr(expr)) {
|
|
374
|
+
const refStack = expr.stack;
|
|
375
|
+
const refStage = expr.stage ?? stage;
|
|
376
|
+
const output = yield* state
|
|
377
|
+
.getOutput({
|
|
378
|
+
stack: refStack,
|
|
379
|
+
stage: refStage,
|
|
380
|
+
})
|
|
381
|
+
.pipe(Effect.orDie);
|
|
382
|
+
if (output == null) {
|
|
383
|
+
return yield* Effect.die(
|
|
384
|
+
new Output.InvalidReferenceError({
|
|
385
|
+
message: `Reference to stack '${refStack}' at stage '${refStage}' not found. Have you deployed stage '${refStage}' of '${refStack}'?`,
|
|
386
|
+
stack: refStack,
|
|
387
|
+
stage: refStage,
|
|
388
|
+
resourceId: refStack,
|
|
389
|
+
}),
|
|
390
|
+
);
|
|
391
|
+
}
|
|
392
|
+
return output;
|
|
393
|
+
} else if (Output.isNamedExpr(expr)) {
|
|
394
|
+
return yield* resolveOutput(expr.expr);
|
|
352
395
|
}
|
|
353
|
-
return yield* Effect.die(
|
|
396
|
+
return yield* Effect.die(
|
|
397
|
+
new Error("Not implemented yet" + (expr as any).kind),
|
|
398
|
+
);
|
|
354
399
|
});
|
|
355
400
|
|
|
356
401
|
// map of resource FQN -> its downstream dependencies (resources that depend on it)
|
|
@@ -413,16 +458,59 @@ export const make = <A>(
|
|
|
413
458
|
}),
|
|
414
459
|
);
|
|
415
460
|
|
|
416
|
-
//
|
|
461
|
+
// Resources that participate in a cycle when both prop and binding
|
|
462
|
+
// edges are considered. Used below to decide whether an acyclic
|
|
463
|
+
// binding edge should also become a downstream edge.
|
|
464
|
+
const combinedCycleMembers = findCycleMembers(allUpstreamDependencies);
|
|
465
|
+
|
|
466
|
+
// Map FQN -> list of downstream FQNs (resources that depend on this one).
|
|
467
|
+
//
|
|
468
|
+
// Prop edges always become downstream edges — they can't form cycles
|
|
469
|
+
// (the resource graph is a DAG by construction once props are fully
|
|
470
|
+
// resolved). Binding edges become downstream edges too, except when
|
|
471
|
+
// they participate in a cycle in the combined graph: mutual bindings
|
|
472
|
+
// (A binds to B's data, B binds to A's data) intentionally do not
|
|
473
|
+
// create downstream edges, so deletion does not deadlock waiting on
|
|
474
|
+
// each other (see the "binding-only cycles inside a construct" test
|
|
475
|
+
// in `plan.test.ts`).
|
|
476
|
+
//
|
|
477
|
+
// Including acyclic binding edges is required for cloud APIs that
|
|
478
|
+
// enforce the binding at the provider level — e.g. a Cloudflare
|
|
479
|
+
// Worker with a `service` binding to another Worker cannot delete
|
|
480
|
+
// the upstream worker until the downstream worker's binding has been
|
|
481
|
+
// removed. Without the binding edge in `downstream`, the two delete
|
|
482
|
+
// concurrently and the upstream delete fails with
|
|
483
|
+
// `ServiceBindingConflict`.
|
|
417
484
|
const newDownstreamDependencies: {
|
|
418
485
|
[fqn: string]: string[];
|
|
419
486
|
} = Object.fromEntries(
|
|
420
|
-
resources.map((resource) =>
|
|
421
|
-
resource.FQN
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
487
|
+
resources.map((resource) => {
|
|
488
|
+
const upFqn = resource.FQN;
|
|
489
|
+
const downstream: string[] = [];
|
|
490
|
+
for (const [downFqn, upstreams] of Object.entries(
|
|
491
|
+
rawUpstreamDependencies,
|
|
492
|
+
)) {
|
|
493
|
+
if (downFqn === upFqn) continue;
|
|
494
|
+
if (!upstreams.includes(upFqn)) continue;
|
|
495
|
+
const isPropEdge = (newUpstreamDependencies[downFqn] ?? []).includes(
|
|
496
|
+
upFqn,
|
|
497
|
+
);
|
|
498
|
+
if (isPropEdge) {
|
|
499
|
+
downstream.push(downFqn);
|
|
500
|
+
continue;
|
|
501
|
+
}
|
|
502
|
+
// Binding-only edge — exclude when both endpoints sit inside
|
|
503
|
+
// the same SCC of the combined graph.
|
|
504
|
+
if (
|
|
505
|
+
combinedCycleMembers.has(upFqn) &&
|
|
506
|
+
combinedCycleMembers.has(downFqn)
|
|
507
|
+
) {
|
|
508
|
+
continue;
|
|
509
|
+
}
|
|
510
|
+
downstream.push(downFqn);
|
|
511
|
+
}
|
|
512
|
+
return [upFqn, downstream];
|
|
513
|
+
}),
|
|
426
514
|
);
|
|
427
515
|
|
|
428
516
|
const resourceGraph = Object.fromEntries(
|
|
@@ -789,10 +877,11 @@ export const make = <A>(
|
|
|
789
877
|
)).map((update) => [update.resource.FQN, update]),
|
|
790
878
|
) as Plan["resources"];
|
|
791
879
|
|
|
792
|
-
//
|
|
793
|
-
// update node must publish its prior attr early to
|
|
794
|
-
// can simply wait for upstreams like a DAG node
|
|
795
|
-
|
|
880
|
+
// SCC membership of the combined upstream graph. Apply uses it to
|
|
881
|
+
// decide whether an update node must publish its prior attr early to
|
|
882
|
+
// break a cycle, or can simply wait for upstreams like a DAG node
|
|
883
|
+
// (the common case). Computed once, above, for the downstream graph.
|
|
884
|
+
const cycleMembers = combinedCycleMembers;
|
|
796
885
|
|
|
797
886
|
// Detect unsatisfiable dependency cycles among create/replace nodes.
|
|
798
887
|
// Update/noop nodes signal their Deferred before waitForDeps when in a
|
package/src/Platform.ts
CHANGED
|
@@ -242,8 +242,18 @@ export const Platform = <
|
|
|
242
242
|
}),
|
|
243
243
|
)
|
|
244
244
|
).pipe(
|
|
245
|
-
Effect.tap(
|
|
246
|
-
|
|
245
|
+
Effect.tap((resource) =>
|
|
246
|
+
hooks.onCreate
|
|
247
|
+
? Effect.flatMap(
|
|
248
|
+
// `props` may itself be an Effect (e.g. when wrapped by
|
|
249
|
+
// `Cloudflare.Vite` via `Effect.map`); resolve it before
|
|
250
|
+
// handing it to the hook so `onCreate` always sees the
|
|
251
|
+
// plain props object — the second call site (in
|
|
252
|
+
// `cls.make`) already does this.
|
|
253
|
+
Effect.isEffect(props) ? props : Effect.succeed(props ?? {}),
|
|
254
|
+
(resolved) => hooks.onCreate!(resource as R, resolved),
|
|
255
|
+
)
|
|
256
|
+
: Effect.void,
|
|
247
257
|
),
|
|
248
258
|
);
|
|
249
259
|
return Object.assign(
|
|
@@ -324,8 +334,13 @@ export const Platform = <
|
|
|
324
334
|
yield* impl.pipe(
|
|
325
335
|
Effect.flatMap((impl) =>
|
|
326
336
|
impl?.fetch
|
|
327
|
-
?
|
|
328
|
-
|
|
337
|
+
? // Hand the full impl to `serve` so the runtime can
|
|
338
|
+
// expose any non-handler methods on the impl shape
|
|
339
|
+
// (e.g. RPC methods on a Cloudflare Worker) alongside
|
|
340
|
+
// the standard `fetch` handler.
|
|
341
|
+
(runtimeContext.serve?.(impl.fetch, {
|
|
342
|
+
shape: impl as Record<string, unknown>,
|
|
343
|
+
}) ?? Effect.die("No serve handler"))
|
|
329
344
|
: Effect.void,
|
|
330
345
|
),
|
|
331
346
|
Effect.provide(
|
package/src/Ref.ts
CHANGED
|
@@ -22,11 +22,16 @@ export interface RefMetadata<R extends ResourceLike> {
|
|
|
22
22
|
stage?: string;
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
export const ref = <R extends ResourceLike>(
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
25
|
+
export const ref = <R extends ResourceLike>(
|
|
26
|
+
id: string,
|
|
27
|
+
{
|
|
28
|
+
stack,
|
|
29
|
+
stage,
|
|
30
|
+
}: {
|
|
31
|
+
stack?: string;
|
|
32
|
+
stage?: string;
|
|
33
|
+
} = {},
|
|
34
|
+
): Ref<R> => {
|
|
30
35
|
const ref = new Proxy(
|
|
31
36
|
{},
|
|
32
37
|
{
|
package/src/Resource.ts
CHANGED
|
@@ -7,6 +7,7 @@ import type { Input, InputProps } from "./Input.ts";
|
|
|
7
7
|
import { CurrentNamespace, type NamespaceNode } from "./Namespace.ts";
|
|
8
8
|
import * as Output from "./Output.ts";
|
|
9
9
|
import { Provider } from "./Provider.ts";
|
|
10
|
+
import { ref as makeRef } from "./Ref.ts";
|
|
10
11
|
import { RemovalPolicy } from "./RemovalPolicy.ts";
|
|
11
12
|
import { Self } from "./Self.ts";
|
|
12
13
|
import { Stack } from "./Stack.ts";
|
|
@@ -47,6 +48,10 @@ export type ResourceClassWithMethods<
|
|
|
47
48
|
Effect.Effect<ResourceConstructor<R>> & {
|
|
48
49
|
Self: Self<R>;
|
|
49
50
|
Provider: Provider<R>;
|
|
51
|
+
ref(
|
|
52
|
+
id: string,
|
|
53
|
+
options?: { stage?: string; stack?: string },
|
|
54
|
+
): Effect.Effect<R>;
|
|
50
55
|
} & Methods;
|
|
51
56
|
|
|
52
57
|
export type ResourceClass<R extends ResourceLike> = ResourceConstructor<
|
|
@@ -56,6 +61,10 @@ export type ResourceClass<R extends ResourceLike> = ResourceConstructor<
|
|
|
56
61
|
Effect.Effect<ResourceConstructor<R>> & {
|
|
57
62
|
Self: Self<R>;
|
|
58
63
|
Provider: Provider<R>;
|
|
64
|
+
ref(
|
|
65
|
+
id: string,
|
|
66
|
+
options?: { stage?: string; stack?: string },
|
|
67
|
+
): Effect.Effect<R>;
|
|
59
68
|
};
|
|
60
69
|
|
|
61
70
|
export type LogicalId = string;
|
|
@@ -286,6 +295,19 @@ export function Resource<R extends ResourceLike>(
|
|
|
286
295
|
constructor(id, props),
|
|
287
296
|
);
|
|
288
297
|
},
|
|
298
|
+
/**
|
|
299
|
+
* Build a typed reference to a deployed instance of this resource
|
|
300
|
+
* — in the current stack/stage by default, or in another via
|
|
301
|
+
* `options`. Resolves to the same shape as `yield*
|
|
302
|
+
* MyResource("id", props)` so downstream code can read attributes
|
|
303
|
+
* (`ref.someAttr`) exactly the way it would for a locally-declared
|
|
304
|
+
* resource.
|
|
305
|
+
*/
|
|
306
|
+
ref: (
|
|
307
|
+
id: string,
|
|
308
|
+
options?: { stage?: string; stack?: string },
|
|
309
|
+
): Effect.Effect<R> =>
|
|
310
|
+
Effect.succeed(Output.of(makeRef<R>(id, options)) as unknown as R),
|
|
289
311
|
Type: type,
|
|
290
312
|
Provider: ProviderTag,
|
|
291
313
|
Self: self,
|
package/src/RuntimeContext.ts
CHANGED
package/src/Secret.ts
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import * as Config from "effect/Config";
|
|
2
|
+
import * as Effect from "effect/Effect";
|
|
3
|
+
import * as Redacted from "effect/Redacted";
|
|
4
|
+
import * as Output from "./Output.ts";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The shapes a {@link secret} can be derived from.
|
|
8
|
+
*
|
|
9
|
+
* - `string` — wrapped in `Redacted.make` and bound as a literal.
|
|
10
|
+
* - `Redacted<string>` — bound as-is.
|
|
11
|
+
* - `Effect<string | Redacted<string>>` — resolved at deploy time.
|
|
12
|
+
* - `Config<string | Redacted<string>>` — resolved against the active
|
|
13
|
+
* `ConfigProvider` at deploy time.
|
|
14
|
+
*/
|
|
15
|
+
export type SecretInput<R = never> =
|
|
16
|
+
| string
|
|
17
|
+
| Redacted.Redacted<string>
|
|
18
|
+
| Effect.Effect<string | Redacted.Redacted<string>, any, R>
|
|
19
|
+
| Config.Config<string | Redacted.Redacted<string>>;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Bind a secret value into the active {@link RuntimeContext} (e.g. a
|
|
23
|
+
* Cloudflare Worker's `env`) under the given `name` so the resource
|
|
24
|
+
* provider deploys it as a secret (e.g. `secret_text` for Cloudflare).
|
|
25
|
+
*
|
|
26
|
+
* The result is an `Output<Redacted<string>>` — `yield*`-ing it returns
|
|
27
|
+
* an `Accessor<Redacted<string>>` that resolves the value at runtime.
|
|
28
|
+
*
|
|
29
|
+
* @example One-arg shortcut — reads `Config.redacted("API_KEY")`
|
|
30
|
+
* ```ts
|
|
31
|
+
* const apiKey = yield* Alchemy.Secret("API_KEY");
|
|
32
|
+
* ```
|
|
33
|
+
*
|
|
34
|
+
* @example From a literal
|
|
35
|
+
* ```ts
|
|
36
|
+
* const apiKey = yield* Alchemy.Secret("API_KEY", "sk-123");
|
|
37
|
+
* ```
|
|
38
|
+
*
|
|
39
|
+
* @example From an Effect
|
|
40
|
+
* ```ts
|
|
41
|
+
* const apiKey = yield* Alchemy.Secret(
|
|
42
|
+
* "API_KEY",
|
|
43
|
+
* Effect.succeed(Redacted.make("sk-123")),
|
|
44
|
+
* );
|
|
45
|
+
* ```
|
|
46
|
+
*
|
|
47
|
+
* @example From a Config
|
|
48
|
+
* ```ts
|
|
49
|
+
* const apiKey = yield* Alchemy.Secret("API_KEY", Config.redacted("API_KEY"));
|
|
50
|
+
* ```
|
|
51
|
+
*/
|
|
52
|
+
export function Secret(
|
|
53
|
+
name: string,
|
|
54
|
+
): Output.Output<Redacted.Redacted<string>, never>;
|
|
55
|
+
export function Secret<R = never>(
|
|
56
|
+
name: string,
|
|
57
|
+
value: SecretInput<R>,
|
|
58
|
+
): Output.Output<Redacted.Redacted<string>, R>;
|
|
59
|
+
export function Secret<R = never>(
|
|
60
|
+
name: string,
|
|
61
|
+
value?: SecretInput<R>,
|
|
62
|
+
): Output.Output<Redacted.Redacted<string>, R> {
|
|
63
|
+
const source = value ?? Config.redacted(name);
|
|
64
|
+
const base = toOutput<string | Redacted.Redacted<string>, R>(source);
|
|
65
|
+
return Output.named(
|
|
66
|
+
base.pipe(
|
|
67
|
+
Output.map((v) => (Redacted.isRedacted(v) ? v : Redacted.make(v))),
|
|
68
|
+
) as Output.Output<Redacted.Redacted<string>, R>,
|
|
69
|
+
name,
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** @internal */
|
|
74
|
+
export const toOutput = <T, R = never>(
|
|
75
|
+
source: T | Effect.Effect<T, any, R> | Config.Config<T> | Output.Output<T, R>,
|
|
76
|
+
): Output.Output<T, R> => {
|
|
77
|
+
if (Output.isOutput(source)) {
|
|
78
|
+
return source;
|
|
79
|
+
}
|
|
80
|
+
if (Config.isConfig(source)) {
|
|
81
|
+
return Output.asOutput(source.asEffect() as any) as Output.Output<T, R>;
|
|
82
|
+
}
|
|
83
|
+
if (Effect.isEffect(source)) {
|
|
84
|
+
return Output.asOutput(source as any) as Output.Output<T, R>;
|
|
85
|
+
}
|
|
86
|
+
return Output.asOutput(source as T) as Output.Output<T, R>;
|
|
87
|
+
};
|
|
@@ -3,7 +3,16 @@ import type { HttpEffect } from "../Http.ts";
|
|
|
3
3
|
import type { BaseRuntimeContext } from "../RuntimeContext.ts";
|
|
4
4
|
|
|
5
5
|
export interface FunctionContext extends BaseRuntimeContext {
|
|
6
|
-
|
|
6
|
+
/**
|
|
7
|
+
* Register an HTTP handler. The optional `shape` payload is the user's
|
|
8
|
+
* full default-export shape — Cloudflare Workers use it to expose
|
|
9
|
+
* non-handler methods (e.g. `greet`) as RPC methods on the deployed
|
|
10
|
+
* `WorkerEntrypoint`. Other platforms (Lambda, etc.) ignore it.
|
|
11
|
+
*/
|
|
12
|
+
serve<Req = never>(
|
|
13
|
+
handler: HttpEffect<Req>,
|
|
14
|
+
options?: { shape?: Record<string, unknown> },
|
|
15
|
+
): Effect.Effect<void, never, Req>;
|
|
7
16
|
listen<A, Req = never>(
|
|
8
17
|
handler: FunctionListener<A, Req>,
|
|
9
18
|
): Effect.Effect<void, never, Req>;
|
package/src/Stack.ts
CHANGED
|
@@ -6,7 +6,7 @@ import * as Layer from "effect/Layer";
|
|
|
6
6
|
import * as Logger from "effect/Logger";
|
|
7
7
|
import * as Option from "effect/Option";
|
|
8
8
|
import { Path } from "effect/Path";
|
|
9
|
-
import
|
|
9
|
+
import * as Scope from "effect/Scope";
|
|
10
10
|
import * as FetchHttpClient from "effect/unstable/http/FetchHttpClient";
|
|
11
11
|
import type { HttpClient } from "effect/unstable/http/HttpClient";
|
|
12
12
|
import type { ChildProcessSpawner } from "effect/unstable/process/ChildProcessSpawner";
|
|
@@ -18,19 +18,18 @@ import { Profile, ProfileLive } from "./Auth/Profile.ts";
|
|
|
18
18
|
import { Cli } from "./Cli/Cli.ts";
|
|
19
19
|
import type { Input, InputProps } from "./Input.ts";
|
|
20
20
|
import * as Output from "./Output.ts";
|
|
21
|
-
import { ref } from "./Ref.ts";
|
|
22
21
|
import type { ResourceBinding, ResourceLike } from "./Resource.ts";
|
|
23
22
|
import { Stage } from "./Stage.ts";
|
|
24
23
|
import type { State } from "./State/State.ts";
|
|
25
24
|
import { loadConfigProvider } from "./Util/ConfigProvider.ts";
|
|
26
|
-
import { taggedFunction } from "./Util/effect.ts";
|
|
25
|
+
import { effectClass, taggedFunction } from "./Util/effect.ts";
|
|
27
26
|
import { fileLogger } from "./Util/FileLogger.ts";
|
|
28
27
|
import { PlatformServices } from "./Util/PlatformServices.ts";
|
|
29
28
|
|
|
30
29
|
export type StackServices =
|
|
31
30
|
| Stack
|
|
32
31
|
| Stage
|
|
33
|
-
| Scope
|
|
32
|
+
| Scope.Scope
|
|
34
33
|
| FileSystem
|
|
35
34
|
| Path
|
|
36
35
|
| AlchemyContext
|
|
@@ -46,7 +45,7 @@ export type StackEffect<A, Err = never, Req = never> = Effect.Effect<
|
|
|
46
45
|
Err,
|
|
47
46
|
| PlatformServices
|
|
48
47
|
| HttpClient
|
|
49
|
-
| Scope
|
|
48
|
+
| Scope.Scope
|
|
50
49
|
| AuthProviders
|
|
51
50
|
| AlchemyContext
|
|
52
51
|
| Cli
|
|
@@ -98,6 +97,7 @@ export const Stack: Context.ServiceClass<
|
|
|
98
97
|
(stackName: string): Effect.Effect<Self> & {
|
|
99
98
|
new (_: never): Output.ToOutput<Shape>;
|
|
100
99
|
make: <A, Req>(
|
|
100
|
+
options: StackProps<NoInfer<Req>>,
|
|
101
101
|
effect: Effect.Effect<A, never, Req>,
|
|
102
102
|
) => Effect.Effect<CompiledStack<A>>;
|
|
103
103
|
stage: {
|
|
@@ -114,36 +114,46 @@ export const Stack: Context.ServiceClass<
|
|
|
114
114
|
taggedFunction(
|
|
115
115
|
Context.Service<Stack, Omit<StackSpec, "output">>()("Stack"),
|
|
116
116
|
<A, Req>(
|
|
117
|
-
stackName
|
|
118
|
-
options
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
117
|
+
stackName?: string,
|
|
118
|
+
options?: StackProps<NoInfer<Req>>,
|
|
119
|
+
eff?: Effect.Effect<A, never, Req>,
|
|
120
|
+
) => {
|
|
121
|
+
if (!stackName) {
|
|
122
|
+
return (stackName: string) =>
|
|
123
|
+
Object.assign(
|
|
124
|
+
// by default, reference the stack at the "current" stage of the importer
|
|
125
|
+
Output.stackRef<A>(stackName).pipe(effectClass),
|
|
126
|
+
{
|
|
127
|
+
stage: createStageProxy(stackName),
|
|
128
|
+
make: <Req = never>(
|
|
129
|
+
options: StackProps<NoInfer<Req>>,
|
|
130
|
+
eff: Effect.Effect<A, never, Req>,
|
|
131
|
+
) => Stack(stackName, options, eff),
|
|
132
|
+
},
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
return eff!.pipe(
|
|
125
136
|
make({
|
|
126
137
|
name: stackName,
|
|
127
|
-
...options
|
|
138
|
+
...options!,
|
|
128
139
|
}),
|
|
129
140
|
(eff) =>
|
|
130
141
|
Object.assign(eff, {
|
|
131
|
-
stage:
|
|
132
|
-
{},
|
|
133
|
-
{
|
|
134
|
-
get: (_, stage: string) =>
|
|
135
|
-
ref({
|
|
136
|
-
stack: stackName,
|
|
137
|
-
stage,
|
|
138
|
-
id: stackName,
|
|
139
|
-
}),
|
|
140
|
-
},
|
|
141
|
-
),
|
|
142
|
+
stage: createStageProxy(stackName),
|
|
142
143
|
}),
|
|
143
|
-
)
|
|
144
|
+
);
|
|
145
|
+
},
|
|
144
146
|
),
|
|
145
147
|
) as any;
|
|
146
148
|
|
|
149
|
+
const createStageProxy = (stackName: string) =>
|
|
150
|
+
new Proxy(
|
|
151
|
+
{},
|
|
152
|
+
{
|
|
153
|
+
get: (_, stage: string) => Output.stackRef(stackName, { stage }),
|
|
154
|
+
},
|
|
155
|
+
);
|
|
156
|
+
|
|
147
157
|
export interface StackSpec<Output = any> {
|
|
148
158
|
name: string;
|
|
149
159
|
stage: string;
|
|
@@ -283,9 +293,18 @@ export const evalStack = <A, B, Err, Req>(
|
|
|
283
293
|
options: {
|
|
284
294
|
stage: string;
|
|
285
295
|
dev?: boolean;
|
|
296
|
+
/**
|
|
297
|
+
* Optional caller-supplied scope. When provided, scoped resources
|
|
298
|
+
* (e.g. the dev sidecar process) live until the caller closes this
|
|
299
|
+
* scope instead of being torn down when `evalStack` resolves. Used
|
|
300
|
+
* by the test harness so the sidecar survives across `beforeAll`,
|
|
301
|
+
* tests, and `afterAll`. When omitted, behaves as before with a
|
|
302
|
+
* private scope closed on completion.
|
|
303
|
+
*/
|
|
304
|
+
scope?: Scope.Scope;
|
|
286
305
|
},
|
|
287
|
-
) =>
|
|
288
|
-
Effect.gen(function* () {
|
|
306
|
+
) => {
|
|
307
|
+
const body = Effect.gen(function* () {
|
|
289
308
|
const stack = yield* effect;
|
|
290
309
|
const configProvider = yield* loadConfigProvider(Option.none());
|
|
291
310
|
|
|
@@ -305,5 +324,9 @@ export const evalStack = <A, B, Err, Req>(
|
|
|
305
324
|
),
|
|
306
325
|
Effect.provide(Layer.succeed(Stage, options.stage)),
|
|
307
326
|
Effect.provide(Layer.provideMerge(alchemy({ dev: options.dev }), platform)),
|
|
308
|
-
Effect.scoped,
|
|
309
327
|
);
|
|
328
|
+
|
|
329
|
+
return options.scope === undefined
|
|
330
|
+
? Effect.scoped(body)
|
|
331
|
+
: Scope.provide(body, options.scope);
|
|
332
|
+
};
|
|
@@ -155,6 +155,25 @@ export const GetReplacedResources = HttpApiEndpoint.get(
|
|
|
155
155
|
},
|
|
156
156
|
);
|
|
157
157
|
|
|
158
|
+
export const GetStackOutput = HttpApiEndpoint.get(
|
|
159
|
+
"getStackOutput",
|
|
160
|
+
"/state/stacks/:stack/stages/:stage/output",
|
|
161
|
+
{
|
|
162
|
+
params: StackStage,
|
|
163
|
+
success: Schema.UndefinedOr(ResourceStateSchema),
|
|
164
|
+
},
|
|
165
|
+
);
|
|
166
|
+
|
|
167
|
+
export const SetStackOutput = HttpApiEndpoint.put(
|
|
168
|
+
"setStackOutput",
|
|
169
|
+
"/state/stacks/:stack/stages/:stage/output",
|
|
170
|
+
{
|
|
171
|
+
params: StackStage,
|
|
172
|
+
payload: ResourceStateSchema,
|
|
173
|
+
success: ResourceStateSchema,
|
|
174
|
+
},
|
|
175
|
+
);
|
|
176
|
+
|
|
158
177
|
/** Response shape for the unauthenticated `/version` probe. */
|
|
159
178
|
export const VersionResponse = Schema.Struct({
|
|
160
179
|
version: Schema.Number,
|
|
@@ -179,6 +198,8 @@ export class StateGroup extends HttpApiGroup.make("state")
|
|
|
179
198
|
.add(DeleteState)
|
|
180
199
|
.add(GetReplacedResources)
|
|
181
200
|
.add(DeleteStack)
|
|
201
|
+
.add(GetStackOutput)
|
|
202
|
+
.add(SetStackOutput)
|
|
182
203
|
.middleware(StateAuth) {}
|
|
183
204
|
|
|
184
205
|
export class VersionGroup extends HttpApiGroup.make("version").add(
|
|
@@ -129,6 +129,27 @@ export const makeHttpStateStore = ({
|
|
|
129
129
|
query: request.stage === undefined ? {} : { stage: request.stage },
|
|
130
130
|
})
|
|
131
131
|
.pipe(Effect.asVoid, mapStateStoreError),
|
|
132
|
+
getOutput: (request) =>
|
|
133
|
+
state
|
|
134
|
+
.getStackOutput({
|
|
135
|
+
params: { stack: request.stack, stage: request.stage },
|
|
136
|
+
})
|
|
137
|
+
.pipe(
|
|
138
|
+
Effect.map((s) =>
|
|
139
|
+
s == null ? undefined : reviveStateRecursive(s),
|
|
140
|
+
),
|
|
141
|
+
mapStateStoreError,
|
|
142
|
+
),
|
|
143
|
+
setOutput: (request) =>
|
|
144
|
+
state
|
|
145
|
+
.setStackOutput({
|
|
146
|
+
params: { stack: request.stack, stage: request.stage },
|
|
147
|
+
payload: encodeState(request.value as any),
|
|
148
|
+
})
|
|
149
|
+
.pipe(
|
|
150
|
+
Effect.map(() => request.value),
|
|
151
|
+
mapStateStoreError,
|
|
152
|
+
),
|
|
132
153
|
};
|
|
133
154
|
return service;
|
|
134
155
|
});
|
|
@@ -13,14 +13,18 @@ export const inMemoryState = (
|
|
|
13
13
|
StackId,
|
|
14
14
|
Record<StageId, Record<Fqn, ResourceState>>
|
|
15
15
|
> = {},
|
|
16
|
+
initialOutputs: Record<StackId, Record<StageId, unknown>> = {},
|
|
16
17
|
) =>
|
|
17
18
|
Layer.effect(
|
|
18
19
|
State,
|
|
19
|
-
Effect.succeed(InMemoryService(initialState)).pipe(
|
|
20
|
+
Effect.succeed(InMemoryService(initialState, initialOutputs)).pipe(
|
|
21
|
+
recordStateStoreInit,
|
|
22
|
+
),
|
|
20
23
|
);
|
|
21
24
|
|
|
22
25
|
export const InMemoryService = (
|
|
23
26
|
state: Record<StackId, Record<StageId, Record<Fqn, ResourceState>>> = {},
|
|
27
|
+
outputs: Record<StackId, Record<StageId, unknown>> = {},
|
|
24
28
|
) =>
|
|
25
29
|
State.of({
|
|
26
30
|
id: "inmemory",
|
|
@@ -60,12 +64,13 @@ export const InMemoryService = (
|
|
|
60
64
|
stage: string;
|
|
61
65
|
fqn: string;
|
|
62
66
|
value: V;
|
|
63
|
-
}) =>
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
67
|
+
}) =>
|
|
68
|
+
Effect.sync(() => {
|
|
69
|
+
const stackState = (state[stack] ??= {});
|
|
70
|
+
const stageState = (stackState[stage] ??= {});
|
|
71
|
+
stageState[fqn] = value;
|
|
72
|
+
return value;
|
|
73
|
+
}),
|
|
69
74
|
delete: ({
|
|
70
75
|
stack,
|
|
71
76
|
stage,
|
|
@@ -74,7 +79,7 @@ export const InMemoryService = (
|
|
|
74
79
|
stack: string;
|
|
75
80
|
stage: string;
|
|
76
81
|
fqn: string;
|
|
77
|
-
}) => Effect.
|
|
82
|
+
}) => Effect.sync(() => delete state[stack]?.[stage]?.[fqn]),
|
|
78
83
|
deleteStack: ({ stack, stage }: { stack: string; stage?: string }) =>
|
|
79
84
|
Effect.sync(() => {
|
|
80
85
|
if (stage === undefined) {
|
|
@@ -87,4 +92,20 @@ export const InMemoryService = (
|
|
|
87
92
|
Effect.succeed(
|
|
88
93
|
Array.from(Object.keys(state[stack]?.[stage] ?? {}) ?? []),
|
|
89
94
|
),
|
|
95
|
+
getOutput: ({ stack, stage }: { stack: string; stage: string }) =>
|
|
96
|
+
Effect.succeed(outputs[stack]?.[stage]),
|
|
97
|
+
setOutput: ({
|
|
98
|
+
stack,
|
|
99
|
+
stage,
|
|
100
|
+
value,
|
|
101
|
+
}: {
|
|
102
|
+
stack: string;
|
|
103
|
+
stage: string;
|
|
104
|
+
value: unknown;
|
|
105
|
+
}) =>
|
|
106
|
+
Effect.sync(() => {
|
|
107
|
+
const stackOutputs = (outputs[stack] ??= {});
|
|
108
|
+
stackOutputs[stage] = value;
|
|
109
|
+
return value;
|
|
110
|
+
}),
|
|
90
111
|
});
|