@softize/opus 13.0.0 → 14.0.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.
Files changed (164) hide show
  1. package/CHANGELOG.md +81 -0
  2. package/PROMOTED.md +46 -0
  3. package/README.md +28 -19
  4. package/bin/cli.mjs +87 -216
  5. package/bin/lib/cli-shared.mjs +131 -0
  6. package/bin/lib/copy.mjs +276 -6
  7. package/bin/lib/db.mjs +16 -74
  8. package/bin/lib/gen-openapi.mjs +3 -3
  9. package/bin/lib/gen-runner.mjs +1 -1
  10. package/bin/lib/gen.mjs +14 -69
  11. package/bin/lib/mcp.mjs +3 -1
  12. package/bin/lib/seed.mjs +5 -62
  13. package/docs/code-style.md +4 -1
  14. package/docs/ownership-vs-shadcn-lock.md +2 -3
  15. package/docs/protocol.md +7 -7
  16. package/docs/releasing.md +8 -2
  17. package/package.json +7 -3
  18. package/registry/instructions/opus.md +3 -3
  19. package/registry/templates/app/package.json +1 -1
  20. package/registry/templates/app/src/App.tsx +11 -6
  21. package/registry/templates/app/src/main.tsx +4 -4
  22. package/src/audit/drivers/console.ts +1 -0
  23. package/src/auth/drivers/better-auth.ts +1 -0
  24. package/src/auth/drivers/jwt.ts +1 -0
  25. package/src/cache/drivers/memory.ts +1 -0
  26. package/src/client/drivers/fetch.ts +2 -1
  27. package/src/core/actions.ts +6 -1
  28. package/src/core/audit.ts +9 -3
  29. package/src/core/contracts.ts +7 -0
  30. package/src/core/domain.ts +1 -1
  31. package/src/core/errors.ts +18 -15
  32. package/src/core/index.ts +4 -2
  33. package/src/core/package-version.ts +26 -0
  34. package/src/core/reactions.ts +1 -1
  35. package/src/core/runtime.ts +33 -23
  36. package/src/core/schedules.ts +1 -1
  37. package/src/core/types.ts +5 -6
  38. package/src/dsl/eval.ts +2 -2
  39. package/src/dsl/kysely.ts +2 -2
  40. package/src/dsl/loads.ts +1 -1
  41. package/src/dsl/parser.ts +5 -5
  42. package/src/events/drivers/mitt.ts +1 -0
  43. package/src/mcp/index.ts +2 -1
  44. package/src/observability/drivers/opentelemetry.ts +1 -0
  45. package/src/queue/drivers/bullmq.ts +3 -3
  46. package/src/scheduler/drivers/node-cron.ts +3 -2
  47. package/src/scheduler/every.ts +7 -7
  48. package/src/schema/openapi.ts +3 -3
  49. package/src/seed/index.ts +29 -0
  50. package/src/server/drivers/fastify.ts +5 -2
  51. package/src/server/drivers/node.ts +9 -6
  52. package/src/server/index.ts +3 -1
  53. package/src/storage/drivers/fs.ts +1 -0
  54. package/src/testing/index.ts +3 -3
  55. package/src/ui/components/patterns/action-list-dialog.tsx +10 -3
  56. package/src/ui/components/patterns/confirm.tsx +2 -31
  57. package/src/ui/components/patterns/content-header.tsx +44 -137
  58. package/src/ui/components/patterns/data-state.tsx +42 -68
  59. package/src/ui/components/patterns/dock.tsx +20 -3
  60. package/src/ui/components/patterns/form.tsx +26 -22
  61. package/src/ui/components/patterns/list.tsx +18 -12
  62. package/src/ui/components/patterns/page-state.tsx +39 -51
  63. package/src/ui/components/patterns/page.tsx +37 -54
  64. package/src/ui/components/patterns/sidebar.tsx +17 -6
  65. package/src/ui/components/patterns/state-surface.tsx +148 -0
  66. package/src/ui/components/patterns/surface-header.tsx +119 -0
  67. package/src/ui/components/patterns/trigger.tsx +21 -25
  68. package/src/ui/components/patterns/view.tsx +29 -22
  69. package/src/ui/components/primitives/alert.tsx +1 -27
  70. package/src/ui/components/primitives/ask.tsx +3 -3
  71. package/src/ui/components/primitives/avatar.tsx +15 -5
  72. package/src/ui/components/primitives/badge.tsx +5 -41
  73. package/src/ui/components/primitives/breadcrumb.tsx +2 -2
  74. package/src/ui/components/primitives/button.tsx +39 -30
  75. package/src/ui/components/primitives/calendar.tsx +28 -2
  76. package/src/ui/components/primitives/carousel.tsx +3 -3
  77. package/src/ui/components/primitives/chat.tsx +1 -1
  78. package/src/ui/components/primitives/checkbox.tsx +1 -1
  79. package/src/ui/components/primitives/command.tsx +2 -2
  80. package/src/ui/components/primitives/control.ts +72 -0
  81. package/src/ui/components/primitives/copyable.tsx +1 -1
  82. package/src/ui/components/primitives/dialog.tsx +12 -7
  83. package/src/ui/components/primitives/dot.tsx +1 -25
  84. package/src/ui/components/primitives/drawer.tsx +10 -3
  85. package/src/ui/components/primitives/field.tsx +3 -3
  86. package/src/ui/components/primitives/icon-picker.tsx +3 -1
  87. package/src/ui/components/primitives/input-group.tsx +12 -9
  88. package/src/ui/components/primitives/input-otp.tsx +1 -1
  89. package/src/ui/components/primitives/input.tsx +2 -2
  90. package/src/ui/components/primitives/item.tsx +3 -1
  91. package/src/ui/components/primitives/menu.tsx +1 -7
  92. package/src/ui/components/primitives/pagination.tsx +16 -8
  93. package/src/ui/components/primitives/progress.tsx +32 -3
  94. package/src/ui/components/primitives/radio-group.tsx +1 -1
  95. package/src/ui/components/primitives/resizable.tsx +3 -1
  96. package/src/ui/components/primitives/select.tsx +6 -6
  97. package/src/ui/components/primitives/slider.tsx +5 -1
  98. package/src/ui/components/primitives/sonner.tsx +3 -0
  99. package/src/ui/components/primitives/spinner.tsx +13 -16
  100. package/src/ui/components/primitives/switch.tsx +5 -1
  101. package/src/ui/components/primitives/tabs.tsx +6 -3
  102. package/src/ui/components/primitives/textarea.tsx +1 -1
  103. package/src/ui/components/primitives/toggle.tsx +9 -4
  104. package/src/ui/components/primitives/tooltip.tsx +1 -0
  105. package/src/ui/docs/changelog.tsx +1 -1
  106. package/src/ui/docs/content/action-form.md +36 -3
  107. package/src/ui/docs/content/action-list-dialog.md +2 -2
  108. package/src/ui/docs/content/action-list.md +13 -2
  109. package/src/ui/docs/content/action-trigger.md +12 -5
  110. package/src/ui/docs/content/action-view.md +11 -3
  111. package/src/ui/docs/content/ask.md +11 -0
  112. package/src/ui/docs/content/avatar.md +7 -3
  113. package/src/ui/docs/content/button.md +30 -14
  114. package/src/ui/docs/content/calendar.md +13 -0
  115. package/src/ui/docs/content/card.md +26 -0
  116. package/src/ui/docs/content/chat.md +20 -0
  117. package/src/ui/docs/content/cli.md +71 -19
  118. package/src/ui/docs/content/communication.md +36 -0
  119. package/src/ui/docs/content/composer.md +15 -0
  120. package/src/ui/docs/content/content.md +17 -1
  121. package/src/ui/docs/content/copyable.md +8 -0
  122. package/src/ui/docs/content/data-state.md +17 -13
  123. package/src/ui/docs/content/detail.md +19 -1
  124. package/src/ui/docs/content/dialog.md +1 -4
  125. package/src/ui/docs/content/dictionary-value.md +9 -2
  126. package/src/ui/docs/content/dock.md +8 -0
  127. package/src/ui/docs/content/dot.md +8 -0
  128. package/src/ui/docs/content/empty.md +2 -2
  129. package/src/ui/docs/content/getting-started.md +2 -2
  130. package/src/ui/docs/content/icon-picker.md +11 -0
  131. package/src/ui/docs/content/input.md +1 -1
  132. package/src/ui/docs/content/item.md +1 -1
  133. package/src/ui/docs/content/label.md +7 -0
  134. package/src/ui/docs/content/menu.md +6 -0
  135. package/src/ui/docs/content/metric-card.md +13 -0
  136. package/src/ui/docs/content/page.md +20 -4
  137. package/src/ui/docs/content/pagination.md +11 -9
  138. package/src/ui/docs/content/popover.md +6 -0
  139. package/src/ui/docs/content/progress.md +8 -11
  140. package/src/ui/docs/content/select.md +5 -5
  141. package/src/ui/docs/content/semantic-context.md +5 -4
  142. package/src/ui/docs/content/sidebar.md +13 -47
  143. package/src/ui/docs/content/skeleton.md +6 -0
  144. package/src/ui/docs/content/spinner.md +9 -6
  145. package/src/ui/docs/content/split.md +21 -0
  146. package/src/ui/docs/content/switch.md +1 -1
  147. package/src/ui/docs/content/tabs.md +1 -1
  148. package/src/ui/docs/content/textarea.md +7 -0
  149. package/src/ui/docs/content/toggle.md +1 -1
  150. package/src/ui/docs/content/tokens.md +4 -4
  151. package/src/ui/docs/content/truncate.md +8 -0
  152. package/src/ui/docs/content/ui.md +14 -0
  153. package/src/ui/docs/doc-client.tsx +5 -5
  154. package/src/ui/docs/doc.tsx +26 -14
  155. package/src/ui/docs/registry.tsx +5 -5
  156. package/src/ui/docs/standalone.tsx +2 -2
  157. package/src/ui/drivers/react.tsx +12 -12
  158. package/src/ui/lib/action-errors.ts +45 -0
  159. package/src/ui/lib/zod-pt-br.ts +31 -4
  160. package/src/ui/meta.ts +8 -8
  161. package/src/ui/react.tsx +10 -16
  162. package/src/ui/theme.css +10 -8
  163. package/src/vite/design.ts +6 -18
  164. package/src/ui/components/patterns/shell-nav.tsx +0 -147
