@effect/workflow 0.1.1 → 0.1.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/README.md +13 -1
- package/WorkflowProxy/package.json +6 -0
- package/WorkflowProxyServer/package.json +6 -0
- package/dist/cjs/Activity.js +6 -11
- package/dist/cjs/Activity.js.map +1 -1
- package/dist/cjs/Workflow.js +37 -6
- package/dist/cjs/Workflow.js.map +1 -1
- package/dist/cjs/WorkflowProxy.js +111 -0
- package/dist/cjs/WorkflowProxy.js.map +1 -0
- package/dist/cjs/WorkflowProxyServer.js +61 -0
- package/dist/cjs/WorkflowProxyServer.js.map +1 -0
- package/dist/cjs/index.js +5 -1
- package/dist/dts/Activity.d.ts +1 -17
- package/dist/dts/Activity.d.ts.map +1 -1
- package/dist/dts/Workflow.d.ts +85 -1
- package/dist/dts/Workflow.d.ts.map +1 -1
- package/dist/dts/WorkflowProxy.d.ts +90 -0
- package/dist/dts/WorkflowProxy.d.ts.map +1 -0
- package/dist/dts/WorkflowProxyServer.d.ts +27 -0
- package/dist/dts/WorkflowProxyServer.d.ts.map +1 -0
- package/dist/dts/index.d.ts +8 -0
- package/dist/dts/index.d.ts.map +1 -1
- package/dist/esm/Activity.js +4 -10
- package/dist/esm/Activity.js.map +1 -1
- package/dist/esm/Workflow.js +36 -4
- package/dist/esm/Workflow.js.map +1 -1
- package/dist/esm/WorkflowProxy.js +101 -0
- package/dist/esm/WorkflowProxy.js.map +1 -0
- package/dist/esm/WorkflowProxyServer.js +52 -0
- package/dist/esm/WorkflowProxyServer.js.map +1 -0
- package/dist/esm/index.js +8 -0
- package/dist/esm/index.js.map +1 -1
- package/package.json +20 -2
- package/src/Activity.ts +6 -34
- package/src/Workflow.ts +155 -2
- package/src/WorkflowProxy.ts +178 -0
- package/src/WorkflowProxyServer.ts +103 -0
- package/src/index.ts +10 -0
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import * as HttpApiBuilder from "@effect/platform/HttpApiBuilder";
|
|
2
|
+
import * as Context from "effect/Context";
|
|
3
|
+
import * as Effect from "effect/Effect";
|
|
4
|
+
import * as Layer from "effect/Layer";
|
|
5
|
+
/**
|
|
6
|
+
* @since 1.0.0
|
|
7
|
+
* @category Layers
|
|
8
|
+
*/
|
|
9
|
+
export const layerHttpApi = (api, name, workflows) => HttpApiBuilder.group(api, name, Effect.fnUntraced(function* (handlers_) {
|
|
10
|
+
let handlers = handlers_;
|
|
11
|
+
for (const workflow_ of workflows) {
|
|
12
|
+
const workflow = workflow_;
|
|
13
|
+
handlers = handlers.handle(workflow.name, ({
|
|
14
|
+
payload
|
|
15
|
+
}) => workflow.execute(payload)).handle(workflow.name + "Discard", ({
|
|
16
|
+
payload
|
|
17
|
+
}) => workflow.execute(payload, {
|
|
18
|
+
discard: true
|
|
19
|
+
}));
|
|
20
|
+
}
|
|
21
|
+
return handlers;
|
|
22
|
+
}));
|
|
23
|
+
/**
|
|
24
|
+
* @since 1.0.0
|
|
25
|
+
* @category Layers
|
|
26
|
+
*/
|
|
27
|
+
export const layerRpcHandlers = (workflows, options) => Layer.effectContext(Effect.gen(function* () {
|
|
28
|
+
const context = yield* Effect.context();
|
|
29
|
+
const prefix = options?.prefix ?? "";
|
|
30
|
+
const handlers = new Map();
|
|
31
|
+
for (const workflow_ of workflows) {
|
|
32
|
+
const workflow = workflow_;
|
|
33
|
+
const tag = `${prefix}${workflow.name}`;
|
|
34
|
+
const tagDiscard = `${tag}Discard`;
|
|
35
|
+
const key = `@effect/rpc/Rpc/${tag}`;
|
|
36
|
+
const keyDiscard = `${key}Discard`;
|
|
37
|
+
handlers.set(key, {
|
|
38
|
+
context,
|
|
39
|
+
tag,
|
|
40
|
+
handler: payload => workflow.execute(payload)
|
|
41
|
+
});
|
|
42
|
+
handlers.set(keyDiscard, {
|
|
43
|
+
context,
|
|
44
|
+
tag: tagDiscard,
|
|
45
|
+
handler: payload => workflow.execute(payload, {
|
|
46
|
+
discard: true
|
|
47
|
+
})
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
return Context.unsafeMake(handlers);
|
|
51
|
+
}));
|
|
52
|
+
//# sourceMappingURL=WorkflowProxyServer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"WorkflowProxyServer.js","names":["HttpApiBuilder","Context","Effect","Layer","layerHttpApi","api","name","workflows","group","fnUntraced","handlers_","handlers","workflow_","workflow","handle","payload","execute","discard","layerRpcHandlers","options","effectContext","gen","context","prefix","Map","tag","tagDiscard","key","keyDiscard","set","handler","unsafeMake"],"sources":["../../src/WorkflowProxyServer.ts"],"sourcesContent":[null],"mappings":"AAIA,OAAO,KAAKA,cAAc,MAAM,iCAAiC;AAIjE,OAAO,KAAKC,OAAO,MAAM,gBAAgB;AACzC,OAAO,KAAKC,MAAM,MAAM,eAAe;AACvC,OAAO,KAAKC,KAAK,MAAM,cAAc;AAIrC;;;;AAIA,OAAO,MAAMC,YAAY,GAAGA,CAQ1BC,GAA+C,EAC/CC,IAAU,EACVC,SAAoB,KAMpBP,cAAc,CAACQ,KAAK,CAClBH,GAAG,EACHC,IAAI,EACJJ,MAAM,CAACO,UAAU,CAAC,WAAUC,SAAS;EACnC,IAAIC,QAAQ,GAAGD,SAAgB;EAC/B,KAAK,MAAME,SAAS,IAAIL,SAAS,EAAE;IACjC,MAAMM,QAAQ,GAAGD,SAAqD;IACtED,QAAQ,GAAGA,QAAQ,CAChBG,MAAM,CACLD,QAAQ,CAACP,IAAW,EACpB,CAAC;MAAES;IAAO,CAAoB,KAAKF,QAAQ,CAACG,OAAO,CAACD,OAAO,CAAC,CAC7D,CACAD,MAAM,CACLD,QAAQ,CAACP,IAAI,GAAG,SAAgB,EAChC,CAAC;MAAES;IAAO,CAAoB,KAAKF,QAAQ,CAACG,OAAO,CAACD,OAAO,EAAE;MAAEE,OAAO,EAAE;IAAI,CAAS,CAAC,CACvF;EACL;EACA,OAAON,QAAwD;AACjE,CAAC,CAAC,CACH;AAEH;;;;AAIA,OAAO,MAAMO,gBAAgB,GAAGA,CAG9BX,SAAoB,EAAEY,OAEvB,KAKChB,KAAK,CAACiB,aAAa,CAAClB,MAAM,CAACmB,GAAG,CAAC,aAAS;EACtC,MAAMC,OAAO,GAAG,OAAOpB,MAAM,CAACoB,OAAO,EAAS;EAC9C,MAAMC,MAAM,GAAGJ,OAAO,EAAEI,MAAM,IAAI,EAAE;EACpC,MAAMZ,QAAQ,GAAG,IAAIa,GAAG,EAA+B;EACvD,KAAK,MAAMZ,SAAS,IAAIL,SAAS,EAAE;IACjC,MAAMM,QAAQ,GAAGD,SAAqD;IACtE,MAAMa,GAAG,GAAG,GAAGF,MAAM,GAAGV,QAAQ,CAACP,IAAI,EAAE;IACvC,MAAMoB,UAAU,GAAG,GAAGD,GAAG,SAAS;IAClC,MAAME,GAAG,GAAG,mBAAmBF,GAAG,EAAE;IACpC,MAAMG,UAAU,GAAG,GAAGD,GAAG,SAAS;IAClChB,QAAQ,CAACkB,GAAG,CAACF,GAAG,EAAE;MAChBL,OAAO;MACPG,GAAG;MACHK,OAAO,EAAGf,OAAY,IAAKF,QAAQ,CAACG,OAAO,CAACD,OAAO;KAC7C,CAAC;IACTJ,QAAQ,CAACkB,GAAG,CAACD,UAAU,EAAE;MACvBN,OAAO;MACPG,GAAG,EAAEC,UAAU;MACfI,OAAO,EAAGf,OAAY,IAAKF,QAAQ,CAACG,OAAO,CAACD,OAAO,EAAE;QAAEE,OAAO,EAAE;MAAI,CAAS;KACvE,CAAC;EACX;EACA,OAAOhB,OAAO,CAAC8B,UAAU,CAACpB,QAAQ,CAAC;AACrC,CAAC,CAAC,CAAC","ignoreList":[]}
|
package/dist/esm/index.js
CHANGED
|
@@ -18,4 +18,12 @@ export * as Workflow from "./Workflow.js";
|
|
|
18
18
|
* @since 1.0.0
|
|
19
19
|
*/
|
|
20
20
|
export * as WorkflowEngine from "./WorkflowEngine.js";
|
|
21
|
+
/**
|
|
22
|
+
* @since 1.0.0
|
|
23
|
+
*/
|
|
24
|
+
export * as WorkflowProxy from "./WorkflowProxy.js";
|
|
25
|
+
/**
|
|
26
|
+
* @since 1.0.0
|
|
27
|
+
*/
|
|
28
|
+
export * as WorkflowProxyServer from "./WorkflowProxyServer.js";
|
|
21
29
|
//# sourceMappingURL=index.js.map
|
package/dist/esm/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["Activity","DurableClock","DurableDeferred","Workflow","WorkflowEngine"],"sources":["../../src/index.ts"],"sourcesContent":[null],"mappings":"AAAA;;;AAGA,OAAO,KAAKA,QAAQ,MAAM,eAAe;AAEzC;;;AAGA,OAAO,KAAKC,YAAY,MAAM,mBAAmB;AAEjD;;;AAGA,OAAO,KAAKC,eAAe,MAAM,sBAAsB;AAEvD;;;AAGA,OAAO,KAAKC,QAAQ,MAAM,eAAe;AAEzC;;;AAGA,OAAO,KAAKC,cAAc,MAAM,qBAAqB","ignoreList":[]}
|
|
1
|
+
{"version":3,"file":"index.js","names":["Activity","DurableClock","DurableDeferred","Workflow","WorkflowEngine","WorkflowProxy","WorkflowProxyServer"],"sources":["../../src/index.ts"],"sourcesContent":[null],"mappings":"AAAA;;;AAGA,OAAO,KAAKA,QAAQ,MAAM,eAAe;AAEzC;;;AAGA,OAAO,KAAKC,YAAY,MAAM,mBAAmB;AAEjD;;;AAGA,OAAO,KAAKC,eAAe,MAAM,sBAAsB;AAEvD;;;AAGA,OAAO,KAAKC,QAAQ,MAAM,eAAe;AAEzC;;;AAGA,OAAO,KAAKC,cAAc,MAAM,qBAAqB;AAErD;;;AAGA,OAAO,KAAKC,aAAa,MAAM,oBAAoB;AAEnD;;;AAGA,OAAO,KAAKC,mBAAmB,MAAM,0BAA0B","ignoreList":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@effect/workflow",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "Durable workflows for Effect",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -11,7 +11,9 @@
|
|
|
11
11
|
"sideEffects": [],
|
|
12
12
|
"homepage": "https://effect.website",
|
|
13
13
|
"peerDependencies": {
|
|
14
|
-
"effect": "^
|
|
14
|
+
"@effect/platform": "^0.84.6",
|
|
15
|
+
"effect": "^3.16.3",
|
|
16
|
+
"@effect/rpc": "^0.61.6"
|
|
15
17
|
},
|
|
16
18
|
"publishConfig": {
|
|
17
19
|
"provenance": true
|
|
@@ -50,6 +52,16 @@
|
|
|
50
52
|
"types": "./dist/dts/WorkflowEngine.d.ts",
|
|
51
53
|
"import": "./dist/esm/WorkflowEngine.js",
|
|
52
54
|
"default": "./dist/cjs/WorkflowEngine.js"
|
|
55
|
+
},
|
|
56
|
+
"./WorkflowProxy": {
|
|
57
|
+
"types": "./dist/dts/WorkflowProxy.d.ts",
|
|
58
|
+
"import": "./dist/esm/WorkflowProxy.js",
|
|
59
|
+
"default": "./dist/cjs/WorkflowProxy.js"
|
|
60
|
+
},
|
|
61
|
+
"./WorkflowProxyServer": {
|
|
62
|
+
"types": "./dist/dts/WorkflowProxyServer.d.ts",
|
|
63
|
+
"import": "./dist/esm/WorkflowProxyServer.js",
|
|
64
|
+
"default": "./dist/cjs/WorkflowProxyServer.js"
|
|
53
65
|
}
|
|
54
66
|
},
|
|
55
67
|
"typesVersions": {
|
|
@@ -68,6 +80,12 @@
|
|
|
68
80
|
],
|
|
69
81
|
"WorkflowEngine": [
|
|
70
82
|
"./dist/dts/WorkflowEngine.d.ts"
|
|
83
|
+
],
|
|
84
|
+
"WorkflowProxy": [
|
|
85
|
+
"./dist/dts/WorkflowProxy.d.ts"
|
|
86
|
+
],
|
|
87
|
+
"WorkflowProxyServer": [
|
|
88
|
+
"./dist/dts/WorkflowProxyServer.d.ts"
|
|
71
89
|
]
|
|
72
90
|
}
|
|
73
91
|
}
|
package/src/Activity.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @since 1.0.0
|
|
3
3
|
*/
|
|
4
|
-
import type * as Cause from "effect/Cause"
|
|
5
4
|
import * as Context from "effect/Context"
|
|
6
5
|
import * as Effect from "effect/Effect"
|
|
7
6
|
import * as Effectable from "effect/Effectable"
|
|
@@ -47,6 +46,11 @@ export interface Activity<
|
|
|
47
46
|
Exit.Exit<Success["Encoded"], Error["Encoded"]>,
|
|
48
47
|
Success["Context"] | Error["Context"]
|
|
49
48
|
>
|
|
49
|
+
readonly execute: Effect.Effect<
|
|
50
|
+
Success["Type"],
|
|
51
|
+
Error["Type"],
|
|
52
|
+
Success["Context"] | Error["Context"] | R | WorkflowEngine | WorkflowInstance
|
|
53
|
+
>
|
|
50
54
|
readonly executeEncoded: Effect.Effect<
|
|
51
55
|
Success["Encoded"],
|
|
52
56
|
Error["Encoded"],
|
|
@@ -95,6 +99,7 @@ export const make = <
|
|
|
95
99
|
failure: errorSchema,
|
|
96
100
|
defect: Schema.Defect
|
|
97
101
|
}),
|
|
102
|
+
execute: options.execute,
|
|
98
103
|
executeEncoded: Effect.matchEffect(options.execute, {
|
|
99
104
|
onFailure: (error) => Effect.flatMap(Effect.orDie(Schema.encode(self.errorSchema as any)(error)), Effect.fail),
|
|
100
105
|
onSuccess: (value) => Effect.orDie(Schema.encode(self.successSchema)(value))
|
|
@@ -122,39 +127,6 @@ export const retry: typeof Effect.retry = dual(
|
|
|
122
127
|
})
|
|
123
128
|
)
|
|
124
129
|
|
|
125
|
-
/**
|
|
126
|
-
* @since 1.0.0
|
|
127
|
-
* @category Error handling
|
|
128
|
-
*/
|
|
129
|
-
export const onError: {
|
|
130
|
-
/**
|
|
131
|
-
* @since 1.0.0
|
|
132
|
-
* @category Error handling
|
|
133
|
-
*/
|
|
134
|
-
<Error extends Schema.Schema.All, R2>(
|
|
135
|
-
onError: (cause: Cause.Cause<Error["Type"]>) => Effect.Effect<void, never, R2>
|
|
136
|
-
): <Success extends Schema.Schema.Any, R>(
|
|
137
|
-
self: Activity<Success, Error, R>
|
|
138
|
-
) => Activity<Success, Error, R | Exclude<R2, WorkflowEngine | WorkflowInstance>>
|
|
139
|
-
/**
|
|
140
|
-
* @since 1.0.0
|
|
141
|
-
* @category Error handling
|
|
142
|
-
*/
|
|
143
|
-
<Success extends Schema.Schema.Any, Error extends Schema.Schema.All, R, R2>(
|
|
144
|
-
self: Activity<Success, Error, R>,
|
|
145
|
-
onError: (cause: Cause.Cause<Error["Type"]>) => Effect.Effect<void, never, R2>
|
|
146
|
-
): Activity<Success, Error, R | Exclude<R2, WorkflowEngine | WorkflowInstance>>
|
|
147
|
-
} = dual(2, <Success extends Schema.Schema.Any, Error extends Schema.Schema.All, R, R2>(
|
|
148
|
-
self: Activity<Success, Error, R>,
|
|
149
|
-
onError: (cause: Cause.Cause<Error["Type"]>) => Effect.Effect<void, never, R2>
|
|
150
|
-
): Activity<Success, Error, R | Exclude<R2, WorkflowEngine | WorkflowInstance>> =>
|
|
151
|
-
make({
|
|
152
|
-
name: `${self.name}/onError`,
|
|
153
|
-
success: self.successSchema,
|
|
154
|
-
error: self.errorSchema,
|
|
155
|
-
execute: Effect.onError(self, onError)
|
|
156
|
-
}))
|
|
157
|
-
|
|
158
130
|
/**
|
|
159
131
|
* @since 1.0.0
|
|
160
132
|
* @category Attempts
|
package/src/Workflow.ts
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @since 1.0.0
|
|
3
3
|
*/
|
|
4
|
+
import type * as Cause from "effect/Cause"
|
|
4
5
|
import * as Context from "effect/Context"
|
|
5
6
|
import * as Data from "effect/Data"
|
|
6
7
|
import * as Effect from "effect/Effect"
|
|
7
8
|
import * as Exit from "effect/Exit"
|
|
9
|
+
import { dual } from "effect/Function"
|
|
8
10
|
import * as Layer from "effect/Layer"
|
|
9
11
|
import type { Pipeable } from "effect/Pipeable"
|
|
10
12
|
import * as Predicate from "effect/Predicate"
|
|
@@ -12,6 +14,7 @@ import * as PrimaryKey from "effect/PrimaryKey"
|
|
|
12
14
|
import * as Schedule from "effect/Schedule"
|
|
13
15
|
import * as Schema from "effect/Schema"
|
|
14
16
|
import type * as AST from "effect/SchemaAST"
|
|
17
|
+
import type * as Scope from "effect/Scope"
|
|
15
18
|
import { makeHashDigest } from "./internal/crypto.js"
|
|
16
19
|
import type { WorkflowEngine, WorkflowInstance } from "./WorkflowEngine.js"
|
|
17
20
|
|
|
@@ -42,6 +45,27 @@ export interface Workflow<
|
|
|
42
45
|
readonly payloadSchema: Payload
|
|
43
46
|
readonly successSchema: Success
|
|
44
47
|
readonly errorSchema: Error
|
|
48
|
+
readonly annotations: Context.Context<never>
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Add an annotation to the workflow.
|
|
52
|
+
*/
|
|
53
|
+
annotate<I, S>(tag: Context.Tag<I, S>, value: S): Workflow<
|
|
54
|
+
Name,
|
|
55
|
+
Payload,
|
|
56
|
+
Success,
|
|
57
|
+
Error
|
|
58
|
+
>
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Add the annotations from a Context object to the workflow.
|
|
62
|
+
*/
|
|
63
|
+
annotateContext<I>(context: Context.Context<I>): Workflow<
|
|
64
|
+
Name,
|
|
65
|
+
Payload,
|
|
66
|
+
Success,
|
|
67
|
+
Error
|
|
68
|
+
>
|
|
45
69
|
|
|
46
70
|
/**
|
|
47
71
|
* Execute the workflow with the given payload.
|
|
@@ -76,7 +100,7 @@ export interface Workflow<
|
|
|
76
100
|
Registration<Name> | WorkflowEngine,
|
|
77
101
|
never,
|
|
78
102
|
| WorkflowEngine
|
|
79
|
-
| Exclude<R, WorkflowEngine | WorkflowInstance>
|
|
103
|
+
| Exclude<R, WorkflowEngine | WorkflowInstance | Execution<Name> | Scope.Scope>
|
|
80
104
|
| Payload["Context"]
|
|
81
105
|
| Success["Context"]
|
|
82
106
|
| Error["Context"]
|
|
@@ -88,6 +112,26 @@ export interface Workflow<
|
|
|
88
112
|
readonly executionId: (
|
|
89
113
|
payload: Schema.Simplify<Schema.Struct.Constructor<Payload["fields"]>>
|
|
90
114
|
) => Effect.Effect<string>
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Add compensation logic to an effect inside a Workflow. The compensation finalizer will be
|
|
118
|
+
* called if the entire workflow fails, allowing you to perform cleanup or
|
|
119
|
+
* other actions based on the success value and the cause of the workflow failure.
|
|
120
|
+
*
|
|
121
|
+
* NOTE: Compensation will not work for nested activities. Compensation
|
|
122
|
+
* finalizers are only registered for top-level effects in the workflow.
|
|
123
|
+
*/
|
|
124
|
+
readonly withCompensation: {
|
|
125
|
+
<A, R2>(
|
|
126
|
+
compensation: (value: A, cause: Cause.Cause<Error["Type"]>) => Effect.Effect<void, never, R2>
|
|
127
|
+
): <E, R>(
|
|
128
|
+
effect: Effect.Effect<A, E, R>
|
|
129
|
+
) => Effect.Effect<A, E, R | R2 | WorkflowInstance | Execution<Name> | Scope.Scope>
|
|
130
|
+
<A, E, R, R2>(
|
|
131
|
+
effect: Effect.Effect<A, E, R>,
|
|
132
|
+
compensation: (value: A, cause: Cause.Cause<Error["Type"]>) => Effect.Effect<void, never, R2>
|
|
133
|
+
): Effect.Effect<A, E, R | R2 | WorkflowInstance | Execution<Name> | Scope.Scope>
|
|
134
|
+
}
|
|
91
135
|
}
|
|
92
136
|
|
|
93
137
|
/**
|
|
@@ -124,6 +168,15 @@ export interface Registration<Name extends string> {
|
|
|
124
168
|
readonly name: Name
|
|
125
169
|
}
|
|
126
170
|
|
|
171
|
+
/**
|
|
172
|
+
* @since 1.0.0
|
|
173
|
+
* @category Models
|
|
174
|
+
*/
|
|
175
|
+
export interface Execution<Name extends string> {
|
|
176
|
+
readonly _: unique symbol
|
|
177
|
+
readonly name: Name
|
|
178
|
+
}
|
|
179
|
+
|
|
127
180
|
/**
|
|
128
181
|
* @since 1.0.0
|
|
129
182
|
* @category Models
|
|
@@ -134,9 +187,34 @@ export interface Any {
|
|
|
134
187
|
readonly payloadSchema: AnyStructSchema
|
|
135
188
|
readonly successSchema: Schema.Schema.Any
|
|
136
189
|
readonly errorSchema: Schema.Schema.All
|
|
190
|
+
readonly annotations: Context.Context<never>
|
|
137
191
|
readonly executionId: (payload: any) => Effect.Effect<string>
|
|
138
192
|
}
|
|
139
193
|
|
|
194
|
+
/**
|
|
195
|
+
* @since 1.0.0
|
|
196
|
+
* @category Models
|
|
197
|
+
*/
|
|
198
|
+
export type Registrations<Workflows extends Any> = Workflows extends Workflow<
|
|
199
|
+
infer _Name,
|
|
200
|
+
infer _Payload,
|
|
201
|
+
infer _Success,
|
|
202
|
+
infer _Error
|
|
203
|
+
> ? Registration<_Name> :
|
|
204
|
+
never
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* @since 1.0.0
|
|
208
|
+
* @category Models
|
|
209
|
+
*/
|
|
210
|
+
export type Requirements<Workflows extends Any> = Workflows extends Workflow<
|
|
211
|
+
infer _Name,
|
|
212
|
+
infer _Payload,
|
|
213
|
+
infer _Success,
|
|
214
|
+
infer _Error
|
|
215
|
+
> ? _Payload["Context"] | _Success["Context"] | _Error["Context"] :
|
|
216
|
+
never
|
|
217
|
+
|
|
140
218
|
const EngineTag = Context.GenericTag<WorkflowEngine, WorkflowEngine["Type"]>(
|
|
141
219
|
"@effect/workflow/WorkflowEngine" satisfies typeof WorkflowEngine.key
|
|
142
220
|
)
|
|
@@ -164,6 +242,7 @@ export const make = <
|
|
|
164
242
|
readonly success?: Success
|
|
165
243
|
readonly error?: Error
|
|
166
244
|
readonly suspendedRetrySchedule?: Schedule.Schedule<any, unknown> | undefined
|
|
245
|
+
readonly annotations?: Context.Context<never>
|
|
167
246
|
}
|
|
168
247
|
): Workflow<Name, Payload extends Schema.Struct.Fields ? Schema.Struct<Payload> : Payload, Success, Error> => {
|
|
169
248
|
const suspendedRetrySchedule = options.suspendedRetrySchedule ?? defaultRetrySchedule
|
|
@@ -174,6 +253,19 @@ export const make = <
|
|
|
174
253
|
payloadSchema: Schema.isSchema(options.payload) ? options.payload : Schema.Struct(options.payload as any),
|
|
175
254
|
successSchema: options.success ?? Schema.Void as any,
|
|
176
255
|
errorSchema: options.error ?? Schema.Never as any,
|
|
256
|
+
annotations: options.annotations ?? Context.empty(),
|
|
257
|
+
annotate(tag, value) {
|
|
258
|
+
return make({
|
|
259
|
+
...options,
|
|
260
|
+
annotations: Context.add(self.annotations, tag, value)
|
|
261
|
+
})
|
|
262
|
+
},
|
|
263
|
+
annotateContext(context) {
|
|
264
|
+
return make({
|
|
265
|
+
...options,
|
|
266
|
+
annotations: Context.merge(self.annotations, context)
|
|
267
|
+
})
|
|
268
|
+
},
|
|
177
269
|
execute: Effect.fnUntraced(
|
|
178
270
|
function*(fields: any, opts) {
|
|
179
271
|
const payload = self.payloadSchema.make(fields)
|
|
@@ -229,7 +321,8 @@ export const make = <
|
|
|
229
321
|
) as any)
|
|
230
322
|
return EngineTag.context(engine)
|
|
231
323
|
})) as any,
|
|
232
|
-
executionId: (payload) => makeExecutionId(self.payloadSchema.make(payload))
|
|
324
|
+
executionId: (payload) => makeExecutionId(self.payloadSchema.make(payload)),
|
|
325
|
+
withCompensation
|
|
233
326
|
}
|
|
234
327
|
|
|
235
328
|
return self
|
|
@@ -399,3 +492,63 @@ export const intoResult = <A, E, R>(
|
|
|
399
492
|
})
|
|
400
493
|
)
|
|
401
494
|
)
|
|
495
|
+
|
|
496
|
+
/**
|
|
497
|
+
* Add compensation logic to an effect inside a Workflow. The compensation finalizer will be
|
|
498
|
+
* called if the entire workflow fails, allowing you to perform cleanup or
|
|
499
|
+
* other actions based on the success value and the cause of the workflow failure.
|
|
500
|
+
*
|
|
501
|
+
* NOTE: Compensation will not work for nested activities. Compensation
|
|
502
|
+
* finalizers are only registered for top-level effects in the workflow.
|
|
503
|
+
*
|
|
504
|
+
* @since 1.0.0
|
|
505
|
+
* @category Compensation
|
|
506
|
+
*/
|
|
507
|
+
export const withCompensation: {
|
|
508
|
+
/**
|
|
509
|
+
* Add compensation logic to an effect inside a Workflow. The compensation finalizer will be
|
|
510
|
+
* called if the entire workflow fails, allowing you to perform cleanup or
|
|
511
|
+
* other actions based on the success value and the cause of the workflow failure.
|
|
512
|
+
*
|
|
513
|
+
* NOTE: Compensation will not work for nested activities. Compensation
|
|
514
|
+
* finalizers are only registered for top-level effects in the workflow.
|
|
515
|
+
*
|
|
516
|
+
* @since 1.0.0
|
|
517
|
+
* @category Compensation
|
|
518
|
+
*/
|
|
519
|
+
<A, R2>(
|
|
520
|
+
compensation: (value: A, cause: Cause.Cause<unknown>) => Effect.Effect<void, never, R2>
|
|
521
|
+
): <E, R>(
|
|
522
|
+
effect: Effect.Effect<A, E, R>
|
|
523
|
+
) => Effect.Effect<A, E, R | R2 | WorkflowInstance | Scope.Scope>
|
|
524
|
+
/**
|
|
525
|
+
* Add compensation logic to an effect inside a Workflow. The compensation finalizer will be
|
|
526
|
+
* called if the entire workflow fails, allowing you to perform cleanup or
|
|
527
|
+
* other actions based on the success value and the cause of the workflow failure.
|
|
528
|
+
*
|
|
529
|
+
* NOTE: Compensation will not work for nested activities. Compensation
|
|
530
|
+
* finalizers are only registered for top-level effects in the workflow.
|
|
531
|
+
*
|
|
532
|
+
* @since 1.0.0
|
|
533
|
+
* @category Compensation
|
|
534
|
+
*/
|
|
535
|
+
<A, E, R, R2>(
|
|
536
|
+
effect: Effect.Effect<A, E, R>,
|
|
537
|
+
compensation: (value: A, cause: Cause.Cause<unknown>) => Effect.Effect<void, never, R2>
|
|
538
|
+
): Effect.Effect<A, E, R | R2 | WorkflowInstance | Scope.Scope>
|
|
539
|
+
} = dual(2, <A, E, R, R2>(
|
|
540
|
+
effect: Effect.Effect<A, E, R>,
|
|
541
|
+
compensation: (value: A, cause: Cause.Cause<unknown>) => Effect.Effect<void, never, R2>
|
|
542
|
+
): Effect.Effect<A, E, R | R2 | WorkflowInstance | Scope.Scope> =>
|
|
543
|
+
Effect.uninterruptibleMask((restore) =>
|
|
544
|
+
Effect.contextWithEffect((context: Context.Context<WorkflowInstance>) => {
|
|
545
|
+
const instance = Context.get(context, InstanceTag)
|
|
546
|
+
return Effect.tap(restore(effect), (value) =>
|
|
547
|
+
Effect.addFinalizer((exit) => {
|
|
548
|
+
if (Exit.isSuccess(exit) || instance.suspended) {
|
|
549
|
+
return Effect.void
|
|
550
|
+
}
|
|
551
|
+
return compensation(value, exit.cause)
|
|
552
|
+
}))
|
|
553
|
+
})
|
|
554
|
+
))
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @since 1.0.0
|
|
3
|
+
*/
|
|
4
|
+
import * as HttpApiEndpoint from "@effect/platform/HttpApiEndpoint"
|
|
5
|
+
import * as HttpApiGroup from "@effect/platform/HttpApiGroup"
|
|
6
|
+
import * as Rpc from "@effect/rpc/Rpc"
|
|
7
|
+
import * as RpcGroup from "@effect/rpc/RpcGroup"
|
|
8
|
+
import type { NonEmptyReadonlyArray } from "effect/Array"
|
|
9
|
+
import type * as Workflow from "./Workflow.js"
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Derives an `RpcGroup` from a list of workflows.
|
|
13
|
+
*
|
|
14
|
+
* ```ts
|
|
15
|
+
* import { RpcServer } from "@effect/rpc"
|
|
16
|
+
* import { Workflow, WorkflowProxy, WorkflowProxyServer } from "@effect/workflow"
|
|
17
|
+
* import { Layer, Schema } from "effect"
|
|
18
|
+
*
|
|
19
|
+
* const EmailWorkflow = Workflow.make({
|
|
20
|
+
* name: "EmailWorkflow",
|
|
21
|
+
* payload: {
|
|
22
|
+
* id: Schema.String,
|
|
23
|
+
* to: Schema.String
|
|
24
|
+
* },
|
|
25
|
+
* idempotencyKey: ({ id }) => id
|
|
26
|
+
* })
|
|
27
|
+
*
|
|
28
|
+
* const myWorkflows = [EmailWorkflow] as const
|
|
29
|
+
*
|
|
30
|
+
* // Use WorkflowProxy.toRpcGroup to create a `RpcGroup` from the
|
|
31
|
+
* // workflows
|
|
32
|
+
* class MyRpcs extends WorkflowProxy.toRpcGroup(myWorkflows) {}
|
|
33
|
+
*
|
|
34
|
+
* // Use WorkflowProxyServer.layerRpcHandlers to create a layer that implements
|
|
35
|
+
* // the rpc handlers
|
|
36
|
+
* const ApiLayer = RpcServer.layer(MyRpcs).pipe(
|
|
37
|
+
* Layer.provide(WorkflowProxyServer.layerRpcHandlers(myWorkflows))
|
|
38
|
+
* )
|
|
39
|
+
* ```
|
|
40
|
+
*
|
|
41
|
+
* @since 1.0.0
|
|
42
|
+
* @category Constructors
|
|
43
|
+
*/
|
|
44
|
+
export const toRpcGroup = <
|
|
45
|
+
const Workflows extends NonEmptyReadonlyArray<Workflow.Any>,
|
|
46
|
+
const Prefix extends string = ""
|
|
47
|
+
>(
|
|
48
|
+
workflows: Workflows,
|
|
49
|
+
options?: {
|
|
50
|
+
readonly prefix?: Prefix | undefined
|
|
51
|
+
}
|
|
52
|
+
): RpcGroup.RpcGroup<ConvertRpcs<Workflows[number], Prefix>> => {
|
|
53
|
+
const prefix = options?.prefix ?? ""
|
|
54
|
+
const rpcs: Array<Rpc.Any> = []
|
|
55
|
+
for (const workflow of workflows) {
|
|
56
|
+
rpcs.push(
|
|
57
|
+
Rpc.make(`${prefix}${workflow.name}`, {
|
|
58
|
+
payload: workflow.payloadSchema,
|
|
59
|
+
error: workflow.errorSchema,
|
|
60
|
+
success: workflow.successSchema
|
|
61
|
+
}).annotateContext(workflow.annotations),
|
|
62
|
+
Rpc.make(`${prefix}${workflow.name}Discard`, {
|
|
63
|
+
payload: workflow.payloadSchema
|
|
64
|
+
}).annotateContext(workflow.annotations)
|
|
65
|
+
)
|
|
66
|
+
}
|
|
67
|
+
return RpcGroup.make(...rpcs) as any
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* @since 1.0.0
|
|
72
|
+
*/
|
|
73
|
+
export type ConvertRpcs<Workflows extends Workflow.Any, Prefix extends string> = Workflows extends Workflow.Workflow<
|
|
74
|
+
infer _Name,
|
|
75
|
+
infer _Payload,
|
|
76
|
+
infer _Success,
|
|
77
|
+
infer _Error
|
|
78
|
+
> ?
|
|
79
|
+
| Rpc.Rpc<`${Prefix}${_Name}`, _Payload, _Success, _Error>
|
|
80
|
+
| Rpc.Rpc<`${Prefix}${_Name}Discard`, _Payload>
|
|
81
|
+
: never
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Derives an `HttpApiGroup` from a list of workflows.
|
|
85
|
+
*
|
|
86
|
+
* ```ts
|
|
87
|
+
* import { HttpApi, HttpApiBuilder } from "@effect/platform"
|
|
88
|
+
* import { Workflow, WorkflowProxy, WorkflowProxyServer } from "@effect/workflow"
|
|
89
|
+
* import { Layer, Schema } from "effect"
|
|
90
|
+
*
|
|
91
|
+
* const EmailWorkflow = Workflow.make({
|
|
92
|
+
* name: "EmailWorkflow",
|
|
93
|
+
* payload: {
|
|
94
|
+
* id: Schema.String,
|
|
95
|
+
* to: Schema.String
|
|
96
|
+
* },
|
|
97
|
+
* idempotencyKey: ({ id }) => id
|
|
98
|
+
* })
|
|
99
|
+
*
|
|
100
|
+
* const myWorkflows = [EmailWorkflow] as const
|
|
101
|
+
*
|
|
102
|
+
* // Use WorkflowProxy.toHttpApiGroup to create a `HttpApiGroup` from the
|
|
103
|
+
* // workflows
|
|
104
|
+
* class MyApi extends HttpApi.make("api")
|
|
105
|
+
* .add(WorkflowProxy.toHttpApiGroup("workflows", myWorkflows))
|
|
106
|
+
* {}
|
|
107
|
+
*
|
|
108
|
+
* // Use WorkflowProxyServer.layerHttpApi to create a layer that implements the
|
|
109
|
+
* // workflows HttpApiGroup
|
|
110
|
+
* const ApiLayer = HttpApiBuilder.api(MyApi).pipe(
|
|
111
|
+
* Layer.provide(WorkflowProxyServer.layerHttpApi(MyApi, "workflows", myWorkflows))
|
|
112
|
+
* )
|
|
113
|
+
* ```
|
|
114
|
+
*
|
|
115
|
+
* @since 1.0.0
|
|
116
|
+
* @category Constructors
|
|
117
|
+
*/
|
|
118
|
+
export const toHttpApiGroup = <const Name extends string, const Workflows extends NonEmptyReadonlyArray<Workflow.Any>>(
|
|
119
|
+
name: Name,
|
|
120
|
+
workflows: Workflows
|
|
121
|
+
): HttpApiGroup.HttpApiGroup<Name, ConvertHttpApi<Workflows[number]>> => {
|
|
122
|
+
let group = HttpApiGroup.make(name)
|
|
123
|
+
for (const workflow of workflows) {
|
|
124
|
+
const path = `/${tagToPath(workflow.name)}` as const
|
|
125
|
+
group = group.add(
|
|
126
|
+
HttpApiEndpoint.post(workflow.name, path)
|
|
127
|
+
.setPayload(workflow.payloadSchema)
|
|
128
|
+
.addSuccess(workflow.successSchema)
|
|
129
|
+
.addError(workflow.errorSchema as any)
|
|
130
|
+
.annotateContext(workflow.annotations)
|
|
131
|
+
).add(
|
|
132
|
+
HttpApiEndpoint.post(workflow.name + "Discard", `${path}/discard`)
|
|
133
|
+
.setPayload(workflow.payloadSchema)
|
|
134
|
+
.annotateContext(workflow.annotations)
|
|
135
|
+
) as any
|
|
136
|
+
}
|
|
137
|
+
return group as any
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const tagToPath = (tag: string): string =>
|
|
141
|
+
tag
|
|
142
|
+
.replace(/[^a-zA-Z0-9]+/g, "-") // Replace non-alphanumeric characters with hyphen
|
|
143
|
+
.replace(/([a-z])([A-Z])/g, "$1-$2") // Insert hyphen before uppercase letters
|
|
144
|
+
.toLowerCase()
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* @since 1.0.0
|
|
148
|
+
*/
|
|
149
|
+
export type ConvertHttpApi<Workflows extends Workflow.Any> = Workflows extends Workflow.Workflow<
|
|
150
|
+
infer _Name,
|
|
151
|
+
infer _Payload,
|
|
152
|
+
infer _Success,
|
|
153
|
+
infer _Error
|
|
154
|
+
> ?
|
|
155
|
+
| HttpApiEndpoint.HttpApiEndpoint<
|
|
156
|
+
_Name,
|
|
157
|
+
"POST",
|
|
158
|
+
never,
|
|
159
|
+
never,
|
|
160
|
+
_Payload["Type"],
|
|
161
|
+
never,
|
|
162
|
+
_Success["Type"],
|
|
163
|
+
_Error["Type"],
|
|
164
|
+
_Payload["Context"] | _Success["Context"],
|
|
165
|
+
_Error["Context"]
|
|
166
|
+
>
|
|
167
|
+
| HttpApiEndpoint.HttpApiEndpoint<
|
|
168
|
+
`${_Name}Discard`,
|
|
169
|
+
"POST",
|
|
170
|
+
never,
|
|
171
|
+
never,
|
|
172
|
+
_Payload["Type"],
|
|
173
|
+
never,
|
|
174
|
+
void,
|
|
175
|
+
never,
|
|
176
|
+
_Payload["Context"]
|
|
177
|
+
> :
|
|
178
|
+
never
|