Skip to content

Commit 8427687

Browse files
committed
feat: first class support for Effect-based workflows
1 parent 4f91ea0 commit 8427687

File tree

8 files changed

+206
-30
lines changed

8 files changed

+206
-30
lines changed

.changeset/breezy-geese-scream.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"typefusion": patch
3+
---
4+
5+
support Effect in workflows

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,7 @@ For detailed API documentation, visit the [reference docs](https://aniravi24.git
196196

197197
## Effect Integration
198198

199-
Typefusion is built with [Effect](https://effect.website). Refer to the reference docs for details on Effect-suffixed functions and their usage.
199+
Typefusion is built with [Effect](https://effect.website). Refer to the reference docs for details on Effect-suffixed functions and their usage. Most of the time, you just need to suffix the types and functions with `Effect` to use them. For example, `typefusionRef` becomes `typefusionRefEffect`, `TypefusionDbScript` becomes `TypefusionDbScriptEffect`, and `run` becomes `runEffect`.
200200

201201
## Troubleshooting
202202

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { Effect } from "effect";
2+
import { pgType } from "../src/db/postgres/types.js";
3+
import { TypefusionDbScriptEffect } from "../src/store.js";
4+
5+
export const mainSchema = {
6+
id: pgType.integer().notNull(),
7+
name: pgType.text().notNull(),
8+
age: pgType.integer().notNull(),
9+
email: pgType.text().notNull(),
10+
address: pgType.text().notNull(),
11+
};
12+
13+
export default {
14+
name: "main_effect",
15+
schema: mainSchema,
16+
resultDatabase: "postgresql",
17+
runEffect: () => {
18+
console.log("MAIN IS RUN");
19+
return Effect.succeed({
20+
data: [
21+
{
22+
id: 1,
23+
name: "John Doe",
24+
age: 30,
25+
26+
address: "123 Main St",
27+
},
28+
],
29+
});
30+
},
31+
} satisfies TypefusionDbScriptEffect<typeof mainSchema>;
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { Effect } from "effect";
2+
import { pgType } from "../../src/index.js";
3+
import { typefusionRefEffect } from "../../src/lib.js";
4+
import { TypefusionDbScriptEffect } from "../../src/store.js";
5+
import mainEffect from "../main_effect.js";
6+
const smallSchema = {
7+
small: pgType.text().notNull(),
8+
};
9+
10+
export default {
11+
name: "typefusion_effect_ref",
12+
schema: smallSchema,
13+
resultDatabase: "postgresql",
14+
runEffect: () =>
15+
Effect.gen(function* () {
16+
const result = yield* typefusionRefEffect(mainEffect);
17+
console.log("TYPEFUSION EFFECT REF IS RUN", result);
18+
return {
19+
data: [
20+
{
21+
small: "smallString" as const,
22+
},
23+
],
24+
};
25+
}),
26+
} satisfies TypefusionDbScriptEffect<typeof smallSchema>;

packages/typefusion/src/helpers.ts

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -73,14 +73,17 @@ export function runTypefusionScript(leaf: string) {
7373
}),
7474
});
7575

76-
const result = yield* Effect.tryPromise({
77-
try: async () => moduleDefault.run(),
78-
catch: (error) =>
79-
new ModuleExecutionError({
80-
cause: error,
81-
message: `Error executing module '${leaf}'`,
82-
}),
83-
});
76+
const result = yield* moduleDefault.runEffect
77+
? moduleDefault.runEffect()
78+
: Effect.tryPromise({
79+
// Either runEffect or run must be defined, so we'll use the non-null assertion to satisfy TypeScript
80+
try: async () => moduleDefault.run!(),
81+
catch: (error) =>
82+
new ModuleExecutionError({
83+
cause: error,
84+
message: `Error executing module '${leaf}'`,
85+
}),
86+
});
8487

8588
return yield* dbInsert(moduleDefault, result);
8689
});