@@ -64,6 +64,7 @@ export type ViewContract<In = unknown, Out = unknown, ParsedIn = any> = Omit<
64
64
  BindingKeys
65
65
  >
66
66
 
67
+ /** União dos contratos por kind — o que `bindAction` aceita e a UI tipada consome. */
67
68
  export type ActionContract<In = any, Out = any, ParsedIn = any> =
68
69
  | SimpleContract<In, Out, ParsedIn>
69
70
  | FormContract<In & Record<string, unknown>, Out, ParsedIn>
@@ -97,6 +98,12 @@ export interface ActionBinding<ParsedIn, Out> {
97
98
  // defineContract (overloads por kind, igual defineAction)
98
99
  // =============================================================================
99
100
 
101
+ /**
102
+ * Declara a parte compartilhável de uma action (identidade, schemas, docs e
103
+ * `authorize` action-level) sem handler nem dependência server-only — pode
104
+ * ser importada pelo cliente. Identity function tipada; o kind é inferido
105
+ * pelo overload. Complete com `bindAction` no servidor.
106
+ */
100
107
  export function defineContract<In, Out, ParsedIn = In>(
101
108
  contract: SimpleContract<In, Out, ParsedIn>,
102
109
  ): SimpleContract<In, Out, ParsedIn>
@@ -1,5 +1,5 @@
1
1
  /**
2
- * tbdlib — `defineDomain` factory + flatten helper.
2
+ * Opus — `defineDomain` factory + flatten helper.
3
3
  *
4
4
  * Domain agrupa as peças que pertencem a um mesmo recorte funcional
5
5
  * (dicts, entities, repository, service, actions, reactions, schedules,
@@ -1,5 +1,5 @@
1
1
  /**
2
- * tbdlib — Error factory
2
+ * Opus — Error factory
3
3
  *
4
4
  * `error()` é a maneira canônica de emitir `ActionError` de dentro de um handler.
5
5
  * Aplica defaults sensatos baseados na categoria (severity, retriable) e
@@ -9,11 +9,12 @@
9
9
  * throw error({
10
10
  * code: 'deal.archive.notFound',
11
11
  * category: 'not_found',
12
- * message: `Deal ${id} not found`,
12
+ * message: `Negócio ${id} não encontrado`,
13
13
  * })
14
14
  *
15
15
  * Erros não criados via `error()` (ex: `throw new Error(...)`, exceções de driver)
16
- * são normalizados pelo runtime em `{ category: 'internal', code: 'internal.unhandled' }`.
16
+ * são normalizados pelo runtime em `{ category: 'internal', code: 'internal.unhandled' }`
17
+ * com a microcopy fixa `UNHANDLED_ERROR_MESSAGE`; o texto original vai só para `cause`.
17
18
  */
18
19
 
19
20
  import type {
@@ -24,7 +25,7 @@ import type {
24
25
  } from './types.ts'
25
26
 
26
27
  /** Brand interno pra distinguir ActionError construído via `error()` de Error genérico. */
27
- const ACTION_ERROR_BRAND = Symbol('tbdlib.ActionError')
28
+ const ACTION_ERROR_BRAND = Symbol('opus.ActionError')
28
29
 
29
30
  /**
30
31
  * Defaults de severity por categoria.
@@ -148,27 +149,29 @@ export function isActionError(value: unknown): value is ActionError {
148
149
  )
149
150
  }
150
151
 
152
+ /**
153
+ * Microcopy que a pessoa recebe quando um handler falha com exceção não tratada.
154
+ * Fixa de propósito: a mensagem crua (driver, SQL, stack) fica no logger do
155
+ * runtime e em `cause`, nunca vira o texto exibido.
156
+ */
157
+ export const UNHANDLED_ERROR_MESSAGE =
158
+ 'Não foi possível concluir a operação. Tente novamente.'
159
+
151
160
  /**
152
161
  * Normaliza uma exceção qualquer em ActionError. Usado pelo runtime quando
153
162
  * handler lança algo que não foi criado via `error()`.
163
+ *
164
+ * A `message` é sempre `UNHANDLED_ERROR_MESSAGE`; o valor lançado é preservado
165
+ * em `cause` (só a mensagem quando for `Error` — stack não vaza pra wire).
154
166
  */
155
167
  export function normalizeError(thrown: unknown): ActionError {
156
168
  if (isActionError(thrown)) return thrown
157
169
 
158
- if (thrown instanceof Error) {
159
- return error({
160
- code: 'internal.unhandled',
161
- category: 'internal',
162
- message: thrown.message,
163
- cause: thrown.message, // só mensagem; stack não vaza pra wire
164
- })
165
- }
166
-
167
170
  return error({
168
171
  code: 'internal.unhandled',
169
172
  category: 'internal',
170
- message: typeof thrown === 'string' ? thrown : 'Unhandled error',
171
- cause: thrown,
173
+ message: UNHANDLED_ERROR_MESSAGE,
174
+ cause: thrown instanceof Error ? thrown.message : thrown,
172
175
  })
173
176
  }
174
177
 
package/src/core/index.ts CHANGED
@@ -1,7 +1,9 @@
1
1
  /**
2
2
  * @softize/opus/core — public entry
3
3
  *
4
- * Re-exports da superfície pública. Source of truth: `/docs/protocol.md`.
4
+ * Re-exports da superfície pública. A fonte de verdade são as declarações
5
+ * (`defineContract`, `bindAction`, `defineDomain`…); `/docs/protocol.md`, o
6
+ * manifest e o OpenAPI são projeções delas.
5
7
  */
6
8
 
7
9
  // — Tipos —————————————————————————————————————————————————————————————————————
@@ -128,7 +130,7 @@ export type {
128
130
  } from './types.ts'
129
131
 
130
132
  // — Erro ——————————————————————————————————————————————————————————————————————
131
- export { error, isActionError, normalizeError } from './errors.ts'
133
+ export { error, isActionError, normalizeError, UNHANDLED_ERROR_MESSAGE } from './errors.ts'
132
134
  export type { ErrorInput } from './errors.ts'
133
135
  export { normalizeTraceContext } from './trace.ts'
134
136
 
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Opus — versão do pacote lida do `package.json` que viaja junto do source.
3
+ *
4
+ * Server-only (usa `node:fs`); não é re-exportado por `core/index.ts`. Usado
5
+ * pelos drivers de servidor (info do OpenAPI), pelo servidor MCP e pelo plugin
6
+ * de design pra identificar a versão do SDK sem duplicar leitura de arquivo.
7
+ */
8
+
9
+ import { readFileSync } from 'node:fs'
10
+
11
+ const FALLBACK_VERSION = '0.0.0'
12
+
13
+ /** Versão de `@softize/opus` resolvida do `package.json` real; `0.0.0` se a leitura falhar. */
14
+ export function readPackageVersion(): string {
15
+ // `import.meta.url` sobrevive ao bundling do vite.config (o vite injeta a
16
+ // URL original do arquivo), então o package.json resolve do source real.
17
+ try {
18
+ const pkg = JSON.parse(
19
+ readFileSync(new URL('../../package.json', import.meta.url), 'utf8'),
20
+ ) as { version?: string }
21
+ return pkg.version ?? FALLBACK_VERSION
22
+ /* v8 ignore next 3 — defensivo; o package.json viaja junto do source */
23
+ } catch {
24
+ return FALLBACK_VERSION
25
+ }
26
+ }
@@ -1,5 +1,5 @@
1
1
  /**
2
- * tbdlib — `defineReaction` factory + type guards
2
+ * Opus — `defineReaction` factory + type guards
3
3
  *
4
4
  * `defineReaction` é identity function tipada que aceita uma `ReactionDef`
5
5
  * e preserva o tipo do evento pra inferência. Runtime registra no
@@ -1,5 +1,5 @@
1
1
  /**
2
- * tbdlib — Runtime
2
+ * Opus — Runtime
3
3
  *
4
4
  * Orquestrador central. Recebe actions + reactions + adapters + config no setup,
5
5
  * registra cada peça, e executa o pipeline padrão (validate → load → auth →
@@ -281,7 +281,7 @@ export class Runtime {
281
281
  this.config = applyConfigDefaults(setup.config)
282
282
  this.logger = setup.logger
283
283
  this.observability = setup.observability
284
- this.log = setup.logger ?? new ConsoleLogger({ runtime: 'tbdlib' })
284
+ this.log = setup.logger ?? new ConsoleLogger({ runtime: 'opus' })
285
285
 
286
286
  this.audit = new AuditEmitter({ mode: this.config.auditMode, log: this.log })
287
287
  if (setup.audit !== undefined) {
@@ -345,7 +345,7 @@ export class Runtime {
345
345
  throw error({
346
346
  code: 'runtime.duplicate_loader',
347
347
  category: 'internal',
348
- message: `Loader resolver "${entity}" already registered`,
348
+ message: ` existe um resolver de loader registrado para "${entity}"`,
349
349
  })
350
350
  }
351
351
  this.loaderResolvers.set(entity, resolver)
@@ -368,7 +368,7 @@ export class Runtime {
368
368
  throw error({
369
369
  code: 'runtime.duplicate_action',
370
370
  category: 'internal',
371
- message: `Action "${action.name}" registered twice`,
371
+ message: `Action "${action.name}" registrada duas vezes`,
372
372
  })
373
373
  }
374
374
  this.actions.set(action.name, action)
@@ -381,7 +381,7 @@ export class Runtime {
381
381
  throw error({
382
382
  code: 'runtime.duplicate_reaction',
383
383
  category: 'internal',
384
- message: `Reaction "${reaction.name}" registered twice`,
384
+ message: `Reaction "${reaction.name}" registrada duas vezes`,
385
385
  })
386
386
  }
387
387
  this.reactions.set(reaction.name, reaction)
@@ -394,7 +394,7 @@ export class Runtime {
394
394
  throw error({
395
395
  code: 'runtime.duplicate_schedule',
396
396
  category: 'internal',
397
- message: `Schedule "${schedule.name}" registered twice`,
397
+ message: `Schedule "${schedule.name}" registrado duas vezes`,
398
398
  })
399
399
  }
400
400
  this.schedules.set(schedule.name, schedule)
@@ -410,7 +410,7 @@ export class Runtime {
410
410
  throw error({
411
411
  code: 'runtime.already_started',
412
412
  category: 'internal',
413
- message: 'Runtime.start() called twice',
413
+ message: 'Runtime.start() foi chamado mais de uma vez',
414
414
  })
415
415
  }
416
416
 
@@ -431,7 +431,7 @@ export class Runtime {
431
431
  throw error({
432
432
  code: 'runtime.eventbus_required',
433
433
  category: 'internal',
434
- message: `${this.reactions.size} reaction(s) registered without an EventBusAdapter`,
434
+ message: `${this.reactions.size} reaction(s) registrada(s) sem um EventBusAdapter configurado`,
435
435
  })
436
436
  }
437
437
 
@@ -441,7 +441,7 @@ export class Runtime {
441
441
  throw error({
442
442
  code: 'runtime.scheduler_required',
443
443
  category: 'internal',
444
- message: `${this.schedules.size} schedule(s) registered without a SchedulerAdapter`,
444
+ message: `${this.schedules.size} schedule(s) registrado(s) sem um SchedulerAdapter configurado`,
445
445
  })
446
446
  }
447
447
 
@@ -514,7 +514,7 @@ export class Runtime {
514
514
  return this.errorResult(actionName, error({
515
515
  code: 'runtime.action_not_found',
516
516
  category: 'not_found',
517
- message: `Action "${actionName}" is not registered`,
517
+ message: `Action "${actionName}" não está registrada`,
518
518
  }), 0, ctxBase.requestId)
519
519
  }
520
520
 
@@ -535,21 +535,21 @@ export class Runtime {
535
535
  return this.errorResult(spec.action, error({
536
536
  code: 'runtime.action_not_found',
537
537
  category: 'not_found',
538
- message: `Action "${spec.action}" is not registered`,
538
+ message: `Action "${spec.action}" não está registrada`,
539
539
  }), 0, spec.ctx.requestId)
540
540
  }
541
541
  if (!isBackgroundAction(action)) {
542
542
  return this.errorResult(spec.action, error({
543
543
  code: 'runtime.action_not_background',
544
544
  category: 'validation',
545
- message: `Action "${spec.action}" is not declared as background`,
545
+ message: `Action "${spec.action}" não está declarada como background`,
546
546
  }), 0, spec.ctx.requestId)
547
547
  }
548
548
  if ((ctxBase.user?.id ?? null) !== spec.ctx.userId) {
549
549
  return this.errorResult(spec.action, error({
550
550
  code: 'runtime.job_actor_mismatch',
551
551
  category: 'authentication',
552
- message: 'Worker context actor does not match the job envelope',
552
+ message: 'O ator do contexto do worker não corresponde ao envelope do job',
553
553
  }), 0, spec.ctx.requestId)
554
554
  }
555
555
 
@@ -604,7 +604,7 @@ export class Runtime {
604
604
  throw error({
605
605
  code: 'auth.unauthenticated',
606
606
  category: 'authentication',
607
- message: 'Authentication required',
607
+ message: 'Autenticação necessária',
608
608
  })
609
609
  }
610
610
 
@@ -616,7 +616,7 @@ export class Runtime {
616
616
  throw error({
617
617
  code: 'auth.forbidden',
618
618
  category: 'authorization',
619
- message: 'Forbidden',
619
+ message: 'Acesso negado',
620
620
  })
621
621
  }
622
622
  if (decision !== true) throw decision
@@ -627,7 +627,7 @@ export class Runtime {
627
627
  throw error({
628
628
  code: 'runtime.queue_required',
629
629
  category: 'internal',
630
- message: `Background action "${action.name}" requires a QueueAdapter`,
630
+ message: `A action background "${action.name}" exige um QueueAdapter`,
631
631
  })
632
632
  }
633
633
  const background = action.background
@@ -635,7 +635,7 @@ export class Runtime {
635
635
  throw error({
636
636
  code: 'runtime.invalid_background_config',
637
637
  category: 'internal',
638
- message: `Background action "${action.name}" has no background config`,
638
+ message: `A action background "${action.name}" não tem configuração de background`,
639
639
  })
640
640
  }
641
641
  const spec: JobSpec = {
@@ -729,6 +729,14 @@ export class Runtime {
729
729
  meta: this.buildMeta(actionId, action.name, durationMs, ctxBase.requestId, trace),
730
730
  }
731
731
  } catch (thrown) {
732
+ // Exceção fora de `error()` vira microcopy fixa no envelope; o texto
733
+ // original só sobrevive aqui, no logger da action, e em `cause`.
734
+ if (!isActionError(thrown)) {
735
+ ctx.log.error('action failed with unhandled error', {
736
+ error: thrown instanceof Error ? thrown.message : String(thrown),
737
+ ...(thrown instanceof Error && thrown.stack !== undefined ? { stack: thrown.stack } : {}),
738
+ })
739
+ }
732
740
  const actionError = normalizeError(thrown)
733
741
  const durationMs = performance.now() - startedAt
734
742
  await this.audit.emit(
@@ -898,7 +906,7 @@ export class Runtime {
898
906
  throw error({
899
907
  code: 'emit.no_bus',
900
908
  category: 'internal',
901
- message: `ctx.emit('${event}') called without EventBusAdapter`,
909
+ message: `ctx.emit('${event}') chamado sem EventBusAdapter configurado`,
902
910
  })
903
911
  }
904
912
  this.log.warn('emit dropped — no EventBusAdapter', { event })
@@ -931,7 +939,7 @@ export class Runtime {
931
939
  throw error({
932
940
  code: 'emit.failed',
933
941
  category: 'internal',
934
- message: `failed to publish event "${event}"`,
942
+ message: `Falha ao publicar o evento "${event}"`,
935
943
  cause: String(err),
936
944
  })
937
945
  }
@@ -953,7 +961,7 @@ export class Runtime {
953
961
  throw error({
954
962
  code: kind === 'input' ? 'validation.invalid_input' : 'validation.invalid_output',
955
963
  category: kind === 'input' ? 'validation' : 'internal',
956
- message: kind === 'input' ? 'Input validation failed' : 'Output validation failed (dev)',
964
+ message: kind === 'input' ? 'Dados de entrada inválidos' : 'Dados de saída inválidos (validação em dev)',
957
965
  issues: result.issues.map((iss) => ({
958
966
  path: (iss.path ?? []).join('.'),
959
967
  code: 'invalid',
@@ -978,7 +986,7 @@ export class Runtime {
978
986
  throw error({
979
987
  code: `${action.name}.${key}.not_found`,
980
988
  category: 'not_found',
981
- message: `Resource "${key}" not found for action "${action.name}"`,
989
+ message: `Recurso "${key}" não encontrado para a action "${action.name}"`,
982
990
  })
983
991
  }
984
992
  loaded[key] = value
@@ -1128,7 +1136,7 @@ export class Runtime {
1128
1136
  throw error({
1129
1137
  code: 'runtime.subscribe_unsupported',
1130
1138
  category: 'internal',
1131
- message: 'EventBusAdapter does not support subscribe()',
1139
+ message: 'O EventBusAdapter não suporta subscribe()',
1132
1140
  })
1133
1141
  }
1134
1142
  const patterns = Array.isArray(reaction.on) ? reaction.on : [reaction.on]
@@ -1222,10 +1230,12 @@ export class Runtime {
1222
1230
  },
1223
1231
  )
1224
1232
  } catch (err) {
1225
- const actionError = isActionError(err) ? err : normalizeError(err)
1233
+ const actionError = normalizeError(err)
1226
1234
  reactionLog.error('reaction failed', {
1227
1235
  code: actionError.code,
1228
1236
  message: actionError.message,
1237
+ // Erro não tratado tem microcopy fixa; o texto original está em `cause`.
1238
+ ...(actionError.cause !== undefined ? { cause: actionError.cause } : {}),
1229
1239
  })
1230
1240
  }
1231
1241
  }
@@ -1,5 +1,5 @@
1
1
  /**
2
- * tbdlib — `defineSchedule` factory + type guards.
2
+ * Opus — `defineSchedule` factory + type guards.
3
3
  *
4
4
  * Schedule é a primitiva pra **ação iniciada pelo próprio sistema** — sem
5
5
  * trigger externo (HTTP, evento, AI). Usado pra monitoramento, relatórios,
package/src/core/types.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * tbdlib — Core types
2
+ * Opus — Core types
3
3
  *
4
4
  * Tipos centrais do protocolo. Source of truth: `/docs/protocol.md`.
5
5
  * Qualquer divergência entre este arquivo e a doc é bug; alinhar a doc primeiro,
@@ -250,7 +250,7 @@ export interface User {
250
250
 
251
251
  /**
252
252
  * Função que checa permissão. Plugada pelo `AuthAdapter`.
253
- * tbdlib não implementa RBAC/ABAC — apenas delega.
253
+ * O Opus não implementa RBAC/ABAC — apenas delega.
254
254
  */
255
255
  export type CanFn = (permission: string, resource?: unknown) => boolean | Promise<boolean>
256
256
 
@@ -551,9 +551,8 @@ export type FieldWidget = BuiltInFieldWidget | (string & { readonly __opusCustom
551
551
  export interface FieldSpec {
552
552
  label: I18nRef
553
553
  placeholder?: I18nRef
554
- hint?: I18nRef
555
- /** Ajuda no hover/foco da label (ícone + tooltip). `hint` = texto auxiliar SOB o campo;
556
- * `help` = explicação mais longa, escondida atrás do ícone na label. */
554
+ /** Ajuda junto à label (ícone ⓘ + tooltip): critério, efeito ou limitação que a label não
555
+ * diz. Ajuda que repete a label deve ser omitida. */
557
556
  help?: I18nRef
558
557
 
559
558
  default?: unknown
@@ -629,7 +628,7 @@ export interface ListColumnSpec {
629
628
  * 'badge' (chip `outline` com o valor — legado; coluna de dicionário com `presentation`
630
629
  * declarado usa `DictionaryValue` e dispensa este tipo). */
631
630
  type?: 'text' | 'number' | 'date' | 'badge'
632
- /** Referência do dicionário registrado em `TbdlibProvider dicts` que esta coluna mostra,
631
+ /** Referência do dicionário registrado em `OpusProvider dicts` que esta coluna mostra,
633
632
  * quando o schema de saída não carrega a meta de `t.dict` (ex.: campo `z.string()`).
634
633
  * Coluna cujo campo de saída É um `t.dict().zod()` resolve sozinha, sem esta chave. */
635
634
  dictionary?: string
package/src/dsl/eval.ts CHANGED
@@ -64,7 +64,7 @@ export function evalExpression(node: AstNode, ctx: EvalContext): unknown {
64
64
  return (left as number) / (right as number)
65
65
  /* v8 ignore next 2 */
66
66
  default:
67
- throw new Error(`DSL eval: unknown binary op ${node.op as string}`)
67
+ throw new Error(`DSL eval: operador binário desconhecido ${node.op as string}`)
68
68
  }
69
69
  }
70
70
  case 'in': {
@@ -132,5 +132,5 @@ function callFunction(name: string, args: unknown[]): unknown {
132
132
  }
133
133
  return null
134
134
  }
135
- throw new Error(`DSL eval: unknown function '${name}'`)
135
+ throw new Error(`DSL eval: função desconhecida '${name}'`)
136
136
  }
package/src/dsl/kysely.ts CHANGED
@@ -136,7 +136,7 @@ function compile(node: AstNode, eb: KyselyEb, bindings: KyselyBindings): any {
136
136
  return (left as number) / (right as number)
137
137
  /* v8 ignore next 2 */
138
138
  default:
139
- throw new Error(`DSL kysely: unhandled binary ${node.op as string}`)
139
+ throw new Error(`DSL kysely: operador binário não tratado ${node.op as string}`)
140
140
  }
141
141
  }
142
142
  case 'in': {
@@ -195,7 +195,7 @@ function compile(node: AstNode, eb: KyselyEb, bindings: KyselyBindings): any {
195
195
  `DSL kysely: function '${node.name}' precisa de mapping de coluna explícito (use eb.fn)`,
196
196
  )
197
197
  }
198
- throw new Error(`DSL kysely: unknown function '${node.name}'`)
198
+ throw new Error(`DSL kysely: função desconhecida '${node.name}'`)
199
199
  }
200
200
  }
201
201
  }
