alchemy-effect 0.6.2 → 0.6.3
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 -9
- package/bin/alchemy-effect.js.map +1 -1
- package/package.json +22 -19
- package/src/AWS/AutoScaling/AutoScalingGroup.ts +2 -2
- package/src/AWS/DynamoDB/PutItem.ts +2 -2
- package/src/AWS/EC2/Instance.ts +3 -3
- package/src/AWS/Providers.ts +2 -0
- package/src/Apply.ts +0 -4
- package/src/Cloudflare/D1/D1Connection.ts +74 -0
- package/src/Cloudflare/D1/D1Database.ts +222 -0
- package/src/Cloudflare/D1/D1DatabaseBinding.ts +25 -0
- package/src/Cloudflare/D1/index.ts +3 -0
- package/src/Cloudflare/Logs.ts +12 -29
- package/src/Cloudflare/Providers.ts +5 -0
- package/src/Cloudflare/Workers/HttpServer.ts +5 -1
- package/src/Cloudflare/Workers/Request.ts +5 -0
- package/src/Cloudflare/Workers/index.ts +1 -0
- package/src/Cloudflare/index.ts +1 -0
- package/src/Daemon/Client.ts +12 -8
- package/src/Daemon/Errors.ts +17 -1
- package/src/Daemon/index.ts +2 -0
- package/src/Plan.ts +24 -13
- package/src/Random.ts +61 -0
- package/src/Tags.ts +5 -3
- package/src/index.ts +1 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "alchemy-effect",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.3",
|
|
4
4
|
"homepage": "https://alchemy.run",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Sam Goodwin <sam@alchemy.run>",
|
|
@@ -81,11 +81,6 @@
|
|
|
81
81
|
"bun": "./src/Construct/index.ts",
|
|
82
82
|
"import": "./lib/Construct/index.js"
|
|
83
83
|
},
|
|
84
|
-
"./Kubernetes": {
|
|
85
|
-
"types": "./src/Kubernetes/index.d.ts",
|
|
86
|
-
"bun": "./src/Kubernetes/index.ts",
|
|
87
|
-
"import": "./lib/Kubernetes/index.js"
|
|
88
|
-
},
|
|
89
84
|
"./Bundle": {
|
|
90
85
|
"types": "./src/Bundle/index.d.ts",
|
|
91
86
|
"bun": "./src/Bundle/index.ts",
|
|
@@ -116,10 +111,10 @@
|
|
|
116
111
|
"bun": "./src/Endpoint/index.ts",
|
|
117
112
|
"import": "./lib/Endpoint/index.js"
|
|
118
113
|
},
|
|
119
|
-
"./
|
|
120
|
-
"types": "./src/
|
|
121
|
-
"bun": "./src/
|
|
122
|
-
"import": "./lib/
|
|
114
|
+
"./Kubernetes": {
|
|
115
|
+
"types": "./src/Kubernetes/index.d.ts",
|
|
116
|
+
"bun": "./src/Kubernetes/index.ts",
|
|
117
|
+
"import": "./lib/Kubernetes/index.js"
|
|
123
118
|
},
|
|
124
119
|
"./Output": {
|
|
125
120
|
"types": "./src/Output.d.ts",
|
|
@@ -131,6 +126,11 @@
|
|
|
131
126
|
"bun": "./src/Process/index.ts",
|
|
132
127
|
"import": "./lib/Process/index.js"
|
|
133
128
|
},
|
|
129
|
+
"./SQLite": {
|
|
130
|
+
"types": "./src/SQLite/index.d.ts",
|
|
131
|
+
"bun": "./src/SQLite/index.ts",
|
|
132
|
+
"import": "./lib/SQLite/index.js"
|
|
133
|
+
},
|
|
134
134
|
"./TUI": {
|
|
135
135
|
"types": "./src/TUI/index.d.ts",
|
|
136
136
|
"bun": "./src/TUI/index.ts",
|
|
@@ -168,11 +168,11 @@
|
|
|
168
168
|
"@ai-sdk/openai": "^3.0.23",
|
|
169
169
|
"@ai-sdk/provider": "^3.0.8",
|
|
170
170
|
"@aws-sdk/credential-providers": "^3.0.0",
|
|
171
|
-
"@distilled.cloud/aws": "^0.7.
|
|
172
|
-
"@distilled.cloud/cloudflare": "^0.7.
|
|
173
|
-
"@distilled.cloud/cloudflare-rolldown-plugin": "0.1.2",
|
|
174
|
-
"@distilled.cloud/core": "^0.7.
|
|
175
|
-
"@effect/vitest": "4.0.0-beta.
|
|
171
|
+
"@distilled.cloud/aws": "^0.7.11",
|
|
172
|
+
"@distilled.cloud/cloudflare": "^0.7.11",
|
|
173
|
+
"@distilled.cloud/cloudflare-rolldown-plugin": "^0.1.2",
|
|
174
|
+
"@distilled.cloud/core": "^0.7.11",
|
|
175
|
+
"@effect/vitest": "4.0.0-beta.43",
|
|
176
176
|
"@libsql/client": "^0.17.0",
|
|
177
177
|
"@smithy/node-config-provider": "^4.0.0",
|
|
178
178
|
"@smithy/shared-ini-file-loader": "^4.3.4",
|
|
@@ -180,6 +180,7 @@
|
|
|
180
180
|
"@types/aws-lambda": "^8.10.152",
|
|
181
181
|
"ai": "^6.0.62",
|
|
182
182
|
"aws4fetch": "^1.0.20",
|
|
183
|
+
"capnp-es": "^0.0.14",
|
|
183
184
|
"cloudflare": "^5.2.0",
|
|
184
185
|
"fast-glob": "^3.3.2",
|
|
185
186
|
"fast-xml-parser": "^5.3.4",
|
|
@@ -188,19 +189,21 @@
|
|
|
188
189
|
"rolldown": "1.0.0-rc.13",
|
|
189
190
|
"solid-js": "latest",
|
|
190
191
|
"web-tree-sitter": "0.25.10",
|
|
192
|
+
"workerd": "1.20260405.1",
|
|
191
193
|
"yaml": "^2.0.0"
|
|
192
194
|
},
|
|
193
195
|
"devDependencies": {
|
|
194
196
|
"@clack/prompts": "^0.11.0",
|
|
195
197
|
"@cloudflare/workers-types": "^4.20250805.0",
|
|
196
|
-
"@effect/platform-bun": "4.0.0-beta.
|
|
197
|
-
"@effect/platform-node": "4.0.0-beta.
|
|
198
|
-
"@effect/platform-node-shared": "4.0.0-beta.
|
|
198
|
+
"@effect/platform-bun": "4.0.0-beta.43",
|
|
199
|
+
"@effect/platform-node": "4.0.0-beta.43",
|
|
200
|
+
"@effect/platform-node-shared": "4.0.0-beta.43",
|
|
199
201
|
"@types/aws-lambda": "^8.10.152",
|
|
200
202
|
"@types/bun": "latest",
|
|
201
203
|
"@types/node": "latest",
|
|
202
204
|
"@types/react": "^19.2.2",
|
|
203
|
-
"
|
|
205
|
+
"better-auth": "^1.6.2",
|
|
206
|
+
"effect": "4.0.0-beta.43",
|
|
204
207
|
"ink": "^6.3.1",
|
|
205
208
|
"pathe": "^2.0.3",
|
|
206
209
|
"react": "^19.2.0",
|
|
@@ -348,7 +348,7 @@ export const AutoScalingGroupProvider = () =>
|
|
|
348
348
|
Effect.retry({
|
|
349
349
|
while: () => true,
|
|
350
350
|
schedule: Schedule.recurs(8).pipe(
|
|
351
|
-
Schedule.
|
|
351
|
+
Schedule.both(Schedule.exponential("250 millis")),
|
|
352
352
|
),
|
|
353
353
|
}),
|
|
354
354
|
);
|
|
@@ -430,7 +430,7 @@ export const AutoScalingGroupProvider = () =>
|
|
|
430
430
|
while: (error) =>
|
|
431
431
|
(error as Error).message === "AutoScalingGroupStillExists",
|
|
432
432
|
schedule: Schedule.recurs(12).pipe(
|
|
433
|
-
Schedule.
|
|
433
|
+
Schedule.both(Schedule.exponential("250 millis")),
|
|
434
434
|
),
|
|
435
435
|
}),
|
|
436
436
|
);
|
|
@@ -24,12 +24,12 @@ export class PutItem extends Binding.Service<
|
|
|
24
24
|
export const PutItemLive = Layer.effect(
|
|
25
25
|
PutItem,
|
|
26
26
|
Effect.gen(function* () {
|
|
27
|
-
const
|
|
27
|
+
const bind = yield* PutItemPolicy;
|
|
28
28
|
const putItem = yield* DynamoDB.putItem;
|
|
29
29
|
|
|
30
30
|
return Effect.fn(function* <T extends Table>(table: T) {
|
|
31
31
|
const TableName = yield* table.tableName;
|
|
32
|
-
yield*
|
|
32
|
+
yield* bind(table);
|
|
33
33
|
return Effect.fn(function* (request: PutItemRequest) {
|
|
34
34
|
const tableName = yield* TableName;
|
|
35
35
|
return yield* putItem({
|
package/src/AWS/EC2/Instance.ts
CHANGED
|
@@ -499,7 +499,7 @@ export const InstanceProvider = () =>
|
|
|
499
499
|
error instanceof InstanceStateMismatch ||
|
|
500
500
|
isPendingInstanceLookupError(error),
|
|
501
501
|
schedule: Schedule.exponential("250 millis").pipe(
|
|
502
|
-
Schedule.
|
|
502
|
+
Schedule.both(Schedule.recurs(8)),
|
|
503
503
|
),
|
|
504
504
|
}),
|
|
505
505
|
);
|
|
@@ -526,7 +526,7 @@ export const InstanceProvider = () =>
|
|
|
526
526
|
Effect.retry({
|
|
527
527
|
while: (error) => error instanceof InstanceStillExists,
|
|
528
528
|
schedule: Schedule.exponential("250 millis").pipe(
|
|
529
|
-
Schedule.
|
|
529
|
+
Schedule.both(Schedule.recurs(8)),
|
|
530
530
|
),
|
|
531
531
|
}),
|
|
532
532
|
Effect.catchTag("InvalidInstanceID.NotFound", () => Effect.void),
|
|
@@ -678,7 +678,7 @@ export const InstanceProvider = () =>
|
|
|
678
678
|
Effect.retry({
|
|
679
679
|
while: isPendingInstanceProfileError,
|
|
680
680
|
schedule: Schedule.exponential("500 millis").pipe(
|
|
681
|
-
Schedule.
|
|
681
|
+
Schedule.both(Schedule.recurs(8)),
|
|
682
682
|
),
|
|
683
683
|
}),
|
|
684
684
|
);
|
package/src/AWS/Providers.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { pipe } from "effect/Function";
|
|
|
3
3
|
import * as Layer from "effect/Layer";
|
|
4
4
|
import { CommandProvider } from "../Build/Command.ts";
|
|
5
5
|
import type { Provider } from "../Provider.ts";
|
|
6
|
+
import { RandomProvider } from "../Random.ts";
|
|
6
7
|
import * as Account from "./Account.ts";
|
|
7
8
|
import * as ACM from "./ACM/index.ts";
|
|
8
9
|
import * as Assets from "./Assets.ts";
|
|
@@ -84,6 +85,7 @@ export const credentials = () =>
|
|
|
84
85
|
export const resources = () =>
|
|
85
86
|
Layer.mergeAll(
|
|
86
87
|
CommandProvider(),
|
|
88
|
+
RandomProvider(),
|
|
87
89
|
ACM.CertificateProvider(),
|
|
88
90
|
AutoScaling.AutoScalingGroupProvider(),
|
|
89
91
|
AutoScaling.LaunchTemplateProvider(),
|
package/src/Apply.ts
CHANGED
|
@@ -455,7 +455,6 @@ const executeNode = (
|
|
|
455
455
|
};
|
|
456
456
|
yield* signalReady;
|
|
457
457
|
|
|
458
|
-
yield* report("created");
|
|
459
458
|
yield* markTerminal("created");
|
|
460
459
|
return;
|
|
461
460
|
}
|
|
@@ -562,7 +561,6 @@ const executeNode = (
|
|
|
562
561
|
instanceId,
|
|
563
562
|
};
|
|
564
563
|
|
|
565
|
-
yield* report("updated");
|
|
566
564
|
yield* markTerminal("updated");
|
|
567
565
|
return;
|
|
568
566
|
}
|
|
@@ -579,7 +577,6 @@ const executeNode = (
|
|
|
579
577
|
instanceId,
|
|
580
578
|
};
|
|
581
579
|
yield* signalReady;
|
|
582
|
-
yield* report("created");
|
|
583
580
|
yield* markTerminal("created");
|
|
584
581
|
return;
|
|
585
582
|
}
|
|
@@ -707,7 +704,6 @@ const executeNode = (
|
|
|
707
704
|
|
|
708
705
|
// Keep progress anchored to the live replacement while GC drains the
|
|
709
706
|
// previous generation(s) in the background.
|
|
710
|
-
yield* report("created");
|
|
711
707
|
yield* markTerminal("created");
|
|
712
708
|
return;
|
|
713
709
|
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import type * as runtime from "@cloudflare/workers-types";
|
|
2
|
+
import * as Effect from "effect/Effect";
|
|
3
|
+
import * as Layer from "effect/Layer";
|
|
4
|
+
import * as Option from "effect/Option";
|
|
5
|
+
import * as Binding from "../../Binding.ts";
|
|
6
|
+
import { WorkerEnvironment } from "../Workers/Worker.ts";
|
|
7
|
+
import type { D1Database } from "./D1Database.ts";
|
|
8
|
+
import { DatabaseBinding } from "./D1DatabaseBinding.ts";
|
|
9
|
+
|
|
10
|
+
export interface D1ConnectionClient {
|
|
11
|
+
/**
|
|
12
|
+
* An Effect that resolves to the raw underlying Cloudflare D1Database binding.
|
|
13
|
+
* Use this when you need direct access for libraries like Better Auth.
|
|
14
|
+
*/
|
|
15
|
+
raw: Effect.Effect<runtime.D1Database>;
|
|
16
|
+
/**
|
|
17
|
+
* Prepare a SQL query statement for later execution.
|
|
18
|
+
*/
|
|
19
|
+
prepare: (query: string) => Effect.Effect<runtime.D1PreparedStatement>;
|
|
20
|
+
/**
|
|
21
|
+
* Execute raw SQL without prepared statements.
|
|
22
|
+
*/
|
|
23
|
+
exec: (query: string) => Effect.Effect<runtime.D1ExecResult>;
|
|
24
|
+
/**
|
|
25
|
+
* Send multiple prepared statements in a single call.
|
|
26
|
+
* Statements execute sequentially and are rolled back on failure.
|
|
27
|
+
*/
|
|
28
|
+
batch: <T = unknown>(
|
|
29
|
+
statements: runtime.D1PreparedStatement[],
|
|
30
|
+
) => Effect.Effect<runtime.D1Result<T>[]>;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export class D1Connection extends Binding.Service<
|
|
34
|
+
D1Connection,
|
|
35
|
+
(database: D1Database) => Effect.Effect<D1ConnectionClient>
|
|
36
|
+
>()("Cloudflare.D1.Connection") {}
|
|
37
|
+
|
|
38
|
+
export const D1ConnectionLive = Layer.effect(
|
|
39
|
+
D1Connection,
|
|
40
|
+
Effect.gen(function* () {
|
|
41
|
+
const Policy = yield* D1ConnectionPolicy;
|
|
42
|
+
|
|
43
|
+
return Effect.fn(function* (database: D1Database) {
|
|
44
|
+
yield* Policy(database);
|
|
45
|
+
const d1 = yield* Effect.serviceOption(WorkerEnvironment).pipe(
|
|
46
|
+
Effect.map(Option.getOrUndefined),
|
|
47
|
+
Effect.map((env) => env?.[database.LogicalId]! as runtime.D1Database),
|
|
48
|
+
Effect.cached,
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
raw: d1,
|
|
53
|
+
prepare: (query: string) =>
|
|
54
|
+
d1.pipe(Effect.map((d1) => d1.prepare(query))),
|
|
55
|
+
exec: (query: string) =>
|
|
56
|
+
d1.pipe(Effect.flatMap((d1) => Effect.promise(() => d1.exec(query)))),
|
|
57
|
+
batch: <T = unknown>(statements: runtime.D1PreparedStatement[]) =>
|
|
58
|
+
d1.pipe(
|
|
59
|
+
Effect.flatMap((d1) =>
|
|
60
|
+
Effect.promise(() => d1.batch<T>(statements)),
|
|
61
|
+
),
|
|
62
|
+
),
|
|
63
|
+
} satisfies D1ConnectionClient;
|
|
64
|
+
});
|
|
65
|
+
}),
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
export class D1ConnectionPolicy extends Binding.Policy<
|
|
69
|
+
D1ConnectionPolicy,
|
|
70
|
+
(database: D1Database) => Effect.Effect<void>
|
|
71
|
+
>()("Cloudflare.D1.Connection") {}
|
|
72
|
+
|
|
73
|
+
export const D1ConnectionPolicyLive =
|
|
74
|
+
D1ConnectionPolicy.layer.succeed(DatabaseBinding);
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import * as d1 from "@distilled.cloud/cloudflare/d1";
|
|
2
|
+
import * as Effect from "effect/Effect";
|
|
3
|
+
|
|
4
|
+
import type { Credentials } from "@distilled.cloud/cloudflare";
|
|
5
|
+
import type { Layer } from "effect/Layer";
|
|
6
|
+
import type { HttpClient } from "effect/unstable/http/HttpClient";
|
|
7
|
+
import { isResolved } from "../../Diff.ts";
|
|
8
|
+
import { createPhysicalName } from "../../PhysicalName.ts";
|
|
9
|
+
import type { Provider } from "../../Provider.ts";
|
|
10
|
+
import { Resource } from "../../Resource.ts";
|
|
11
|
+
import type { Stack } from "../../Stack.ts";
|
|
12
|
+
import type { Stage } from "../../Stage.ts";
|
|
13
|
+
import { Account } from "../Account.ts";
|
|
14
|
+
|
|
15
|
+
export type Jurisdiction = "default" | "eu" | "fedramp";
|
|
16
|
+
export type PrimaryLocationHint =
|
|
17
|
+
| "wnam"
|
|
18
|
+
| "enam"
|
|
19
|
+
| "weur"
|
|
20
|
+
| "eeur"
|
|
21
|
+
| "apac"
|
|
22
|
+
| "oc";
|
|
23
|
+
|
|
24
|
+
export type DatabaseProps = {
|
|
25
|
+
/**
|
|
26
|
+
* Name of the database. If omitted, a unique name will be generated.
|
|
27
|
+
* @default ${app}-${stage}-${id}
|
|
28
|
+
*/
|
|
29
|
+
name?: string;
|
|
30
|
+
/**
|
|
31
|
+
* Optional primary location hint for the database.
|
|
32
|
+
*/
|
|
33
|
+
primaryLocationHint?: PrimaryLocationHint;
|
|
34
|
+
/**
|
|
35
|
+
* Read replication configuration. Only mutable property during updates.
|
|
36
|
+
*/
|
|
37
|
+
readReplication?: {
|
|
38
|
+
mode: "auto" | "disabled";
|
|
39
|
+
};
|
|
40
|
+
/**
|
|
41
|
+
* Jurisdiction where data is guaranteed to be stored.
|
|
42
|
+
* @default "default"
|
|
43
|
+
*/
|
|
44
|
+
jurisdiction?: Jurisdiction;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export type D1Database = Resource<
|
|
48
|
+
"Cloudflare.D1Database",
|
|
49
|
+
DatabaseProps,
|
|
50
|
+
{
|
|
51
|
+
databaseId: string;
|
|
52
|
+
databaseName: string;
|
|
53
|
+
jurisdiction: Jurisdiction;
|
|
54
|
+
readReplication: { mode: "auto" | "disabled" } | undefined;
|
|
55
|
+
accountId: string;
|
|
56
|
+
}
|
|
57
|
+
>;
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* A Cloudflare D1 serverless SQL database built on SQLite.
|
|
61
|
+
*
|
|
62
|
+
* @section Creating a Database
|
|
63
|
+
* @example Basic Database
|
|
64
|
+
* ```typescript
|
|
65
|
+
* const db = yield* Database("my-db", {});
|
|
66
|
+
* ```
|
|
67
|
+
*
|
|
68
|
+
* @example Database with Location Hint
|
|
69
|
+
* ```typescript
|
|
70
|
+
* const db = yield* Database("my-db", {
|
|
71
|
+
* primaryLocationHint: "wnam",
|
|
72
|
+
* });
|
|
73
|
+
* ```
|
|
74
|
+
*/
|
|
75
|
+
export const D1Database = Resource<D1Database>("Cloudflare.D1Database");
|
|
76
|
+
|
|
77
|
+
export const DatabaseProvider = (): Layer<
|
|
78
|
+
Provider<D1Database>,
|
|
79
|
+
never,
|
|
80
|
+
Account | Credentials | HttpClient | Stack | Stage
|
|
81
|
+
> =>
|
|
82
|
+
D1Database.provider.effect(
|
|
83
|
+
Effect.gen(function* () {
|
|
84
|
+
const accountId = yield* Account;
|
|
85
|
+
const createDb = yield* d1.createDatabase;
|
|
86
|
+
const getDb = yield* d1.getDatabase;
|
|
87
|
+
const patchDb = yield* d1.patchDatabase;
|
|
88
|
+
const deleteDb = yield* d1.deleteDatabase;
|
|
89
|
+
const listDbs = yield* d1.listDatabases;
|
|
90
|
+
|
|
91
|
+
const createDatabaseName = (id: string, name: string | undefined) =>
|
|
92
|
+
Effect.gen(function* () {
|
|
93
|
+
return name ?? (yield* createPhysicalName({ id }));
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
return {
|
|
97
|
+
stables: ["databaseId", "accountId"],
|
|
98
|
+
diff: Effect.fn(function* ({ id, olds = {}, news = {}, output }) {
|
|
99
|
+
if (!isResolved(news)) return undefined;
|
|
100
|
+
if ((output?.accountId ?? accountId) !== accountId) {
|
|
101
|
+
return { action: "replace" } as const;
|
|
102
|
+
}
|
|
103
|
+
const name = yield* createDatabaseName(id, news.name);
|
|
104
|
+
const oldName = output?.databaseName
|
|
105
|
+
? output.databaseName
|
|
106
|
+
: yield* createDatabaseName(id, olds.name);
|
|
107
|
+
const oldJurisdiction =
|
|
108
|
+
output?.jurisdiction ?? olds.jurisdiction ?? "default";
|
|
109
|
+
if (
|
|
110
|
+
oldName !== name ||
|
|
111
|
+
oldJurisdiction !== (news.jurisdiction ?? "default") ||
|
|
112
|
+
(olds.primaryLocationHint !== news.primaryLocationHint &&
|
|
113
|
+
news.primaryLocationHint !== undefined)
|
|
114
|
+
) {
|
|
115
|
+
return { action: "replace" } as const;
|
|
116
|
+
}
|
|
117
|
+
const oldReplicationMode =
|
|
118
|
+
output?.readReplication?.mode ??
|
|
119
|
+
olds.readReplication?.mode ??
|
|
120
|
+
"disabled";
|
|
121
|
+
const newReplicationMode = news.readReplication?.mode ?? "disabled";
|
|
122
|
+
if (oldReplicationMode !== newReplicationMode) {
|
|
123
|
+
return { action: "update" } as const;
|
|
124
|
+
}
|
|
125
|
+
}),
|
|
126
|
+
create: Effect.fn(function* ({ id, news = {} }) {
|
|
127
|
+
const name = yield* createDatabaseName(id, news.name);
|
|
128
|
+
const jurisdiction = news.jurisdiction ?? "default";
|
|
129
|
+
const db = yield* createDb({
|
|
130
|
+
accountId,
|
|
131
|
+
name,
|
|
132
|
+
jurisdiction: jurisdiction !== "default" ? jurisdiction : undefined,
|
|
133
|
+
primaryLocationHint: news.primaryLocationHint,
|
|
134
|
+
}).pipe(
|
|
135
|
+
Effect.catchTag("InvalidProperty", () =>
|
|
136
|
+
Effect.gen(function* () {
|
|
137
|
+
const dbs = yield* listDbs({ accountId, name });
|
|
138
|
+
const match = dbs.result.find((db) => db.name === name);
|
|
139
|
+
if (match) {
|
|
140
|
+
return match;
|
|
141
|
+
}
|
|
142
|
+
return yield* Effect.die(
|
|
143
|
+
`Database with name "${name}" already exists but could not be found`,
|
|
144
|
+
);
|
|
145
|
+
}),
|
|
146
|
+
),
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
const databaseId = db.uuid!;
|
|
150
|
+
|
|
151
|
+
if (news.readReplication?.mode) {
|
|
152
|
+
yield* patchDb({
|
|
153
|
+
accountId,
|
|
154
|
+
databaseId,
|
|
155
|
+
readReplication: news.readReplication,
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return {
|
|
160
|
+
databaseId,
|
|
161
|
+
databaseName: db.name ?? name,
|
|
162
|
+
jurisdiction,
|
|
163
|
+
readReplication: news.readReplication,
|
|
164
|
+
accountId,
|
|
165
|
+
};
|
|
166
|
+
}),
|
|
167
|
+
update: Effect.fn(function* ({ news = {}, output }) {
|
|
168
|
+
const replicationMode = news.readReplication?.mode ?? "disabled";
|
|
169
|
+
const updated = yield* patchDb({
|
|
170
|
+
accountId: output.accountId,
|
|
171
|
+
databaseId: output.databaseId,
|
|
172
|
+
readReplication: { mode: replicationMode },
|
|
173
|
+
});
|
|
174
|
+
return {
|
|
175
|
+
databaseId: updated.uuid ?? output.databaseId,
|
|
176
|
+
databaseName: updated.name ?? output.databaseName,
|
|
177
|
+
jurisdiction: output.jurisdiction,
|
|
178
|
+
readReplication: news.readReplication,
|
|
179
|
+
accountId: output.accountId,
|
|
180
|
+
};
|
|
181
|
+
}),
|
|
182
|
+
delete: Effect.fn(function* ({ output }) {
|
|
183
|
+
yield* deleteDb({
|
|
184
|
+
accountId: output.accountId,
|
|
185
|
+
databaseId: output.databaseId,
|
|
186
|
+
}).pipe(Effect.catchTag("DatabaseNotFound", () => Effect.void));
|
|
187
|
+
}),
|
|
188
|
+
read: Effect.fn(function* ({ id, output, olds }) {
|
|
189
|
+
if (output?.databaseId) {
|
|
190
|
+
return yield* getDb({
|
|
191
|
+
accountId: output.accountId,
|
|
192
|
+
databaseId: output.databaseId,
|
|
193
|
+
}).pipe(
|
|
194
|
+
Effect.map((db) => ({
|
|
195
|
+
databaseId: db.uuid ?? output.databaseId,
|
|
196
|
+
databaseName: db.name ?? output.databaseName,
|
|
197
|
+
jurisdiction: output.jurisdiction,
|
|
198
|
+
readReplication: db.readReplication ?? undefined,
|
|
199
|
+
accountId: output.accountId,
|
|
200
|
+
})),
|
|
201
|
+
Effect.catchTag("DatabaseNotFound", () =>
|
|
202
|
+
Effect.succeed(undefined),
|
|
203
|
+
),
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
const name = yield* createDatabaseName(id, olds?.name);
|
|
207
|
+
const dbs = yield* listDbs({ accountId, name });
|
|
208
|
+
const match = dbs.result.find((db) => db.name === name);
|
|
209
|
+
if (match) {
|
|
210
|
+
return {
|
|
211
|
+
databaseId: match.uuid!,
|
|
212
|
+
databaseName: match.name ?? name,
|
|
213
|
+
jurisdiction: (olds?.jurisdiction ?? "default") as Jurisdiction,
|
|
214
|
+
readReplication: olds?.readReplication,
|
|
215
|
+
accountId,
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
return undefined;
|
|
219
|
+
}),
|
|
220
|
+
};
|
|
221
|
+
}),
|
|
222
|
+
);
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import * as Effect from "effect/Effect";
|
|
2
|
+
import type { ResourceLike } from "../../Resource.ts";
|
|
3
|
+
import { isWorker } from "../Workers/Worker.ts";
|
|
4
|
+
import type { D1Database } from "./D1Database.ts";
|
|
5
|
+
|
|
6
|
+
export const DatabaseBinding = Effect.fn(function* (
|
|
7
|
+
host: ResourceLike,
|
|
8
|
+
database: D1Database,
|
|
9
|
+
) {
|
|
10
|
+
if (isWorker(host)) {
|
|
11
|
+
yield* host.bind`Bind(${database})`({
|
|
12
|
+
bindings: [
|
|
13
|
+
{
|
|
14
|
+
type: "d1",
|
|
15
|
+
name: database.LogicalId,
|
|
16
|
+
id: database.databaseId,
|
|
17
|
+
},
|
|
18
|
+
],
|
|
19
|
+
});
|
|
20
|
+
} else {
|
|
21
|
+
return yield* Effect.die(
|
|
22
|
+
new Error(`DatabaseBinding does not support runtime '${host.Type}'`),
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
});
|
package/src/Cloudflare/Logs.ts
CHANGED
|
@@ -3,19 +3,7 @@ import * as Effect from "effect/Effect";
|
|
|
3
3
|
import * as Stream from "effect/Stream";
|
|
4
4
|
import type { LogLine, LogsInput } from "../Provider.ts";
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
* Progressively wider lookback windows used when `since` is omitted.
|
|
8
|
-
* We try each in order, stopping as soon as we get results.
|
|
9
|
-
* This keeps the common case (recent activity) fast while still finding
|
|
10
|
-
* older logs without asking Cloudflare to scan an enormous range up-front.
|
|
11
|
-
*/
|
|
12
|
-
const LOOKBACK_WINDOWS_MS = [
|
|
13
|
-
1 * 60 * 60 * 1000, // 1 hour
|
|
14
|
-
6 * 60 * 60 * 1000, // 6 hours
|
|
15
|
-
24 * 60 * 60 * 1000, // 1 day
|
|
16
|
-
7 * 24 * 60 * 60 * 1000, // 7 days
|
|
17
|
-
30 * 24 * 60 * 60 * 1000, // 30 days
|
|
18
|
-
];
|
|
6
|
+
const DEFAULT_LOOKBACK_MS = 1 * 60 * 60 * 1000;
|
|
19
7
|
|
|
20
8
|
export interface TelemetryFilter {
|
|
21
9
|
key: string;
|
|
@@ -81,23 +69,18 @@ export const CloudflareLogs = Effect.gen(function* () {
|
|
|
81
69
|
return parseEvents(response);
|
|
82
70
|
}
|
|
83
71
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
},
|
|
95
|
-
});
|
|
96
|
-
const lines = parseEvents(response);
|
|
97
|
-
if (lines.length > 0) return lines;
|
|
98
|
-
}
|
|
72
|
+
const response = yield* queryTelemetry({
|
|
73
|
+
accountId: opts.accountId,
|
|
74
|
+
queryId: "events",
|
|
75
|
+
view: "events",
|
|
76
|
+
timeframe: { from: now - DEFAULT_LOOKBACK_MS, to: now },
|
|
77
|
+
limit,
|
|
78
|
+
parameters: {
|
|
79
|
+
filters: opts.filters,
|
|
80
|
+
},
|
|
81
|
+
});
|
|
99
82
|
|
|
100
|
-
return
|
|
83
|
+
return parseEvents(response);
|
|
101
84
|
});
|
|
102
85
|
|
|
103
86
|
const tailStream = (opts: {
|
|
@@ -5,8 +5,10 @@ import * as FetchHttpClient from "effect/unstable/http/FetchHttpClient";
|
|
|
5
5
|
import * as Socket from "effect/unstable/socket/Socket";
|
|
6
6
|
import { CommandProvider } from "../Build/Command.ts";
|
|
7
7
|
import type { Provider } from "../Provider.ts";
|
|
8
|
+
import { RandomProvider } from "../Random.ts";
|
|
8
9
|
import * as Account from "./Account.ts";
|
|
9
10
|
import { ContainerProvider } from "./Container.ts";
|
|
11
|
+
import * as D1 from "./D1/index.ts";
|
|
10
12
|
import * as KV from "./KV/index.ts";
|
|
11
13
|
import * as R2 from "./R2/index.ts";
|
|
12
14
|
import { AssetsProvider } from "./Workers/Assets.ts";
|
|
@@ -45,9 +47,11 @@ export const credentials = () =>
|
|
|
45
47
|
export const resources = () =>
|
|
46
48
|
Layer.mergeAll(
|
|
47
49
|
CommandProvider(),
|
|
50
|
+
RandomProvider(),
|
|
48
51
|
ContainerProvider(),
|
|
49
52
|
WorkerProvider(),
|
|
50
53
|
WorkflowProvider(),
|
|
54
|
+
D1.DatabaseProvider(),
|
|
51
55
|
KV.NamespaceProvider(),
|
|
52
56
|
R2.BucketProvider(),
|
|
53
57
|
);
|
|
@@ -57,6 +61,7 @@ export const resources = () =>
|
|
|
57
61
|
*/
|
|
58
62
|
export const bindings = () =>
|
|
59
63
|
Layer.mergeAll(
|
|
64
|
+
D1.D1ConnectionPolicyLive,
|
|
60
65
|
R2.GetObjectPolicyLive,
|
|
61
66
|
R2.PutObjectPolicyLive,
|
|
62
67
|
R2.DeleteObjectPolicyLive,
|
|
@@ -10,7 +10,10 @@ import * as HttpServerResponse from "effect/unstable/http/HttpServerResponse";
|
|
|
10
10
|
import * as Socket from "effect/unstable/socket/Socket";
|
|
11
11
|
import * as util from "node:util";
|
|
12
12
|
import * as Http from "../../Http.ts";
|
|
13
|
-
import {
|
|
13
|
+
import { Request } from "./Request.ts";
|
|
14
|
+
import { isWorkerEvent, type WorkerServices } from "./Worker.ts";
|
|
15
|
+
|
|
16
|
+
export type HttpEffect = Http.HttpEffect<WorkerServices>;
|
|
14
17
|
|
|
15
18
|
export const workersHttpHandler = <Req = never>(
|
|
16
19
|
handler: Http.HttpEffect<Req>,
|
|
@@ -130,6 +133,7 @@ export const serveWebRequest = <Req = never>(
|
|
|
130
133
|
const request = make();
|
|
131
134
|
const response = yield* handler.pipe(
|
|
132
135
|
Effect.provideService(HttpServerRequest.HttpServerRequest, request),
|
|
136
|
+
Effect.provideService(Request, webRequest as any),
|
|
133
137
|
Effect.catchCause((cause) => {
|
|
134
138
|
console.error(
|
|
135
139
|
"[serveWebRequest] handler error:",
|