packages/typefusion/src/lib.ts

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,15 @@ export const typefusionRef = async <T extends TypefusionScriptExport>(
2020
? R
2121
: never;
2222
}[]
23-
: Awaited<ReturnType<T["run"]>>["data"]
23+
: T extends { runEffect: (...args: any[]) => any }
24+
? Effect.Effect.Success<ReturnType<T["runEffect"]>> extends {
25+
data: infer D;
26+
}
27+
? D
28+
: never
29+
: T extends { run: (...args: any[]) => any }
30+
? Awaited<ReturnType<T["run"]>>["data"]
31+
: never
2432
> => {
2533
return dbSelect(module).pipe(
2634
Effect.provide([PgFinalLive, MySqlFinalLive]),
@@ -41,13 +49,19 @@ export const typefusionRefEffect = <T extends TypefusionScriptExport>(
4149
}
4250
? R
4351
: never;
44-
}
45-
: Awaited<ReturnType<T["run"]>>["data"],
52+
}[]
53+
: T extends { runEffect: (...args: any[]) => any }
54+
? Effect.Effect.Success<ReturnType<T["runEffect"]>> extends {
55+
data: infer D;
56+
}
57+
? D
58+
: never
59+
: T extends { run: (...args: any[]) => any }
60+
? Awaited<ReturnType<T["run"]>>["data"]
61+
: never,
4662
DatabaseSelectError | ConfigError
4763
> => {
48-
return dbSelect(module).pipe(
49-
Effect.provide([PgFinalLive, MySqlFinalLive]),
50-
) as any;
64+
return dbSelect(module) as any;
5165
};
5266

5367
/**

packages/typefusion/src/store.ts

Lines changed: 97 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { Schema } from "@effect/schema";
22
import { Effect, Data } from "effect";
33
import {
4+
TypefusionContextEffect,
45
TypefusionScriptExport,
56
TypefusionScriptResult,
67
TypefusionSupportedDatabases,
@@ -28,22 +29,30 @@ const MySqlTypeSchema = Schema.declare(
2829
},
2930
);
3031

31-
const ScriptExportSchema = Schema.Struct({
32-
name: Schema.String,
33-
resultDatabase: Schema.Literal("postgresql", "mysql"),
34-
schema: Schema.Union(
35-
Schema.Record({
36-
key: Schema.String,
37-
value: PgTypeSchema,
32+
const ScriptExportSchema = Schema.extend(
33+
Schema.Struct({
34+
name: Schema.String,
35+
resultDatabase: Schema.Literal("postgresql", "mysql"),
36+
schema: Schema.Union(
37+
Schema.Record({
38+
key: Schema.String,
39+
value: PgTypeSchema,
40+
}),
41+
Schema.Record({
42+
key: Schema.String,
43+
value: MySqlTypeSchema,
44+
}),
45+
).pipe(Schema.optional),
46+
}),
47+
Schema.Union(
48+
Schema.Struct({
49+
run: Schema.Any,
3850
}),
39-
Schema.Record({
40-
key: Schema.String,
41-
value: MySqlTypeSchema,
51+
Schema.Struct({
52+
runEffect: Schema.Any,
4253
}),
43-
).pipe(Schema.optional),
44-
45-
run: Schema.Any,
46-
});
54+
),
55+
);
4756

4857
/**
4958
* The schema for the return type of a Typefusion script `run` function.
@@ -69,6 +78,22 @@ export interface TypefusionScriptDataOnly<
6978
run: () => PromiseLike<TypefusionScriptResult<DataElement>>;
7079
}
7180

81+
/**
82+
* The type of a Typefusion script export ({@link TypefusionScriptExport}) when the result of the `runEffect` function contains the data without any schema.
83+
*/
84+
export interface TypefusionScriptDataOnlyEffect<
85+
DataElement extends Record<string, unknown>,
86+
> extends TypefusionScriptExport {
87+
schema?: {
88+
[key in keyof DataElement]: DbType<DataElement[key]>;
89+
};
90+
runEffect: <R extends TypefusionContextEffect>() => Effect.Effect<
91+
TypefusionScriptResult<DataElement>,
92+
any,
93+
R
94+
>;
95+
}
96+
7297
/**
7398
* The type of a Typefusion script export ({@link TypefusionScriptExport}) when the result of the `run` function contains both the 'schema' and return data
7499
* you want to use your existing {@link PgType} or {@link MySqlType} schema.
@@ -83,6 +108,23 @@ export interface TypefusionDbScript<T extends Record<string, DbType<unknown>>>
83108
>;
84109
}
85110

111+
/**
112+
* The type of a Typefusion script export ({@link TypefusionScriptExport}) when the result of the `runEffect` function contains both the 'schema' and return data
113+
* you want to use your existing {@link PgType} or {@link MySqlType} schema.
114+
*/
115+
export interface TypefusionDbScriptEffect<
116+
T extends Record<string, DbType<unknown>>,
117+
> extends TypefusionScriptExport {
118+
schema: T;
119+
runEffect: <R extends TypefusionContextEffect>() => Effect.Effect<
120+
TypefusionScriptResult<{
121+
[key in keyof T]: T[key] extends DbType<infer U> ? U : never;
122+
}>,
123+
any,
124+
R
125+
>;
126+
}
127+
86128
/**
87129
* The type of a Typefusion script export ({@link TypefusionScriptExport}) when the result of the `run` function contains both the 'schema' and return data
88130
* you want to use your existing {@link PgType} or {@link MySqlType} schema.
@@ -94,6 +136,23 @@ export interface TypefusionDbScriptDataUnknown<
94136
schema: T;
95137
run: () => PromiseLike<TypefusionScriptResult<Record<any, any>>>;
96138
}
139+
140+
/**
141+
* The type of a Typefusion script export ({@link TypefusionScriptExport}) when the result of the `runEffect` function contains both the 'schema' and return data
142+
* you want to use your existing {@link PgType} or {@link MySqlType} schema.
143+
* However, the data is unknown, so you can pass in any data array and it will type check.
144+
*/
145+
export interface TypefusionDbScriptDataUnknownEffect<
146+
T extends Record<string, DbType<unknown>>,
147+
> extends TypefusionScriptExport {
148+
schema: T;
149+
runEffect: <R extends TypefusionContextEffect>() => Effect.Effect<
150+
TypefusionScriptResult<Record<any, any>>,
151+
any,
152+
R
153+
>;
154+
}
155+
97156
/**
98157
* The type of a Typefusion script export ({@link TypefusionScriptExport}) when the result of the `run` function contains both the 'schema' and return data.
99158
* This will check that your `pgType` schema matches the data you are returning, but it's more verbose than using {@link TypefusionDbScript}.
@@ -107,13 +166,37 @@ export interface TypefusionScript<DataElement extends Record<string, unknown>>
107166
run: () => PromiseLike<TypefusionScriptResult<DataElement>>;
108167
}
109168

169+
/**
170+
* The type of a Typefusion script export ({@link TypefusionScriptExport}) when the result of the `runEffect` function contains both the 'schema' and return data.
171+
* This will check that your `pgType` schema matches the data you are returning, but it's more verbose than using {@link TypefusionDbScript}.
172+
*/
173+
export interface TypefusionScriptEffect<
174+
DataElement extends Record<string, unknown>,
175+
> extends TypefusionScriptExport {
176+
schema: {
177+
[key in keyof DataElement]: DbType<DataElement[key]>;
178+
};
179+
runEffect: <R extends TypefusionContextEffect>() => Effect.Effect<
180+
TypefusionScriptResult<DataElement>,
181+
any,
182+
R
183+
>;
184+
}
185+
110186
/**
111187
* The type of a Typefusion script export ({@link TypefusionScriptExport}) when the result of the `run` function contains potentially only the return data.
112188
* However, the data is unknown, so you can pass in any data array and it will type check.
113189
*/
114190
export interface TypefusionScriptUnknown
115191
extends TypefusionScript<Record<string, unknown>> {}
116192

