alchemy-effect 0.6.2 → 0.6.4
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/src/Daemon/Client.ts
CHANGED
|
@@ -10,6 +10,7 @@ import { ChildProcessSpawner } from "effect/unstable/process/ChildProcessSpawner
|
|
|
10
10
|
import { RpcClient, RpcSerialization } from "effect/unstable/rpc";
|
|
11
11
|
import * as Socket from "effect/unstable/socket/Socket";
|
|
12
12
|
import { resolveSocketPath } from "./Config.ts";
|
|
13
|
+
import { DaemonConnectFailed, DaemonSocketNotReady } from "./Errors.ts";
|
|
13
14
|
import { DaemonRpcs } from "./RpcSchema.ts";
|
|
14
15
|
|
|
15
16
|
export type DaemonClient = Effect.Success<ReturnType<typeof makeClient>>;
|
|
@@ -58,17 +59,19 @@ export const DaemonLive: Layer.Layer<
|
|
|
58
59
|
const socketPath = yield* resolveSocketPath;
|
|
59
60
|
|
|
60
61
|
const client = yield* tryConnect(socketPath).pipe(
|
|
61
|
-
Effect.
|
|
62
|
+
Effect.catchTag("DaemonConnectFailed", () =>
|
|
62
63
|
Effect.gen(function* () {
|
|
63
64
|
yield* Effect.logInfo("Starting daemon…");
|
|
64
65
|
yield* startDaemonProcess.pipe(Effect.forkChild);
|
|
65
66
|
yield* waitForSocket(socketPath);
|
|
66
67
|
yield* Effect.sleep("200 millis");
|
|
67
|
-
return yield*
|
|
68
|
+
return yield* tryConnect(socketPath);
|
|
68
69
|
}),
|
|
69
70
|
),
|
|
70
71
|
Effect.catchTag("PlatformError", (e) => Effect.die(e)),
|
|
71
|
-
Effect.
|
|
72
|
+
Effect.catchTag("DaemonConnectFailed", () =>
|
|
73
|
+
Effect.die(new Error("Failed to connect to daemon")),
|
|
74
|
+
),
|
|
72
75
|
);
|
|
73
76
|
|
|
74
77
|
yield* client
|
|
@@ -90,19 +93,20 @@ const waitForSocket = (socketPath: string) =>
|
|
|
90
93
|
const fs = yield* FileSystem.FileSystem;
|
|
91
94
|
yield* fs.exists(socketPath).pipe(
|
|
92
95
|
Effect.flatMap((exists) =>
|
|
93
|
-
exists ? Effect.void : Effect.fail(
|
|
96
|
+
exists ? Effect.void : Effect.fail(new DaemonSocketNotReady()),
|
|
94
97
|
),
|
|
95
98
|
Effect.retry(
|
|
96
|
-
Schedule.spaced("100 millis").pipe(
|
|
97
|
-
|
|
98
|
-
|
|
99
|
+
Schedule.spaced("100 millis").pipe(Schedule.both(Schedule.recurs(50))),
|
|
100
|
+
),
|
|
101
|
+
Effect.catchTag("DaemonSocketNotReady", () =>
|
|
102
|
+
Effect.fail(new DaemonConnectFailed()),
|
|
99
103
|
),
|
|
100
104
|
);
|
|
101
105
|
});
|
|
102
106
|
|
|
103
107
|
const tryConnect = (socketPath: string) =>
|
|
104
108
|
makeClient(socketPath).pipe(
|
|
105
|
-
Effect.catch(() => Effect.fail(
|
|
109
|
+
Effect.catch(() => Effect.fail(new DaemonConnectFailed())),
|
|
106
110
|
);
|
|
107
111
|
|
|
108
112
|
const startDaemonProcess = Effect.gen(function* () {
|
package/src/Daemon/Errors.ts
CHANGED
|
@@ -13,7 +13,7 @@ export class ProcessNotFound extends Schema.TaggedClass<ProcessNotFound>()(
|
|
|
13
13
|
{ id: Schema.String },
|
|
14
14
|
) {}
|
|
15
15
|
|
|
16
|
-
// Data-based errors used
|
|
16
|
+
// Data-based errors used internally by the daemon runtime
|
|
17
17
|
|
|
18
18
|
export class DaemonAlreadyRunning extends Data.TaggedError(
|
|
19
19
|
"DaemonAlreadyRunning",
|
|
@@ -30,3 +30,19 @@ export class DaemonAlreadyRunning extends Data.TaggedError(
|
|
|
30
30
|
export class LockCompromised extends Data.TaggedError("LockCompromised")<{
|
|
31
31
|
readonly lockDir: string;
|
|
32
32
|
}> {}
|
|
33
|
+
|
|
34
|
+
export class DaemonSocketNotReady extends Data.TaggedError(
|
|
35
|
+
"DaemonSocketNotReady",
|
|
36
|
+
) {
|
|
37
|
+
get message() {
|
|
38
|
+
return "Daemon socket is not ready yet";
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export class DaemonConnectFailed extends Data.TaggedError(
|
|
43
|
+
"DaemonConnectFailed",
|
|
44
|
+
) {
|
|
45
|
+
get message() {
|
|
46
|
+
return "Failed to connect to daemon";
|
|
47
|
+
}
|
|
48
|
+
}
|
package/src/Daemon/index.ts
CHANGED
package/src/Plan.ts
CHANGED
|
@@ -342,20 +342,29 @@ export const make = <A>(
|
|
|
342
342
|
]),
|
|
343
343
|
);
|
|
344
344
|
|
|
345
|
-
// Combined prop + binding upstream
|
|
346
|
-
|
|
345
|
+
// Combined prop + binding upstream for the desired graph, including
|
|
346
|
+
// references to resources outside the current graph so delete validation can
|
|
347
|
+
// tell whether any surviving resource still points at an orphan.
|
|
348
|
+
const rawUpstreamDependencies: {
|
|
347
349
|
[fqn: string]: string[];
|
|
348
350
|
} = Object.fromEntries(
|
|
349
351
|
resources.map((resource) => {
|
|
350
352
|
const fqn = resource.FQN;
|
|
351
353
|
const propDeps = newUpstreamDependencies[fqn] ?? [];
|
|
352
354
|
const bindDeps = bindingUpstreamDependencies[fqn] ?? [];
|
|
353
|
-
return [
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
355
|
+
return [fqn, [...new Set([...propDeps, ...bindDeps])]];
|
|
356
|
+
}),
|
|
357
|
+
);
|
|
358
|
+
|
|
359
|
+
// Combined prop + binding upstream, filtered to resources in this graph for
|
|
360
|
+
// scheduling and cycle detection.
|
|
361
|
+
const allUpstreamDependencies: {
|
|
362
|
+
[fqn: string]: string[];
|
|
363
|
+
} = Object.fromEntries(
|
|
364
|
+
resources.map((resource) => {
|
|
365
|
+
const fqn = resource.FQN;
|
|
366
|
+
const deps = rawUpstreamDependencies[fqn] ?? [];
|
|
367
|
+
return [fqn, deps.filter((dep) => newResourceFqns.has(dep))];
|
|
359
368
|
}),
|
|
360
369
|
);
|
|
361
370
|
|
|
@@ -755,11 +764,13 @@ export const make = <A>(
|
|
|
755
764
|
)).filter((v) => !!v),
|
|
756
765
|
);
|
|
757
766
|
|
|
758
|
-
for (const
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
767
|
+
for (const resourceFqn of Object.keys(deletions)) {
|
|
768
|
+
const dependencies = Object.entries(rawUpstreamDependencies)
|
|
769
|
+
.filter(
|
|
770
|
+
([survivorFqn, upstream]) =>
|
|
771
|
+
survivorFqn in resourceGraph && upstream.includes(resourceFqn),
|
|
772
|
+
)
|
|
773
|
+
.map(([survivorFqn]) => survivorFqn);
|
|
763
774
|
if (dependencies.length > 0) {
|
|
764
775
|
return yield* new DeleteResourceHasDownstreamDependencies({
|
|
765
776
|
message: `Resource ${resourceFqn} has downstream dependencies`,
|
package/src/Random.ts
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import * as Effect from "effect/Effect";
|
|
2
|
+
import * as Redacted from "effect/Redacted";
|
|
3
|
+
import { Resource } from "./Resource.ts";
|
|
4
|
+
|
|
5
|
+
export interface RandomProps {
|
|
6
|
+
/**
|
|
7
|
+
* Number of random bytes to generate before hex encoding.
|
|
8
|
+
* @default 32
|
|
9
|
+
*/
|
|
10
|
+
bytes?: number;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export type Random = Resource<
|
|
14
|
+
"Alchemy.Random",
|
|
15
|
+
RandomProps,
|
|
16
|
+
{
|
|
17
|
+
text: Redacted.Redacted<string>;
|
|
18
|
+
}
|
|
19
|
+
>;
|
|
20
|
+
|
|
21
|
+
export const makeRandom = (id: string, props?: RandomProps) =>
|
|
22
|
+
Random(id, props).pipe(Effect.flatMap((rand) => rand.text.asEffect()));
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* A deterministic-in-state random secret generator.
|
|
26
|
+
*
|
|
27
|
+
* The value is generated once on create and then persisted in state so
|
|
28
|
+
* subsequent deploys keep the same secret unless the resource is replaced.
|
|
29
|
+
*/
|
|
30
|
+
export const Random = Resource<Random>("Alchemy.Random");
|
|
31
|
+
|
|
32
|
+
export const RandomProvider = () =>
|
|
33
|
+
Random.provider.succeed({
|
|
34
|
+
create: Effect.fn(function* ({ news = {}, output }) {
|
|
35
|
+
if (output?.text) {
|
|
36
|
+
return output;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const byteLength = news.bytes ?? 32;
|
|
40
|
+
const text = yield* Effect.sync(() => {
|
|
41
|
+
const bytes = new Uint8Array(byteLength);
|
|
42
|
+
crypto.getRandomValues(bytes);
|
|
43
|
+
return Redacted.make(
|
|
44
|
+
Array.from(bytes)
|
|
45
|
+
.map((b) => b.toString(16).padStart(2, "0"))
|
|
46
|
+
.join(""),
|
|
47
|
+
);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
return { text };
|
|
51
|
+
}),
|
|
52
|
+
update: Effect.fn(function* ({ output }) {
|
|
53
|
+
return output;
|
|
54
|
+
}),
|
|
55
|
+
delete: Effect.fn(function* () {
|
|
56
|
+
return undefined;
|
|
57
|
+
}),
|
|
58
|
+
read: Effect.fn(function* ({ output }) {
|
|
59
|
+
return output;
|
|
60
|
+
}),
|
|
61
|
+
});
|
package/src/Tags.ts
CHANGED
|
@@ -31,7 +31,7 @@ export const createTagsList = (tags: Tags) =>
|
|
|
31
31
|
Value,
|
|
32
32
|
}));
|
|
33
33
|
|
|
34
|
-
export const createInternalTags = Effect.
|
|
34
|
+
export const createInternalTags = Effect.fnUntraced(function* (id: string) {
|
|
35
35
|
const stack = yield* Stack;
|
|
36
36
|
const stage = yield* Stage;
|
|
37
37
|
return {
|
|
@@ -45,7 +45,9 @@ export const createInternalTags = Effect.fn(function* (id: string) {
|
|
|
45
45
|
* Creates AWS-compatible tag filters for finding resources by alchemy tags.
|
|
46
46
|
* Use with AWS describe APIs that accept Filter parameters.
|
|
47
47
|
*/
|
|
48
|
-
export const createAlchemyTagFilters = Effect.
|
|
48
|
+
export const createAlchemyTagFilters = Effect.fnUntraced(function* (
|
|
49
|
+
id: string,
|
|
50
|
+
) {
|
|
49
51
|
const stack = yield* Stack;
|
|
50
52
|
const stage = yield* Stage;
|
|
51
53
|
return [
|
|
@@ -58,7 +60,7 @@ export const createAlchemyTagFilters = Effect.fn(function* (id: string) {
|
|
|
58
60
|
/**
|
|
59
61
|
* Checks if a resource has the expected alchemy tags for this app/stage/id.
|
|
60
62
|
*/
|
|
61
|
-
export const hasAlchemyTags = Effect.
|
|
63
|
+
export const hasAlchemyTags = Effect.fnUntraced(function* (
|
|
62
64
|
id: string,
|
|
63
65
|
tags: Tags | undefined,
|
|
64
66
|
) {
|
package/src/index.ts
CHANGED
|
@@ -17,6 +17,7 @@ export * as Output from "./Output.ts";
|
|
|
17
17
|
export * from "./PhysicalName.ts";
|
|
18
18
|
export * as Plan from "./Plan.ts";
|
|
19
19
|
export * from "./Provider.ts";
|
|
20
|
+
export * from "./Random.ts";
|
|
20
21
|
export * from "./Ref.ts";
|
|
21
22
|
export * as RemovalPolicy from "./RemovalPolicy.ts";
|
|
22
23
|
export * from "./Resource.ts";
|