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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "alchemy",
|
|
3
|
-
"version": "2.0.0-beta.
|
|
3
|
+
"version": "2.0.0-beta.37",
|
|
4
4
|
"homepage": "https://alchemy.run",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Sam Goodwin <sam@alchemy.run>",
|
|
@@ -275,14 +275,14 @@
|
|
|
275
275
|
"@ai-sdk/provider": "^3.0.8",
|
|
276
276
|
"@aws-sdk/credential-providers": "^3.0.0",
|
|
277
277
|
"@clack/prompts": "^0.11.0",
|
|
278
|
-
"@distilled.cloud/aws": "^0.19.
|
|
279
|
-
"@distilled.cloud/axiom": "^0.19.
|
|
280
|
-
"@distilled.cloud/cloudflare": "^0.19.
|
|
278
|
+
"@distilled.cloud/aws": "^0.19.1",
|
|
279
|
+
"@distilled.cloud/axiom": "^0.19.1",
|
|
280
|
+
"@distilled.cloud/cloudflare": "^0.19.1",
|
|
281
281
|
"@distilled.cloud/cloudflare-rolldown-plugin": "0.3.0",
|
|
282
282
|
"@distilled.cloud/cloudflare-runtime": "0.3.2",
|
|
283
283
|
"@distilled.cloud/cloudflare-vite-plugin": "0.2.0",
|
|
284
|
-
"@distilled.cloud/core": "^0.19.
|
|
285
|
-
"@distilled.cloud/neon": "^0.19.
|
|
284
|
+
"@distilled.cloud/core": "^0.19.1",
|
|
285
|
+
"@distilled.cloud/neon": "^0.19.1",
|
|
286
286
|
"pg": "^8.13.0",
|
|
287
287
|
"@effect/vitest": ">=4.0.0-beta.60 || >=4.0.0",
|
|
288
288
|
"@libsql/client": "^0.17.0",
|
|
@@ -291,7 +291,6 @@
|
|
|
291
291
|
"@smithy/shared-ini-file-loader": "^4.3.4",
|
|
292
292
|
"@smithy/types": "^4.8.1",
|
|
293
293
|
"@types/aws-lambda": "^8.10.152",
|
|
294
|
-
"@types/libsodium-wrappers": "^0.8.2",
|
|
295
294
|
"ai": "^6.0.62",
|
|
296
295
|
"aws4fetch": "^1.0.20",
|
|
297
296
|
"capnweb": "^0.6.1",
|
|
@@ -309,6 +308,7 @@
|
|
|
309
308
|
"react": "^19.2.0",
|
|
310
309
|
"rolldown": "1.0.0-rc.17",
|
|
311
310
|
"sonda": "^0.11.1",
|
|
311
|
+
"undici": "^7.16.0",
|
|
312
312
|
"web-tree-sitter": "0.25.10",
|
|
313
313
|
"yaml": "^2.0.0"
|
|
314
314
|
},
|
|
@@ -10,6 +10,7 @@ import * as Context from "effect/Context";
|
|
|
10
10
|
import * as Effect from "effect/Effect";
|
|
11
11
|
import * as FileSystem from "effect/FileSystem";
|
|
12
12
|
import * as Option from "effect/Option";
|
|
13
|
+
import * as Redacted from "effect/Redacted";
|
|
13
14
|
import * as Schedule from "effect/Schedule";
|
|
14
15
|
import * as Stream from "effect/Stream";
|
|
15
16
|
import type * as rolldown from "rolldown";
|
|
@@ -368,7 +369,21 @@ export const Function: Platform<
|
|
|
368
369
|
set: (id: string, output: Output.Output) =>
|
|
369
370
|
Effect.sync(() => {
|
|
370
371
|
const key = id.replaceAll(/[^a-zA-Z0-9]/g, "_");
|
|
371
|
-
|
|
372
|
+
// Preserve `Redacted`-ness across the Output → Lambda env var
|
|
373
|
+
// round-trip. `JSON.stringify(Redacted)` would emit the literal
|
|
374
|
+
// string `"<redacted>"` and lose the value, so secrets are
|
|
375
|
+
// serialized with a `{_tag: "Redacted", value: ...}` marker
|
|
376
|
+
// that the runtime `get` path detects and rebuilds.
|
|
377
|
+
env[key] = output.pipe(
|
|
378
|
+
Output.map((value) =>
|
|
379
|
+
Redacted.isRedacted(value)
|
|
380
|
+
? JSON.stringify({
|
|
381
|
+
_tag: "Redacted",
|
|
382
|
+
value: Redacted.value(value),
|
|
383
|
+
})
|
|
384
|
+
: JSON.stringify(value),
|
|
385
|
+
),
|
|
386
|
+
);
|
|
372
387
|
return key;
|
|
373
388
|
}),
|
|
374
389
|
get: <T>(key: string) =>
|
|
@@ -377,7 +392,20 @@ export const Function: Platform<
|
|
|
377
392
|
.pipe(
|
|
378
393
|
Effect.flatMap((val) =>
|
|
379
394
|
Effect.try({
|
|
380
|
-
try: () =>
|
|
395
|
+
try: () => {
|
|
396
|
+
const value = JSON.parse(val);
|
|
397
|
+
if (
|
|
398
|
+
value !== null &&
|
|
399
|
+
typeof value === "object" &&
|
|
400
|
+
(value as { _tag?: unknown })._tag === "Redacted" &&
|
|
401
|
+
"value" in (value as object)
|
|
402
|
+
) {
|
|
403
|
+
return Redacted.make(
|
|
404
|
+
(value as { value: unknown }).value,
|
|
405
|
+
) as unknown as T;
|
|
406
|
+
}
|
|
407
|
+
return value as T;
|
|
408
|
+
},
|
|
381
409
|
catch: () => val, // assume it's just a string
|
|
382
410
|
}),
|
|
383
411
|
),
|
package/src/Apply.ts
CHANGED
|
@@ -185,7 +185,14 @@ export const apply = <P extends Plan>(
|
|
|
185
185
|
const outputs = Object.fromEntries(
|
|
186
186
|
Object.entries(tracker).map(([fqn, t]) => [fqn, t.output]),
|
|
187
187
|
);
|
|
188
|
-
|
|
188
|
+
const resolved = yield* Output.evaluate(plan.output, outputs);
|
|
189
|
+
|
|
190
|
+
// Persist the stack's evaluated outputs so cross-stack references
|
|
191
|
+
// (`yield* OtherStack` / `OtherStack.stage.<name>` / `Output.stackRef`)
|
|
192
|
+
// can read them back out of the state store.
|
|
193
|
+
yield* state.setOutput({ stack: stackName, stage, value: resolved });
|
|
194
|
+
|
|
195
|
+
return resolved;
|
|
189
196
|
}).pipe(
|
|
190
197
|
ensureArtifactStore,
|
|
191
198
|
Effect.withSpan("apply", {
|
package/src/Cli/main.ts
CHANGED
|
@@ -8,9 +8,12 @@ import { AlchemyContextLive } from "alchemy/AlchemyContext";
|
|
|
8
8
|
import { CredentialsStoreLive } from "alchemy/Auth/Credentials";
|
|
9
9
|
import { ProfileLive } from "alchemy/Auth/Profile";
|
|
10
10
|
import { TelemetryLive } from "alchemy/Telemetry/Layer";
|
|
11
|
+
import { installLocalhostDns } from "alchemy/Util/LocalhostDns";
|
|
11
12
|
import { PlatformServices } from "alchemy/Util/PlatformServices";
|
|
12
13
|
import packageJson from "../../package.json" with { type: "json" };
|
|
13
14
|
|
|
15
|
+
installLocalhostDns();
|
|
16
|
+
|
|
14
17
|
import { checkLatestVersion } from "./checkVersion.ts";
|
|
15
18
|
import { handleCancellation } from "./commands/_shared.ts";
|
|
16
19
|
import { awsCommand } from "./commands/aws.ts";
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import * as Effect from "effect/Effect";
|
|
2
|
+
import { AnalyticsEngineDatasetBinding } from "./AnalyticsEngineDatasetBinding.ts";
|
|
3
|
+
|
|
4
|
+
type AnalyticsEngineDatasetTypeId = typeof AnalyticsEngineDatasetTypeId;
|
|
5
|
+
const AnalyticsEngineDatasetTypeId =
|
|
6
|
+
"Cloudflare.AnalyticsEngineDataset" as const;
|
|
7
|
+
|
|
8
|
+
export type AnalyticsEngineDatasetProps = {
|
|
9
|
+
/**
|
|
10
|
+
* Dataset name. If omitted, the logical ID is used.
|
|
11
|
+
*/
|
|
12
|
+
dataset?: string;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* A Cloudflare Workers Analytics Engine dataset binding.
|
|
17
|
+
*
|
|
18
|
+
* Analytics Engine datasets are configured as Worker bindings. The binding
|
|
19
|
+
* exposes `writeDataPoint()` at runtime and does not require separate
|
|
20
|
+
* provisioning through the Cloudflare API.
|
|
21
|
+
*
|
|
22
|
+
* @resource
|
|
23
|
+
*
|
|
24
|
+
* @section Binding to a Worker
|
|
25
|
+
* @example Basic Analytics Engine binding
|
|
26
|
+
* ```typescript
|
|
27
|
+
* const Analytics = yield* Cloudflare.AnalyticsEngineDataset("Analytics", {
|
|
28
|
+
* dataset: "app-events",
|
|
29
|
+
* });
|
|
30
|
+
*
|
|
31
|
+
* export const Worker = Cloudflare.Worker("Worker", {
|
|
32
|
+
* main: "./src/worker.ts",
|
|
33
|
+
* bindings: { Analytics },
|
|
34
|
+
* });
|
|
35
|
+
* ```
|
|
36
|
+
*
|
|
37
|
+
* @example Effect-style worker
|
|
38
|
+
* ```typescript
|
|
39
|
+
* const analytics = yield* Cloudflare.AnalyticsEngineDataset.bind(Analytics);
|
|
40
|
+
* yield* analytics.writeDataPoint({ blobs: ["signup"] });
|
|
41
|
+
* ```
|
|
42
|
+
*/
|
|
43
|
+
export type AnalyticsEngineDataset = {
|
|
44
|
+
kind: AnalyticsEngineDatasetTypeId;
|
|
45
|
+
name: string;
|
|
46
|
+
dataset: string;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export const isAnalyticsEngineDataset = (
|
|
50
|
+
value: unknown,
|
|
51
|
+
): value is AnalyticsEngineDataset =>
|
|
52
|
+
typeof value === "object" &&
|
|
53
|
+
value !== null &&
|
|
54
|
+
"kind" in value &&
|
|
55
|
+
(value as AnalyticsEngineDataset).kind === AnalyticsEngineDatasetTypeId;
|
|
56
|
+
|
|
57
|
+
export const AnalyticsEngineDataset: {
|
|
58
|
+
(
|
|
59
|
+
name: string,
|
|
60
|
+
props?: AnalyticsEngineDatasetProps,
|
|
61
|
+
): Effect.Effect<AnalyticsEngineDataset>;
|
|
62
|
+
/**
|
|
63
|
+
* Bind Analytics Engine to the surrounding Worker, returning an
|
|
64
|
+
* Effect-native client with access to the native Workers runtime binding.
|
|
65
|
+
*/
|
|
66
|
+
bind: typeof AnalyticsEngineDatasetBinding.bind;
|
|
67
|
+
} = Object.assign(
|
|
68
|
+
Effect.fnUntraced(function* (
|
|
69
|
+
name: string,
|
|
70
|
+
props?: AnalyticsEngineDatasetProps,
|
|
71
|
+
) {
|
|
72
|
+
return {
|
|
73
|
+
kind: AnalyticsEngineDatasetTypeId,
|
|
74
|
+
name,
|
|
75
|
+
dataset: props?.dataset ?? name,
|
|
76
|
+
} satisfies AnalyticsEngineDataset;
|
|
77
|
+
}),
|
|
78
|
+
{
|
|
79
|
+
bind: (...args: Parameters<typeof AnalyticsEngineDatasetBinding.bind>) =>
|
|
80
|
+
AnalyticsEngineDatasetBinding.bind(...args),
|
|
81
|
+
},
|
|
82
|
+
);
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import * as Data from "effect/Data";
|
|
2
|
+
import * as Effect from "effect/Effect";
|
|
3
|
+
import * as Layer from "effect/Layer";
|
|
4
|
+
import * as Binding from "../../Binding.ts";
|
|
5
|
+
import type { ResourceLike } from "../../Resource.ts";
|
|
6
|
+
import { isWorker, WorkerEnvironment } from "../Workers/Worker.ts";
|
|
7
|
+
import type { AnalyticsEngineDataset as AnalyticsEngineDatasetLike } from "./AnalyticsEngineDataset.ts";
|
|
8
|
+
|
|
9
|
+
export interface AnalyticsEngineDataPoint {
|
|
10
|
+
indexes?: string[];
|
|
11
|
+
blobs?: string[];
|
|
12
|
+
doubles?: number[];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface RuntimeAnalyticsEngineDataset {
|
|
16
|
+
writeDataPoint(dataPoint: AnalyticsEngineDataPoint): void;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export class AnalyticsEngineDatasetError extends Data.TaggedError(
|
|
20
|
+
"AnalyticsEngineDatasetError",
|
|
21
|
+
)<{
|
|
22
|
+
message: string;
|
|
23
|
+
cause: Error;
|
|
24
|
+
}> {}
|
|
25
|
+
|
|
26
|
+
export interface AnalyticsEngineDatasetClient {
|
|
27
|
+
raw: Effect.Effect<RuntimeAnalyticsEngineDataset, never, WorkerEnvironment>;
|
|
28
|
+
writeDataPoint(
|
|
29
|
+
dataPoint: AnalyticsEngineDataPoint,
|
|
30
|
+
): Effect.Effect<void, AnalyticsEngineDatasetError, WorkerEnvironment>;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export class AnalyticsEngineDatasetBinding extends Binding.Service<
|
|
34
|
+
AnalyticsEngineDatasetBinding,
|
|
35
|
+
(
|
|
36
|
+
dataset: AnalyticsEngineDatasetLike,
|
|
37
|
+
) => Effect.Effect<AnalyticsEngineDatasetClient>
|
|
38
|
+
>()("Cloudflare.AnalyticsEngineDataset.Binding") {}
|
|
39
|
+
|
|
40
|
+
export const AnalyticsEngineDatasetBindingLive = Layer.effect(
|
|
41
|
+
AnalyticsEngineDatasetBinding,
|
|
42
|
+
Effect.gen(function* () {
|
|
43
|
+
const bind = yield* AnalyticsEngineDatasetBindingPolicy;
|
|
44
|
+
|
|
45
|
+
return Effect.fnUntraced(function* (dataset: AnalyticsEngineDatasetLike) {
|
|
46
|
+
yield* bind(dataset);
|
|
47
|
+
|
|
48
|
+
const raw = WorkerEnvironment.asEffect().pipe(
|
|
49
|
+
Effect.map(
|
|
50
|
+
(env) =>
|
|
51
|
+
(env as Record<string, RuntimeAnalyticsEngineDataset>)[
|
|
52
|
+
dataset.name
|
|
53
|
+
]!,
|
|
54
|
+
),
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
return {
|
|
58
|
+
raw,
|
|
59
|
+
writeDataPoint: (dataPoint) =>
|
|
60
|
+
raw.pipe(
|
|
61
|
+
Effect.flatMap((raw) =>
|
|
62
|
+
Effect.try({
|
|
63
|
+
try: () => raw.writeDataPoint(dataPoint),
|
|
64
|
+
catch: (error: any) =>
|
|
65
|
+
new AnalyticsEngineDatasetError({
|
|
66
|
+
message: error?.message ?? "Unknown error",
|
|
67
|
+
cause: error,
|
|
68
|
+
}),
|
|
69
|
+
}),
|
|
70
|
+
),
|
|
71
|
+
),
|
|
72
|
+
} satisfies AnalyticsEngineDatasetClient;
|
|
73
|
+
});
|
|
74
|
+
}),
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
export class AnalyticsEngineDatasetBindingPolicy extends Binding.Policy<
|
|
78
|
+
AnalyticsEngineDatasetBindingPolicy,
|
|
79
|
+
(dataset: AnalyticsEngineDatasetLike) => Effect.Effect<void>
|
|
80
|
+
>()("Cloudflare.AnalyticsEngineDataset.Binding") {}
|
|
81
|
+
|
|
82
|
+
export const AnalyticsEngineDatasetBindingPolicyLive =
|
|
83
|
+
AnalyticsEngineDatasetBindingPolicy.layer.succeed(
|
|
84
|
+
Effect.fnUntraced(function* (
|
|
85
|
+
host: ResourceLike,
|
|
86
|
+
dataset: AnalyticsEngineDatasetLike,
|
|
87
|
+
) {
|
|
88
|
+
if (isWorker(host)) {
|
|
89
|
+
yield* host.bind(dataset.name, {
|
|
90
|
+
bindings: [
|
|
91
|
+
{
|
|
92
|
+
type: "analytics_engine",
|
|
93
|
+
name: dataset.name,
|
|
94
|
+
dataset: dataset.dataset,
|
|
95
|
+
},
|
|
96
|
+
],
|
|
97
|
+
});
|
|
98
|
+
} else {
|
|
99
|
+
return yield* Effect.die(
|
|
100
|
+
new Error(
|
|
101
|
+
`AnalyticsEngineDatasetBinding does not support runtime '${host.Type}'`,
|
|
102
|
+
),
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
}),
|
|
106
|
+
);
|
|
@@ -7,6 +7,86 @@ import { WorkerEnvironment } from "../Workers/Worker.ts";
|
|
|
7
7
|
import type { D1Database } from "./D1Database.ts";
|
|
8
8
|
import { DatabaseBinding } from "./D1DatabaseBinding.ts";
|
|
9
9
|
|
|
10
|
+
/**
|
|
11
|
+
* Effect-native wrapper around a Cloudflare D1 prepared statement.
|
|
12
|
+
*
|
|
13
|
+
* Construction (`prepare`) and binding (`bind`) are synchronous —
|
|
14
|
+
* they're plan builders and don't touch D1. Only the executors
|
|
15
|
+
* (`all`, `first`, `run`, `raw`) round-trip to the database, and
|
|
16
|
+
* those return Effects so they participate in the Effect runtime.
|
|
17
|
+
*/
|
|
18
|
+
export class D1PreparedStatement {
|
|
19
|
+
/** @internal */
|
|
20
|
+
constructor(
|
|
21
|
+
private readonly query: string,
|
|
22
|
+
private readonly binds: ReadonlyArray<unknown>,
|
|
23
|
+
private readonly rawEff: Effect.Effect<runtime.D1Database>,
|
|
24
|
+
) {}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Return a new prepared statement bound to `values`. Subsequent
|
|
28
|
+
* calls replace the previous binding, matching the underlying
|
|
29
|
+
* Cloudflare runtime semantics.
|
|
30
|
+
*/
|
|
31
|
+
bind(...values: unknown[]): D1PreparedStatement {
|
|
32
|
+
return new D1PreparedStatement(this.query, values, this.rawEff);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Run the query and return all matching rows. */
|
|
36
|
+
all<T = unknown>(): Effect.Effect<runtime.D1Result<T>> {
|
|
37
|
+
return this.withRuntime((stmt) => stmt.all<T>());
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Run the query and return the first row, or `null` if no rows. */
|
|
41
|
+
first<T = unknown>(): Effect.Effect<T | null>;
|
|
42
|
+
first<T = unknown>(column: string): Effect.Effect<T | null>;
|
|
43
|
+
first<T = unknown>(column?: string): Effect.Effect<T | null> {
|
|
44
|
+
return this.withRuntime((stmt) =>
|
|
45
|
+
column !== undefined ? stmt.first<T>(column) : stmt.first<T>(),
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Run the query as a mutation; returns row metadata. */
|
|
50
|
+
run<T = unknown>(): Effect.Effect<runtime.D1Result<T>> {
|
|
51
|
+
return this.withRuntime((stmt) => stmt.run<T>());
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Run the query and return rows as flat arrays. */
|
|
55
|
+
raw<T = unknown[]>(): Effect.Effect<T[]>;
|
|
56
|
+
raw<T = unknown[]>(options: {
|
|
57
|
+
columnNames: true;
|
|
58
|
+
}): Effect.Effect<[string[], ...T[]]>;
|
|
59
|
+
raw<T = unknown[]>(options?: {
|
|
60
|
+
columnNames: true;
|
|
61
|
+
}): Effect.Effect<T[] | [string[], ...T[]]> {
|
|
62
|
+
return this.withRuntime((stmt) =>
|
|
63
|
+
options
|
|
64
|
+
? stmt.raw<T>(options)
|
|
65
|
+
: (stmt.raw<T>() as Promise<T[] | [string[], ...T[]]>),
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Materialize the underlying Cloudflare prepared statement against
|
|
71
|
+
* a concrete D1 binding. Used by `D1ConnectionClient.batch` to
|
|
72
|
+
* collect statements for a single transactional call.
|
|
73
|
+
*
|
|
74
|
+
* @internal
|
|
75
|
+
*/
|
|
76
|
+
_build(raw: runtime.D1Database): runtime.D1PreparedStatement {
|
|
77
|
+
const stmt = raw.prepare(this.query);
|
|
78
|
+
return this.binds.length > 0 ? stmt.bind(...this.binds) : stmt;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
private withRuntime<A>(
|
|
82
|
+
fn: (stmt: runtime.D1PreparedStatement) => Promise<A>,
|
|
83
|
+
): Effect.Effect<A> {
|
|
84
|
+
return Effect.flatMap(this.rawEff, (raw) =>
|
|
85
|
+
Effect.promise(() => fn(this._build(raw))),
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
10
90
|
export interface D1ConnectionClient {
|
|
11
91
|
/**
|
|
12
92
|
* An Effect that resolves to the raw underlying Cloudflare D1Database binding.
|
|
@@ -14,9 +94,11 @@ export interface D1ConnectionClient {
|
|
|
14
94
|
*/
|
|
15
95
|
raw: Effect.Effect<runtime.D1Database>;
|
|
16
96
|
/**
|
|
17
|
-
* Prepare a SQL
|
|
97
|
+
* Prepare a SQL statement. Returns synchronously — the network
|
|
98
|
+
* round-trip happens when you yield one of the statement's
|
|
99
|
+
* executors (`all`, `first`, `run`, `raw`).
|
|
18
100
|
*/
|
|
19
|
-
prepare: (query: string) =>
|
|
101
|
+
prepare: (query: string) => D1PreparedStatement;
|
|
20
102
|
/**
|
|
21
103
|
* Execute raw SQL without prepared statements.
|
|
22
104
|
*/
|
|
@@ -26,7 +108,7 @@ export interface D1ConnectionClient {
|
|
|
26
108
|
* Statements execute sequentially and are rolled back on failure.
|
|
27
109
|
*/
|
|
28
110
|
batch: <T = unknown>(
|
|
29
|
-
statements:
|
|
111
|
+
statements: D1PreparedStatement[],
|
|
30
112
|
) => Effect.Effect<runtime.D1Result<T>[]>;
|
|
31
113
|
}
|
|
32
114
|
|
|
@@ -42,22 +124,23 @@ export const D1ConnectionLive = Layer.effect(
|
|
|
42
124
|
|
|
43
125
|
return Effect.fn(function* (database: D1Database) {
|
|
44
126
|
yield* Policy(database);
|
|
45
|
-
const
|
|
127
|
+
const rawEff = yield* Effect.serviceOption(WorkerEnvironment).pipe(
|
|
46
128
|
Effect.map(Option.getOrUndefined),
|
|
47
129
|
Effect.map((env) => env?.[database.LogicalId]! as runtime.D1Database),
|
|
48
130
|
Effect.cached,
|
|
49
131
|
);
|
|
50
132
|
|
|
51
133
|
return {
|
|
52
|
-
raw:
|
|
53
|
-
prepare: (query: string) =>
|
|
54
|
-
d1.pipe(Effect.map((d1) => d1.prepare(query))),
|
|
134
|
+
raw: rawEff,
|
|
135
|
+
prepare: (query: string) => new D1PreparedStatement(query, [], rawEff),
|
|
55
136
|
exec: (query: string) =>
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
137
|
+
Effect.flatMap(rawEff, (raw) =>
|
|
138
|
+
Effect.promise(() => raw.exec(query)),
|
|
139
|
+
),
|
|
140
|
+
batch: <T = unknown>(statements: D1PreparedStatement[]) =>
|
|
141
|
+
Effect.flatMap(rawEff, (raw) =>
|
|
142
|
+
Effect.promise(() =>
|
|
143
|
+
raw.batch<T>(statements.map((s) => s._build(raw))),
|
|
61
144
|
),
|
|
62
145
|
),
|
|
63
146
|
} satisfies D1ConnectionClient;
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import * as Effect from "effect/Effect";
|
|
2
|
+
import { ImagesBinding } from "./ImagesBinding.ts";
|
|
3
|
+
|
|
4
|
+
type ImagesTypeId = typeof ImagesTypeId;
|
|
5
|
+
const ImagesTypeId = "Cloudflare.Images" as const;
|
|
6
|
+
|
|
7
|
+
export type ImagesProps = {
|
|
8
|
+
/**
|
|
9
|
+
* Binding name used when `Cloudflare.Images.bind(images)` attaches Images
|
|
10
|
+
* from inside a Worker init phase. When Images is passed through
|
|
11
|
+
* `Worker({ bindings: { ... } })`, the object key remains the binding name.
|
|
12
|
+
*
|
|
13
|
+
* @default "IMAGES"
|
|
14
|
+
*/
|
|
15
|
+
name?: string;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Marker for a Cloudflare Images binding.
|
|
20
|
+
*
|
|
21
|
+
* Images bindings are configured directly on Workers and do not have a
|
|
22
|
+
* standalone provisioning API. The Worker provider sees this object in
|
|
23
|
+
* `bindings: { ... }` and emits the corresponding `{ type: "images" }`
|
|
24
|
+
* metadata binding to the script.
|
|
25
|
+
*/
|
|
26
|
+
export type Images = {
|
|
27
|
+
kind: ImagesTypeId;
|
|
28
|
+
name: string;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export const isImages = (value: unknown): value is Images =>
|
|
32
|
+
typeof value === "object" &&
|
|
33
|
+
value !== null &&
|
|
34
|
+
"kind" in value &&
|
|
35
|
+
(value as Images).kind === ImagesTypeId;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* A Cloudflare Images binding for image transformation and manipulation inside
|
|
39
|
+
* Workers.
|
|
40
|
+
*
|
|
41
|
+
* The Effect-native interface (`Cloudflare.Images.bind(...)`) returns an
|
|
42
|
+
* `ImagesClient` whose methods take Effect `Stream.Stream<Uint8Array>`
|
|
43
|
+
* inputs and return `Effect`s — `info`, `input(...).transform(...)
|
|
44
|
+
* .draw(...).output(...)`. The runtime conversion to Cloudflare's
|
|
45
|
+
* `ReadableStream` is handled internally.
|
|
46
|
+
*
|
|
47
|
+
* @section Declaring Images
|
|
48
|
+
* @example
|
|
49
|
+
* ```typescript
|
|
50
|
+
* const Pipeline = yield* Cloudflare.Images({ name: "PIPELINE" });
|
|
51
|
+
* ```
|
|
52
|
+
*
|
|
53
|
+
* @section Effect-style Worker (recommended)
|
|
54
|
+
* @example Read image format and dimensions from the request body
|
|
55
|
+
* ```typescript
|
|
56
|
+
* import { HttpServerRequest } from "effect/unstable/http/HttpServerRequest";
|
|
57
|
+
*
|
|
58
|
+
* Cloudflare.Worker("ImageWorker", { main: import.meta.filename },
|
|
59
|
+
* Effect.gen(function* () {
|
|
60
|
+
* const pipeline = yield* Pipeline;
|
|
61
|
+
* const images = yield* Cloudflare.Images.bind(pipeline);
|
|
62
|
+
* return {
|
|
63
|
+
* fetch: Effect.gen(function* () {
|
|
64
|
+
* const request = yield* HttpServerRequest;
|
|
65
|
+
* // request.stream is Stream.Stream<Uint8Array>
|
|
66
|
+
* const info = yield* images.info(request.stream);
|
|
67
|
+
* return yield* HttpServerResponse.json(info);
|
|
68
|
+
* }),
|
|
69
|
+
* };
|
|
70
|
+
* }).pipe(Effect.provide(Cloudflare.ImagesBindingLive)),
|
|
71
|
+
* );
|
|
72
|
+
* ```
|
|
73
|
+
*
|
|
74
|
+
* @example Transform an image — chainable pipeline, single Effect at the end
|
|
75
|
+
* ```typescript
|
|
76
|
+
* const result = yield* (yield* images.input(request.stream))
|
|
77
|
+
* .transform({ width: 128 })
|
|
78
|
+
* .output({ format: "image/jpeg" });
|
|
79
|
+
*
|
|
80
|
+
* const response = yield* result.response;
|
|
81
|
+
* ```
|
|
82
|
+
*
|
|
83
|
+
* @section Binding to a Worker (declarative)
|
|
84
|
+
* @example
|
|
85
|
+
* ```typescript
|
|
86
|
+
* export const Worker = Cloudflare.Worker("Worker", {
|
|
87
|
+
* main: "./src/worker.ts",
|
|
88
|
+
* bindings: { MEDIA: Pipeline },
|
|
89
|
+
* });
|
|
90
|
+
*
|
|
91
|
+
* export type WorkerEnv = Cloudflare.InferEnv<typeof Worker>;
|
|
92
|
+
* // { MEDIA: ImagesBinding }
|
|
93
|
+
* ```
|
|
94
|
+
*
|
|
95
|
+
* Inside the Worker, the raw Cloudflare runtime binding is reachable via
|
|
96
|
+
* `client.raw` if you need to call `info()` / `input()` directly with
|
|
97
|
+
* `async`/`await`. The Effect-native interface is preferred — it returns
|
|
98
|
+
* tagged `ImagesError`s, threads `WorkerEnvironment`, and lets you stream
|
|
99
|
+
* Effect `Stream<Uint8Array>` sources without manual conversion.
|
|
100
|
+
*
|
|
101
|
+
* @see https://developers.cloudflare.com/images/transform-images/bindings/
|
|
102
|
+
*/
|
|
103
|
+
export const Images: {
|
|
104
|
+
(props?: ImagesProps): Effect.Effect<Images>;
|
|
105
|
+
/**
|
|
106
|
+
* Bind Cloudflare Images to the surrounding Worker, returning an
|
|
107
|
+
* Effect-native client with access to the native Workers runtime binding.
|
|
108
|
+
*/
|
|
109
|
+
bind: typeof ImagesBinding.bind;
|
|
110
|
+
} = Object.assign(
|
|
111
|
+
Effect.fn(function* (props?: ImagesProps) {
|
|
112
|
+
return {
|
|
113
|
+
kind: ImagesTypeId,
|
|
114
|
+
name: props?.name ?? "IMAGES",
|
|
115
|
+
} satisfies Images;
|
|
116
|
+
}),
|
|
117
|
+
{
|
|
118
|
+
bind: (...args: Parameters<typeof ImagesBinding.bind>) =>
|
|
119
|
+
ImagesBinding.bind(...args),
|
|
120
|
+
},
|
|
121
|
+
);
|