@softize/opus 8.6.6
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 +1616 -0
- package/LICENSE +21 -0
- package/README.md +113 -0
- package/bin/cli.mjs +528 -0
- package/bin/lib/check.mjs +307 -0
- package/bin/lib/components.mjs +151 -0
- package/bin/lib/create.mjs +208 -0
- package/bin/lib/db-check-runner.mjs +86 -0
- package/bin/lib/db-migrate-runner.mjs +89 -0
- package/bin/lib/db-scaffold-runner.mjs +84 -0
- package/bin/lib/db.mjs +261 -0
- package/bin/lib/docs-include.mjs +48 -0
- package/bin/lib/gen-dicts.mjs +134 -0
- package/bin/lib/gen-docs.mjs +288 -0
- package/bin/lib/gen-manifest.mjs +102 -0
- package/bin/lib/gen-openapi.mjs +195 -0
- package/bin/lib/gen-runner.mjs +472 -0
- package/bin/lib/gen-stubs.mjs +463 -0
- package/bin/lib/gen.mjs +311 -0
- package/bin/lib/init.mjs +514 -0
- package/bin/lib/introspect.mjs +107 -0
- package/bin/lib/mcp.mjs +85 -0
- package/bin/lib/postinstall.mjs +56 -0
- package/docs/chat-event-protocol.md +85 -0
- package/docs/code-style.md +16 -0
- package/docs/data-layer.md +246 -0
- package/docs/ownership-vs-shadcn-lock.md +102 -0
- package/docs/protocol.md +2053 -0
- package/docs/releasing.md +110 -0
- package/docs/shellnav.md +131 -0
- package/package.json +338 -0
- package/registry/hooks/hooks.json +26 -0
- package/registry/hooks/link-memory-on-start.mjs +46 -0
- package/registry/hooks/opus-check-on-stop.mjs +114 -0
- package/registry/skills/create-action/SKILL.md +49 -0
- package/registry/skills/create-action/scaffold.mjs +122 -0
- package/registry/templates/app/_gitignore +3 -0
- package/registry/templates/app/_npmrc +1 -0
- package/registry/templates/app/_opus/_gitignore +5 -0
- package/registry/templates/app/_prettierrc.json +6 -0
- package/registry/templates/app/index.html +13 -0
- package/registry/templates/app/opus.config.ts +16 -0
- package/registry/templates/app/package.json +43 -0
- package/registry/templates/app/pnpm-workspace.yaml +11 -0
- package/registry/templates/app/public/favicon.svg +4 -0
- package/registry/templates/app/src/App.tsx +37 -0
- package/registry/templates/app/src/domains/tasks/actions/list.test.ts +34 -0
- package/registry/templates/app/src/domains/tasks/actions/list.ts +33 -0
- package/registry/templates/app/src/domains/tasks/index.ts +13 -0
- package/registry/templates/app/src/index.css +18 -0
- package/registry/templates/app/src/main.tsx +25 -0
- package/registry/templates/app/tsconfig.json +20 -0
- package/registry/templates/app/vite.config.ts +46 -0
- package/registry/templates/monorepo/_gitignore +3 -0
- package/registry/templates/monorepo/_npmrc +1 -0
- package/registry/templates/monorepo/package.json +9 -0
- package/registry/templates/monorepo/pnpm-workspace.yaml +14 -0
- package/src/ai/ask.ts +64 -0
- package/src/ai/drivers/anthropic.ts +309 -0
- package/src/ai/index.ts +17 -0
- package/src/audit/drivers/console.ts +117 -0
- package/src/audit/drivers/pg.ts +172 -0
- package/src/audit/index.ts +51 -0
- package/src/auth/drivers/better-auth.ts +103 -0
- package/src/auth/drivers/jwt.ts +188 -0
- package/src/auth/index.ts +9 -0
- package/src/client/drivers/fetch.ts +202 -0
- package/src/client/index.ts +22 -0
- package/src/core/actions.ts +110 -0
- package/src/core/audit.ts +239 -0
- package/src/core/contracts.ts +137 -0
- package/src/core/domain.ts +310 -0
- package/src/core/errors.ts +181 -0
- package/src/core/index.ts +174 -0
- package/src/core/logical-type.ts +31 -0
- package/src/core/reactions.ts +81 -0
- package/src/core/runtime.ts +1167 -0
- package/src/core/schedules.ts +41 -0
- package/src/core/types.ts +1356 -0
- package/src/data/drivers/kysely.ts +389 -0
- package/src/data/index.ts +10 -0
- package/src/data/readonly-pool.ts +160 -0
- package/src/dsl/eval.ts +136 -0
- package/src/dsl/index.ts +29 -0
- package/src/dsl/kysely.ts +230 -0
- package/src/dsl/loads.ts +123 -0
- package/src/dsl/parser.ts +423 -0
- package/src/dsl/types.ts +113 -0
- package/src/events/drivers/mitt.ts +70 -0
- package/src/events/index.ts +9 -0
- package/src/log/drivers/pino.ts +57 -0
- package/src/log/index.ts +9 -0
- package/src/mcp/index.ts +62 -0
- package/src/queue/drivers/bullmq.ts +190 -0
- package/src/queue/index.ts +9 -0
- package/src/scheduler/drivers/node-cron.ts +93 -0
- package/src/scheduler/every.ts +45 -0
- package/src/scheduler/index.ts +9 -0
- package/src/schema/drivers/zod.ts +765 -0
- package/src/schema/entity.ts +439 -0
- package/src/schema/format/locale.ts +144 -0
- package/src/schema/index.ts +65 -0
- package/src/schema/openapi.ts +302 -0
- package/src/schema/scaffold.ts +160 -0
- package/src/server/drivers/fastify.ts +224 -0
- package/src/server/drivers/node.ts +386 -0
- package/src/server/index.ts +142 -0
- package/src/storage/drivers/fs.ts +90 -0
- package/src/storage/drivers/s3.ts +117 -0
- package/src/storage/index.ts +27 -0
- package/src/testing/fake.ts +298 -0
- package/src/testing/index.ts +324 -0
- package/src/ui/components/patterns/action-form-card.tsx +48 -0
- package/src/ui/components/patterns/action-list-dialog.tsx +93 -0
- package/src/ui/components/patterns/app-shell.tsx +227 -0
- package/src/ui/components/patterns/confirm.tsx +226 -0
- package/src/ui/components/patterns/data-state.tsx +75 -0
- package/src/ui/components/patterns/form-dialog.tsx +64 -0
- package/src/ui/components/patterns/form.tsx +584 -0
- package/src/ui/components/patterns/list.tsx +1488 -0
- package/src/ui/components/patterns/page.tsx +46 -0
- package/src/ui/components/patterns/section-shell.tsx +246 -0
- package/src/ui/components/patterns/shell-nav.tsx +150 -0
- package/src/ui/components/patterns/sidebar.tsx +89 -0
- package/src/ui/components/patterns/split.tsx +93 -0
- package/src/ui/components/patterns/trigger.tsx +196 -0
- package/src/ui/components/patterns/view.tsx +84 -0
- package/src/ui/components/primitives/accordion.tsx +64 -0
- package/src/ui/components/primitives/alert-dialog.tsx +190 -0
- package/src/ui/components/primitives/alert.tsx +116 -0
- package/src/ui/components/primitives/aspect-ratio.tsx +9 -0
- package/src/ui/components/primitives/avatar.tsx +107 -0
- package/src/ui/components/primitives/badge.tsx +37 -0
- package/src/ui/components/primitives/breadcrumb.tsx +109 -0
- package/src/ui/components/primitives/button-group.tsx +83 -0
- package/src/ui/components/primitives/button.tsx +102 -0
- package/src/ui/components/primitives/calendar.tsx +218 -0
- package/src/ui/components/primitives/card.tsx +56 -0
- package/src/ui/components/primitives/carousel.tsx +239 -0
- package/src/ui/components/primitives/chat.tsx +407 -0
- package/src/ui/components/primitives/checkbox.tsx +30 -0
- package/src/ui/components/primitives/collapsible.tsx +31 -0
- package/src/ui/components/primitives/command.tsx +182 -0
- package/src/ui/components/primitives/composer.tsx +121 -0
- package/src/ui/components/primitives/copyable.tsx +50 -0
- package/src/ui/components/primitives/dialog.tsx +147 -0
- package/src/ui/components/primitives/drawer.tsx +141 -0
- package/src/ui/components/primitives/empty.tsx +104 -0
- package/src/ui/components/primitives/field.tsx +246 -0
- package/src/ui/components/primitives/icon-picker.tsx +180 -0
- package/src/ui/components/primitives/input-group.tsx +168 -0
- package/src/ui/components/primitives/input-otp.tsx +75 -0
- package/src/ui/components/primitives/input.tsx +72 -0
- package/src/ui/components/primitives/item.tsx +193 -0
- package/src/ui/components/primitives/kbd.tsx +28 -0
- package/src/ui/components/primitives/label.tsx +22 -0
- package/src/ui/components/primitives/markdown.tsx +35 -0
- package/src/ui/components/primitives/menu.tsx +255 -0
- package/src/ui/components/primitives/pagination.tsx +127 -0
- package/src/ui/components/primitives/popover.tsx +87 -0
- package/src/ui/components/primitives/progress.tsx +29 -0
- package/src/ui/components/primitives/radio-group.tsx +43 -0
- package/src/ui/components/primitives/resizable.tsx +51 -0
- package/src/ui/components/primitives/scroll-area.tsx +56 -0
- package/src/ui/components/primitives/select.tsx +479 -0
- package/src/ui/components/primitives/separator.tsx +26 -0
- package/src/ui/components/primitives/skeleton.tsx +13 -0
- package/src/ui/components/primitives/slider.tsx +61 -0
- package/src/ui/components/primitives/sonner.tsx +46 -0
- package/src/ui/components/primitives/spinner.tsx +29 -0
- package/src/ui/components/primitives/switch.tsx +33 -0
- package/src/ui/components/primitives/table.tsx +114 -0
- package/src/ui/components/primitives/tabs.tsx +104 -0
- package/src/ui/components/primitives/textarea.tsx +18 -0
- package/src/ui/components/primitives/toggle-group.tsx +81 -0
- package/src/ui/components/primitives/toggle.tsx +45 -0
- package/src/ui/components/primitives/tooltip.tsx +55 -0
- package/src/ui/components/primitives/truncate.tsx +49 -0
- package/src/ui/docs/DocBrowser.tsx +90 -0
- package/src/ui/docs/changelog.tsx +80 -0
- package/src/ui/docs/content/accordion.md +86 -0
- package/src/ui/docs/content/action-form-card.md +24 -0
- package/src/ui/docs/content/action-form-dialog.md +30 -0
- package/src/ui/docs/content/action-form.md +125 -0
- package/src/ui/docs/content/action-list-dialog.md +68 -0
- package/src/ui/docs/content/action-list.md +194 -0
- package/src/ui/docs/content/action-trigger.md +72 -0
- package/src/ui/docs/content/action-view.md +47 -0
- package/src/ui/docs/content/actions.md +138 -0
- package/src/ui/docs/content/ai.md +112 -0
- package/src/ui/docs/content/alert-dialog.md +73 -0
- package/src/ui/docs/content/alert.md +69 -0
- package/src/ui/docs/content/app-shell.md +155 -0
- package/src/ui/docs/content/aspect-ratio.md +66 -0
- package/src/ui/docs/content/audit.md +84 -0
- package/src/ui/docs/content/auth.md +70 -0
- package/src/ui/docs/content/avatar.md +94 -0
- package/src/ui/docs/content/badge.md +48 -0
- package/src/ui/docs/content/breadcrumb.md +87 -0
- package/src/ui/docs/content/button-group.md +71 -0
- package/src/ui/docs/content/button.md +60 -0
- package/src/ui/docs/content/calendar.md +62 -0
- package/src/ui/docs/content/card.md +49 -0
- package/src/ui/docs/content/carousel.md +85 -0
- package/src/ui/docs/content/chat.md +69 -0
- package/src/ui/docs/content/checkbox.md +75 -0
- package/src/ui/docs/content/cli.md +58 -0
- package/src/ui/docs/content/collapsible.md +64 -0
- package/src/ui/docs/content/command.md +56 -0
- package/src/ui/docs/content/composer.md +50 -0
- package/src/ui/docs/content/confirm.md +120 -0
- package/src/ui/docs/content/copyable.md +30 -0
- package/src/ui/docs/content/customization.md +110 -0
- package/src/ui/docs/content/cycle.md +34 -0
- package/src/ui/docs/content/data-state.md +47 -0
- package/src/ui/docs/content/data.md +99 -0
- package/src/ui/docs/content/dialog.md +60 -0
- package/src/ui/docs/content/drawer.md +55 -0
- package/src/ui/docs/content/empty.md +66 -0
- package/src/ui/docs/content/events.md +61 -0
- package/src/ui/docs/content/field.md +58 -0
- package/src/ui/docs/content/getting-started.md +109 -0
- package/src/ui/docs/content/icon-picker.md +51 -0
- package/src/ui/docs/content/input-group.md +78 -0
- package/src/ui/docs/content/input-otp.md +72 -0
- package/src/ui/docs/content/input.md +78 -0
- package/src/ui/docs/content/item.md +84 -0
- package/src/ui/docs/content/kbd.md +62 -0
- package/src/ui/docs/content/label.md +32 -0
- package/src/ui/docs/content/log.md +55 -0
- package/src/ui/docs/content/markdown.md +41 -0
- package/src/ui/docs/content/mcp.md +44 -0
- package/src/ui/docs/content/menu.md +114 -0
- package/src/ui/docs/content/microcopy.md +83 -0
- package/src/ui/docs/content/page.md +34 -0
- package/src/ui/docs/content/pagination.md +99 -0
- package/src/ui/docs/content/popover.md +49 -0
- package/src/ui/docs/content/progress.md +69 -0
- package/src/ui/docs/content/queue.md +62 -0
- package/src/ui/docs/content/radio-group.md +77 -0
- package/src/ui/docs/content/resizable.md +86 -0
- package/src/ui/docs/content/router.md +56 -0
- package/src/ui/docs/content/runtime.md +77 -0
- package/src/ui/docs/content/scheduler.md +66 -0
- package/src/ui/docs/content/scroll-area.md +89 -0
- package/src/ui/docs/content/section-shell.md +121 -0
- package/src/ui/docs/content/select.md +342 -0
- package/src/ui/docs/content/separator.md +33 -0
- package/src/ui/docs/content/sidebar.md +38 -0
- package/src/ui/docs/content/skeleton.md +34 -0
- package/src/ui/docs/content/slider.md +64 -0
- package/src/ui/docs/content/spinner.md +37 -0
- package/src/ui/docs/content/split.md +33 -0
- package/src/ui/docs/content/storage.md +69 -0
- package/src/ui/docs/content/switch.md +69 -0
- package/src/ui/docs/content/table.md +102 -0
- package/src/ui/docs/content/tabs.md +94 -0
- package/src/ui/docs/content/testing.md +89 -0
- package/src/ui/docs/content/textarea.md +30 -0
- package/src/ui/docs/content/toast.md +67 -0
- package/src/ui/docs/content/toggle-group.md +81 -0
- package/src/ui/docs/content/toggle.md +72 -0
- package/src/ui/docs/content/tokens.md +171 -0
- package/src/ui/docs/content/tooltip.md +50 -0
- package/src/ui/docs/content/truncate.md +37 -0
- package/src/ui/docs/content/ui.md +40 -0
- package/src/ui/docs/content/upgrading.md +48 -0
- package/src/ui/docs/doc-client.tsx +214 -0
- package/src/ui/docs/doc.tsx +301 -0
- package/src/ui/docs/folder.tsx +149 -0
- package/src/ui/docs/index.ts +21 -0
- package/src/ui/docs/markdown.tsx +130 -0
- package/src/ui/docs/md-raw.d.ts +4 -0
- package/src/ui/docs/plugin.ts +104 -0
- package/src/ui/docs/registry.tsx +424 -0
- package/src/ui/docs/standalone.tsx +107 -0
- package/src/ui/drivers/react.tsx +627 -0
- package/src/ui/index.ts +92 -0
- package/src/ui/lib/cn.ts +10 -0
- package/src/ui/lib/zod-pt-br.ts +38 -0
- package/src/ui/meta.ts +412 -0
- package/src/ui/react.tsx +235 -0
- package/src/ui/router.ts +96 -0
- package/src/ui/theme.css +234 -0
- package/src/vite/design.ts +652 -0
- package/src/vite/index.ts +8 -0
|
@@ -0,0 +1,1356 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* tbdlib — Core types
|
|
3
|
+
*
|
|
4
|
+
* Tipos centrais do protocolo. Source of truth: `/docs/protocol.md`.
|
|
5
|
+
* Qualquer divergência entre este arquivo e a doc é bug; alinhar a doc primeiro,
|
|
6
|
+
* depois ajustar o código.
|
|
7
|
+
*
|
|
8
|
+
* Organização (numerada igual ao protocolo quando aplicável):
|
|
9
|
+
* 1. Primitivos & shared
|
|
10
|
+
* 2. Erro (§3)
|
|
11
|
+
* 3. Result (§6)
|
|
12
|
+
* 4. Contexto (§4)
|
|
13
|
+
* 5. Audit (§5)
|
|
14
|
+
* 6. Eventos (§11)
|
|
15
|
+
* 7. Jobs / background (§10)
|
|
16
|
+
* 8. Tipos lógicos (§7)
|
|
17
|
+
* 9. Specs (FieldSpec, FilterSpec, ConfirmSpec, ...) (§2)
|
|
18
|
+
* 10. Action — discriminated union (§2)
|
|
19
|
+
* 11. Reaction (§12)
|
|
20
|
+
* 12. Adapters (§8)
|
|
21
|
+
* 13. RuntimeConfig (§14)
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
// =============================================================================
|
|
25
|
+
// 1. Primitivos & shared
|
|
26
|
+
// =============================================================================
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Referência i18n. Opt-in.
|
|
30
|
+
* - `string` literal vira a si mesma (passthrough quando sem resolver).
|
|
31
|
+
* - `{ key, default }` resolve via `RuntimeConfig.i18n.resolver`, com fallback no `default`.
|
|
32
|
+
* Ver §13.
|
|
33
|
+
*/
|
|
34
|
+
export type I18nRef = string | { key: string; default: string }
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Função pra cancelar uma inscrição (event bus, queue listener, etc).
|
|
38
|
+
*/
|
|
39
|
+
export type Unsubscribe = () => void
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Logger genérico que mora no contexto. Implementado por `LoggerAdapter`.
|
|
43
|
+
* Detalhes: §8.
|
|
44
|
+
*/
|
|
45
|
+
export interface Logger {
|
|
46
|
+
trace(msg: string, meta?: object): void
|
|
47
|
+
debug(msg: string, meta?: object): void
|
|
48
|
+
info(msg: string, meta?: object): void
|
|
49
|
+
warn(msg: string, meta?: object): void
|
|
50
|
+
error(msg: string, meta?: object): void
|
|
51
|
+
fatal(msg: string, meta?: object): void
|
|
52
|
+
/** Cria logger derivado com bindings pré-injetados (per-request, per-action). */
|
|
53
|
+
child(bindings: object): Logger
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Path segment numa validation issue. Suporta tanto chave crua quanto wrapper.
|
|
58
|
+
* Espelha Standard Schema v1 spec.
|
|
59
|
+
*/
|
|
60
|
+
export interface SchemaPathSegment {
|
|
61
|
+
readonly key: PropertyKey
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export interface SchemaIssue {
|
|
65
|
+
readonly message: string
|
|
66
|
+
readonly path?: ReadonlyArray<PropertyKey | SchemaPathSegment> | undefined
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export interface SchemaResultSuccess<TOutput> {
|
|
70
|
+
readonly value: TOutput
|
|
71
|
+
readonly issues?: undefined
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export interface SchemaResultFailure {
|
|
75
|
+
readonly issues: ReadonlyArray<SchemaIssue>
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export type SchemaResult<TOutput> = SchemaResultSuccess<TOutput> | SchemaResultFailure
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Schema compatível com Standard Schema v1. Default vem de Zod (lib expõe `t.*`
|
|
82
|
+
* em `@softize/opus/core`), mas qualquer lib que implemente o spec funciona
|
|
83
|
+
* (Zod 3.24+, ArkType, Valibot via adapter).
|
|
84
|
+
*
|
|
85
|
+
* Mantemos o shape inline pra não acoplar core a `@standard-schema/spec`.
|
|
86
|
+
*/
|
|
87
|
+
export interface Schema<TInput = unknown, TOutput = TInput> {
|
|
88
|
+
readonly '~standard': {
|
|
89
|
+
readonly version: 1
|
|
90
|
+
readonly vendor: string
|
|
91
|
+
readonly validate: (
|
|
92
|
+
value: unknown,
|
|
93
|
+
) => SchemaResult<TOutput> | Promise<SchemaResult<TOutput>>
|
|
94
|
+
readonly types?: { readonly input: TInput; readonly output: TOutput } | undefined
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** Helper pra extrair o tipo de output de um Schema. */
|
|
99
|
+
export type InferOutput<S> = S extends Schema<unknown, infer Out> ? Out : never
|
|
100
|
+
|
|
101
|
+
/** Helper pra extrair o tipo de input de um Schema. */
|
|
102
|
+
export type InferInput<S> = S extends Schema<infer In, unknown> ? In : never
|
|
103
|
+
|
|
104
|
+
// =============================================================================
|
|
105
|
+
// 2. Erro (§3)
|
|
106
|
+
// =============================================================================
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Categorias de erro com mapping HTTP default sugerido. Ver §3.
|
|
110
|
+
*/
|
|
111
|
+
export type ErrorCategory =
|
|
112
|
+
| 'validation'
|
|
113
|
+
| 'authentication'
|
|
114
|
+
| 'authorization'
|
|
115
|
+
| 'not_found'
|
|
116
|
+
| 'conflict'
|
|
117
|
+
| 'rate_limit'
|
|
118
|
+
| 'dependency'
|
|
119
|
+
| 'internal'
|
|
120
|
+
|
|
121
|
+
export type Severity = 'warning' | 'error' | 'fatal'
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Issue dentro de `ActionError.issues` — usado tipicamente em erros de validation
|
|
125
|
+
* pra reportar múltiplos campos inválidos numa única resposta.
|
|
126
|
+
*/
|
|
127
|
+
export interface ValidationIssue {
|
|
128
|
+
path: string
|
|
129
|
+
code: string
|
|
130
|
+
message: string
|
|
131
|
+
i18nKey?: string
|
|
132
|
+
i18nParams?: Record<string, unknown>
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Shape padronizado de erro emitido por qualquer action. Adapters de transporte
|
|
137
|
+
* serializam preservando estrutura. Ver §3.
|
|
138
|
+
*/
|
|
139
|
+
export interface ActionError {
|
|
140
|
+
code: string
|
|
141
|
+
category: ErrorCategory
|
|
142
|
+
message: string
|
|
143
|
+
severity: Severity
|
|
144
|
+
retriable: boolean
|
|
145
|
+
i18nKey?: string
|
|
146
|
+
i18nParams?: Record<string, unknown>
|
|
147
|
+
field?: string
|
|
148
|
+
issues?: ValidationIssue[]
|
|
149
|
+
cause?: unknown
|
|
150
|
+
meta?: Record<string, unknown>
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Catálogo declarativo de erros que uma action pode emitir.
|
|
155
|
+
* Drive OpenAPI/AI tools — não é checado em runtime.
|
|
156
|
+
*/
|
|
157
|
+
export interface ErrorSpec {
|
|
158
|
+
code: string
|
|
159
|
+
category: ErrorCategory
|
|
160
|
+
description: string
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// =============================================================================
|
|
164
|
+
// 3. Result (§6)
|
|
165
|
+
// =============================================================================
|
|
166
|
+
|
|
167
|
+
export interface ResultMeta {
|
|
168
|
+
actionId: string
|
|
169
|
+
action: string
|
|
170
|
+
durationMs: number
|
|
171
|
+
requestId?: string
|
|
172
|
+
cached?: boolean
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Envelope discriminated union retornado de toda execução síncrona.
|
|
177
|
+
* Background actions retornam `BackgroundResult<T>`.
|
|
178
|
+
*/
|
|
179
|
+
export type ActionResult<T> =
|
|
180
|
+
| { ok: true; data: T; meta: ResultMeta }
|
|
181
|
+
| { ok: false; error: ActionError; meta: ResultMeta }
|
|
182
|
+
|
|
183
|
+
export type BackgroundResult<T> =
|
|
184
|
+
| { ok: true; data: JobHandle<T>; meta: ResultMeta }
|
|
185
|
+
| { ok: false; error: ActionError; meta: ResultMeta }
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Output de search actions: sempre paginado.
|
|
189
|
+
*/
|
|
190
|
+
export interface Paginated<T> {
|
|
191
|
+
items: T[]
|
|
192
|
+
cursor: { next: string | null; prev?: string | null }
|
|
193
|
+
total?: number
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// =============================================================================
|
|
197
|
+
// 4. Contexto (§4)
|
|
198
|
+
// =============================================================================
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Origem (provenance) de uma execução. Identifica QUEM/O QUÊ disparou
|
|
202
|
+
* a action — humano, schedule, reaction, AI agent, integração externa.
|
|
203
|
+
*
|
|
204
|
+
* Discriminated union por `kind`. Provenance é populada pelo runtime ao
|
|
205
|
+
* receber a request/trigger; chega ao handler via `ctx.provenance` e
|
|
206
|
+
* é persistida no `AuditRecord.provenance`.
|
|
207
|
+
*
|
|
208
|
+
* Provenance chains: actions disparadas por `background` ou `reaction`
|
|
209
|
+
* preservam a `originalProvenance` permitindo rastrear a cadeia inteira
|
|
210
|
+
* até a origem humana/sistema.
|
|
211
|
+
*/
|
|
212
|
+
export type Provenance =
|
|
213
|
+
| { kind: 'http'; userId: string | null; requestId?: string }
|
|
214
|
+
| { kind: 'schedule'; scheduleName: string }
|
|
215
|
+
| {
|
|
216
|
+
kind: 'reaction'
|
|
217
|
+
reactionName: string
|
|
218
|
+
sourceActionId: string
|
|
219
|
+
eventType: string
|
|
220
|
+
originalProvenance?: Provenance
|
|
221
|
+
}
|
|
222
|
+
| {
|
|
223
|
+
kind: 'background'
|
|
224
|
+
queueName: string
|
|
225
|
+
jobId: string
|
|
226
|
+
originalProvenance?: Provenance
|
|
227
|
+
}
|
|
228
|
+
| { kind: 'ai-agent'; agentId: string; model?: string; sessionId?: string }
|
|
229
|
+
| { kind: 'integration'; name: string; payload?: Record<string, unknown> }
|
|
230
|
+
| { kind: 'self-observation'; monitorName: string }
|
|
231
|
+
| { kind: 'system'; source: string }
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Representação opaca do usuário autenticado. Shape exato é decidido pelo
|
|
235
|
+
* `AuthAdapter`. Core não opina além do `id`.
|
|
236
|
+
*/
|
|
237
|
+
export interface User {
|
|
238
|
+
id: string
|
|
239
|
+
[key: string]: unknown
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Função que checa permissão. Plugada pelo `AuthAdapter`.
|
|
244
|
+
* tbdlib não implementa RBAC/ABAC — apenas delega.
|
|
245
|
+
*/
|
|
246
|
+
export type CanFn = (permission: string, resource?: unknown) => boolean | Promise<boolean>
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Função que emite um `DomainEvent`. Disponível em `ActionContext.emit` e
|
|
250
|
+
* `ReactionContext.emit`. Plugada pelo `EventBusAdapter`.
|
|
251
|
+
*/
|
|
252
|
+
export type EmitFn = <T = unknown>(
|
|
253
|
+
event: string,
|
|
254
|
+
data: T,
|
|
255
|
+
meta?: { correlation?: string; [k: string]: unknown },
|
|
256
|
+
) => Promise<void>
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Contexto entregue ao handler de action. Composto por adapters: auth fornece
|
|
260
|
+
* `user`/`tenantId`/`can`; data fornece `db`; logger fornece `log`; eventbus
|
|
261
|
+
* fornece `emit`; storage/ai fornecem `storage`/`ai` (null quando não configurados).
|
|
262
|
+
*/
|
|
263
|
+
export interface ActionContext {
|
|
264
|
+
user: User | null
|
|
265
|
+
tenantId: string | null
|
|
266
|
+
can: CanFn
|
|
267
|
+
db: unknown
|
|
268
|
+
log: Logger
|
|
269
|
+
emit: EmitFn
|
|
270
|
+
storage: StorageAdapter | null
|
|
271
|
+
ai: BoundAi | null
|
|
272
|
+
provenance: Provenance
|
|
273
|
+
meta: Record<string, unknown>
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Contexto entregue ao handler de reaction. Mais enxuto que `ActionContext`
|
|
278
|
+
* (sem `input`/`loaded`). Ver §12.
|
|
279
|
+
*/
|
|
280
|
+
export interface ReactionContext {
|
|
281
|
+
user: User | null
|
|
282
|
+
tenantId: string | null
|
|
283
|
+
db: unknown
|
|
284
|
+
log: Logger
|
|
285
|
+
emit: EmitFn
|
|
286
|
+
storage: StorageAdapter | null
|
|
287
|
+
ai: BoundAi | null
|
|
288
|
+
provenance: Provenance
|
|
289
|
+
meta: Record<string, unknown>
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// =============================================================================
|
|
293
|
+
// 5. Audit (§5)
|
|
294
|
+
// =============================================================================
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Registro emitido após execução de action (sucesso ou falha).
|
|
298
|
+
* Persistido pelos `AuditSink` configurados. Ver §5.
|
|
299
|
+
*/
|
|
300
|
+
export interface AuditRecord {
|
|
301
|
+
id: string
|
|
302
|
+
timestamp: string
|
|
303
|
+
action: string
|
|
304
|
+
/**
|
|
305
|
+
* O `kind` da action executada — sink que filtra leitura (list/view = ruído)
|
|
306
|
+
* decide pelo record, sem receber os DOMAINS por fora pra montar Set de nomes.
|
|
307
|
+
*/
|
|
308
|
+
actionKind?: ActionDef['kind']
|
|
309
|
+
outcome: 'success' | 'error'
|
|
310
|
+
durationMs: number
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* Provenance estruturada — origem do trigger (humano, schedule, reaction,
|
|
314
|
+
* AI, etc). Permite rastreabilidade da cadeia de execução.
|
|
315
|
+
*/
|
|
316
|
+
provenance: Provenance
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* Actor derivado de provenance, mantido pra retrocompat.
|
|
320
|
+
* Em provenance de tipo `http`/`ai-agent`/`integration`, `actor.id` é
|
|
321
|
+
* derivado; em system/schedule/reaction sem chain, `actor.id` é null.
|
|
322
|
+
* `meta` vem populado pelo runtime com `name`/`email` do user resolvido
|
|
323
|
+
* (quando presentes) — sink não precisa re-resolver id→nome.
|
|
324
|
+
*/
|
|
325
|
+
actor: {
|
|
326
|
+
id: string | null
|
|
327
|
+
type?: 'user' | 'system' | 'integration'
|
|
328
|
+
meta?: Record<string, unknown>
|
|
329
|
+
}
|
|
330
|
+
tenant?: string | null
|
|
331
|
+
|
|
332
|
+
input: unknown
|
|
333
|
+
output?: unknown
|
|
334
|
+
error?: ActionError
|
|
335
|
+
|
|
336
|
+
severity: 'info' | 'warning' | 'error'
|
|
337
|
+
trace?: {
|
|
338
|
+
requestId?: string
|
|
339
|
+
parentActionId?: string
|
|
340
|
+
}
|
|
341
|
+
meta?: Record<string, unknown>
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* Configuração opcional de audit por action. Quando `audit: true` (default),
|
|
346
|
+
* captura tudo. Quando `audit: false`, desabilita. Quando objeto, customiza.
|
|
347
|
+
*/
|
|
348
|
+
export interface AuditConfig {
|
|
349
|
+
fields?: string[]
|
|
350
|
+
redact?: string[]
|
|
351
|
+
severity?: 'info' | 'warning' | 'error'
|
|
352
|
+
sink?: string
|
|
353
|
+
output?: boolean | { fields?: string[]; redact?: string[] }
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// =============================================================================
|
|
357
|
+
// 6. Eventos (§11)
|
|
358
|
+
// =============================================================================
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* Evento de domínio emitido pelo handler via `ctx.emit`. Shape preservado pelo
|
|
362
|
+
* `EventBusAdapter` ao publicar/distribuir.
|
|
363
|
+
*/
|
|
364
|
+
export interface DomainEvent<T = unknown> {
|
|
365
|
+
type: string
|
|
366
|
+
id: string
|
|
367
|
+
timestamp: string
|
|
368
|
+
actor: { id: string | null; type?: 'user' | 'system' | 'integration' }
|
|
369
|
+
tenant?: string | null
|
|
370
|
+
data: T
|
|
371
|
+
source: {
|
|
372
|
+
action: string
|
|
373
|
+
actionId: string
|
|
374
|
+
correlation?: string
|
|
375
|
+
}
|
|
376
|
+
meta?: Record<string, unknown>
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
// =============================================================================
|
|
380
|
+
// 7. Jobs / background (§10)
|
|
381
|
+
// =============================================================================
|
|
382
|
+
|
|
383
|
+
export type JobStatus = 'queued' | 'running' | 'done' | 'failed' | 'cancelled'
|
|
384
|
+
|
|
385
|
+
/**
|
|
386
|
+
* Handle retornado por background action. Cliente pola ou subscribe pra
|
|
387
|
+
* acompanhar status até `done`/`failed`.
|
|
388
|
+
*/
|
|
389
|
+
export interface JobHandle<T = unknown> {
|
|
390
|
+
jobId: string
|
|
391
|
+
action: string
|
|
392
|
+
status: JobStatus
|
|
393
|
+
progress?: { percent?: number; message?: string; data?: unknown }
|
|
394
|
+
data?: T
|
|
395
|
+
error?: ActionError
|
|
396
|
+
enqueuedAt: string
|
|
397
|
+
startedAt?: string
|
|
398
|
+
finishedAt?: string
|
|
399
|
+
attempts: number
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
/**
|
|
403
|
+
* Especificação enviada pra `QueueAdapter.enqueue`. Core constrói; adapter
|
|
404
|
+
* persiste e processa.
|
|
405
|
+
*/
|
|
406
|
+
export interface JobSpec {
|
|
407
|
+
jobId: string
|
|
408
|
+
action: string
|
|
409
|
+
input: unknown
|
|
410
|
+
ctx: {
|
|
411
|
+
userId: string | null
|
|
412
|
+
tenantId: string | null
|
|
413
|
+
requestId?: string
|
|
414
|
+
}
|
|
415
|
+
config: {
|
|
416
|
+
queue?: string
|
|
417
|
+
priority?: 'high' | 'normal' | 'low'
|
|
418
|
+
retry?: { attempts: number; backoff?: BackoffSpec }
|
|
419
|
+
timeout?: number
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
export type BackoffSpec =
|
|
424
|
+
| { kind: 'fixed'; delayMs: number }
|
|
425
|
+
| { kind: 'exponential'; initialMs: number; multiplier?: number; maxMs?: number }
|
|
426
|
+
|
|
427
|
+
/**
|
|
428
|
+
* Config de execução em background, declarada na action.
|
|
429
|
+
* Apenas `simple`/`form` aceitam.
|
|
430
|
+
*/
|
|
431
|
+
export interface BackgroundConfig {
|
|
432
|
+
enabled: true
|
|
433
|
+
queue?: string
|
|
434
|
+
priority?: 'high' | 'normal' | 'low'
|
|
435
|
+
retry?: { attempts: number; backoff?: BackoffSpec }
|
|
436
|
+
timeout?: number
|
|
437
|
+
progress?: boolean
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
/**
|
|
441
|
+
* Reporter injetado como 4º argumento do handler quando `background.progress: true`.
|
|
442
|
+
*/
|
|
443
|
+
export interface ProgressReporter {
|
|
444
|
+
report(p: {
|
|
445
|
+
percent?: number
|
|
446
|
+
message?: string
|
|
447
|
+
data?: unknown
|
|
448
|
+
}): Promise<void> | void
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
// =============================================================================
|
|
452
|
+
// 8. Tipos lógicos (§7) — metadata anexada
|
|
453
|
+
// =============================================================================
|
|
454
|
+
|
|
455
|
+
/**
|
|
456
|
+
* Metadata anexada por `t.*` constructors. Adapters consultam pra mapear tipo
|
|
457
|
+
* lógico → coluna DB / widget UI / validação extra.
|
|
458
|
+
*/
|
|
459
|
+
export interface LogicalTypeMeta {
|
|
460
|
+
logicalType: string
|
|
461
|
+
params?: Record<string, unknown>
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
// =============================================================================
|
|
465
|
+
// 9. Specs (§2)
|
|
466
|
+
// =============================================================================
|
|
467
|
+
|
|
468
|
+
export interface ConfirmSpec {
|
|
469
|
+
title: I18nRef
|
|
470
|
+
message?: I18nRef
|
|
471
|
+
destructive?: boolean
|
|
472
|
+
confirmLabel?: I18nRef
|
|
473
|
+
cancelLabel?: I18nRef
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
export interface RateLimitSpec {
|
|
477
|
+
window: number
|
|
478
|
+
max: number
|
|
479
|
+
key?: (ctx: ActionContext, input: unknown) => string
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
export interface AIConfig {
|
|
483
|
+
enabled?: boolean
|
|
484
|
+
description?: string
|
|
485
|
+
destructive?: boolean
|
|
486
|
+
requiresConfirmation?: boolean
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
export interface Example {
|
|
490
|
+
name: string
|
|
491
|
+
description?: string
|
|
492
|
+
input: unknown
|
|
493
|
+
output?: unknown
|
|
494
|
+
error?: unknown
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
export interface ActionMessages {
|
|
498
|
+
success?: I18nRef
|
|
499
|
+
error?: I18nRef
|
|
500
|
+
confirmation?: I18nRef
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
/**
|
|
504
|
+
* Origem de opções pra um campo (select/radio/lookup/autocomplete).
|
|
505
|
+
* Discriminated union por `kind`.
|
|
506
|
+
*/
|
|
507
|
+
export type OptionsSpec =
|
|
508
|
+
| { kind: 'static'; items: Array<{ value: string; label: I18nRef }> }
|
|
509
|
+
| { kind: 'dictionary'; ref: string }
|
|
510
|
+
| { kind: 'lookup'; source: string; depends?: string[] }
|
|
511
|
+
|
|
512
|
+
/**
|
|
513
|
+
* Descrição de campo em `FormAction.fields`. Drive UI rendering + AI tool
|
|
514
|
+
* description per-field.
|
|
515
|
+
*/
|
|
516
|
+
export interface FieldSpec {
|
|
517
|
+
label: I18nRef
|
|
518
|
+
placeholder?: I18nRef
|
|
519
|
+
hint?: I18nRef
|
|
520
|
+
/** Ajuda no hover/foco da label (ícone ⓘ + tooltip). `hint` = texto auxiliar SOB o campo;
|
|
521
|
+
* `help` = explicação mais longa, escondida atrás do ícone na label. */
|
|
522
|
+
help?: I18nRef
|
|
523
|
+
|
|
524
|
+
default?: unknown
|
|
525
|
+
mask?: string
|
|
526
|
+
|
|
527
|
+
depends?: string[]
|
|
528
|
+
showWhen?: (input: Record<string, unknown>) => boolean
|
|
529
|
+
requireWhen?: (input: Record<string, unknown>) => boolean
|
|
530
|
+
|
|
531
|
+
group?: string
|
|
532
|
+
order?: number
|
|
533
|
+
|
|
534
|
+
widget?: string
|
|
535
|
+
options?: OptionsSpec
|
|
536
|
+
|
|
537
|
+
aiDescription?: string
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
export type FilterOp =
|
|
541
|
+
| 'eq'
|
|
542
|
+
| 'neq'
|
|
543
|
+
| 'gt'
|
|
544
|
+
| 'gte'
|
|
545
|
+
| 'lt'
|
|
546
|
+
| 'lte'
|
|
547
|
+
| 'in'
|
|
548
|
+
| 'nin'
|
|
549
|
+
| 'contains'
|
|
550
|
+
| 'startsWith'
|
|
551
|
+
| 'endsWith'
|
|
552
|
+
| 'between'
|
|
553
|
+
| 'null'
|
|
554
|
+
| 'notNull'
|
|
555
|
+
|
|
556
|
+
/**
|
|
557
|
+
* Descrição de filtro em `ListAction.filters`. Mode `'server'` (default,
|
|
558
|
+
* filtra na query do DB) ou `'client'` (filtra no React adapter sobre
|
|
559
|
+
* `items` já carregados).
|
|
560
|
+
*/
|
|
561
|
+
export interface FilterSpec {
|
|
562
|
+
label: I18nRef
|
|
563
|
+
placeholder?: I18nRef
|
|
564
|
+
|
|
565
|
+
type: 'text' | 'select' | 'lookup' | 'date' | 'number'
|
|
566
|
+
multiple?: boolean
|
|
567
|
+
mode?: 'server' | 'client'
|
|
568
|
+
operators?: FilterOp[]
|
|
569
|
+
path?: string
|
|
570
|
+
|
|
571
|
+
/** Filtro AVANÇADO: sai da barra inline e vai pro modal "Filtros" (com contador de
|
|
572
|
+
* ativos no botão). Os não-avançados rendem inline na toolbar da listagem. */
|
|
573
|
+
advanced?: boolean
|
|
574
|
+
|
|
575
|
+
section?: string
|
|
576
|
+
depends?: string[]
|
|
577
|
+
|
|
578
|
+
options?: OptionsSpec
|
|
579
|
+
|
|
580
|
+
aiDescription?: string
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
/**
|
|
584
|
+
* Coluna declarativa de uma `ListAction` — a UI (ActionList) deriva a tabela
|
|
585
|
+
* daqui; células custom entram POR CIMA na UI (prop `cells`, chave = key). É a base
|
|
586
|
+
* do futuro column picker (o usuário escolher colunas): a lista completa vive no
|
|
587
|
+
* contrato, `hidden` marca as que nascem fora.
|
|
588
|
+
*/
|
|
589
|
+
export interface ListColumnSpec {
|
|
590
|
+
/** Chave do item (`Out`) que a coluna mostra. */
|
|
591
|
+
key: string
|
|
592
|
+
label: I18nRef
|
|
593
|
+
/** Render padrão: 'text' (default) · 'number' (alinha à direita) · 'date' (Intl) ·
|
|
594
|
+
* 'badge' (chip com o valor; dict/cores via célula custom por enquanto). */
|
|
595
|
+
type?: 'text' | 'number' | 'date' | 'badge'
|
|
596
|
+
/** Header clicável (asc ↔ desc). Convenção: a UI escreve `sort: '<key>:<dir>'` no
|
|
597
|
+
* input; o handler implementa o orderBy. */
|
|
598
|
+
sortable?: boolean
|
|
599
|
+
/** Coluna encolhe ao conteúdo (não expande). */
|
|
600
|
+
fit?: boolean
|
|
601
|
+
/** Nasce oculta (candidata ao column picker). */
|
|
602
|
+
hidden?: boolean
|
|
603
|
+
/** Formato quando `type: 'date'` (Intl.DateTimeFormatOptions). */
|
|
604
|
+
dateFormat?: Intl.DateTimeFormatOptions
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
export interface SortSpec {
|
|
608
|
+
field: string
|
|
609
|
+
dir: 'asc' | 'desc'
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
export interface PeriodSpec {
|
|
613
|
+
value: string
|
|
614
|
+
label: I18nRef
|
|
615
|
+
/** Preset inicial da listagem. Sem marcado, o PRIMEIRO da lista é o default —
|
|
616
|
+
* período é recorte obrigatório do caso de uso (não existe "sem período"). */
|
|
617
|
+
default?: boolean
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
export interface ExpandSpec {
|
|
621
|
+
description?: string
|
|
622
|
+
default?: boolean
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
// =============================================================================
|
|
626
|
+
// 10. Action — discriminated union (§2)
|
|
627
|
+
// =============================================================================
|
|
628
|
+
|
|
629
|
+
export type ActionKind = 'simple' | 'form' | 'list' | 'view'
|
|
630
|
+
|
|
631
|
+
export type AuthorizeFn<In, Loaded = Record<string, unknown>> = (
|
|
632
|
+
ctx: ActionContext,
|
|
633
|
+
input: In,
|
|
634
|
+
loaded?: Loaded,
|
|
635
|
+
) => boolean | ActionError | Promise<boolean | ActionError>
|
|
636
|
+
|
|
637
|
+
/**
|
|
638
|
+
* Authorize aceita string DSL ou closure.
|
|
639
|
+
*
|
|
640
|
+
* - **String DSL**: avaliada pelo runtime contra um contexto formado por
|
|
641
|
+
* `{ user, input, ctx, ...loaded }`. Resultado coercido pra boolean.
|
|
642
|
+
* Ex: `'user.kind == "employee" OR ticket.assigneeId == user.id'`.
|
|
643
|
+
* - **Closure (`AuthorizeFn`)**: escape hatch pra lógica que a DSL não
|
|
644
|
+
* cobre (acesso a serviços externos, side effects, etc).
|
|
645
|
+
*/
|
|
646
|
+
export type AuthorizeSpec<In, Loaded = Record<string, unknown>> =
|
|
647
|
+
| string
|
|
648
|
+
| AuthorizeFn<In, Loaded>
|
|
649
|
+
|
|
650
|
+
export type HandlerFn<In, Out, Loaded = Record<string, unknown>> = (
|
|
651
|
+
ctx: ActionContext,
|
|
652
|
+
input: In,
|
|
653
|
+
loaded?: Loaded,
|
|
654
|
+
progress?: ProgressReporter,
|
|
655
|
+
) => Out | Promise<Out>
|
|
656
|
+
|
|
657
|
+
export type LoaderFn<In> = (ctx: ActionContext, input: In) => Promise<unknown>
|
|
658
|
+
|
|
659
|
+
/**
|
|
660
|
+
* Resolver registrado via `Runtime.registerLoaders`. Mapeia entidade DSL
|
|
661
|
+
* (ex: `'ticket'`) → função que recebe args já resolvidos do input mais o
|
|
662
|
+
* `ActionContext` e retorna a entidade carregada (ou null/undefined → not_found).
|
|
663
|
+
*/
|
|
664
|
+
export type LoaderResolver = (
|
|
665
|
+
args: Record<string, unknown>,
|
|
666
|
+
ctx: ActionContext,
|
|
667
|
+
) => Promise<unknown> | unknown
|
|
668
|
+
|
|
669
|
+
/**
|
|
670
|
+
* Spec individual de load:
|
|
671
|
+
*
|
|
672
|
+
* - **String DSL** (`'ticket(:id)'`): runtime parsa via `parseLoad`, resolve
|
|
673
|
+
* args contra o input e delega ao `LoaderResolver` registrado pra entidade.
|
|
674
|
+
* - **Closure (`LoaderFn`)**: escape hatch que recebe `(ctx, input)` direto.
|
|
675
|
+
*/
|
|
676
|
+
export type LoadSpec<In> = string | LoaderFn<In>
|
|
677
|
+
|
|
678
|
+
/**
|
|
679
|
+
* Specs de loads de um action.
|
|
680
|
+
*
|
|
681
|
+
* - **Record nomeado** (`{ ticket: 'ticket(:id)', meta: async () => ... }`):
|
|
682
|
+
* chave = nome da propriedade em `loaded`; valor = string DSL ou closure.
|
|
683
|
+
* - **Array de strings** (`['ticket(:id)', 'shipment(:shipmentId)']`): cada
|
|
684
|
+
* string é parseada e a entidade vira a chave em `loaded` (ex: `loaded.ticket`).
|
|
685
|
+
*/
|
|
686
|
+
export type LoadsSpec<In> = Record<string, LoadSpec<In>> | string[]
|
|
687
|
+
|
|
688
|
+
/**
|
|
689
|
+
* Forma legada: `Record<string, LoaderFn<In>>`. Alias mantido pra
|
|
690
|
+
* retrocompat — código existente que importa `LoadersSpec` continua valendo,
|
|
691
|
+
* mas `LoadsSpec` é o novo nome (e aceita também string DSL).
|
|
692
|
+
*/
|
|
693
|
+
export type LoadersSpec<In> = Record<string, LoaderFn<In>>
|
|
694
|
+
|
|
695
|
+
/**
|
|
696
|
+
* Campos compartilhados por todos os kinds de action. Discriminator (`kind`)
|
|
697
|
+
* e campos kind-specific vivem nas variantes abaixo.
|
|
698
|
+
*
|
|
699
|
+
* Dois tipos de input, um schema:
|
|
700
|
+
*
|
|
701
|
+
* - **`In`** — o lado do FIO (o que o cliente envia, pré-parse). É o tipo
|
|
702
|
+
* que client/contracts/fields enxergam. Campo com `.default()` é opcional
|
|
703
|
+
* aqui.
|
|
704
|
+
* - **`ParsedIn`** — o lado PARSEADO (o output do schema de input, defaults
|
|
705
|
+
* aplicados). O runtime SEMPRE valida antes de executar, então `handler`,
|
|
706
|
+
* `mockHandler`, `authorize` e `loads` recebem `ParsedIn`.
|
|
707
|
+
*
|
|
708
|
+
* O default `ParsedIn = In` mantém retrocompat com anotações à mão de dois
|
|
709
|
+
* genéricos; `defineAction`/`defineContract` inferem os dois lados do schema.
|
|
710
|
+
*/
|
|
711
|
+
export interface ActionBase<In, Out, ParsedIn = In> {
|
|
712
|
+
// — Identidade —
|
|
713
|
+
name: string
|
|
714
|
+
kind: ActionKind
|
|
715
|
+
|
|
716
|
+
// — Documentação / Application Interface —
|
|
717
|
+
label?: I18nRef
|
|
718
|
+
title?: I18nRef
|
|
719
|
+
summary?: string
|
|
720
|
+
description?: string
|
|
721
|
+
icon?: string
|
|
722
|
+
color?: 'primary' | 'success' | 'warning' | 'danger' | 'neutral' | string
|
|
723
|
+
messages?: ActionMessages
|
|
724
|
+
tags?: string[]
|
|
725
|
+
examples?: Example[]
|
|
726
|
+
errors?: ErrorSpec[]
|
|
727
|
+
|
|
728
|
+
// — Contrato —
|
|
729
|
+
input: Schema<In, ParsedIn>
|
|
730
|
+
output: Schema<Out>
|
|
731
|
+
|
|
732
|
+
// — Autorização (fail-closed default) —
|
|
733
|
+
public?: boolean
|
|
734
|
+
requires?: string | string[]
|
|
735
|
+
authorize?: AuthorizeSpec<ParsedIn>
|
|
736
|
+
loads?: LoadsSpec<ParsedIn>
|
|
737
|
+
|
|
738
|
+
// — Execução —
|
|
739
|
+
handler: HandlerFn<ParsedIn, Out>
|
|
740
|
+
|
|
741
|
+
/**
|
|
742
|
+
* Handler alternativo executado quando `process.env.OPUS_MODE === 'design'`.
|
|
743
|
+
* Use pra prototipar UI com dados realistas sem precisar do backend pronto.
|
|
744
|
+
*
|
|
745
|
+
* Em modo design: runtime tenta `mockHandler` primeiro; se ausente, cai
|
|
746
|
+
* no `handler` normal (permitindo testar pontualmente real ao lado de
|
|
747
|
+
* mocks).
|
|
748
|
+
*
|
|
749
|
+
* Em modo default (sem env): `mockHandler` é ignorado. Útil pra apagar
|
|
750
|
+
* em revisão ou manter pro Designer reusar.
|
|
751
|
+
*
|
|
752
|
+
* Pattern recomendado pra ações que só têm mock durante dev:
|
|
753
|
+
*
|
|
754
|
+
* ```ts
|
|
755
|
+
* defineAction({
|
|
756
|
+
* name: 'ticket.list',
|
|
757
|
+
* // ...
|
|
758
|
+
* mockHandler: async () => MOCK_TICKETS,
|
|
759
|
+
* handler: () => {
|
|
760
|
+
* throw new Error('handler real ainda não implementado — use OPUS_MODE=design')
|
|
761
|
+
* },
|
|
762
|
+
* })
|
|
763
|
+
* ```
|
|
764
|
+
*/
|
|
765
|
+
mockHandler?: HandlerFn<ParsedIn, Out>
|
|
766
|
+
|
|
767
|
+
// — Comportamento —
|
|
768
|
+
audit?: boolean | AuditConfig
|
|
769
|
+
confirm?: ConfirmSpec
|
|
770
|
+
/** Avaliada no CLIENTE com o input do fio (pré-parse) — por isso `In`. */
|
|
771
|
+
invalidates?: string[] | ((input: In) => string[])
|
|
772
|
+
rateLimit?: RateLimitSpec
|
|
773
|
+
/** Server-side (pós-validação) — recebe o parseado. */
|
|
774
|
+
idempotency?: (input: ParsedIn) => string
|
|
775
|
+
|
|
776
|
+
// — Integração —
|
|
777
|
+
ai?: boolean | AIConfig
|
|
778
|
+
automatable?: boolean
|
|
779
|
+
internal?: boolean
|
|
780
|
+
successStatus?: number
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
export interface SimpleAction<In, Out, ParsedIn = In> extends ActionBase<In, Out, ParsedIn> {
|
|
784
|
+
kind: 'simple'
|
|
785
|
+
background?: BackgroundConfig
|
|
786
|
+
emits?: string[]
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
export interface FormAction<In extends Record<string, unknown>, Out, ParsedIn = In>
|
|
790
|
+
extends ActionBase<In, Out, ParsedIn> {
|
|
791
|
+
kind: 'form'
|
|
792
|
+
// Parcial: `fields` descreve as chaves RENDERÁVEIS do input — nem toda chave precisa
|
|
793
|
+
// de UI (ex.: `id` num update, ou campos derivados no servidor). O que não está aqui
|
|
794
|
+
// não é renderizado (mas segue no input, via defaultValues/submit).
|
|
795
|
+
fields: Partial<Record<keyof In & string, FieldSpec>>
|
|
796
|
+
background?: BackgroundConfig
|
|
797
|
+
emits?: string[]
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
export interface SearchFilters {
|
|
801
|
+
/** Colunas declarativas da listagem (a UI deriva a tabela; ver ListColumnSpec). */
|
|
802
|
+
columns?: ListColumnSpec[]
|
|
803
|
+
filters?: Record<string, FilterSpec>
|
|
804
|
+
sort?: { fields: string[]; default?: SortSpec[] }
|
|
805
|
+
paginate?: 'cursor' | 'offset' | false
|
|
806
|
+
text?: { fields: string[]; placeholder?: I18nRef }
|
|
807
|
+
periods?: PeriodSpec[]
|
|
808
|
+
/**
|
|
809
|
+
* Cláusula DSL aplicada **sempre** pelo repository do consumer (ex:
|
|
810
|
+
* `'NOT archived'`). Runtime NÃO executa SQL — quem consome a search
|
|
811
|
+
* compila via `compileWhere` do `@softize/opus/dsl` no momento de
|
|
812
|
+
* montar a query.
|
|
813
|
+
*
|
|
814
|
+
* Documentação que entra no manifest (próxima fase) e sinaliza intent
|
|
815
|
+
* de baseline imutável de filtragem.
|
|
816
|
+
*/
|
|
817
|
+
where?: string
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
/**
|
|
821
|
+
* Em `ListAction`, `output` descreve o **item** (não o envelope).
|
|
822
|
+
* Handler retorna `Paginated<Out>`. Runtime não envelopa nem valida o
|
|
823
|
+
* envelope — handler é fonte de verdade.
|
|
824
|
+
*/
|
|
825
|
+
export interface ListAction<In, Out, ParsedIn = In>
|
|
826
|
+
extends Omit<ActionBase<In, Out, ParsedIn>, 'handler' | 'mockHandler'> {
|
|
827
|
+
kind: 'list'
|
|
828
|
+
handler: (
|
|
829
|
+
ctx: ActionContext,
|
|
830
|
+
input: ParsedIn,
|
|
831
|
+
loaded?: Record<string, unknown>,
|
|
832
|
+
) => Paginated<Out> | Promise<Paginated<Out>>
|
|
833
|
+
/**
|
|
834
|
+
* Mesma semântica de `ActionBase.mockHandler`, mas devolve `Paginated<Out>`
|
|
835
|
+
* (search retorna lista paginada, não item solto).
|
|
836
|
+
*/
|
|
837
|
+
mockHandler?: (
|
|
838
|
+
ctx: ActionContext,
|
|
839
|
+
input: ParsedIn,
|
|
840
|
+
loaded?: Record<string, unknown>,
|
|
841
|
+
) => Paginated<Out> | Promise<Paginated<Out>>
|
|
842
|
+
/** Colunas declarativas da listagem (a UI deriva a tabela; ver ListColumnSpec). */
|
|
843
|
+
columns?: ListColumnSpec[]
|
|
844
|
+
filters?: Record<string, FilterSpec>
|
|
845
|
+
sort?: { fields: string[]; default?: SortSpec[] }
|
|
846
|
+
paginate?: 'cursor' | 'offset' | false
|
|
847
|
+
text?: { fields: string[]; placeholder?: I18nRef }
|
|
848
|
+
periods?: PeriodSpec[]
|
|
849
|
+
/**
|
|
850
|
+
* Ver `SearchFilters.where`. Baseline DSL aplicado pelo repository
|
|
851
|
+
* via `compileWhere` (do `@softize/opus/dsl`). Runtime não executa SQL.
|
|
852
|
+
*/
|
|
853
|
+
where?: string
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
export interface ViewAction<In, Out, ParsedIn = In> extends ActionBase<In, Out, ParsedIn> {
|
|
857
|
+
kind: 'view'
|
|
858
|
+
/** Entidades relacionadas projetáveis. View simples não projeta nada — omita. */
|
|
859
|
+
projection?: string[]
|
|
860
|
+
expand?: Record<string, ExpandSpec>
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
/**
|
|
864
|
+
* Union completa de actions. Usada em runtime/adapters como "qualquer action";
|
|
865
|
+
* pra inferência precisa do subtipo, use o tipo concreto (SimpleAction, etc).
|
|
866
|
+
*
|
|
867
|
+
* `any` é proposital aqui — union de tipos genéricos com `unknown` quebra
|
|
868
|
+
* narrowing (porque `unknown` é supertipo, não subtipo). `defineAction` é
|
|
869
|
+
* generic e preserva o tipo exato passado.
|
|
870
|
+
*/
|
|
871
|
+
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
|
|
872
|
+
export type ActionDef =
|
|
873
|
+
| SimpleAction<any, any>
|
|
874
|
+
| FormAction<any, any>
|
|
875
|
+
| ListAction<any, any>
|
|
876
|
+
| ViewAction<any, any>
|
|
877
|
+
|
|
878
|
+
// =============================================================================
|
|
879
|
+
// 11. Reaction (§12)
|
|
880
|
+
// =============================================================================
|
|
881
|
+
|
|
882
|
+
export type ReactionHandlerFn<Event = unknown> = (
|
|
883
|
+
ctx: ReactionContext,
|
|
884
|
+
event: DomainEvent<Event>,
|
|
885
|
+
) => Promise<void> | void
|
|
886
|
+
|
|
887
|
+
/**
|
|
888
|
+
* Declaração de listener de evento. Auto-registrada pelo `Runtime` no
|
|
889
|
+
* `EventBusAdapter` no `start()`. Ver §12.
|
|
890
|
+
*/
|
|
891
|
+
export interface ReactionDef<Event = unknown> {
|
|
892
|
+
name: string
|
|
893
|
+
on: string | string[]
|
|
894
|
+
|
|
895
|
+
description?: string
|
|
896
|
+
tags?: string[]
|
|
897
|
+
|
|
898
|
+
handler: ReactionHandlerFn<Event>
|
|
899
|
+
|
|
900
|
+
retry?: { attempts: number; backoff?: BackoffSpec }
|
|
901
|
+
timeout?: number
|
|
902
|
+
dedup?: (event: DomainEvent<Event>) => string
|
|
903
|
+
concurrency?: number
|
|
904
|
+
|
|
905
|
+
authorize?: (
|
|
906
|
+
ctx: ReactionContext,
|
|
907
|
+
event: DomainEvent<Event>,
|
|
908
|
+
) => boolean | Promise<boolean>
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
// =============================================================================
|
|
912
|
+
// 12. Adapters (§8)
|
|
913
|
+
// =============================================================================
|
|
914
|
+
|
|
915
|
+
export type AdapterKind =
|
|
916
|
+
| 'server'
|
|
917
|
+
| 'data'
|
|
918
|
+
| 'auth'
|
|
919
|
+
| 'audit'
|
|
920
|
+
| 'logger'
|
|
921
|
+
| 'queue'
|
|
922
|
+
| 'eventbus'
|
|
923
|
+
| 'scheduler'
|
|
924
|
+
| 'client'
|
|
925
|
+
| 'storage'
|
|
926
|
+
| 'ai'
|
|
927
|
+
| 'ui'
|
|
928
|
+
| 'schema'
|
|
929
|
+
|
|
930
|
+
/**
|
|
931
|
+
* Resultado de health check de um adapter (ou do runtime agregado).
|
|
932
|
+
*/
|
|
933
|
+
export interface HealthStatus {
|
|
934
|
+
ok: boolean
|
|
935
|
+
details?: Record<string, unknown>
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
/**
|
|
939
|
+
* Interface base de adapter. Todos os adapters implementam `name`/`kind`;
|
|
940
|
+
* `init`/`dispose`/`healthCheck` são opcionais.
|
|
941
|
+
*/
|
|
942
|
+
export interface Adapter {
|
|
943
|
+
name: string
|
|
944
|
+
kind: AdapterKind
|
|
945
|
+
init?: (runtime: RuntimeRef) => Promise<void> | void
|
|
946
|
+
dispose?: () => Promise<void> | void
|
|
947
|
+
healthCheck?: () => Promise<HealthStatus>
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
/**
|
|
951
|
+
* Contexto mínimo que o caller (server adapter, etc) entrega ao runtime
|
|
952
|
+
* ao invocar uma action. Auth adapter resolveContext() produz isso.
|
|
953
|
+
*/
|
|
954
|
+
export interface ContextBase {
|
|
955
|
+
user: User | null
|
|
956
|
+
tenantId: string | null
|
|
957
|
+
can: CanFn
|
|
958
|
+
/**
|
|
959
|
+
* Provenance do trigger. Server adapter passa `{ kind: 'http', ... }`;
|
|
960
|
+
* schedule fires com `{ kind: 'schedule', ... }`; runtime preenche
|
|
961
|
+
* `{ kind: 'reaction', ... }` ao disparar reactions. Default
|
|
962
|
+
* `{ kind: 'system', source: 'unknown' }` se caller não passa.
|
|
963
|
+
*/
|
|
964
|
+
provenance?: Provenance
|
|
965
|
+
meta?: Record<string, unknown>
|
|
966
|
+
requestId?: string
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
/**
|
|
970
|
+
* Referência ao runtime exposta pra adapters durante `init`.
|
|
971
|
+
* Adapters usam pra invocar actions e acessar auth/healthCheck.
|
|
972
|
+
*
|
|
973
|
+
* Mantemos as definições mínimas necessárias aqui (não a classe Runtime
|
|
974
|
+
* inteira) pra evitar dependência circular com `runtime.ts`.
|
|
975
|
+
*/
|
|
976
|
+
export interface RuntimeRef {
|
|
977
|
+
readonly config: RuntimeConfig
|
|
978
|
+
readonly log: Logger
|
|
979
|
+
readonly auth: AuthAdapter | undefined
|
|
980
|
+
execute(
|
|
981
|
+
actionName: string,
|
|
982
|
+
input: unknown,
|
|
983
|
+
ctx: ContextBase,
|
|
984
|
+
): Promise<ActionResult<unknown>>
|
|
985
|
+
healthCheck(): Promise<{
|
|
986
|
+
ok: boolean
|
|
987
|
+
adapters: Record<string, HealthStatus>
|
|
988
|
+
}>
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
// — Endpoint specs (server adapter) ——————————————————————————————————————————
|
|
992
|
+
|
|
993
|
+
export interface LogsEndpointSpec {
|
|
994
|
+
enabled: boolean
|
|
995
|
+
path?: string
|
|
996
|
+
auth: string | string[]
|
|
997
|
+
bufferSize?: number
|
|
998
|
+
source?: (query: LogQuery) => Promise<LogEntry[]>
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
export interface AuditEndpointSpec {
|
|
1002
|
+
enabled: boolean
|
|
1003
|
+
path?: string
|
|
1004
|
+
auth: string | string[]
|
|
1005
|
+
source?: (query: AuditQuery) => Promise<AuditRecord[]>
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
export interface EndpointSpec {
|
|
1009
|
+
health?: boolean | { path?: string }
|
|
1010
|
+
ready?: boolean | { path?: string }
|
|
1011
|
+
openapi?: boolean | { path?: string; ui?: boolean }
|
|
1012
|
+
actions?: boolean | { path?: string; auth?: string | string[] }
|
|
1013
|
+
reactions?: boolean | { path?: string; auth?: string | string[] }
|
|
1014
|
+
logs?: boolean | LogsEndpointSpec
|
|
1015
|
+
audit?: boolean | AuditEndpointSpec
|
|
1016
|
+
}
|
|
1017
|
+
|
|
1018
|
+
export interface LogQuery {
|
|
1019
|
+
level?: 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'fatal'
|
|
1020
|
+
since?: string
|
|
1021
|
+
until?: string
|
|
1022
|
+
action?: string
|
|
1023
|
+
tenant?: string
|
|
1024
|
+
requestId?: string
|
|
1025
|
+
userId?: string
|
|
1026
|
+
limit?: number
|
|
1027
|
+
cursor?: string
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
export interface LogEntry {
|
|
1031
|
+
timestamp: string
|
|
1032
|
+
level: 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'fatal'
|
|
1033
|
+
msg: string
|
|
1034
|
+
meta?: Record<string, unknown>
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
export interface AuditQuery {
|
|
1038
|
+
action?: string
|
|
1039
|
+
actorId?: string
|
|
1040
|
+
tenant?: string
|
|
1041
|
+
outcome?: 'success' | 'error'
|
|
1042
|
+
since?: string
|
|
1043
|
+
until?: string
|
|
1044
|
+
limit?: number
|
|
1045
|
+
cursor?: string
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
// — Adapter interfaces específicas ————————————————————————————————————————————
|
|
1049
|
+
|
|
1050
|
+
export interface ServerAdapter extends Adapter {
|
|
1051
|
+
kind: 'server'
|
|
1052
|
+
mount(action: ActionDef): void
|
|
1053
|
+
mountEndpoints(spec: EndpointSpec): void
|
|
1054
|
+
}
|
|
1055
|
+
|
|
1056
|
+
export interface DataAdapter extends Adapter {
|
|
1057
|
+
kind: 'data'
|
|
1058
|
+
contextExtension(): { db: unknown }
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
export interface AuthAdapter extends Adapter {
|
|
1062
|
+
kind: 'auth'
|
|
1063
|
+
resolveContext(req: unknown): Promise<{
|
|
1064
|
+
user: User | null
|
|
1065
|
+
tenantId?: string | null
|
|
1066
|
+
can: CanFn
|
|
1067
|
+
}>
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
export interface AuditSink extends Adapter {
|
|
1071
|
+
kind: 'audit'
|
|
1072
|
+
emit(record: AuditRecord): Promise<void> | void
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
export interface LoggerAdapter extends Adapter, Logger {
|
|
1076
|
+
kind: 'logger'
|
|
1077
|
+
}
|
|
1078
|
+
|
|
1079
|
+
export interface QueueAdapter extends Adapter {
|
|
1080
|
+
kind: 'queue'
|
|
1081
|
+
enqueue(spec: JobSpec): Promise<JobHandle>
|
|
1082
|
+
status(jobId: string): Promise<JobHandle | null>
|
|
1083
|
+
cancel(jobId: string): Promise<boolean>
|
|
1084
|
+
subscribe?(jobId: string, listener: (h: JobHandle) => void): Unsubscribe
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
export interface EventBusAdapter extends Adapter {
|
|
1088
|
+
kind: 'eventbus'
|
|
1089
|
+
publish(event: DomainEvent): Promise<void> | void
|
|
1090
|
+
subscribe?(
|
|
1091
|
+
pattern: string,
|
|
1092
|
+
listener: (event: DomainEvent) => void,
|
|
1093
|
+
): Unsubscribe
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
/** Opções do put de storage. `contentType` viaja pro driver (S3 grava; fs anota). */
|
|
1097
|
+
export interface StoragePutOptions {
|
|
1098
|
+
contentType?: string
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1101
|
+
/** Objeto lido do storage: bytes + o contentType quando o driver o conhece. */
|
|
1102
|
+
export interface StorageObject {
|
|
1103
|
+
data: Uint8Array
|
|
1104
|
+
contentType?: string
|
|
1105
|
+
}
|
|
1106
|
+
|
|
1107
|
+
/**
|
|
1108
|
+
* EXPERIMENTAL — storage de objetos (arquivos): a superfície MÍNIMA que backoffice
|
|
1109
|
+
* precisa (upload, download, remoção, URL de acesso). `key` é caminho lógico
|
|
1110
|
+
* (`clientes/123/contrato.pdf`) — o driver resolve onde mora. Nasceu sem consumidor
|
|
1111
|
+
* interno (jul/2026): a superfície cresce por reincidência, não por especulação
|
|
1112
|
+
* (streams, list e metadata ricos entram quando um caso real os cobrar).
|
|
1113
|
+
*/
|
|
1114
|
+
export interface StorageAdapter extends Adapter {
|
|
1115
|
+
kind: 'storage'
|
|
1116
|
+
/** Grava (cria ou sobrescreve) o objeto. */
|
|
1117
|
+
put(key: string, data: Uint8Array, opts?: StoragePutOptions): Promise<void>
|
|
1118
|
+
/** Lê o objeto; null quando não existe. */
|
|
1119
|
+
get(key: string): Promise<StorageObject | null>
|
|
1120
|
+
/** Remove. Idempotente: remover o que não existe não é erro. */
|
|
1121
|
+
delete(key: string): Promise<void>
|
|
1122
|
+
/** URL de acesso (assinada/expirável no S3; `baseUrl` estática no fs). */
|
|
1123
|
+
url(key: string, opts?: { expiresInSeconds?: number }): Promise<string>
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
/** Opções por chamada do `AiAdapter` — tudo tem default do driver. */
|
|
1127
|
+
export interface AiCompleteOptions {
|
|
1128
|
+
/** System prompt da chamada. */
|
|
1129
|
+
system?: string
|
|
1130
|
+
/** Modelo (id do provider); default do driver. */
|
|
1131
|
+
model?: string
|
|
1132
|
+
/** Teto de tokens de saída; default do driver. */
|
|
1133
|
+
maxTokens?: number
|
|
1134
|
+
temperature?: number
|
|
1135
|
+
}
|
|
1136
|
+
|
|
1137
|
+
/**
|
|
1138
|
+
* EXPERIMENTAL — capability de IA generativa: a superfície MÍNIMA que backoffice
|
|
1139
|
+
* precisa (completar texto e extrair dado estruturado). O diferencial do protocolo
|
|
1140
|
+
* é o `extract`: o MESMO `Schema` das actions vira o contrato da resposta do modelo
|
|
1141
|
+
* — o driver força saída estruturada e valida o retorno pelo schema antes de
|
|
1142
|
+
* devolver. Chega nos handlers via `ctx.ai` (null quando não configurado).
|
|
1143
|
+
* Superfície cresce por reincidência (streaming, chat multi-turn, tools livres
|
|
1144
|
+
* entram quando um caso real os cobrar).
|
|
1145
|
+
*/
|
|
1146
|
+
export interface AiAdapter extends Adapter {
|
|
1147
|
+
kind: 'ai'
|
|
1148
|
+
/** Prompt de texto → resposta de texto. */
|
|
1149
|
+
complete(prompt: string, opts?: AiCompleteOptions): Promise<string>
|
|
1150
|
+
/** Prompt de texto → objeto VALIDADO pelo schema (saída estruturada). */
|
|
1151
|
+
extract<T>(prompt: string, schema: Schema<T>, opts?: AiCompleteOptions): Promise<T>
|
|
1152
|
+
/**
|
|
1153
|
+
* Loop AGÊNTICO (tool-use multi-turn): o modelo escolhe uma tool → o `execute` injetado
|
|
1154
|
+
* roda a action → o resultado volta pro modelo → repete até ele responder em texto (ou
|
|
1155
|
+
* bater `maxSteps`). `tools` e `execute` vêm de FORA — o driver NÃO conhece o registry
|
|
1156
|
+
* nem o runtime (DI); a cola que os monta a partir das actions `ai:enabled` mora no runtime.
|
|
1157
|
+
* OPCIONAL: driver sem `run` só não oferece o loop (complete/extract seguem).
|
|
1158
|
+
*/
|
|
1159
|
+
run?(input: string | AiMessage[], opts: AiRunOptions): Promise<AiRunResult>
|
|
1160
|
+
/**
|
|
1161
|
+
* Variante em STREAMING do `run` — mesmo loop, emitindo `ChatEvent` conforme acontece
|
|
1162
|
+
* (tool em uso, texto incremental, fim). OPCIONAL e aditiva: driver sem `runStream`
|
|
1163
|
+
* segue válido; quem consome decide o modo pelo que o driver oferece.
|
|
1164
|
+
*/
|
|
1165
|
+
runStream?(input: string | AiMessage[], opts: AiRunOptions): AsyncIterable<ChatEvent>
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1168
|
+
/** Uma tool exposta ao modelo: nome da action + descrição + JSON Schema do input. */
|
|
1169
|
+
export interface AiTool {
|
|
1170
|
+
name: string
|
|
1171
|
+
description: string
|
|
1172
|
+
inputSchema: unknown
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
/** Turno de conversa (histórico multi-turn do chat). */
|
|
1176
|
+
export interface AiMessage {
|
|
1177
|
+
role: 'user' | 'assistant'
|
|
1178
|
+
content: string
|
|
1179
|
+
}
|
|
1180
|
+
|
|
1181
|
+
/** Uma opção de resposta de uma pergunta de esclarecimento (`ask_user`). */
|
|
1182
|
+
export interface AskOption {
|
|
1183
|
+
label: string
|
|
1184
|
+
description?: string
|
|
1185
|
+
}
|
|
1186
|
+
|
|
1187
|
+
/** Uma pergunta que o agente faz À PESSOA no meio do turno, com opções. Espelha o
|
|
1188
|
+
* AskUserQuestion do Claude Code — mas servida por um round-trip REAL (ver `onAsk`), então
|
|
1189
|
+
* não volta vazia como o embutido faz em modo headless. */
|
|
1190
|
+
export interface AskQuestion {
|
|
1191
|
+
question: string
|
|
1192
|
+
header?: string
|
|
1193
|
+
multiSelect?: boolean
|
|
1194
|
+
options: AskOption[]
|
|
1195
|
+
}
|
|
1196
|
+
|
|
1197
|
+
/** Resposta de UMA pergunta: os rótulos escolhidos + um texto livre opcional. */
|
|
1198
|
+
export interface AskAnswer {
|
|
1199
|
+
header?: string
|
|
1200
|
+
selected: string[]
|
|
1201
|
+
text?: string
|
|
1202
|
+
}
|
|
1203
|
+
|
|
1204
|
+
export interface AiRunOptions extends AiCompleteOptions {
|
|
1205
|
+
/** Tools disponíveis (o runtime deriva das actions `ai:enabled`). */
|
|
1206
|
+
tools: AiTool[]
|
|
1207
|
+
/** Executa a tool escolhida (o runtime liga na action, com o contexto de quem chamou).
|
|
1208
|
+
* Devolve o resultado — ou um objeto de erro — que volta pro modelo. */
|
|
1209
|
+
execute: (name: string, input: unknown) => Promise<unknown> | unknown
|
|
1210
|
+
/** Teto de iterações do loop (default 8). */
|
|
1211
|
+
maxSteps?: number
|
|
1212
|
+
/** Round-trip de "perguntar à pessoa" (elicitação): se presente, o driver INTERCEPTA a tool
|
|
1213
|
+
* reservada `ask_user` (injetada automaticamente) e chama isto, devolvendo as respostas ao
|
|
1214
|
+
* modelo. AUSENTE = sem canal interativo: o driver RECUSA a `ask_user` com um erro acionável
|
|
1215
|
+
* (pergunte em texto e siga) em vez de deixar a chamada morrer no vazio — a pegadinha do
|
|
1216
|
+
* headless. Guarda QUALQUER consumidor por construção. */
|
|
1217
|
+
onAsk?: (questions: AskQuestion[]) => Promise<AskAnswer[]> | AskAnswer[]
|
|
1218
|
+
}
|
|
1219
|
+
|
|
1220
|
+
export interface AiRunResult {
|
|
1221
|
+
/** Resposta final em texto. */
|
|
1222
|
+
text: string
|
|
1223
|
+
/** Iterações que o loop rodou. */
|
|
1224
|
+
steps: number
|
|
1225
|
+
/** Tools chamadas (observability). */
|
|
1226
|
+
calls: Array<{ name: string; input: unknown }>
|
|
1227
|
+
}
|
|
1228
|
+
|
|
1229
|
+
/**
|
|
1230
|
+
* Evento de conversa — o protocolo que o loop agêntico em streaming emite e o `<Chat>`
|
|
1231
|
+
* consome (docs/chat-event-protocol.md). O mínimo que os casos reais cobraram:
|
|
1232
|
+
* texto incremental, tool em uso (dado cru; humanizar é apresentação), artefato
|
|
1233
|
+
* produzido na conversa e fim de turno. O request/response clássico é o caso
|
|
1234
|
+
* degenerado: `Promise<string>` ≡ um `text` + um `done`.
|
|
1235
|
+
*/
|
|
1236
|
+
export type ChatEvent =
|
|
1237
|
+
| { type: 'text'; delta: string }
|
|
1238
|
+
| { type: 'tool'; name: string; detail?: string }
|
|
1239
|
+
| { type: 'artifact'; kind: string; ref: string; title?: string }
|
|
1240
|
+
| { type: 'done'; ok: boolean; error?: string }
|
|
1241
|
+
|
|
1242
|
+
/**
|
|
1243
|
+
* Adapter de IA já LIGADO ao runtime — o que chega em `ctx.ai`. O `run` sai com as tools
|
|
1244
|
+
* (actions `ai:enabled`) e o execute-como-usuário PRÉ-injetados; `complete`/`extract`
|
|
1245
|
+
* passam direto pro driver.
|
|
1246
|
+
*/
|
|
1247
|
+
export interface BoundAi {
|
|
1248
|
+
complete(prompt: string, opts?: AiCompleteOptions): Promise<string>
|
|
1249
|
+
extract<T>(prompt: string, schema: Schema<T>, opts?: AiCompleteOptions): Promise<T>
|
|
1250
|
+
run(input: string | AiMessage[], opts?: BoundAiRunOptions): Promise<AiRunResult>
|
|
1251
|
+
/** Variante em streaming do `run` (mesmas tools/execute/confirm); exige driver com `runStream`. */
|
|
1252
|
+
runStream(input: string | AiMessage[], opts?: BoundAiRunOptions): AsyncIterable<ChatEvent>
|
|
1253
|
+
}
|
|
1254
|
+
|
|
1255
|
+
export interface BoundAiRunOptions extends AiCompleteOptions {
|
|
1256
|
+
maxSteps?: number
|
|
1257
|
+
/** Aprovação pras actions `destructive`/`requiresConfirmation`. Sem ela, essas actions
|
|
1258
|
+
* são RECUSADAS (o modelo avisa o usuário) — nada destrutivo sem o de-acordo. */
|
|
1259
|
+
confirm?: (call: { name: string; input: unknown }) => boolean | Promise<boolean>
|
|
1260
|
+
/** Elicitação: round-trip de perguntar à pessoa (ver `AiRunOptions.onAsk`). Passa direto
|
|
1261
|
+
* pro driver pelo spread de `runOptions` no bind. */
|
|
1262
|
+
onAsk?: (questions: AskQuestion[]) => Promise<AskAnswer[]> | AskAnswer[]
|
|
1263
|
+
}
|
|
1264
|
+
|
|
1265
|
+
/**
|
|
1266
|
+
* Schedule declarativo. Tempo dispara o trigger; runtime executa a action
|
|
1267
|
+
* referenciada com provenance `{ kind: 'schedule', scheduleName }`.
|
|
1268
|
+
*
|
|
1269
|
+
* Especifica timing via UM destes campos (exclusivo):
|
|
1270
|
+
* - `cron`: expressão cron padrão (5 ou 6 campos)
|
|
1271
|
+
* - `every`: intervalo simples ('1h', '30m', '15s', etc — ISO 8601 duration ou shorthand)
|
|
1272
|
+
*
|
|
1273
|
+
* Action referenciada deve existir no registry. Input dinâmico via função
|
|
1274
|
+
* (chamada toda vez que schedule dispara) ou estático.
|
|
1275
|
+
*/
|
|
1276
|
+
export interface ScheduleDef {
|
|
1277
|
+
name: string
|
|
1278
|
+
action: string
|
|
1279
|
+
|
|
1280
|
+
/** Cron expression — ex: '0 9 * * *' (9am daily). */
|
|
1281
|
+
cron?: string
|
|
1282
|
+
/** Shorthand interval — ex: '1h', '30m', '15s'. */
|
|
1283
|
+
every?: string
|
|
1284
|
+
/** Timezone IANA — ex: 'America/Sao_Paulo'. Default: UTC. */
|
|
1285
|
+
timezone?: string
|
|
1286
|
+
|
|
1287
|
+
/** Input passado ao runtime.execute(); estático ou dinâmico. */
|
|
1288
|
+
input?: unknown | (() => unknown | Promise<unknown>)
|
|
1289
|
+
|
|
1290
|
+
/** Habilitação opt-in/out. Default true. */
|
|
1291
|
+
enabled?: boolean
|
|
1292
|
+
|
|
1293
|
+
/** Documentação. */
|
|
1294
|
+
description?: string
|
|
1295
|
+
tags?: string[]
|
|
1296
|
+
}
|
|
1297
|
+
|
|
1298
|
+
/**
|
|
1299
|
+
* Adapter que materializa schedules — registra triggers temporais e chama
|
|
1300
|
+
* o runtime quando o tempo bate.
|
|
1301
|
+
*
|
|
1302
|
+
* Drivers: `@softize/opus/scheduler/node-cron` (in-process), futuros: `/bullmq`
|
|
1303
|
+
* (Redis-backed repeatable jobs), `/temporal`, etc.
|
|
1304
|
+
*/
|
|
1305
|
+
export interface SchedulerAdapter extends Adapter {
|
|
1306
|
+
kind: 'scheduler'
|
|
1307
|
+
/**
|
|
1308
|
+
* Registra um schedule e retorna função pra cancelar. Chamado pelo
|
|
1309
|
+
* runtime no `start()` pra cada schedule.
|
|
1310
|
+
*
|
|
1311
|
+
* Quando o tempo bater, o adapter chama `fire()` que internamente
|
|
1312
|
+
* invoca runtime.execute() com provenance schedule.
|
|
1313
|
+
*/
|
|
1314
|
+
register(spec: ScheduleDef, fire: () => Promise<void> | void): Unsubscribe
|
|
1315
|
+
}
|
|
1316
|
+
|
|
1317
|
+
export interface ClientAdapter extends Adapter {
|
|
1318
|
+
kind: 'client'
|
|
1319
|
+
run<T>(action: ActionDef, input: unknown): Promise<ActionResult<T>>
|
|
1320
|
+
}
|
|
1321
|
+
|
|
1322
|
+
// =============================================================================
|
|
1323
|
+
// 13. RuntimeConfig (§14)
|
|
1324
|
+
// =============================================================================
|
|
1325
|
+
|
|
1326
|
+
/**
|
|
1327
|
+
* Behavior knobs do runtime. Recebe valores literais — core nunca lê `process.env`.
|
|
1328
|
+
* Defaults aplicados quando campo ausente. Ver §14.
|
|
1329
|
+
*/
|
|
1330
|
+
export interface RuntimeConfig {
|
|
1331
|
+
// — Modos de falha —
|
|
1332
|
+
auditMode?: 'lenient' | 'strict'
|
|
1333
|
+
emitMode?: 'lenient' | 'strict'
|
|
1334
|
+
|
|
1335
|
+
// — Ambiente —
|
|
1336
|
+
env?: 'development' | 'production' | 'test'
|
|
1337
|
+
dev?: boolean
|
|
1338
|
+
|
|
1339
|
+
// — Validação —
|
|
1340
|
+
validateOutputInDev?: boolean
|
|
1341
|
+
warnUndeclaredEmits?: boolean
|
|
1342
|
+
|
|
1343
|
+
// — Resiliência —
|
|
1344
|
+
dedupCacheTtl?: number
|
|
1345
|
+
defaultRetry?: { attempts: number; backoff?: BackoffSpec }
|
|
1346
|
+
|
|
1347
|
+
// — i18n —
|
|
1348
|
+
i18n?: {
|
|
1349
|
+
resolver?: (ref: I18nRef, locale?: string) => string
|
|
1350
|
+
defaultLocale?: string
|
|
1351
|
+
}
|
|
1352
|
+
|
|
1353
|
+
// — Limites —
|
|
1354
|
+
maxConcurrentActions?: number
|
|
1355
|
+
maxConcurrentReactions?: number
|
|
1356
|
+
}
|