package/src/dsl/loads.ts CHANGED
@@ -31,7 +31,7 @@ export function parseLoad(src: string): LoadSpec {
31
31
  const trimmed = src.trim()
32
32
  const m = LOAD_RE.exec(trimmed)
33
33
  if (m === null) {
34
- throw new Error(`DSL loads: invalid syntax '${src}'`)
34
+ throw new Error(`DSL loads: sintaxe inválida '${src}'`)
35
35
  }
36
36
  const entity = m[1]!
37
37
  const argsRaw = m[2]!.trim()
package/src/dsl/parser.ts CHANGED
@@ -89,7 +89,7 @@ function tokenize(src: string): Token[] {
89
89
  }
90
90
  }
91
91
  if (i >= src.length) {
92
- throw new Error(`DSL parse error: unterminated string at ${startPos}`)
92
+ throw new Error(`DSL parse: string não terminada na posição ${startPos}`)
93
93
  }
94
94
  i += 1 // skip closing quote
95
95
  tokens.push({ type: 'STRING', value: val, pos: startPos })
@@ -150,7 +150,7 @@ function tokenize(src: string): Token[] {
150
150
  }
151
151
 
152
152
  throw new Error(
153
- `DSL parse error: unexpected character '${c}' at position ${i}`,
153
+ `DSL parse: caractere inesperado '${c}' na posição ${i}`,
154
154
  )
