@atscript/moost-wf 0.1.68 → 0.1.70
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 +16 -11
- package/dist/index.d.mts +147 -136
- package/dist/index.mjs +189 -251
- package/package.json +18 -18
package/README.md
CHANGED
|
@@ -6,18 +6,23 @@
|
|
|
6
6
|
|
|
7
7
|
# @atscript/moost-wf
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
Documentation: [ui.atscript.dev](https://ui.atscript.dev)
|
|
10
10
|
|
|
11
|
-
Server-side workflow integration for [Moost](https://github.com/moostjs/moost) — decorators,
|
|
11
|
+
Server-side workflow integration for [Moost](https://github.com/moostjs/moost) — decorators, composables, and serialization that pair with [`@atscript/vue-wf`](../vue-wf) to drive multi-step forms from atscript-annotated `.as` types.
|
|
12
12
|
|
|
13
13
|
Part of the [atscript-ui](https://github.com/moostjs/atscript-ui) monorepo.
|
|
14
14
|
|
|
15
15
|
## What it provides
|
|
16
16
|
|
|
17
|
-
-
|
|
18
|
-
-
|
|
19
|
-
-
|
|
20
|
-
-
|
|
17
|
+
- `useAtscriptWf(Type)` composable — schema-aware `resolveInput()` / `resolveAction()` / `requireInput()` for step handlers.
|
|
18
|
+
- `@WfInput()` / `@WfAction()` parameter decorators — sugar over `useAtscriptWf()` with the full action-vs-input policy matrix.
|
|
19
|
+
- `useWfActionSlot()` for low-level action context access (transport adapters writing the action; raw read+clear flows). Step handlers should prefer `useAtscriptWf(Type).resolveAction()`.
|
|
20
|
+
- Schema helpers: `serializeFormSchema`, `extractPassContext`, `getFormActions`.
|
|
21
|
+
- HTTP outlet integration: `createAsHttpOutlet`, `handleAsOutletRequest`.
|
|
22
|
+
- Finish-screen envelope helpers: `finishWf`, `abortWf`, `isWfFinished`, and the `WfFinished` / `WfNext` / `WfMessage` / `WfButton` / `WfActionRequest` types rendered by `<AsWfFinish>`. See [Finish Screens](https://ui.atscript.dev/workflows/finish-screens).
|
|
23
|
+
- Opt-in persistent state store `AsWfStore` (subpath `/store`) backed by `@atscript/db`.
|
|
24
|
+
|
|
25
|
+
The workflow engine catches `StepRetriableError` (thrown by `requireInput()`) natively — no global interceptor wiring required.
|
|
21
26
|
|
|
22
27
|
## Install
|
|
23
28
|
|
|
@@ -25,17 +30,17 @@ Part of the [atscript-ui](https://github.com/moostjs/atscript-ui) monorepo.
|
|
|
25
30
|
pnpm add @atscript/moost-wf
|
|
26
31
|
```
|
|
27
32
|
|
|
28
|
-
Peer requirements: `moost`, `@moostjs/event-wf`, `@atscript/core`, `@atscript/typescript`.
|
|
33
|
+
Peer requirements: `moost`, `@moostjs/event-wf`, `@wooksjs/event-core`, `@wooksjs/event-wf`, `@atscript/core`, `@atscript/typescript`.
|
|
29
34
|
|
|
30
35
|
## Entry points
|
|
31
36
|
|
|
32
37
|
| Subpath | What it exports |
|
|
33
38
|
| ----------------------------- | ---------------------------------------------------------------------------------- |
|
|
34
|
-
| `@atscript/moost-wf` | Workflow decorators,
|
|
35
|
-
| `@atscript/moost-wf/plugin` | atscript build-time plugin
|
|
36
|
-
| `@atscript/moost-wf/store` |
|
|
39
|
+
| `@atscript/moost-wf` | Workflow decorators, composables, outlet helpers, finish-screen envelope helpers |
|
|
40
|
+
| `@atscript/moost-wf/plugin` | atscript build-time plugin (registers `@wf.*` annotations) |
|
|
41
|
+
| `@atscript/moost-wf/store` | `AsWfStore` runtime + `AsWfStateRecord` model |
|
|
37
42
|
| `@atscript/moost-wf/store.as` | Raw `.as` source for the workflow-state record — re-import if you customize fields |
|
|
38
43
|
|
|
39
44
|
## License
|
|
40
45
|
|
|
41
|
-
MIT
|
|
46
|
+
MIT © Artem Maltsev
|
package/dist/index.d.mts
CHANGED
|
@@ -1,101 +1,8 @@
|
|
|
1
|
+
import { InferDataType, TAtscriptAnnotatedType, TAtscriptTypeDef } from "@atscript/typescript/utils";
|
|
1
2
|
import { WfOutlet, WfOutletTriggerConfig, WfOutletTriggerDeps } from "@moostjs/event-wf";
|
|
2
|
-
import {
|
|
3
|
-
import { TAtscriptAnnotatedType } from "@atscript/typescript/utils";
|
|
3
|
+
import { StepRetriableError } from "@wooksjs/event-wf";
|
|
4
4
|
|
|
5
|
-
//#region src/
|
|
6
|
-
/**
|
|
7
|
-
* Thrown by @FormInput() to signal that the workflow should pause
|
|
8
|
-
* and request form input from the client.
|
|
9
|
-
*
|
|
10
|
-
* Caught by {@link formInputInterceptor} and converted to an
|
|
11
|
-
* `inputRequired` outlet response.
|
|
12
|
-
*/
|
|
13
|
-
declare class FormInputRequired {
|
|
14
|
-
readonly schema: unknown;
|
|
15
|
-
readonly errors?: Record<string, string> | undefined;
|
|
16
|
-
readonly context?: Record<string, unknown> | undefined;
|
|
17
|
-
constructor(schema: unknown, errors?: Record<string, string> | undefined, context?: Record<string, unknown> | undefined);
|
|
18
|
-
}
|
|
19
|
-
//#endregion
|
|
20
|
-
//#region src/form-input/use.d.ts
|
|
21
|
-
/**
|
|
22
|
-
* Composable that provides access to form data and the `requireInput()` helper
|
|
23
|
-
* inside workflow step handlers.
|
|
24
|
-
*
|
|
25
|
-
* Called by the `@FormInput()` Resolve callback. Can also be used standalone
|
|
26
|
-
* when you need to manually re-pause with errors:
|
|
27
|
-
*
|
|
28
|
-
* ```ts
|
|
29
|
-
* const { requireInput } = useFormInput()
|
|
30
|
-
* throw requireInput({ password: 'Invalid credentials' })
|
|
31
|
-
* ```
|
|
32
|
-
*/
|
|
33
|
-
declare function useFormInput(type?: TAtscriptAnnotatedType): {
|
|
34
|
-
data: <T = unknown>() => T | undefined;
|
|
35
|
-
requireInput: (errors?: Record<string, string>) => FormInputRequired;
|
|
36
|
-
};
|
|
37
|
-
//#endregion
|
|
38
|
-
//#region src/form-input/decorator.d.ts
|
|
39
|
-
/**
|
|
40
|
-
* Parameter decorator for workflow steps that need form input.
|
|
41
|
-
*
|
|
42
|
-
* Combines parameter injection (via Resolve) with a method interceptor
|
|
43
|
-
* (via Intercept) that validates input before the step handler executes.
|
|
44
|
-
*
|
|
45
|
-
* The injected value is `{ data(), requireInput(errors?) }`.
|
|
46
|
-
*
|
|
47
|
-
* @example
|
|
48
|
-
* ```ts
|
|
49
|
-
* @Step('login')
|
|
50
|
-
* async login(@FormInput() form: TFormInput<LoginForm>) {
|
|
51
|
-
* const input = form.data()
|
|
52
|
-
* try {
|
|
53
|
-
* await this.auth.login(input.username, input.password)
|
|
54
|
-
* } catch (e) {
|
|
55
|
-
* throw form.requireInput({ password: 'Invalid credentials' })
|
|
56
|
-
* }
|
|
57
|
-
* }
|
|
58
|
-
* ```
|
|
59
|
-
*/
|
|
60
|
-
declare function FormInput(): ParameterDecorator;
|
|
61
|
-
type TFormInput<_T = unknown> = ReturnType<typeof useFormInput>;
|
|
62
|
-
//#endregion
|
|
63
|
-
//#region src/form-input/alt-action.decorator.d.ts
|
|
64
|
-
/**
|
|
65
|
-
* Parameter decorator that resolves the action name from the current
|
|
66
|
-
* workflow event context. Returns `undefined` for normal form submits.
|
|
67
|
-
*
|
|
68
|
-
* @example
|
|
69
|
-
* ```ts
|
|
70
|
-
* @Step('mfa-verify')
|
|
71
|
-
* async mfaVerify(
|
|
72
|
-
* @FormInput() form: TFormInput<PincodeForm>,
|
|
73
|
-
* @AltAction() action: string | undefined,
|
|
74
|
-
* ) {
|
|
75
|
-
* if (action === 'resend') {
|
|
76
|
-
* await this.sendOtp(ctx.email)
|
|
77
|
-
* return
|
|
78
|
-
* }
|
|
79
|
-
* await this.verifyCode(form.data().code)
|
|
80
|
-
* }
|
|
81
|
-
* ```
|
|
82
|
-
*/
|
|
83
|
-
declare const AltAction: () => ParameterDecorator & PropertyDecorator;
|
|
84
|
-
//#endregion
|
|
85
|
-
//#region src/form-input/interceptor.d.ts
|
|
86
|
-
/**
|
|
87
|
-
* Global interceptor that catches {@link FormInputRequired} signals
|
|
88
|
-
* thrown by step handlers (via `form.requireInput()`) and converts them
|
|
89
|
-
* to `inputRequired` outlet responses.
|
|
90
|
-
*
|
|
91
|
-
* Apply globally:
|
|
92
|
-
* ```ts
|
|
93
|
-
* app.applyGlobalInterceptors(formInputInterceptor())
|
|
94
|
-
* ```
|
|
95
|
-
*/
|
|
96
|
-
declare function formInputInterceptor(): TInterceptorDef;
|
|
97
|
-
//#endregion
|
|
98
|
-
//#region src/form-input/serialize.d.ts
|
|
5
|
+
//#region src/wf-io/serialize.d.ts
|
|
99
6
|
/**
|
|
100
7
|
* Serialize an atscript annotated type to a JSON-transportable form schema.
|
|
101
8
|
*
|
|
@@ -112,7 +19,7 @@ declare function formInputInterceptor(): TInterceptorDef;
|
|
|
112
19
|
*/
|
|
113
20
|
declare function serializeFormSchema(type: TAtscriptAnnotatedType): unknown;
|
|
114
21
|
//#endregion
|
|
115
|
-
//#region src/
|
|
22
|
+
//#region src/wf-io/context.d.ts
|
|
116
23
|
interface TFormActions {
|
|
117
24
|
actions: string[];
|
|
118
25
|
actionsWithData: string[];
|
|
@@ -124,32 +31,123 @@ interface TFormActions {
|
|
|
124
31
|
declare function extractPassContext(type: TAtscriptAnnotatedType, wfContext: Record<string, unknown>): Record<string, unknown>;
|
|
125
32
|
/**
|
|
126
33
|
* Read declared action names from `@ui.form.action` and `@wf.action.withData` annotations.
|
|
127
|
-
* Also reads legacy `@ui.altAction` as a stateless action fallback.
|
|
128
34
|
* Results are cached per type identity.
|
|
129
35
|
*/
|
|
130
36
|
declare function getFormActions(type: TAtscriptAnnotatedType): TFormActions;
|
|
131
37
|
//#endregion
|
|
132
|
-
//#region src/
|
|
38
|
+
//#region src/wf-io/use-wf-action-slot.d.ts
|
|
133
39
|
/**
|
|
134
|
-
*
|
|
40
|
+
* Low-level accessor for the workflow action slot in the current wf event context.
|
|
41
|
+
*
|
|
42
|
+
* Used by:
|
|
43
|
+
* - Transport adapters (HTTP / CLI / WS controllers) to **write** the action
|
|
44
|
+
* from the incoming request (`useWfActionSlot().setAction(body.action)`).
|
|
45
|
+
* - Composable helpers that need to **read + clear** the slot atomically
|
|
46
|
+
* (e.g. one-shot action consumption patterns).
|
|
47
|
+
*
|
|
48
|
+
* In step handlers, prefer `useAtscriptWf(Type).resolveAction()` — it reads
|
|
49
|
+
* the same slot but validates the value against the schema's
|
|
50
|
+
* `@ui.form.action` / `@wf.action.withData` declarations and throws
|
|
51
|
+
* `StepRetriableError` on unknown actions.
|
|
135
52
|
*
|
|
136
|
-
* **In
|
|
53
|
+
* **In a transport adapter** (to set the action from the request body):
|
|
137
54
|
* ```ts
|
|
138
|
-
* const { setAction } =
|
|
55
|
+
* const { setAction } = useWfActionSlot()
|
|
139
56
|
* setAction(body.action)
|
|
140
57
|
* ```
|
|
141
58
|
*
|
|
142
|
-
* **In step handlers** (
|
|
59
|
+
* **In step handlers** (raw read — prefer `@WfAction()` / `useAtscriptWf().resolveAction()`):
|
|
143
60
|
* ```ts
|
|
144
|
-
* const { getAction } =
|
|
61
|
+
* const { getAction } = useWfActionSlot()
|
|
145
62
|
* const action = getAction()
|
|
146
63
|
* ```
|
|
147
64
|
*/
|
|
148
|
-
declare function
|
|
65
|
+
declare function useWfActionSlot(): {
|
|
149
66
|
getAction: () => string | undefined;
|
|
150
67
|
setAction: (action: string | undefined) => void;
|
|
151
68
|
};
|
|
152
69
|
//#endregion
|
|
70
|
+
//#region src/wf-io/use-atscript-wf.d.ts
|
|
71
|
+
/**
|
|
72
|
+
* Schema-driven workflow I/O primitives for atscript types. Returned helpers
|
|
73
|
+
* are pure and independent — composable consumers can interleave their own
|
|
74
|
+
* logic between checking the action and validating the input.
|
|
75
|
+
*
|
|
76
|
+
* - `resolveInput(opts?)` validates the current step input against the type
|
|
77
|
+
* schema and returns it typed; throws `StepRetriableError` when input is
|
|
78
|
+
* missing or invalid. Does NOT look at the wf action.
|
|
79
|
+
* - `resolveAction()` returns the current wf action name (or `undefined`),
|
|
80
|
+
* throwing `StepRetriableError` when the action is unknown to the schema.
|
|
81
|
+
* Does NOT look at the wf input.
|
|
82
|
+
* - `requireInput(opts?)` builds the `StepRetriableError` carrying the form
|
|
83
|
+
* schema + whitelisted context. Exposed so callers (composables, the
|
|
84
|
+
* `@WfInput` decorator) can throw their own custom failures.
|
|
85
|
+
*
|
|
86
|
+
* Validator instances are cached per `(type, opts)` pair.
|
|
87
|
+
*/
|
|
88
|
+
declare function useAtscriptWf<T extends TAtscriptTypeDef>(type: TAtscriptAnnotatedType<T>): {
|
|
89
|
+
resolveInput(opts?: {
|
|
90
|
+
partial?: "deep";
|
|
91
|
+
}): InferDataType<T>;
|
|
92
|
+
resolveAction(): string | undefined;
|
|
93
|
+
requireInput(opts?: {
|
|
94
|
+
errors?: Record<string, string>;
|
|
95
|
+
formMessage?: string;
|
|
96
|
+
}): StepRetriableError<{
|
|
97
|
+
outlet: "http";
|
|
98
|
+
payload: unknown;
|
|
99
|
+
context: Record<string, unknown>;
|
|
100
|
+
}>;
|
|
101
|
+
};
|
|
102
|
+
//#endregion
|
|
103
|
+
//#region src/wf-io/wf-input.decorator.d.ts
|
|
104
|
+
/**
|
|
105
|
+
* Parameter decorator that resolves to the validated typed input for the
|
|
106
|
+
* current workflow step. Owns the action-vs-input policy matrix on top of
|
|
107
|
+
* the pure `useAtscriptWf` primitives.
|
|
108
|
+
*
|
|
109
|
+
* Policy:
|
|
110
|
+
* - No action fired → strict full validation.
|
|
111
|
+
* - With-data action → input required, partial-deep validation.
|
|
112
|
+
* - No-data action → input must be absent; returns `undefined` only when
|
|
113
|
+
* `pass: true` opts the step into ignoring the no-data action.
|
|
114
|
+
* - Unknown action → `StepRetriableError` (propagated from `resolveAction`).
|
|
115
|
+
*
|
|
116
|
+
* @example
|
|
117
|
+
* ```ts
|
|
118
|
+
* @Step('login')
|
|
119
|
+
* async login(@WfInput() input: LoginForm) {
|
|
120
|
+
* await this.auth.login(input.username, input.password)
|
|
121
|
+
* }
|
|
122
|
+
* ```
|
|
123
|
+
*/
|
|
124
|
+
declare function WfInput(opts?: {
|
|
125
|
+
pass?: boolean;
|
|
126
|
+
}): ParameterDecorator;
|
|
127
|
+
//#endregion
|
|
128
|
+
//#region src/wf-io/wf-action.decorator.d.ts
|
|
129
|
+
/**
|
|
130
|
+
* Parameter decorator that resolves to the current workflow action name.
|
|
131
|
+
*
|
|
132
|
+
* If the parameter is annotated with an atscript type, the action is
|
|
133
|
+
* validated against the type's `@ui.form.action` / `@wf.action.withData`
|
|
134
|
+
* declarations — unknown actions throw `StepRetriableError`. When no
|
|
135
|
+
* annotated type is available the action is returned raw (or `undefined`).
|
|
136
|
+
*
|
|
137
|
+
* @example
|
|
138
|
+
* ```ts
|
|
139
|
+
* @Step('mfa-verify')
|
|
140
|
+
* async mfaVerify(
|
|
141
|
+
* @WfInput() input: PincodeForm,
|
|
142
|
+
* @WfAction() action: string | undefined,
|
|
143
|
+
* ) {
|
|
144
|
+
* if (action === 'resend') return this.sendOtp()
|
|
145
|
+
* await this.verifyCode(input.code)
|
|
146
|
+
* }
|
|
147
|
+
* ```
|
|
148
|
+
*/
|
|
149
|
+
declare function WfAction(): ParameterDecorator;
|
|
150
|
+
//#endregion
|
|
153
151
|
//#region src/outlet.d.ts
|
|
154
152
|
/**
|
|
155
153
|
* `createHttpOutlet` pre-configured for `<AsWfForm>` consumers.
|
|
@@ -179,7 +177,7 @@ interface WfFinished<TData = unknown> {
|
|
|
179
177
|
finished: true;
|
|
180
178
|
data?: TData;
|
|
181
179
|
message?: WfMessage;
|
|
182
|
-
|
|
180
|
+
next?: WfNext;
|
|
183
181
|
aborted?: boolean;
|
|
184
182
|
reason?: string;
|
|
185
183
|
}
|
|
@@ -187,27 +185,27 @@ interface WfMessage {
|
|
|
187
185
|
level: "info" | "success" | "warn" | "error";
|
|
188
186
|
text: string;
|
|
189
187
|
}
|
|
190
|
-
type
|
|
191
|
-
|
|
192
|
-
action:
|
|
188
|
+
type WfNext = {
|
|
189
|
+
trigger: "immediate";
|
|
190
|
+
action: WfActionRequest;
|
|
193
191
|
} | {
|
|
194
|
-
|
|
192
|
+
trigger: "auto";
|
|
195
193
|
timeoutMs: number;
|
|
196
|
-
action:
|
|
194
|
+
action: WfActionRequest;
|
|
197
195
|
skipButton?: {
|
|
198
196
|
label: string;
|
|
199
197
|
behavior?: "now" | "cancel";
|
|
200
198
|
};
|
|
201
199
|
} | {
|
|
202
|
-
|
|
200
|
+
trigger: "manual";
|
|
203
201
|
primary?: WfButton;
|
|
204
202
|
options?: WfButton[];
|
|
205
203
|
};
|
|
206
204
|
interface WfButton {
|
|
207
205
|
label: string;
|
|
208
|
-
action:
|
|
206
|
+
action: WfActionRequest;
|
|
209
207
|
}
|
|
210
|
-
type
|
|
208
|
+
type WfActionRequest = {
|
|
211
209
|
type: "redirect";
|
|
212
210
|
target: string;
|
|
213
211
|
reason?: string;
|
|
@@ -218,28 +216,41 @@ type WfAction = {
|
|
|
218
216
|
};
|
|
219
217
|
/** Type-guard for the unified envelope. */
|
|
220
218
|
declare function isWfFinished(v: unknown): v is WfFinished;
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
/** Present → `mode: 'auto'` with countdown; absent → `mode: 'immediate'`. */
|
|
228
|
-
autoMs?: number;
|
|
229
|
-
/** Only honored when `autoMs` is set — adds a "skip / cancel" button. */
|
|
230
|
-
skipLabel?: string;
|
|
231
|
-
}
|
|
232
|
-
declare function finishWfWithRedirect(target: string, opts?: RedirectOpts): void;
|
|
233
|
-
interface ChoiceOpts {
|
|
234
|
-
data?: unknown;
|
|
219
|
+
/**
|
|
220
|
+
* Options bag shared by `finishWf` and `abortWf`. Every field is optional
|
|
221
|
+
* — pick whichever envelope properties the terminal screen needs.
|
|
222
|
+
*/
|
|
223
|
+
interface FinishWfOpts<T = unknown> {
|
|
224
|
+
data?: T;
|
|
235
225
|
message?: WfMessage;
|
|
236
|
-
|
|
237
|
-
options?: WfButton[];
|
|
226
|
+
next?: WfNext;
|
|
238
227
|
}
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
})
|
|
228
|
+
/**
|
|
229
|
+
* Build a `WfFinished` envelope and hand it to wooks. All envelope
|
|
230
|
+
* properties are optional; pass `data`, `message`, and/or `next`:
|
|
231
|
+
*
|
|
232
|
+
* finishWf({ data: { id: 42 } });
|
|
233
|
+
* finishWf({ message: { level: "success", text: "Saved." } });
|
|
234
|
+
* finishWf({
|
|
235
|
+
* next: {
|
|
236
|
+
* trigger: "auto",
|
|
237
|
+
* timeoutMs: 3000,
|
|
238
|
+
* action: { type: "redirect", target: "/home" },
|
|
239
|
+
* },
|
|
240
|
+
* });
|
|
241
|
+
*/
|
|
242
|
+
declare function finishWf<T = unknown>(opts?: FinishWfOpts<T>): void;
|
|
243
|
+
/**
|
|
244
|
+
* Build an aborted `WfFinished` envelope (`aborted: true` + `reason`) and
|
|
245
|
+
* hand it to wooks. The same options as `finishWf` are accepted — an
|
|
246
|
+
* aborted flow may still carry partial `data`, a `message`, or a `next`
|
|
247
|
+
* action that lets the user navigate away.
|
|
248
|
+
*
|
|
249
|
+
* abortWf("user-cancelled");
|
|
250
|
+
* abortWf("rate-limited", {
|
|
251
|
+
* message: { level: "warn", text: "Try again later." },
|
|
252
|
+
* });
|
|
253
|
+
*/
|
|
254
|
+
declare function abortWf(reason: string, opts?: FinishWfOpts): void;
|
|
244
255
|
//#endregion
|
|
245
|
-
export {
|
|
256
|
+
export { type FinishWfOpts, WfAction, type WfActionRequest, type WfButton, type WfFinished, WfInput, type WfMessage, type WfNext, abortWf, createAsHttpOutlet, extractPassContext, finishWf, getFormActions, handleAsOutletRequest, isWfFinished, serializeFormSchema, useAtscriptWf, useWfActionSlot };
|
package/dist/index.mjs
CHANGED
|
@@ -1,13 +1,12 @@
|
|
|
1
|
-
import { createHttpOutlet, handleWfOutletRequest, useWfState } from "@moostjs/event-wf";
|
|
2
|
-
import { Intercept, Resolve, TInterceptorPriority, useControllerContext } from "moost";
|
|
3
1
|
import { isAnnotatedType, serializeAnnotatedType } from "@atscript/typescript/utils";
|
|
4
2
|
import { current, key } from "@wooksjs/event-core";
|
|
5
|
-
import {
|
|
6
|
-
|
|
3
|
+
import { createHttpOutlet, handleWfOutletRequest, useWfState } from "@moostjs/event-wf";
|
|
4
|
+
import { StepRetriableError, useWfFinished } from "@wooksjs/event-wf";
|
|
5
|
+
import { Optional, Resolve } from "moost";
|
|
6
|
+
//#region src/wf-io/context.ts
|
|
7
7
|
const WF_CONTEXT_PASS = "wf.context.pass";
|
|
8
8
|
const UI_FORM_ACTION = "ui.form.action";
|
|
9
9
|
const WF_ACTION_WITH_DATA = "wf.action.withData";
|
|
10
|
-
const UI_ALT_ACTION = "ui.altAction";
|
|
11
10
|
const formActionsCache = /* @__PURE__ */ new WeakMap();
|
|
12
11
|
/**
|
|
13
12
|
* Extract whitelisted context keys from workflow state.
|
|
@@ -22,7 +21,6 @@ function extractPassContext(type, wfContext) {
|
|
|
22
21
|
}
|
|
23
22
|
/**
|
|
24
23
|
* Read declared action names from `@ui.form.action` and `@wf.action.withData` annotations.
|
|
25
|
-
* Also reads legacy `@ui.altAction` as a stateless action fallback.
|
|
26
24
|
* Results are cached per type identity.
|
|
27
25
|
*/
|
|
28
26
|
function getFormActions(type) {
|
|
@@ -50,8 +48,6 @@ function getFormActions(type) {
|
|
|
50
48
|
actionsWithData.push(wfAction);
|
|
51
49
|
continue;
|
|
52
50
|
}
|
|
53
|
-
const altAction = fieldType.metadata.get(UI_ALT_ACTION);
|
|
54
|
-
if (altAction) actions.push(typeof altAction === "string" ? altAction : altAction.id);
|
|
55
51
|
}
|
|
56
52
|
const result = {
|
|
57
53
|
actions,
|
|
@@ -61,7 +57,7 @@ function getFormActions(type) {
|
|
|
61
57
|
return result;
|
|
62
58
|
}
|
|
63
59
|
//#endregion
|
|
64
|
-
//#region src/
|
|
60
|
+
//#region src/wf-io/serialize.ts
|
|
65
61
|
const schemaCache = /* @__PURE__ */ new WeakMap();
|
|
66
62
|
/**
|
|
67
63
|
* Serialize an atscript annotated type to a JSON-transportable form schema.
|
|
@@ -88,92 +84,44 @@ function serializeFormSchema(type) {
|
|
|
88
84
|
return schema;
|
|
89
85
|
}
|
|
90
86
|
//#endregion
|
|
91
|
-
//#region src/
|
|
92
|
-
/**
|
|
93
|
-
* Thrown by @FormInput() to signal that the workflow should pause
|
|
94
|
-
* and request form input from the client.
|
|
95
|
-
*
|
|
96
|
-
* Caught by {@link formInputInterceptor} and converted to an
|
|
97
|
-
* `inputRequired` outlet response.
|
|
98
|
-
*/
|
|
99
|
-
var FormInputRequired = class {
|
|
100
|
-
constructor(schema, errors, context) {
|
|
101
|
-
this.schema = schema;
|
|
102
|
-
this.errors = errors;
|
|
103
|
-
this.context = context;
|
|
104
|
-
}
|
|
105
|
-
};
|
|
106
|
-
//#endregion
|
|
107
|
-
//#region src/form-input/use.ts
|
|
87
|
+
//#region src/wf-io/wf-keys.ts
|
|
108
88
|
/**
|
|
109
|
-
*
|
|
110
|
-
* inside workflow step handlers.
|
|
111
|
-
*
|
|
112
|
-
* Called by the `@FormInput()` Resolve callback. Can also be used standalone
|
|
113
|
-
* when you need to manually re-pause with errors:
|
|
89
|
+
* Internal event context key for the workflow action name.
|
|
114
90
|
*
|
|
115
|
-
*
|
|
116
|
-
*
|
|
117
|
-
*
|
|
118
|
-
* ```
|
|
91
|
+
* Not exported from the package barrel — HTTP triggers should call
|
|
92
|
+
* `useWfActionSlot().setAction(body.action)` before `wf.resume()`, and step
|
|
93
|
+
* handlers should read via `@WfAction()` or `useAtscriptWf()`.
|
|
119
94
|
*/
|
|
120
|
-
|
|
121
|
-
const wfState = useWfState();
|
|
122
|
-
/**
|
|
123
|
-
* Returns the current form input data from the workflow event.
|
|
124
|
-
*/
|
|
125
|
-
function data() {
|
|
126
|
-
return wfState.input();
|
|
127
|
-
}
|
|
128
|
-
/**
|
|
129
|
-
* Creates a FormInputRequired signal that re-pauses the workflow
|
|
130
|
-
* with the serialized form schema, whitelisted context, and optional errors.
|
|
131
|
-
*
|
|
132
|
-
* Usage: `throw requireInput({ fieldName: 'Error message' })`
|
|
133
|
-
*/
|
|
134
|
-
function requireInput(errors) {
|
|
135
|
-
if (!type || !isAnnotatedType(type)) throw new Error("useFormInput(): no atscript type available. Ensure @FormInput() is applied on the same method parameter.");
|
|
136
|
-
const wfContext = wfState.ctx();
|
|
137
|
-
return new FormInputRequired(serializeFormSchema(type), errors, extractPassContext(type, wfContext));
|
|
138
|
-
}
|
|
139
|
-
return {
|
|
140
|
-
data,
|
|
141
|
-
requireInput
|
|
142
|
-
};
|
|
143
|
-
}
|
|
95
|
+
const actionKey = key("wf.action");
|
|
144
96
|
//#endregion
|
|
145
|
-
//#region src/
|
|
97
|
+
//#region src/wf-io/use-wf-action-slot.ts
|
|
146
98
|
/**
|
|
147
|
-
*
|
|
99
|
+
* Low-level accessor for the workflow action slot in the current wf event context.
|
|
148
100
|
*
|
|
149
|
-
*
|
|
150
|
-
*
|
|
151
|
-
*
|
|
152
|
-
*
|
|
153
|
-
*
|
|
154
|
-
* ```
|
|
101
|
+
* Used by:
|
|
102
|
+
* - Transport adapters (HTTP / CLI / WS controllers) to **write** the action
|
|
103
|
+
* from the incoming request (`useWfActionSlot().setAction(body.action)`).
|
|
104
|
+
* - Composable helpers that need to **read + clear** the slot atomically
|
|
105
|
+
* (e.g. one-shot action consumption patterns).
|
|
155
106
|
*
|
|
156
|
-
*
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
//#region src/form-input/use-wf-action.ts
|
|
161
|
-
/**
|
|
162
|
-
* Composable that reads and writes the workflow action from the event context.
|
|
107
|
+
* In step handlers, prefer `useAtscriptWf(Type).resolveAction()` — it reads
|
|
108
|
+
* the same slot but validates the value against the schema's
|
|
109
|
+
* `@ui.form.action` / `@wf.action.withData` declarations and throws
|
|
110
|
+
* `StepRetriableError` on unknown actions.
|
|
163
111
|
*
|
|
164
|
-
* **In
|
|
112
|
+
* **In a transport adapter** (to set the action from the request body):
|
|
165
113
|
* ```ts
|
|
166
|
-
* const { setAction } =
|
|
114
|
+
* const { setAction } = useWfActionSlot()
|
|
167
115
|
* setAction(body.action)
|
|
168
116
|
* ```
|
|
169
117
|
*
|
|
170
|
-
* **In step handlers** (
|
|
118
|
+
* **In step handlers** (raw read — prefer `@WfAction()` / `useAtscriptWf().resolveAction()`):
|
|
171
119
|
* ```ts
|
|
172
|
-
* const { getAction } =
|
|
120
|
+
* const { getAction } = useWfActionSlot()
|
|
173
121
|
* const action = getAction()
|
|
174
122
|
* ```
|
|
175
123
|
*/
|
|
176
|
-
function
|
|
124
|
+
function useWfActionSlot() {
|
|
177
125
|
const ctx = current();
|
|
178
126
|
return {
|
|
179
127
|
getAction: () => ctx.has(actionKey) ? ctx.get(actionKey) : void 0,
|
|
@@ -181,154 +129,167 @@ function useWfAction() {
|
|
|
181
129
|
};
|
|
182
130
|
}
|
|
183
131
|
//#endregion
|
|
184
|
-
//#region src/
|
|
132
|
+
//#region src/wf-io/validator-cache.ts
|
|
133
|
+
const cache = /* @__PURE__ */ new WeakMap();
|
|
185
134
|
/**
|
|
186
|
-
*
|
|
187
|
-
*
|
|
188
|
-
*
|
|
189
|
-
* (
|
|
135
|
+
* Memoize `type.validator(opts)` by `(type, opts)`. Outer WeakMap keyed by the
|
|
136
|
+
* atscript type identity; inner Map keyed by the two opts we care about
|
|
137
|
+
* (`partial`, `unknownProps`). Returns the same validator instance for the
|
|
138
|
+
* same `(type, opts)` pair.
|
|
139
|
+
*/
|
|
140
|
+
function getCachedValidator(type, opts) {
|
|
141
|
+
const key = `${String(opts?.partial ?? "-")}|${String(opts?.unknownProps ?? "-")}`;
|
|
142
|
+
let perType = cache.get(type);
|
|
143
|
+
if (!perType) {
|
|
144
|
+
perType = /* @__PURE__ */ new Map();
|
|
145
|
+
cache.set(type, perType);
|
|
146
|
+
}
|
|
147
|
+
let validator = perType.get(key);
|
|
148
|
+
if (!validator) {
|
|
149
|
+
validator = type.validator(opts);
|
|
150
|
+
perType.set(key, validator);
|
|
151
|
+
}
|
|
152
|
+
return validator;
|
|
153
|
+
}
|
|
154
|
+
//#endregion
|
|
155
|
+
//#region src/wf-io/use-atscript-wf.ts
|
|
156
|
+
function flattenValidatorErrors(err) {
|
|
157
|
+
const out = {};
|
|
158
|
+
for (const e of err.errors) out[e.path] = e.message;
|
|
159
|
+
return out;
|
|
160
|
+
}
|
|
161
|
+
function isValidatorError(err) {
|
|
162
|
+
return err !== null && typeof err === "object" && "errors" in err && Array.isArray(err.errors);
|
|
163
|
+
}
|
|
164
|
+
function useAtscriptWf(type) {
|
|
165
|
+
const wfState = useWfState();
|
|
166
|
+
const wfAction = useWfActionSlot();
|
|
167
|
+
function requireInput({ errors, formMessage } = {}) {
|
|
168
|
+
const passContext = extractPassContext(type, wfState.ctx() ?? {});
|
|
169
|
+
const mergedErrors = errors ? { ...errors } : formMessage ? {} : void 0;
|
|
170
|
+
if (formMessage && mergedErrors) mergedErrors.__form = formMessage;
|
|
171
|
+
const context = mergedErrors ? {
|
|
172
|
+
...passContext,
|
|
173
|
+
errors: mergedErrors
|
|
174
|
+
} : { ...passContext };
|
|
175
|
+
return new StepRetriableError(new Error(formMessage ?? "Input required"), void 0, {
|
|
176
|
+
outlet: "http",
|
|
177
|
+
payload: serializeFormSchema(type),
|
|
178
|
+
context
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
function validateOrThrow(input, opts) {
|
|
182
|
+
const validator = getCachedValidator(type, opts);
|
|
183
|
+
try {
|
|
184
|
+
validator.validate(input);
|
|
185
|
+
} catch (err) {
|
|
186
|
+
if (isValidatorError(err)) throw requireInput({ errors: flattenValidatorErrors(err) });
|
|
187
|
+
throw err;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
function resolveInput(opts) {
|
|
191
|
+
const input = wfState.input();
|
|
192
|
+
if (input === void 0) throw requireInput();
|
|
193
|
+
validateOrThrow(input, opts?.partial === "deep" ? {
|
|
194
|
+
partial: "deep",
|
|
195
|
+
unknownProps: "strip"
|
|
196
|
+
} : { unknownProps: "strip" });
|
|
197
|
+
return input;
|
|
198
|
+
}
|
|
199
|
+
function resolveAction() {
|
|
200
|
+
const action = wfAction.getAction();
|
|
201
|
+
if (action === void 0) return void 0;
|
|
202
|
+
const { actions, actionsWithData } = getFormActions(type);
|
|
203
|
+
if (!actions.includes(action) && !actionsWithData.includes(action)) throw requireInput({ formMessage: `Action "${action}" is not supported` });
|
|
204
|
+
return action;
|
|
205
|
+
}
|
|
206
|
+
return {
|
|
207
|
+
resolveInput,
|
|
208
|
+
resolveAction,
|
|
209
|
+
requireInput
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
//#endregion
|
|
213
|
+
//#region src/wf-io/wf-input.decorator.ts
|
|
214
|
+
/**
|
|
215
|
+
* Parameter decorator that resolves to the validated typed input for the
|
|
216
|
+
* current workflow step. Owns the action-vs-input policy matrix on top of
|
|
217
|
+
* the pure `useAtscriptWf` primitives.
|
|
190
218
|
*
|
|
191
|
-
*
|
|
219
|
+
* Policy:
|
|
220
|
+
* - No action fired → strict full validation.
|
|
221
|
+
* - With-data action → input required, partial-deep validation.
|
|
222
|
+
* - No-data action → input must be absent; returns `undefined` only when
|
|
223
|
+
* `pass: true` opts the step into ignoring the no-data action.
|
|
224
|
+
* - Unknown action → `StepRetriableError` (propagated from `resolveAction`).
|
|
192
225
|
*
|
|
193
226
|
* @example
|
|
194
227
|
* ```ts
|
|
195
228
|
* @Step('login')
|
|
196
|
-
* async login(@
|
|
197
|
-
*
|
|
198
|
-
* try {
|
|
199
|
-
* await this.auth.login(input.username, input.password)
|
|
200
|
-
* } catch (e) {
|
|
201
|
-
* throw form.requireInput({ password: 'Invalid credentials' })
|
|
202
|
-
* }
|
|
229
|
+
* async login(@WfInput() input: LoginForm) {
|
|
230
|
+
* await this.auth.login(input.username, input.password)
|
|
203
231
|
* }
|
|
204
232
|
* ```
|
|
205
233
|
*/
|
|
206
|
-
function
|
|
234
|
+
function WfInput(opts) {
|
|
207
235
|
return (target, key, index) => {
|
|
208
236
|
if (typeof index !== "number") return;
|
|
209
237
|
Resolve((metas) => {
|
|
210
238
|
const type = metas?.targetMeta?.type;
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
}
|
|
216
|
-
function formInputCheckFn() {
|
|
217
|
-
return {
|
|
218
|
-
priority: TInterceptorPriority.INTERCEPTOR,
|
|
219
|
-
async before(reply) {
|
|
220
|
-
const wfState = useWfState();
|
|
221
|
-
const input = wfState.input();
|
|
222
|
-
const action = useWfAction().getAction();
|
|
223
|
-
const { getMethodMeta } = useControllerContext();
|
|
224
|
-
const paramMetas = getMethodMeta()?.params;
|
|
225
|
-
let type;
|
|
226
|
-
if (paramMetas) {
|
|
227
|
-
for (const param of paramMetas) if (param?.targetMeta?.type && isAnnotatedType(param.targetMeta.type)) {
|
|
228
|
-
type = param.targetMeta.type;
|
|
229
|
-
break;
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
if (!type) return;
|
|
233
|
-
if (input === void 0 && !action) {
|
|
234
|
-
reply(toInputRequired(type, wfState));
|
|
235
|
-
return;
|
|
236
|
-
}
|
|
239
|
+
if (!type || !isAnnotatedType(type)) throw new Error("@WfInput(): no atscript type available on the parameter. Annotate the parameter with an atscript-derived type.");
|
|
240
|
+
const wf = useAtscriptWf(type);
|
|
241
|
+
const pass = opts?.pass === true;
|
|
242
|
+
const action = wf.resolveAction();
|
|
237
243
|
if (action) {
|
|
244
|
+
const wfInput = useWfState().input();
|
|
238
245
|
const { actions, actionsWithData } = getFormActions(type);
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
}
|
|
246
|
+
const isNoData = actions.includes(action);
|
|
247
|
+
const isWithData = actionsWithData.includes(action);
|
|
248
|
+
if (isNoData) {
|
|
249
|
+
if (!pass) throw wf.requireInput({ formMessage: wfInput === void 0 ? `Action "${action}" requires no data but this step expects input` : `Action "${action}" requires no data; input not allowed here` });
|
|
250
|
+
if (wfInput !== void 0) throw wf.requireInput({ formMessage: `Action "${action}" requires no data; input not allowed here` });
|
|
244
251
|
return;
|
|
245
252
|
}
|
|
246
|
-
if (
|
|
247
|
-
|
|
248
|
-
|
|
253
|
+
if (isWithData) {
|
|
254
|
+
if (wfInput === void 0) throw wf.requireInput({ formMessage: `Action "${action}" expects input` });
|
|
255
|
+
return wf.resolveInput({ partial: "deep" });
|
|
256
|
+
}
|
|
249
257
|
}
|
|
250
|
-
|
|
251
|
-
}
|
|
258
|
+
return wf.resolveInput();
|
|
259
|
+
}, "WfInput")(target, key, index);
|
|
260
|
+
if (opts?.pass === true) Optional()(target, key, index);
|
|
252
261
|
};
|
|
253
262
|
}
|
|
254
|
-
function validateOrReply(type, wfState, input, validatorOpts, reply) {
|
|
255
|
-
const validator = type.validator(validatorOpts);
|
|
256
|
-
try {
|
|
257
|
-
validator.validate(input);
|
|
258
|
-
} catch (err) {
|
|
259
|
-
if (isValidatorError(err)) {
|
|
260
|
-
reply(toInputRequired(type, wfState, flattenErrors(err)));
|
|
261
|
-
return;
|
|
262
|
-
}
|
|
263
|
-
throw err;
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
function toInputRequired(type, wfState, errors) {
|
|
267
|
-
const passedContext = extractPassContext(type, wfState.ctx());
|
|
268
|
-
return { inputRequired: {
|
|
269
|
-
payload: serializeFormSchema(type),
|
|
270
|
-
transport: "http",
|
|
271
|
-
context: errors ? {
|
|
272
|
-
...passedContext,
|
|
273
|
-
errors
|
|
274
|
-
} : { ...passedContext }
|
|
275
|
-
} };
|
|
276
|
-
}
|
|
277
|
-
function flattenErrors(err) {
|
|
278
|
-
const errors = {};
|
|
279
|
-
for (const e of err.errors) errors[e.path] = e.message;
|
|
280
|
-
return errors;
|
|
281
|
-
}
|
|
282
|
-
function isValidatorError(err) {
|
|
283
|
-
return err !== null && typeof err === "object" && "errors" in err && Array.isArray(err.errors);
|
|
284
|
-
}
|
|
285
263
|
//#endregion
|
|
286
|
-
//#region src/
|
|
264
|
+
//#region src/wf-io/wf-action.decorator.ts
|
|
287
265
|
/**
|
|
288
|
-
* Parameter decorator that resolves the action name
|
|
289
|
-
*
|
|
266
|
+
* Parameter decorator that resolves to the current workflow action name.
|
|
267
|
+
*
|
|
268
|
+
* If the parameter is annotated with an atscript type, the action is
|
|
269
|
+
* validated against the type's `@ui.form.action` / `@wf.action.withData`
|
|
270
|
+
* declarations — unknown actions throw `StepRetriableError`. When no
|
|
271
|
+
* annotated type is available the action is returned raw (or `undefined`).
|
|
290
272
|
*
|
|
291
273
|
* @example
|
|
292
274
|
* ```ts
|
|
293
275
|
* @Step('mfa-verify')
|
|
294
276
|
* async mfaVerify(
|
|
295
|
-
* @
|
|
296
|
-
* @
|
|
277
|
+
* @WfInput() input: PincodeForm,
|
|
278
|
+
* @WfAction() action: string | undefined,
|
|
297
279
|
* ) {
|
|
298
|
-
* if (action === 'resend')
|
|
299
|
-
*
|
|
300
|
-
* return
|
|
301
|
-
* }
|
|
302
|
-
* await this.verifyCode(form.data().code)
|
|
280
|
+
* if (action === 'resend') return this.sendOtp()
|
|
281
|
+
* await this.verifyCode(input.code)
|
|
303
282
|
* }
|
|
304
283
|
* ```
|
|
305
284
|
*/
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
* Apply globally:
|
|
315
|
-
* ```ts
|
|
316
|
-
* app.applyGlobalInterceptors(formInputInterceptor())
|
|
317
|
-
* ```
|
|
318
|
-
*/
|
|
319
|
-
function formInputInterceptor() {
|
|
320
|
-
return {
|
|
321
|
-
priority: TInterceptorPriority.CATCH_ERROR,
|
|
322
|
-
error(error, reply) {
|
|
323
|
-
if (error instanceof FormInputRequired) reply({ inputRequired: {
|
|
324
|
-
payload: error.schema,
|
|
325
|
-
transport: "http",
|
|
326
|
-
context: error.errors ? {
|
|
327
|
-
...error.context,
|
|
328
|
-
errors: error.errors
|
|
329
|
-
} : { ...error.context }
|
|
330
|
-
} });
|
|
331
|
-
}
|
|
285
|
+
function WfAction() {
|
|
286
|
+
return (target, key, index) => {
|
|
287
|
+
if (typeof index !== "number") return;
|
|
288
|
+
Resolve((metas) => {
|
|
289
|
+
const type = metas?.targetMeta?.type;
|
|
290
|
+
if (type && isAnnotatedType(type)) return useAtscriptWf(type).resolveAction();
|
|
291
|
+
return useWfActionSlot().getAction();
|
|
292
|
+
}, "WfAction")(target, key, index);
|
|
332
293
|
};
|
|
333
294
|
}
|
|
334
295
|
//#endregion
|
|
@@ -419,67 +380,44 @@ function setEnvelope(envelope) {
|
|
|
419
380
|
value: envelope
|
|
420
381
|
});
|
|
421
382
|
}
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
}
|
|
439
|
-
});
|
|
440
|
-
}
|
|
441
|
-
function finishWfWithRedirect(target, opts = {}) {
|
|
442
|
-
const action = {
|
|
443
|
-
type: "redirect",
|
|
444
|
-
target,
|
|
445
|
-
reason: opts.reason
|
|
446
|
-
};
|
|
447
|
-
const end = opts.autoMs ? {
|
|
448
|
-
mode: "auto",
|
|
449
|
-
timeoutMs: opts.autoMs,
|
|
450
|
-
action,
|
|
451
|
-
skipButton: opts.skipLabel ? { label: opts.skipLabel } : void 0
|
|
452
|
-
} : {
|
|
453
|
-
mode: "immediate",
|
|
454
|
-
action
|
|
455
|
-
};
|
|
456
|
-
finishWf({
|
|
457
|
-
finished: true,
|
|
458
|
-
message: opts.message,
|
|
459
|
-
end
|
|
460
|
-
});
|
|
461
|
-
}
|
|
462
|
-
function finishWfWithChoice(opts) {
|
|
463
|
-
if (!opts.primary && (!opts.options || opts.options.length === 0)) throw new Error("finishWfWithChoice() requires at least a primary button or one option.");
|
|
464
|
-
finishWf({
|
|
383
|
+
/**
|
|
384
|
+
* Build a `WfFinished` envelope and hand it to wooks. All envelope
|
|
385
|
+
* properties are optional; pass `data`, `message`, and/or `next`:
|
|
386
|
+
*
|
|
387
|
+
* finishWf({ data: { id: 42 } });
|
|
388
|
+
* finishWf({ message: { level: "success", text: "Saved." } });
|
|
389
|
+
* finishWf({
|
|
390
|
+
* next: {
|
|
391
|
+
* trigger: "auto",
|
|
392
|
+
* timeoutMs: 3000,
|
|
393
|
+
* action: { type: "redirect", target: "/home" },
|
|
394
|
+
* },
|
|
395
|
+
* });
|
|
396
|
+
*/
|
|
397
|
+
function finishWf(opts) {
|
|
398
|
+
setEnvelope({
|
|
465
399
|
finished: true,
|
|
466
|
-
|
|
467
|
-
message: opts.message,
|
|
468
|
-
end: {
|
|
469
|
-
mode: "manual",
|
|
470
|
-
primary: opts.primary,
|
|
471
|
-
options: opts.options
|
|
472
|
-
}
|
|
400
|
+
...opts
|
|
473
401
|
});
|
|
474
402
|
}
|
|
475
|
-
|
|
476
|
-
|
|
403
|
+
/**
|
|
404
|
+
* Build an aborted `WfFinished` envelope (`aborted: true` + `reason`) and
|
|
405
|
+
* hand it to wooks. The same options as `finishWf` are accepted — an
|
|
406
|
+
* aborted flow may still carry partial `data`, a `message`, or a `next`
|
|
407
|
+
* action that lets the user navigate away.
|
|
408
|
+
*
|
|
409
|
+
* abortWf("user-cancelled");
|
|
410
|
+
* abortWf("rate-limited", {
|
|
411
|
+
* message: { level: "warn", text: "Try again later." },
|
|
412
|
+
* });
|
|
413
|
+
*/
|
|
414
|
+
function abortWf(reason, opts) {
|
|
415
|
+
setEnvelope({
|
|
477
416
|
finished: true,
|
|
478
417
|
aborted: true,
|
|
479
418
|
reason,
|
|
480
|
-
|
|
481
|
-
end: opts.end
|
|
419
|
+
...opts
|
|
482
420
|
});
|
|
483
421
|
}
|
|
484
422
|
//#endregion
|
|
485
|
-
export {
|
|
423
|
+
export { WfAction, WfInput, abortWf, createAsHttpOutlet, extractPassContext, finishWf, getFormActions, handleAsOutletRequest, isWfFinished, serializeFormSchema, useAtscriptWf, useWfActionSlot };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atscript/moost-wf",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.70",
|
|
4
4
|
"description": "Workflow form integration for moost — decorators, interceptors, and serialization driven by atscript type metadata",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"atscript",
|
|
@@ -51,26 +51,26 @@
|
|
|
51
51
|
"access": "public"
|
|
52
52
|
},
|
|
53
53
|
"devDependencies": {
|
|
54
|
-
"@atscript/core": "^0.1.
|
|
55
|
-
"@atscript/db": "^0.1.
|
|
56
|
-
"@atscript/db-sqlite": "^0.1.
|
|
57
|
-
"@atscript/typescript": "^0.1.
|
|
58
|
-
"@moostjs/event-wf": "^0.6.
|
|
54
|
+
"@atscript/core": "^0.1.58",
|
|
55
|
+
"@atscript/db": "^0.1.82",
|
|
56
|
+
"@atscript/db-sqlite": "^0.1.82",
|
|
57
|
+
"@atscript/typescript": "^0.1.58",
|
|
58
|
+
"@moostjs/event-wf": "^0.6.14",
|
|
59
59
|
"@prostojs/wf": "^0.1.1",
|
|
60
|
-
"@wooksjs/event-core": "^0.7.
|
|
61
|
-
"@wooksjs/event-wf": "^0.7.
|
|
62
|
-
"moost": "^0.6.
|
|
63
|
-
"unplugin-atscript": "^0.1.
|
|
64
|
-
"vitest": "npm:@voidzero-dev/vite-plus-test@
|
|
65
|
-
"@atscript/ui": "^0.1.
|
|
60
|
+
"@wooksjs/event-core": "^0.7.13",
|
|
61
|
+
"@wooksjs/event-wf": "^0.7.13",
|
|
62
|
+
"moost": "^0.6.14",
|
|
63
|
+
"unplugin-atscript": "^0.1.58",
|
|
64
|
+
"vitest": "npm:@voidzero-dev/vite-plus-test@0.1.14",
|
|
65
|
+
"@atscript/ui": "^0.1.70"
|
|
66
66
|
},
|
|
67
67
|
"peerDependencies": {
|
|
68
|
-
"@atscript/core": "^0.1.
|
|
69
|
-
"@atscript/typescript": "^0.1.
|
|
70
|
-
"@moostjs/event-wf": "^0.6.
|
|
71
|
-
"@wooksjs/event-core": "^0.7.
|
|
72
|
-
"@wooksjs/event-wf": "^0.7.
|
|
73
|
-
"moost": "^0.6.
|
|
68
|
+
"@atscript/core": "^0.1.58",
|
|
69
|
+
"@atscript/typescript": "^0.1.58",
|
|
70
|
+
"@moostjs/event-wf": "^0.6.14",
|
|
71
|
+
"@wooksjs/event-core": "^0.7.13",
|
|
72
|
+
"@wooksjs/event-wf": "^0.7.13",
|
|
73
|
+
"moost": "^0.6.14"
|
|
74
74
|
},
|
|
75
75
|
"scripts": {
|
|
76
76
|
"build": "vp pack",
|