193+
/**
194+
* The type of a Typefusion script export ({@link TypefusionScriptExport}) when the result of the `runEffect` function contains potentially only the return data.
195+
* However, the data is unknown, so you can pass in any data array and it will type check.
196+
*/
197+
export interface TypefusionScriptUnknownEffect
198+
extends TypefusionScriptEffect<Record<string, unknown>> {}
199+
117200
export class ConvertDataToSQLDDLError extends Data.TaggedError(
118201
"ConvertDataToSQLDDLError",
119202
)<{

packages/typefusion/src/types.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,34 @@
1+
import { Effect } from "effect";
12
import { MySqlType } from "./db/mysql/types.js";
23
import { PgType } from "./db/postgres/types.js";
4+
import { PgDatabaseHelperService, PgService } from "./db/postgres/client.js";
5+
import { MySQLDatabaseHelperService, MySQLService } from "./db/mysql/client.js";
36

47
export type TypefusionSupportedDatabases = "postgresql" | "mysql";
58

69
export interface TypefusionScriptResult<T> {
710
data: T[];
811
}
912

13+
export type TypefusionContextEffect =
14+
| PgService
15+
| MySQLService
16+
| PgDatabaseHelperService
17+
| MySQLDatabaseHelperService;
18+
1019
/**
1120
* This is a partial type for the 'default' export of an ES Module when importing a Typefusion script.
1221
*/
1322
export interface TypefusionScriptExport {
1423
name: string;
1524
schema?: Record<string, PgType<unknown>> | Record<string, MySqlType<unknown>>;
1625
resultDatabase: TypefusionSupportedDatabases;
17-
run: () => PromiseLike<TypefusionScriptResult<unknown>>;
26+
run?: () => PromiseLike<TypefusionScriptResult<unknown>>;
27+
runEffect?: <R extends TypefusionContextEffect>() => Effect.Effect<
28+
TypefusionScriptResult<unknown>,
29+
any,
30+
R
31+
>;
1832
}
1933

2034
/**

0 commit comments

Comments
 (0)