155
155
  }
156
156
  tokens.push({ type: 'EOF', value: '', pos: src.length })
@@ -184,7 +184,7 @@ class Parser {
184
184
  if (!this.match(type, value)) {
185
185
  const t = this.peek()
186
186
  throw new Error(
187
- `DSL parse error: expected ${value ?? type}, got '${t.value}' at ${t.pos}`,
187
+ `DSL parse: esperado ${value ?? type}, recebido '${t.value}' na posição ${t.pos}`,
188
188
  )
189
189
  }
190
190
  return this.consume()
@@ -343,7 +343,7 @@ class Parser {
343
343
  const t = this.consume()
344
344
  const n = Number(t.value)
345
345
  if (Number.isNaN(n)) {
346
- throw new Error(`DSL parse error: invalid number '${t.value}'`)
346
+ throw new Error(`DSL parse: número inválido '${t.value}'`)
347
347
  }
348
348
  return { kind: 'literal', value: n }
349
349
  }
@@ -409,7 +409,7 @@ class Parser {
409
409
  }
410
410
  const t = this.peek()
411
411
  throw new Error(
412
- `DSL parse error: unexpected token '${t.value}' at ${t.pos}`,
412
+ `DSL parse: token inesperado '${t.value}' na posição ${t.pos}`,
413
413
  )
414
414
  }
