@statedelta-actions/actions 0.3.0 → 0.6.0
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/CHANGELOG.md +96 -0
- package/LICENSE +21 -0
- package/README.md +260 -5
- package/dist/index.cjs +9 -9
- package/dist/index.d.cts +298 -9
- package/dist/index.d.ts +298 -9
- package/dist/index.js +9 -9
- package/package.json +16 -3
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# @statedelta-actions/actions
|
|
2
|
+
|
|
3
|
+
## 0.6.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- a3a818b: `HandlerDefinition` is now a discriminated union by execution mode
|
|
8
|
+
|
|
9
|
+
`HandlerDefinition<TCtx>` is `SyncHandlerDefinition<TCtx> | AsyncHandlerDefinition<TCtx> | InteractiveHandlerDefinition<TCtx>` — the `async` / `interactive` flags discriminate, and `execute` refines its return type per mode (`ApplyResult` for sync, `Promise<ApplyResult>` for async, `Generator<unknown, ApplyResult, unknown> | AsyncGenerator<unknown, ApplyResult, unknown>` for interactive). Consumers that call `handler.execute(...)` directly (tests, wrappers) — typing the handler as a subtype, or narrowing the union via the flags — no longer get the wide `ApplyResult | Promise<ApplyResult>` union for a handler they know is sync.
|
|
10
|
+
|
|
11
|
+
New exports: `HandlerBaseDefinition`, `SyncHandlerDefinition`, `AsyncHandlerDefinition`, `InteractiveHandlerDefinition`, `SyncHandlerExecute`, `AsyncHandlerExecute`, `InteractiveHandlerExecute`.
|
|
12
|
+
|
|
13
|
+
**Breaking (minor, pre-1.0):**
|
|
14
|
+
- A V2 handler that is `async` or `interactive` must now declare the `async: true` / `interactive: true` flag to satisfy the type. `{ async execute() {} }` without `async: true` no longer matches `HandlerDefinition` (it matches no subtype: sync requires a sync return, async/interactive require the flag). Runtime auto-detection via `isAsyncFunction` / `isGeneratorFunction` still works as a safety net — the type just forces the declaration to be explicit for V2.
|
|
15
|
+
- `HandlerDefinition<TCtx>["execute"]` is wider than before (it now includes the `Generator | AsyncGenerator` arm) — code that assigned `handler.execute` to `(...) => ApplyResult | Promise<ApplyResult>` needs to narrow or use a subtype. (This also fixes a latent bug: interactive handlers' `execute` previously violated the declared type silently.)
|
|
16
|
+
- V1 handlers (plain functions) and `HandlerInput<TCtx>` are unchanged.
|
|
17
|
+
|
|
18
|
+
### Patch Changes
|
|
19
|
+
|
|
20
|
+
- 26c579a: Polish published package metadata and READMEs
|
|
21
|
+
- `@statedelta-actions/core` now ships a README (was missing on npm).
|
|
22
|
+
- `@statedelta-actions/analyzer` README: fixed pervasive missing accents.
|
|
23
|
+
- `graph` and `events` READMEs: titles are now the package name (`# @statedelta-actions/graph` / `# @statedelta-actions/events`) instead of a conceptual name.
|
|
24
|
+
- All packages: added `keywords`, `homepage`, `bugs`; package.json keys reordered to a canonical layout.
|
|
25
|
+
- All packages: `CHANGELOG.md` added to `files` so it ships to npm; a `LICENSE` (MIT) file is included in the repo root and in each package.
|
|
26
|
+
|
|
27
|
+
- Updated dependencies [26c579a]
|
|
28
|
+
- @statedelta-actions/core@0.5.1
|
|
29
|
+
|
|
30
|
+
## 0.5.0
|
|
31
|
+
|
|
32
|
+
### Minor Changes
|
|
33
|
+
|
|
34
|
+
- f8c2b4a: Fail-fast register errors, controlled `action-not-found`, atomic registration, and events async/interactive modes
|
|
35
|
+
- **actions** — A missing handler for a directive type now throws at register
|
|
36
|
+
time (was a collected error) — fail-fast for typos and forgotten handlers.
|
|
37
|
+
Invoking an unknown action id returns `aborted: true, abortedBy:
|
|
38
|
+
"action-not-found"` instead of a soft failure. `isActionAsync` is exposed on
|
|
39
|
+
the engine ref handed to handlers (symmetric with `isActionInteractive`).
|
|
40
|
+
- **rules** — `register()` is atomic: it builds a staging set, delegates to the
|
|
41
|
+
ActionEngine, then indexes locally only on success. Structural errors from the
|
|
42
|
+
ActionEngine propagate as a throw with no local state mutated.
|
|
43
|
+
- **events** — `register()`/`defineEvents()` are atomic the same way.
|
|
44
|
+
Async detection is now per-listener transitive via the ActionEngine mini-graph,
|
|
45
|
+
so hybrid engines keep `processEvents`/`processEventsAsync` viable for
|
|
46
|
+
listeners whose subtree is fully sync. New `processEventsInteractive()` returns
|
|
47
|
+
a drainable iterator (sync or async generator) — pauses from `type:"pause"` or
|
|
48
|
+
an interactive handler flow via `yield*` up to the consumer; halt scoping is
|
|
49
|
+
preserved (a pause stops the listeners of that event, following events
|
|
50
|
+
continue). New `isInteractive` getter.
|
|
51
|
+
- **core** — `ActionEngineRef` gains an optional `isActionAsync?` accessor.
|
|
52
|
+
- **sdk** — re-exports the new event types/surface; barrel deps moved to
|
|
53
|
+
`dependencies` (`workspace:^`).
|
|
54
|
+
|
|
55
|
+
### Patch Changes
|
|
56
|
+
|
|
57
|
+
- Updated dependencies [f8c2b4a]
|
|
58
|
+
- @statedelta-actions/core@0.5.0
|
|
59
|
+
|
|
60
|
+
## 0.3.0
|
|
61
|
+
|
|
62
|
+
### Minor Changes
|
|
63
|
+
|
|
64
|
+
- Split types em definitions + engine em cada package; novo subpath sdk/definitions
|
|
65
|
+
- core: RegisterWarning movido pra core/types/engine.ts (tipo shared entre packages)
|
|
66
|
+
- actions: types.ts → types/definitions.ts + types/engine.ts
|
|
67
|
+
- rules: types.ts → types/definitions.ts + types/engine.ts
|
|
68
|
+
- events: types.ts → types/definitions.ts + types/engine.ts
|
|
69
|
+
- sdk: novo subpath ./definitions com re-export de definition types puros (zero runtime)
|
|
70
|
+
|
|
71
|
+
### Patch Changes
|
|
72
|
+
|
|
73
|
+
- Updated dependencies []:
|
|
74
|
+
- @statedelta-actions/core@0.3.0
|
|
75
|
+
|
|
76
|
+
## 0.2.0
|
|
77
|
+
|
|
78
|
+
### Minor Changes
|
|
79
|
+
|
|
80
|
+
- Sync all packages to 0.2.0 (match SDK version)
|
|
81
|
+
|
|
82
|
+
### Patch Changes
|
|
83
|
+
|
|
84
|
+
- Updated dependencies []:
|
|
85
|
+
- @statedelta-actions/core@0.2.0
|
|
86
|
+
|
|
87
|
+
## 0.3.0
|
|
88
|
+
|
|
89
|
+
### Minor Changes
|
|
90
|
+
|
|
91
|
+
- Sync all packages to 0.2.0 (match SDK version)
|
|
92
|
+
|
|
93
|
+
### Patch Changes
|
|
94
|
+
|
|
95
|
+
- Updated dependencies []:
|
|
96
|
+
- @statedelta-actions/core@0.3.0
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Anderson D. Rosa
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -16,6 +16,8 @@ Ações são **declarativas**. Uma action é um ID mais uma lista ordenada de di
|
|
|
16
16
|
|
|
17
17
|
**Sem sugar no runtime.** Diretivas devem ser passadas em **forma canônica** — toda diretiva tem campo `type`. Sugar forms (shorthands de autoria) são concern do compilador JSON DSL, que normaliza antes de entregar pro engine. O engine não interpreta nem converte sugar.
|
|
18
18
|
|
|
19
|
+
**Modo interactive opcional.** Actions podem **pausar entre diretivas** e aguardar input externo via generators (sync ou async). Implementado de forma ortogonal ao modo async — a granularidade é per-action transitiva via mini-graph interno. Ver [Modo Interactive](#modo-interactive) abaixo.
|
|
20
|
+
|
|
19
21
|
## Instalação
|
|
20
22
|
|
|
21
23
|
```bash
|
|
@@ -128,6 +130,47 @@ const handlers = {
|
|
|
128
130
|
| `validate` | Register | Validação estrutural (rejeita diretivas malformadas) |
|
|
129
131
|
| `execute` | Runtime | Processa a diretiva e retorna resultado |
|
|
130
132
|
| `analyze` | — | Consumido pelo `ActionAnalyzer` externo, não pelo engine |
|
|
133
|
+
| `async` (flag) | Construct | Marca handler como assíncrono (opt-in explícito) |
|
|
134
|
+
|
|
135
|
+
### Handlers Async
|
|
136
|
+
|
|
137
|
+
Handlers podem retornar `Promise<ApplyResult>`. O engine detecta async de duas formas:
|
|
138
|
+
|
|
139
|
+
```typescript
|
|
140
|
+
const handlers = {
|
|
141
|
+
// 1. Auto-detect via `async function` — pega quando execute é declarado async.
|
|
142
|
+
fetchUser: {
|
|
143
|
+
async execute(directive, frame) {
|
|
144
|
+
const user = await api.get(`/users/${directive.id}`);
|
|
145
|
+
return { ok: true, data: user };
|
|
146
|
+
},
|
|
147
|
+
},
|
|
148
|
+
|
|
149
|
+
// 2. Flag explícita — para wrappers que retornam Promise sem ser `async function`.
|
|
150
|
+
delegateAsync: {
|
|
151
|
+
async: true,
|
|
152
|
+
execute: (d, frame, engine) => engine.invokeAsync("inner", undefined, frame),
|
|
153
|
+
},
|
|
154
|
+
};
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
**Engine híbrido.** O engine pode misturar handlers sync e async no mesmo registry. Se qualquer handler for async (ou qualquer hook for async), o engine inteiro vira async — `invoke()` lança e você passa a usar `invokeAsync()`. Mas o **JIT per-action decide sync/async por action**: actions que só usam handlers sync compilam wrapper sync, sem `await`. FPS/games com 100% handlers sync pagam zero overhead de async; ETL/business com handlers async awaita só onde precisa.
|
|
158
|
+
|
|
159
|
+
```typescript
|
|
160
|
+
const engine = createActionEngine({
|
|
161
|
+
handlers: {
|
|
162
|
+
update: { execute: (d, f) => { /* sync */ return { ok: true }; } },
|
|
163
|
+
fetchDB: { async execute(d, f) { return { ok: true, data: await db.query(...) }; } },
|
|
164
|
+
},
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
engine.isAsync; // true (fetchDB é async)
|
|
168
|
+
engine.invoke("anything"); // throws — use invokeAsync
|
|
169
|
+
|
|
170
|
+
// JIT compila:
|
|
171
|
+
// action que usa só `update` → wrapper sync, zero await
|
|
172
|
+
// action que usa `fetchDB` → wrapper async, await no handler
|
|
173
|
+
```
|
|
131
174
|
|
|
132
175
|
## Ações
|
|
133
176
|
|
|
@@ -156,11 +199,13 @@ Três categorias. **Toda diretiva tem campo `type`** (forma canônica). O engine
|
|
|
156
199
|
{ type: "let", name: "total", value: "$", resolve: (ctx, scope) => ({ value: scope.subtotal * 1.1 }) }
|
|
157
200
|
```
|
|
158
201
|
|
|
159
|
-
**Control** — saída antecipada:
|
|
202
|
+
**Control** — saída antecipada e ramificação:
|
|
160
203
|
|
|
161
204
|
```typescript
|
|
162
205
|
{ type: "return", value: "done" } // success: true, data: "done"
|
|
163
206
|
{ type: "throw", message: "saldo insuficiente" } // success: false
|
|
207
|
+
{ type: "if", cond: (ctx, scope) => scope.hp > 0,
|
|
208
|
+
then: [...], else: [...] } // ramificação inline
|
|
164
209
|
```
|
|
165
210
|
|
|
166
211
|
**Handler** — dispatch pro handler registrado:
|
|
@@ -172,7 +217,21 @@ Três categorias. **Toda diretiva tem campo `type`** (forma canônica). O engine
|
|
|
172
217
|
|
|
173
218
|
O interpreter e JIT operam sobre formato uniforme — um único dispatch via `type` field, sem branching de categorias.
|
|
174
219
|
|
|
175
|
-
Os nomes `const`, `let`, `return`, `throw` são **tipos reservados** — o engine rejeita handlers com esses nomes.
|
|
220
|
+
Os nomes `const`, `let`, `return`, `throw`, `if`, `pause` são **tipos reservados** — o engine rejeita handlers com esses nomes.
|
|
221
|
+
|
|
222
|
+
### `if` (then/else)
|
|
223
|
+
|
|
224
|
+
`cond` aceita função `(ctx, scope) => boolean` ou boolean literal. Branches executam **no mesmo scope da action** — `const`/`let` em branch ficam visíveis depois. `return`/`throw`/`halt` em branch saem da action inteira (semântica esperada). Aninhamento livre.
|
|
225
|
+
|
|
226
|
+
```typescript
|
|
227
|
+
{ type: "if",
|
|
228
|
+
cond: (_ctx, scope) => scope.hp > 0,
|
|
229
|
+
then: [{ type: "state", target: "status", value: "alive" }],
|
|
230
|
+
else: [{ type: "throw", message: "dead" }],
|
|
231
|
+
}
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
`cond` lança → erro coletado em `errors[]`, nenhum branch executa, prossegue. JIT emite `if/else` JS nativo unrolled (zero call overhead).
|
|
176
235
|
|
|
177
236
|
Diretivas suportam:
|
|
178
237
|
- **`as`** — captura `result.data` no scope: `scope["prev"] = result.data`
|
|
@@ -240,7 +299,7 @@ engine.context(ctx, () => {
|
|
|
240
299
|
engine.invoke("combat");
|
|
241
300
|
});
|
|
242
301
|
|
|
243
|
-
// Async (obrigatório se algum hook for async)
|
|
302
|
+
// Async (obrigatório se algum hook ou handler for async)
|
|
244
303
|
const result = await engine.invokeAsync("heal", undefined, ctx);
|
|
245
304
|
```
|
|
246
305
|
|
|
@@ -353,6 +412,8 @@ createActionEngine({ handlers, mode: "auto" }); // Interpreta primeiro, pr
|
|
|
353
412
|
| `jit` | Mais lento (compila) | `new Function` compilado | Produção, ações estáveis |
|
|
354
413
|
| `auto` | Rápido | Promove per-action após threshold | Uso geral (padrão) |
|
|
355
414
|
|
|
415
|
+
**Decisão sync/async é per-action.** No JIT per-action, cada action é compilada sync ou async independentemente, baseado nos handlers que ela usa. Em um engine híbrido, actions 100% sync ficam com wrapper sync e zero `await`; actions com pelo menos um handler async ficam com wrapper `async` e `await` em todos os handlers da action. Isso garante que uso fully-sync (FPS, game loops) não pague o custo de async functions só porque outro handler do mesmo engine é async.
|
|
416
|
+
|
|
356
417
|
Threshold de auto-promote (padrão: 8):
|
|
357
418
|
|
|
358
419
|
```typescript
|
|
@@ -368,7 +429,7 @@ engine.compile();
|
|
|
368
429
|
Informações de compilação:
|
|
369
430
|
|
|
370
431
|
```typescript
|
|
371
|
-
engine.isAsync; // true se
|
|
432
|
+
engine.isAsync; // true se algum hook OU handler é async
|
|
372
433
|
engine.compilationMode; // "interpret" | "jit" (modo atual, não o requestado)
|
|
373
434
|
```
|
|
374
435
|
|
|
@@ -394,7 +455,7 @@ createActionEngine({
|
|
|
394
455
|
});
|
|
395
456
|
```
|
|
396
457
|
|
|
397
|
-
Hooks podem ser sync ou async. Hooks async tornam o engine async (`engine.isAsync === true`), exigindo `invokeAsync()`.
|
|
458
|
+
Hooks podem ser sync ou async. Hooks async tornam o engine async (`engine.isAsync === true`), exigindo `invokeAsync()`. Handlers async (via flag `async: true` ou `async function`) também tornam o engine async — ver [Handlers Async](#handlers-async).
|
|
398
459
|
|
|
399
460
|
**Custo zero quando ausente.** A compilação JIT não emite código de hook para hooks não registrados.
|
|
400
461
|
|
|
@@ -569,6 +630,22 @@ import type {
|
|
|
569
630
|
ActionInterceptResult,
|
|
570
631
|
} from "@statedelta-actions/actions";
|
|
571
632
|
|
|
633
|
+
// Interactive
|
|
634
|
+
import type {
|
|
635
|
+
InteractiveConfig,
|
|
636
|
+
PauseEvent,
|
|
637
|
+
InteractiveSession,
|
|
638
|
+
AsyncInteractiveSession,
|
|
639
|
+
InteractiveApplyResult,
|
|
640
|
+
Responder,
|
|
641
|
+
} from "@statedelta-actions/actions";
|
|
642
|
+
|
|
643
|
+
import {
|
|
644
|
+
drainSync,
|
|
645
|
+
drainAsync,
|
|
646
|
+
replayResponder,
|
|
647
|
+
} from "@statedelta-actions/actions";
|
|
648
|
+
|
|
572
649
|
// Register Pipeline
|
|
573
650
|
import { RESERVED_TYPES } from "@statedelta-actions/actions";
|
|
574
651
|
|
|
@@ -588,6 +665,184 @@ import { buildActionExecutor } from "@statedelta-actions/actions";
|
|
|
588
665
|
import type { GeneratedActionExecutor } from "@statedelta-actions/actions";
|
|
589
666
|
```
|
|
590
667
|
|
|
668
|
+
## Modo Interactive
|
|
669
|
+
|
|
670
|
+
Pausa execução de uma action e aguarda input externo do customer. Implementado via **generators** (sync ou async), ortogonal ao modo async.
|
|
671
|
+
|
|
672
|
+
**Use cases:**
|
|
673
|
+
- UX interativa em produção (wizards, prompts, confirmações)
|
|
674
|
+
- Debug entre diretivas (futuro — modo separado)
|
|
675
|
+
|
|
676
|
+
**Mecânica:**
|
|
677
|
+
- **Handler interactive** declarado com `interactive: true` (espelha `async: true`) — handler é generator function
|
|
678
|
+
- **Diretiva reservada `type: "pause"`** — engine emite yield direto pra confirmação/breakpoint declarativo
|
|
679
|
+
- **Mini-graph interno** propaga `_interactiveActions` transitivamente (ADR-026)
|
|
680
|
+
- **API**: `engine.invokeInteractive(id, params, ctx)` retorna `Iterator | AsyncIterator`
|
|
681
|
+
|
|
682
|
+
### Habilitação
|
|
683
|
+
|
|
684
|
+
```typescript
|
|
685
|
+
const engine = createActionEngine({
|
|
686
|
+
handlers,
|
|
687
|
+
interactive: {}, // habilita modo interactive
|
|
688
|
+
});
|
|
689
|
+
```
|
|
690
|
+
|
|
691
|
+
Sem `interactive` configurado:
|
|
692
|
+
- Handler com `interactive: true` → erro no constructor (fail-fast)
|
|
693
|
+
- Diretiva `type: "pause"` → erro no register
|
|
694
|
+
|
|
695
|
+
### Handler interactive
|
|
696
|
+
|
|
697
|
+
Handlers são **primitivas Lego** — genéricas, sem semântica de domínio (ADR-028). Vocabulário canonical pequeno (`state`, `emit`, `action`, `log`, `halt`) + handler interactive primitivo proposto: **`input`** (yield + schema + retry).
|
|
698
|
+
|
|
699
|
+
> ⚠️ **Não use nomes domain-specific** (`askUser`, `confirmDelete`, `validateEmail`). Cria explosão combinatória, polui vocabulário canonical e viola o princípio Lego. Customer compõe domínio via **payload** das diretivas, não criando handlers.
|
|
700
|
+
|
|
701
|
+
```typescript
|
|
702
|
+
// Pattern recomendado: primitiva `input` genérica com schema + retry interno
|
|
703
|
+
const handlers = {
|
|
704
|
+
input: {
|
|
705
|
+
interactive: true,
|
|
706
|
+
*execute(directive) {
|
|
707
|
+
let lastError: string | null = null;
|
|
708
|
+
let attempt = 1;
|
|
709
|
+
const max = directive.maxAttempts ?? Infinity;
|
|
710
|
+
while (attempt <= max) {
|
|
711
|
+
const answer = yield {
|
|
712
|
+
kind: "input",
|
|
713
|
+
payload: directive.payload,
|
|
714
|
+
schema: directive.schema,
|
|
715
|
+
attempt,
|
|
716
|
+
lastError,
|
|
717
|
+
};
|
|
718
|
+
if (!directive.schema) return { ok: true, data: answer };
|
|
719
|
+
const r = directive.schema.parse(answer);
|
|
720
|
+
if (r.ok) return { ok: true, data: r.data };
|
|
721
|
+
lastError = r.error;
|
|
722
|
+
attempt++;
|
|
723
|
+
}
|
|
724
|
+
return { ok: false, error: `validation failed after ${max} attempts` };
|
|
725
|
+
},
|
|
726
|
+
},
|
|
727
|
+
};
|
|
728
|
+
```
|
|
729
|
+
|
|
730
|
+
Customer compõe actions com **payload domain-specific** sobre a primitiva:
|
|
731
|
+
|
|
732
|
+
```typescript
|
|
733
|
+
{ id: "register", directives: [
|
|
734
|
+
{ type: "input", payload: { kind: "text", label: "Nome" }, as: "name", schema: nameSchema },
|
|
735
|
+
{ type: "input", payload: { kind: "number", label: "Idade" }, as: "age", schema: ageSchema },
|
|
736
|
+
{ type: "input", payload: { kind: "confirm", message: "Confirma?" }, as: "ok" },
|
|
737
|
+
{ type: "state", target: "users", op: "push", value: { name: ..., age: ... } },
|
|
738
|
+
]}
|
|
739
|
+
```
|
|
740
|
+
|
|
741
|
+
Consumer interpreta `payload.kind` — renderiza UI/CLI/voice. Framework não opina.
|
|
742
|
+
|
|
743
|
+
**Schema agnóstico** — interface mínima `{ parse(raw): { ok: true, data } | { ok: false, error } }`. Customer adapta zod/valibot/json-schema/custom em 1 linha.
|
|
744
|
+
|
|
745
|
+
> **`input` (intra-tick) ≠ `request` (cross-tick).** `input` pausa **dentro** de uma invocação (yield/next imediato). `request` (RealmSystem futuro, fora deste package) anota dependência declarativa, lock cross-tick. São conceitos distintos com nomes propositalmente diferentes.
|
|
746
|
+
|
|
747
|
+
Auto-detect via `isGeneratorFunction(execute)` cobre `function*` e `async function*`. Use a flag `interactive: true` pra wrappers que retornam iterator sem ser generator function.
|
|
748
|
+
|
|
749
|
+
### Diretiva `type: "pause"` (engine-level)
|
|
750
|
+
|
|
751
|
+
```typescript
|
|
752
|
+
{ type: "pause", message: "Confirmação destrutiva?", as: "ack" }
|
|
753
|
+
```
|
|
754
|
+
|
|
755
|
+
Engine yield direto:
|
|
756
|
+
```typescript
|
|
757
|
+
session.next(); // → { source: "pause", payload: { message: "..." }, ... }
|
|
758
|
+
session.next("ok"); // → continua, captura "ok" em scope.ack
|
|
759
|
+
session.next(false); // → aborta (abortedBy: "pause")
|
|
760
|
+
session.next("cancel"); // → aborta
|
|
761
|
+
session.next("abort"); // → aborta
|
|
762
|
+
```
|
|
763
|
+
|
|
764
|
+
### Drenagem do iterator
|
|
765
|
+
|
|
766
|
+
```typescript
|
|
767
|
+
// Customer dirigindo manualmente
|
|
768
|
+
const session = engine.invokeInteractive("wizard", undefined, ctx);
|
|
769
|
+
const r1 = session.next(); // PauseEvent ou payload custom
|
|
770
|
+
const r2 = session.next("Anderson"); // entrega resposta
|
|
771
|
+
// quando r.done === true, r.value é o DirectiveResult final
|
|
772
|
+
```
|
|
773
|
+
|
|
774
|
+
Helpers opcionais pra "play mode":
|
|
775
|
+
|
|
776
|
+
```typescript
|
|
777
|
+
import { drainSync, drainAsync, replayResponder } from "@statedelta-actions/actions";
|
|
778
|
+
|
|
779
|
+
// Drena com responder programático
|
|
780
|
+
const result = drainSync(session, (event) => {
|
|
781
|
+
// event pode ser PauseEvent (type:"pause") ou payload custom (handler)
|
|
782
|
+
if ("prompt" in event) return responses[event.prompt];
|
|
783
|
+
if ((event as PauseEvent).source === "pause") return "ok";
|
|
784
|
+
return undefined;
|
|
785
|
+
});
|
|
786
|
+
|
|
787
|
+
// Replay determinístico (testes)
|
|
788
|
+
const result = drainSync(session, replayResponder(["Anderson", 42, "yes"]));
|
|
789
|
+
```
|
|
790
|
+
|
|
791
|
+
### Sub-actions interativas (yield* propaga pausas)
|
|
792
|
+
|
|
793
|
+
Quando action root invoca child interactive, pausas do child fluem pro consumer no nível raiz via `yield*`. Handler `action` (consumer-defined) decide:
|
|
794
|
+
|
|
795
|
+
```typescript
|
|
796
|
+
const handlers = {
|
|
797
|
+
action: {
|
|
798
|
+
execute: (d, f, e) => {
|
|
799
|
+
if (e.isActionInteractive(d.id)) {
|
|
800
|
+
// Target é interactive → propaga via iterator
|
|
801
|
+
return { ok: true, iterator: e.invokeInteractive(d.id, d.params, f) };
|
|
802
|
+
}
|
|
803
|
+
// Target sync → invoke regular
|
|
804
|
+
const r = e.invoke(d.id, d.params, f);
|
|
805
|
+
return { ok: r.success, data: r.data };
|
|
806
|
+
},
|
|
807
|
+
},
|
|
808
|
+
};
|
|
809
|
+
```
|
|
810
|
+
|
|
811
|
+
Engine consulta mini-graph e marca actions transitivamente interativas. JIT detecta `_result.iterator` em runtime e emite `yield*` automaticamente.
|
|
812
|
+
|
|
813
|
+
### Matriz async × interactive
|
|
814
|
+
|
|
815
|
+
| Engine async? | Action interactive? | Compilação |
|
|
816
|
+
|---------------|---------------------|------------|
|
|
817
|
+
| não | não | `function ()` |
|
|
818
|
+
| não | sim | `function* ()` |
|
|
819
|
+
| sim | não | `async function ()` |
|
|
820
|
+
| sim | sim | `async function* ()` |
|
|
821
|
+
|
|
822
|
+
Granularidade per-action via mini-graph (ADR-021/025/026). FPS 100% sync com 1 action interactive isolada não paga overhead de generator nas outras.
|
|
823
|
+
|
|
824
|
+
### `invoke()` per-action transitivo
|
|
825
|
+
|
|
826
|
+
`invoke()` lança per-action (não global):
|
|
827
|
+
|
|
828
|
+
```typescript
|
|
829
|
+
engine.invoke("syncAction"); // OK — sub-árvore inteira sync
|
|
830
|
+
engine.invoke("asyncAction"); // throw "use invokeAsync"
|
|
831
|
+
engine.invoke("interactiveAction"); // throw "use invokeInteractive"
|
|
832
|
+
```
|
|
833
|
+
|
|
834
|
+
Engine híbrido (handler async + actions sync isoladas) permite `invoke()` regular nas sync. ADR-026.
|
|
835
|
+
|
|
836
|
+
### Action hooks fora do generator
|
|
837
|
+
|
|
838
|
+
`beforeAction` / `afterAction` (ADR-019) executam **antes** e **depois** do generator. Skip via `beforeAction` retorna `memoResult` imediatamente. `afterAction` substitui o resultado final.
|
|
839
|
+
|
|
840
|
+
### Catch atômico
|
|
841
|
+
|
|
842
|
+
Diretivas dentro de `catch` executam **atomicamente** (ADR-023) via `engine.runDirectives` — sem yield. Caminho de erro não pausa. Diretivas `type: "pause"` ou handler interactive dentro de catch geram warning em register-time.
|
|
843
|
+
|
|
844
|
+
---
|
|
845
|
+
|
|
591
846
|
## Analyzer
|
|
592
847
|
|
|
593
848
|
Funcionalidades de análise estática foram extraídas para o pacote `@statedelta-actions/analyzer`:
|