Skip to content

Commit 742c205

Browse files
committed
feat: capture Capability ID in Binding declaration
1 parent 1d08d19 commit 742c205

File tree

6 files changed

+108
-64
lines changed

6 files changed

+108
-64
lines changed

@alchemy.run/effect-aws/src/sqs/queue.consume.ts

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Binding, Capability, type From } from "@alchemy.run/effect";
1+
import { Binding, type Capability, type From } from "@alchemy.run/effect";
22
import type * as lambda from "aws-lambda";
33
import * as Effect from "effect/Effect";
44
import { Function } from "../lambda/index.ts";
@@ -14,17 +14,19 @@ export type QueueEvent<Data> = Omit<lambda.SQSEvent, "Records"> & {
1414

1515
export interface Consume<Q = Queue> extends Capability<"AWS.SQS.Consume", Q> {}
1616

17+
export interface QueueEventSourceProps {
18+
batchSize?: number;
19+
maxBatchingWindow?: number;
20+
maxConcurrency?: number;
21+
reportBatchItemFailures?: boolean;
22+
}
23+
1724
export const QueueEventSource = Binding<
18-
<Q extends Queue>(
25+
<Q extends Queue, const Props extends QueueEventSourceProps>(
1926
queue: Q,
20-
props?: {
21-
batchSize?: number;
22-
maxBatchingWindow?: number;
23-
maxConcurrency?: number;
24-
reportBatchItemFailures?: boolean;
25-
},
26-
) => Binding<Function, Consume<From<Q>>>
27-
>(Function, Queue, "AWS.SQS.Consume");
27+
props?: Props,
28+
) => Binding<Function, Consume<From<Q>>, Props, "QueueEventSource">
29+
>(Function, Queue, "AWS.SQS.Consume", "QueueEventSource");
2830

2931
export const consumeFromLambdaFunction = () =>
3032
QueueEventSource.layer.succeed({

@alchemy.run/effect/src/binding.ts

Lines changed: 58 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -5,24 +5,46 @@ import type { Capability } from "./capability.ts";
55
import type { Resource } from "./resource.ts";
66
import type { Runtime } from "./runtime.ts";
77

8+
export type SerializedBinding<B extends AnyBinding = AnyBinding> = Omit<
9+
B,
10+
"resource"
11+
> & {
12+
capability: {
13+
resource: {
14+
type: string;
15+
id: string;
16+
};
17+
};
18+
};
19+
820
export interface BindingProps {
921
[key: string]: any;
1022
}
1123

12-
export const isBinding = (b: any): b is Binding<any, any, any> =>
24+
export const isBinding = (b: any): b is AnyBinding =>
1325
"runtime" in b && "capability" in b && "tag" in b && "output" in b;
1426

15-
export type AnyBinding<F extends Runtime = any> = Binding<F, any, any>;
27+
export type AnyBinding<F extends Runtime = any> = Binding<
28+
F,
29+
any,
30+
any,
31+
string,
32+
boolean
33+
>;
1634

17-
export type Binding<
35+
export interface Binding<
1836
Run extends Runtime,
1937
Cap extends Capability = Capability,
38+
Props = any,
2039
Tag extends string = Cap["type"],
21-
> = {
40+
IsCustomTag extends boolean = Cap["type"] extends Tag ? false : true,
41+
> {
2242
runtime: Run;
2343
capability: Cap;
24-
tag: Bind<Run, Cap, Tag>;
25-
};
44+
tag: Tag;
45+
props: Props;
46+
isCustomTag: IsCustomTag;
47+
}
2648

2749
/** Tag for a Service that can bind a Capability to a Runtime */
2850
export interface Bind<
@@ -41,19 +63,32 @@ export interface Bind<
4163
name: Tag;
4264
}
4365

44-
export const Binding = <F extends (resource: any, props?: any) => AnyBinding>(
45-
runtime: ReturnType<F>["runtime"],
46-
resource: new () => ReturnType<F>["capability"]["resource"],
47-
tag: ReturnType<F>["tag"]["name"],
48-
// _tag: ReturnType<F>,
49-
): F & BindingDeclaration<ReturnType<F>["runtime"], F> => {
50-
type Runtime = ReturnType<F>["runtime"];
51-
type Tag = ReturnType<F>["tag"];
52-
type Resource = new () => ReturnType<F>["capability"]["resource"];
53-
54-
const handler = (() => {
66+
export const Binding: {
67+
<
68+
F extends (
69+
resource: any,
70+
props?: any,
71+
) => AnyBinding & { isCustomTag: true },
72+
>(
73+
runtime: ReturnType<F>["runtime"],
74+
resource: new () => ReturnType<F>["capability"]["resource"],
75+
type: ReturnType<F>["capability"]["type"],
76+
tag: ReturnType<F>["tag"],
77+
): F & BindingDeclaration<ReturnType<F>["runtime"], F>;
78+
<
79+
F extends (
80+
resource: any,
81+
props?: any,
82+
) => AnyBinding & { isCustomTag: false },
83+
>(
84+
runtime: ReturnType<F>["runtime"],
85+
resource: new () => ReturnType<F>["capability"]["resource"],
86+
type: ReturnType<F>["capability"]["type"],
87+
): F & BindingDeclaration<ReturnType<F>["runtime"], F>;
88+
} = (runtime: any, resource: any, type: string, tag?: string) => {
89+
const handler = () => {
5590
throw new Error(`Should never be called`);
56-
}) as unknown as F;
91+
};
5792

5893
return Object.assign(handler, {
5994
layer: {
@@ -69,8 +104,9 @@ export const Binding = <F extends (resource: any, props?: any) => AnyBinding>(
69104

70105
export interface BindingDeclaration<
71106
Run extends Runtime,
72-
F extends (target: any, props?: any) => Binding<Run, any>,
73-
Tag = ReturnType<F>["tag"],
107+
F extends (target: any, props?: any) => AnyBinding<Run>,
108+
Tag extends string = ReturnType<F>["tag"],
109+
Cap extends Capability = ReturnType<F>["capability"],
74110
> {
75111
layer: {
76112
effect<Err, Req>(
@@ -79,10 +115,10 @@ export interface BindingDeclaration<
79115
Err,
80116
Req
81117
>,
82-
): Layer<Tag, Err, Req>;
118+
): Layer<Bind<Run, Cap, Tag>, Err, Req>;
83119
succeed(
84120
service: BindingService<Run["props"], Parameters<F>[0], Parameters<F>[1]>,
85-
): Layer<Tag>;
121+
): Layer<Bind<Run, Cap, Tag>>;
86122
};
87123
}
88124

@alchemy.run/effect/src/capability.ts

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,3 @@
1-
export type SerializedCapability<B extends Capability = Capability> = Omit<
2-
B,
3-
"resource"
4-
> & {
5-
resource: {
6-
type: string;
7-
id: string;
8-
};
9-
};
10-
111
export interface Capability<
122
Type extends string = string,
133
Resource = unknown,

@alchemy.run/effect/src/plan.ts

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import * as Data from "effect/Data";
22
import * as Effect from "effect/Effect";
33
import util from "node:util";
4-
import type { Capability, SerializedCapability } from "./capability.ts";
4+
import type { AnyBinding, SerializedBinding } from "./binding.ts";
5+
import type { Capability } from "./capability.ts";
56
import type { Instance } from "./policy.ts";
67
import { Provider, type ProviderService } from "./provider.ts";
78
import type { Resource } from "./resource.ts";
@@ -23,27 +24,27 @@ export const isBindNode = (node: any): node is BindNode => {
2324
/**
2425
* A node in the plan that represents a binding operation acting on a resource.
2526
*/
26-
export type BindNode<Cap extends Capability = Capability> =
27-
| Attach<Cap>
28-
| Detach<Cap>
29-
| NoopBind<Cap>;
27+
export type BindNode<B extends AnyBinding = AnyBinding> =
28+
| Attach<B>
29+
| Detach<B>
30+
| NoopBind<B>;
3031

31-
export type Attach<Cap extends Capability = Capability> = {
32+
export type Attach<B extends AnyBinding = AnyBinding> = {
3233
action: "attach";
33-
capability: Cap;
34-
olds?: SerializedCapability<Cap>;
34+
binding: B;
35+
olds?: SerializedBinding<B>;
3536
attributes: Capability.Attr<Cap>;
3637
};
3738

38-
export type Detach<Cap extends Capability = Capability> = {
39+
export type Detach<B extends AnyBinding = AnyBinding> = {
3940
action: "detach";
40-
capability: Cap;
41+
binding: B;
4142
attributes: Capability.Attr<Cap>;
4243
};
4344

44-
export type NoopBind<Cap extends Capability = Capability> = {
45+
export type NoopBind<B extends AnyBinding = AnyBinding> = {
4546
action: "noop";
46-
capability: Cap;
47+
binding: B;
4748
attributes: Capability.Attr<Cap>;
4849
};
4950

@@ -187,7 +188,7 @@ export const plan = <
187188
(
188189
resource,
189190
): resource is ResourceState & {
190-
capabilities: SerializedCapability[];
191+
capabilities: SerializedBinding[];
191192
} => !!resource?.capabilities,
192193
)
193194
.flatMap((resource) =>
@@ -441,5 +442,5 @@ const diffCapabilities = (
441442
return actions;
442443
};
443444

444-
const isCapabilityDiff = (oldCap: SerializedCapability, newCap: Capability) =>
445+
const isCapabilityDiff = (oldCap: SerializedBinding, newCap: Capability) =>
445446
oldCap.action !== newCap.action || oldCap.resource.id !== newCap.resource.id;

@alchemy.run/effect/src/policy.ts

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1+
import * as Context from "effect/Context";
12
import * as Effect from "effect/Effect";
2-
import { type AnyBinding } from "./binding.ts";
3+
import { type AnyBinding, type Bind } from "./binding.ts";
34
import type { Capability } from "./capability.ts";
45
import type { Runtime } from "./runtime.ts";
56

@@ -29,15 +30,25 @@ export interface Policy<
2930
export type $ = typeof $;
3031
export const $ = Policy;
3132

33+
type BindingTags<B extends AnyBinding> = B extends any
34+
? Bind<B["runtime"], B["capability"], Extract<B["tag"], string>>
35+
: never;
36+
3237
export function Policy<F extends Runtime>(): Policy<F, never, never>;
3338
export function Policy<B extends AnyBinding[]>(
3439
...capabilities: B
35-
): Policy<B[number]["runtime"], B[number]["capability"], B[number]["tag"]>;
36-
export function Policy(...bindings: AnyBinding[]) {
40+
): Policy<
41+
B[number]["runtime"],
42+
B[number]["capability"],
43+
BindingTags<B[number]>
44+
>;
45+
export function Policy(...bindings: AnyBinding[]): any {
3746
return {
3847
runtime: bindings[0]["runtime"],
3948
capabilities: bindings.map((b) => b.capability),
40-
tags: bindings.map((b) => b.tag),
49+
tags: bindings.map(
50+
(b) => class Tag extends Context.Tag(b.tag as any)<Tag, any>() {},
51+
),
4152
and: (...b2: AnyBinding[]) => Policy(...bindings, ...b2),
4253
};
4354
}

@alchemy.run/effect/src/runtime.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -64,9 +64,7 @@ export interface Runtime<
6464
{ handle }: { handle: Handler },
6565
): <const Props extends this["props"]>(
6666
props: Props,
67-
) => Service<ID, this, Handler, Extract<Props, RuntimeProps<this, any>>> & {
68-
new (): {};
69-
};
67+
) => Service<ID, this, Handler, Extract<Props, RuntimeProps<this, any>>>;
7068
}
7169

7270
export const Runtime =
@@ -102,7 +100,13 @@ export const Runtime =
102100
handler: handle,
103101
props,
104102
runtime: self,
105-
} satisfies Service<string, Self, any, any>,
103+
type: type,
104+
// TODO(sam): is this right?
105+
parent: self,
106+
} satisfies Omit<
107+
Service<string, Self, any, any>,
108+
keyof { new (): {} }
109+
>,
106110
);
107111
}
108112
},

0 commit comments

Comments
 (0)