415
415
  }
@@ -39,6 +39,7 @@ export interface MittEventsOptions {
39
39
  */
40
40
  type EventMap = Record<string, DomainEvent>
41
41
 
42
+ /** EventBusAdapter in-process baseado em `mitt`; entrega no mesmo processo, sem persistência. */
42
43
  export function mittEvents(options: MittEventsOptions = {}): EventBusAdapter {
43
44
  const { name = 'mitt' } = options
44
45
  const emitter: Emitter<EventMap> = mitt<EventMap>()
package/src/mcp/index.ts CHANGED
@@ -13,6 +13,7 @@ import { Server } from '@modelcontextprotocol/sdk/server/index.js'
13
13
  import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js'
14
14
  import { zodToJsonSchema } from 'zod-to-json-schema'
15
15
  import type { ContextBase, Runtime } from '../core/index.ts'
16
+ import { readPackageVersion } from '../core/package-version.ts'
16
17
 
17
18
  export interface OpusMcpOptions {
18
19
  name?: string
@@ -36,7 +37,7 @@ export function createOpusMcpServer(runtime: Runtime, opts: OpusMcpOptions = {})
36
37
  opts.toJsonSchema ?? ((schema: unknown) => zodToJsonSchema(schema as never, { $refStrategy: 'none' }))
37
38
 
38
39
  const server = new Server(
39
- { name: opts.name ?? 'opus', version: opts.version ?? '0.0.0' },
40
+ { name: opts.name ?? 'opus', version: opts.version ?? readPackageVersion() },
40
41
  { capabilities: { tools: {} } },
41
42
  )
42
43
 
@@ -40,6 +40,7 @@ export interface OpenTelemetryObservabilityOptions {
40
40
  healthCheck?: () => Promise<HealthStatus> | HealthStatus
41
41
  }
42
42
 
43
+ /** ObservabilityAdapter que abre um span OpenTelemetry por action/reaction e propaga o trace context. */
43
44
  export function openTelemetryObservability(
44
45
  options: OpenTelemetryObservabilityOptions = {},
45
46
  ): ObservabilityAdapter {
@@ -13,7 +13,7 @@
13
13
  * import { Queue } from 'bullmq'
14
14
  * import { bullmqQueue } from '@softize/opus/queue/bullmq'
15
15
  *
16
- * const queue = new Queue('tbdlib-jobs', { connection: redisOpts })
16
+ * const queue = new Queue('opus-jobs', { connection: redisOpts })
17
17
  * createRuntime({ queue: bullmqQueue({ queue }) })
18
18
  */
19
19
 
@@ -139,9 +139,9 @@ function bullmqOptions(spec: JobSpec): BullMQAddOptions {
139
139
  // =============================================================================
140
140
 
141
141
  /**
142
- * BullMQ state → tbdlib JobStatus.
142
+ * BullMQ state → Opus JobStatus.
143
143
  *
144
- * tbdlib não tem 'paused' nem 'delayed' separados — todos viram 'queued'.
144
+ * O Opus não tem 'paused' nem 'delayed' separados — todos viram 'queued'.
145
145
  * Distinção fina fica no `details.meta.bullmqState` se consumer precisar.
146
146
  */
147
147
  export function mapState(state: BullMQState): JobStatus {
@@ -31,6 +31,7 @@ export interface NodeCronOptions {
31
31
  onError?: (err: unknown, schedule: ScheduleDef) => void
32
32
  }
33
33
 
34
+ /** SchedulerAdapter sobre `node-cron`; aceita `cron` ou o atalho `every` de cada schedule. */
34
35
  export function nodeCronScheduler(
35
36
  options: NodeCronOptions = {},
36
37
  ): SchedulerAdapter {
@@ -83,11 +84,11 @@ function resolveCron(spec: ScheduleDef): string {
83
84
  if (spec.cron !== undefined) return spec.cron
84
85
  if (spec.every !== undefined) return everyToCron(spec.every)
85
86
  throw new Error(
86
- `Schedule "${spec.name}" must declare either 'cron' or 'every'.`,
87
+ `O schedule "${spec.name}" precisa declarar 'cron' ou 'every'.`,
87
88
  )
88
89
  }
89
90
 
90
91
  function defaultOnError(err: unknown, schedule: ScheduleDef): void {
91
92
  // eslint-disable-next-line no-console
92
- console.error(`[tbdlib/scheduler] schedule "${schedule.name}" fire failed:`, err)
93
+ console.error(`[opus/scheduler] falha ao disparar o schedule "${schedule.name}":`, err)
93
94
  }
@@ -15,30 +15,30 @@ export function everyToCron(every: string): string {
15
15
  const match = /^(\d+)(s|m|h|d)$/.exec(every.trim())
16
16
  if (match === null) {
17
17
  throw new Error(
18
- `Invalid 'every' shorthand: "${every}". ` +
19
- `Expected formato "<n><s|m|h|d>" (ex: "15s", "5m", "1h", "1d").`,
18
+ `Atalho 'every' inválido: "${every}". ` +
19
+ `Formato esperado "<n><s|m|h|d>" (ex: "15s", "5m", "1h", "1d").`,
20
20
  )
21
21
  }
22
22
  const n = Number.parseInt(match[1] as string, 10)
23
23
  const unit = match[2] as 's' | 'm' | 'h' | 'd'
24
24
 
25
25
  if (n <= 0) {
26
- throw new Error(`'every' value must be > 0, got "${every}".`)
26
+ throw new Error(`O valor de 'every' precisa ser maior que 0; recebido "${every}".`)
27
27
  }
28
28
 
29
29
  switch (unit) {
30
30
  case 's':
31
- if (n > 59) throw new Error(`'every' seconds must be <= 59, got ${n}.`)
31
+ if (n > 59) throw new Error(`Segundos em 'every' precisam ser no máximo 59; recebido ${n}.`)
32
32
  return `*/${n} * * * * *`
33
33
  case 'm':
34
- if (n > 59) throw new Error(`'every' minutes must be <= 59, got ${n}.`)
34
+ if (n > 59) throw new Error(`Minutos em 'every' precisam ser no máximo 59; recebido ${n}.`)
35
35
  return `*/${n} * * * *`
36
36
  case 'h':
37
- if (n > 23) throw new Error(`'every' hours must be <= 23, got ${n}.`)
37
+ if (n > 23) throw new Error(`Horas em 'every' precisam ser no máximo 23; recebido ${n}.`)
38
38
  return n === 1 ? '0 * * * *' : `0 */${n} * * *`
39
39
  case 'd':
40
40
  if (n !== 1) {
41
- throw new Error(`'every' days only supports 1d. For longer intervals use cron.`)
41
+ throw new Error(`Dias em 'every' aceitam somente 1d. Para intervalos maiores, use cron.`)
42
42
  }
43
43
  return '0 0 * * *'
44
44
  }
@@ -175,15 +175,15 @@ function buildResponses(
175
175
  const successStatus = String(action.successStatus ?? 200)
176
176
  const responses: Record<string, OpenAPIResponse> = {
177
177
  [successStatus]: {
178
- description: 'Success',
178
+ description: 'Sucesso',
179
179
  content: {
180
180
  'application/json': {
181
181
  schema: wrapEnvelope(toJsonSchema(action.output), action.kind),
182
182
  },
183
183
  },
184
184
  },
185
- '4XX': errorResponse('Client error', action.errors),
186
- '5XX': errorResponse('Server error'),
185
+ '4XX': errorResponse('Erro do cliente', action.errors),
186
+ '5XX': errorResponse('Erro do servidor'),
187
187
  }
188
188
  return responses
189
189
  }