@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.
Files changed (286) hide show
  1. package/CHANGELOG.md +1616 -0
  2. package/LICENSE +21 -0
  3. package/README.md +113 -0
  4. package/bin/cli.mjs +528 -0
  5. package/bin/lib/check.mjs +307 -0
  6. package/bin/lib/components.mjs +151 -0
  7. package/bin/lib/create.mjs +208 -0
  8. package/bin/lib/db-check-runner.mjs +86 -0
  9. package/bin/lib/db-migrate-runner.mjs +89 -0
  10. package/bin/lib/db-scaffold-runner.mjs +84 -0
  11. package/bin/lib/db.mjs +261 -0
  12. package/bin/lib/docs-include.mjs +48 -0
  13. package/bin/lib/gen-dicts.mjs +134 -0
  14. package/bin/lib/gen-docs.mjs +288 -0
  15. package/bin/lib/gen-manifest.mjs +102 -0
  16. package/bin/lib/gen-openapi.mjs +195 -0
  17. package/bin/lib/gen-runner.mjs +472 -0
  18. package/bin/lib/gen-stubs.mjs +463 -0
  19. package/bin/lib/gen.mjs +311 -0
  20. package/bin/lib/init.mjs +514 -0
  21. package/bin/lib/introspect.mjs +107 -0
  22. package/bin/lib/mcp.mjs +85 -0
  23. package/bin/lib/postinstall.mjs +56 -0
  24. package/docs/chat-event-protocol.md +85 -0
  25. package/docs/code-style.md +16 -0
  26. package/docs/data-layer.md +246 -0
  27. package/docs/ownership-vs-shadcn-lock.md +102 -0
  28. package/docs/protocol.md +2053 -0
  29. package/docs/releasing.md +110 -0
  30. package/docs/shellnav.md +131 -0
  31. package/package.json +338 -0
  32. package/registry/hooks/hooks.json +26 -0
  33. package/registry/hooks/link-memory-on-start.mjs +46 -0
  34. package/registry/hooks/opus-check-on-stop.mjs +114 -0
  35. package/registry/skills/create-action/SKILL.md +49 -0
  36. package/registry/skills/create-action/scaffold.mjs +122 -0
  37. package/registry/templates/app/_gitignore +3 -0
  38. package/registry/templates/app/_npmrc +1 -0
  39. package/registry/templates/app/_opus/_gitignore +5 -0
  40. package/registry/templates/app/_prettierrc.json +6 -0
  41. package/registry/templates/app/index.html +13 -0
  42. package/registry/templates/app/opus.config.ts +16 -0
  43. package/registry/templates/app/package.json +43 -0
  44. package/registry/templates/app/pnpm-workspace.yaml +11 -0
  45. package/registry/templates/app/public/favicon.svg +4 -0
  46. package/registry/templates/app/src/App.tsx +37 -0
  47. package/registry/templates/app/src/domains/tasks/actions/list.test.ts +34 -0
  48. package/registry/templates/app/src/domains/tasks/actions/list.ts +33 -0
  49. package/registry/templates/app/src/domains/tasks/index.ts +13 -0
  50. package/registry/templates/app/src/index.css +18 -0
  51. package/registry/templates/app/src/main.tsx +25 -0
  52. package/registry/templates/app/tsconfig.json +20 -0
  53. package/registry/templates/app/vite.config.ts +46 -0
  54. package/registry/templates/monorepo/_gitignore +3 -0
  55. package/registry/templates/monorepo/_npmrc +1 -0
  56. package/registry/templates/monorepo/package.json +9 -0
  57. package/registry/templates/monorepo/pnpm-workspace.yaml +14 -0
  58. package/src/ai/ask.ts +64 -0
  59. package/src/ai/drivers/anthropic.ts +309 -0
  60. package/src/ai/index.ts +17 -0
  61. package/src/audit/drivers/console.ts +117 -0
  62. package/src/audit/drivers/pg.ts +172 -0
  63. package/src/audit/index.ts +51 -0
  64. package/src/auth/drivers/better-auth.ts +103 -0
  65. package/src/auth/drivers/jwt.ts +188 -0
  66. package/src/auth/index.ts +9 -0
  67. package/src/client/drivers/fetch.ts +202 -0
  68. package/src/client/index.ts +22 -0
  69. package/src/core/actions.ts +110 -0
  70. package/src/core/audit.ts +239 -0
  71. package/src/core/contracts.ts +137 -0
  72. package/src/core/domain.ts +310 -0
  73. package/src/core/errors.ts +181 -0
  74. package/src/core/index.ts +174 -0
  75. package/src/core/logical-type.ts +31 -0
  76. package/src/core/reactions.ts +81 -0
  77. package/src/core/runtime.ts +1167 -0
  78. package/src/core/schedules.ts +41 -0
  79. package/src/core/types.ts +1356 -0
  80. package/src/data/drivers/kysely.ts +389 -0
  81. package/src/data/index.ts +10 -0
  82. package/src/data/readonly-pool.ts +160 -0
  83. package/src/dsl/eval.ts +136 -0
  84. package/src/dsl/index.ts +29 -0
  85. package/src/dsl/kysely.ts +230 -0
  86. package/src/dsl/loads.ts +123 -0
  87. package/src/dsl/parser.ts +423 -0
  88. package/src/dsl/types.ts +113 -0
  89. package/src/events/drivers/mitt.ts +70 -0
  90. package/src/events/index.ts +9 -0
  91. package/src/log/drivers/pino.ts +57 -0
  92. package/src/log/index.ts +9 -0
  93. package/src/mcp/index.ts +62 -0
  94. package/src/queue/drivers/bullmq.ts +190 -0
  95. package/src/queue/index.ts +9 -0
  96. package/src/scheduler/drivers/node-cron.ts +93 -0
  97. package/src/scheduler/every.ts +45 -0
  98. package/src/scheduler/index.ts +9 -0
  99. package/src/schema/drivers/zod.ts +765 -0
  100. package/src/schema/entity.ts +439 -0
  101. package/src/schema/format/locale.ts +144 -0
  102. package/src/schema/index.ts +65 -0
  103. package/src/schema/openapi.ts +302 -0
  104. package/src/schema/scaffold.ts +160 -0
  105. package/src/server/drivers/fastify.ts +224 -0
  106. package/src/server/drivers/node.ts +386 -0
  107. package/src/server/index.ts +142 -0
  108. package/src/storage/drivers/fs.ts +90 -0
  109. package/src/storage/drivers/s3.ts +117 -0
  110. package/src/storage/index.ts +27 -0
  111. package/src/testing/fake.ts +298 -0
  112. package/src/testing/index.ts +324 -0
  113. package/src/ui/components/patterns/action-form-card.tsx +48 -0
  114. package/src/ui/components/patterns/action-list-dialog.tsx +93 -0
  115. package/src/ui/components/patterns/app-shell.tsx +227 -0
  116. package/src/ui/components/patterns/confirm.tsx +226 -0
  117. package/src/ui/components/patterns/data-state.tsx +75 -0
  118. package/src/ui/components/patterns/form-dialog.tsx +64 -0
  119. package/src/ui/components/patterns/form.tsx +584 -0
  120. package/src/ui/components/patterns/list.tsx +1488 -0
  121. package/src/ui/components/patterns/page.tsx +46 -0
  122. package/src/ui/components/patterns/section-shell.tsx +246 -0
  123. package/src/ui/components/patterns/shell-nav.tsx +150 -0
  124. package/src/ui/components/patterns/sidebar.tsx +89 -0
  125. package/src/ui/components/patterns/split.tsx +93 -0
  126. package/src/ui/components/patterns/trigger.tsx +196 -0
  127. package/src/ui/components/patterns/view.tsx +84 -0
  128. package/src/ui/components/primitives/accordion.tsx +64 -0
  129. package/src/ui/components/primitives/alert-dialog.tsx +190 -0
  130. package/src/ui/components/primitives/alert.tsx +116 -0
  131. package/src/ui/components/primitives/aspect-ratio.tsx +9 -0
  132. package/src/ui/components/primitives/avatar.tsx +107 -0
  133. package/src/ui/components/primitives/badge.tsx +37 -0
  134. package/src/ui/components/primitives/breadcrumb.tsx +109 -0
  135. package/src/ui/components/primitives/button-group.tsx +83 -0
  136. package/src/ui/components/primitives/button.tsx +102 -0
  137. package/src/ui/components/primitives/calendar.tsx +218 -0
  138. package/src/ui/components/primitives/card.tsx +56 -0
  139. package/src/ui/components/primitives/carousel.tsx +239 -0
  140. package/src/ui/components/primitives/chat.tsx +407 -0
  141. package/src/ui/components/primitives/checkbox.tsx +30 -0
  142. package/src/ui/components/primitives/collapsible.tsx +31 -0
  143. package/src/ui/components/primitives/command.tsx +182 -0
  144. package/src/ui/components/primitives/composer.tsx +121 -0
  145. package/src/ui/components/primitives/copyable.tsx +50 -0
  146. package/src/ui/components/primitives/dialog.tsx +147 -0
  147. package/src/ui/components/primitives/drawer.tsx +141 -0
  148. package/src/ui/components/primitives/empty.tsx +104 -0
  149. package/src/ui/components/primitives/field.tsx +246 -0
  150. package/src/ui/components/primitives/icon-picker.tsx +180 -0
  151. package/src/ui/components/primitives/input-group.tsx +168 -0
  152. package/src/ui/components/primitives/input-otp.tsx +75 -0
  153. package/src/ui/components/primitives/input.tsx +72 -0
  154. package/src/ui/components/primitives/item.tsx +193 -0
  155. package/src/ui/components/primitives/kbd.tsx +28 -0
  156. package/src/ui/components/primitives/label.tsx +22 -0
  157. package/src/ui/components/primitives/markdown.tsx +35 -0
  158. package/src/ui/components/primitives/menu.tsx +255 -0
  159. package/src/ui/components/primitives/pagination.tsx +127 -0
  160. package/src/ui/components/primitives/popover.tsx +87 -0
  161. package/src/ui/components/primitives/progress.tsx +29 -0
  162. package/src/ui/components/primitives/radio-group.tsx +43 -0
  163. package/src/ui/components/primitives/resizable.tsx +51 -0
  164. package/src/ui/components/primitives/scroll-area.tsx +56 -0
  165. package/src/ui/components/primitives/select.tsx +479 -0
  166. package/src/ui/components/primitives/separator.tsx +26 -0
  167. package/src/ui/components/primitives/skeleton.tsx +13 -0
  168. package/src/ui/components/primitives/slider.tsx +61 -0
  169. package/src/ui/components/primitives/sonner.tsx +46 -0
  170. package/src/ui/components/primitives/spinner.tsx +29 -0
  171. package/src/ui/components/primitives/switch.tsx +33 -0
  172. package/src/ui/components/primitives/table.tsx +114 -0
  173. package/src/ui/components/primitives/tabs.tsx +104 -0
  174. package/src/ui/components/primitives/textarea.tsx +18 -0
  175. package/src/ui/components/primitives/toggle-group.tsx +81 -0
  176. package/src/ui/components/primitives/toggle.tsx +45 -0
  177. package/src/ui/components/primitives/tooltip.tsx +55 -0
  178. package/src/ui/components/primitives/truncate.tsx +49 -0
  179. package/src/ui/docs/DocBrowser.tsx +90 -0
  180. package/src/ui/docs/changelog.tsx +80 -0
  181. package/src/ui/docs/content/accordion.md +86 -0
  182. package/src/ui/docs/content/action-form-card.md +24 -0
  183. package/src/ui/docs/content/action-form-dialog.md +30 -0
  184. package/src/ui/docs/content/action-form.md +125 -0
  185. package/src/ui/docs/content/action-list-dialog.md +68 -0
  186. package/src/ui/docs/content/action-list.md +194 -0
  187. package/src/ui/docs/content/action-trigger.md +72 -0
  188. package/src/ui/docs/content/action-view.md +47 -0
  189. package/src/ui/docs/content/actions.md +138 -0
  190. package/src/ui/docs/content/ai.md +112 -0
  191. package/src/ui/docs/content/alert-dialog.md +73 -0
  192. package/src/ui/docs/content/alert.md +69 -0
  193. package/src/ui/docs/content/app-shell.md +155 -0
  194. package/src/ui/docs/content/aspect-ratio.md +66 -0
  195. package/src/ui/docs/content/audit.md +84 -0
  196. package/src/ui/docs/content/auth.md +70 -0
  197. package/src/ui/docs/content/avatar.md +94 -0
  198. package/src/ui/docs/content/badge.md +48 -0
  199. package/src/ui/docs/content/breadcrumb.md +87 -0
  200. package/src/ui/docs/content/button-group.md +71 -0
  201. package/src/ui/docs/content/button.md +60 -0
  202. package/src/ui/docs/content/calendar.md +62 -0
  203. package/src/ui/docs/content/card.md +49 -0
  204. package/src/ui/docs/content/carousel.md +85 -0
  205. package/src/ui/docs/content/chat.md +69 -0
  206. package/src/ui/docs/content/checkbox.md +75 -0
  207. package/src/ui/docs/content/cli.md +58 -0
  208. package/src/ui/docs/content/collapsible.md +64 -0
  209. package/src/ui/docs/content/command.md +56 -0
  210. package/src/ui/docs/content/composer.md +50 -0
  211. package/src/ui/docs/content/confirm.md +120 -0
  212. package/src/ui/docs/content/copyable.md +30 -0
  213. package/src/ui/docs/content/customization.md +110 -0
  214. package/src/ui/docs/content/cycle.md +34 -0
  215. package/src/ui/docs/content/data-state.md +47 -0
  216. package/src/ui/docs/content/data.md +99 -0
  217. package/src/ui/docs/content/dialog.md +60 -0
  218. package/src/ui/docs/content/drawer.md +55 -0
  219. package/src/ui/docs/content/empty.md +66 -0
  220. package/src/ui/docs/content/events.md +61 -0
  221. package/src/ui/docs/content/field.md +58 -0
  222. package/src/ui/docs/content/getting-started.md +109 -0
  223. package/src/ui/docs/content/icon-picker.md +51 -0
  224. package/src/ui/docs/content/input-group.md +78 -0
  225. package/src/ui/docs/content/input-otp.md +72 -0
  226. package/src/ui/docs/content/input.md +78 -0
  227. package/src/ui/docs/content/item.md +84 -0
  228. package/src/ui/docs/content/kbd.md +62 -0
  229. package/src/ui/docs/content/label.md +32 -0
  230. package/src/ui/docs/content/log.md +55 -0
  231. package/src/ui/docs/content/markdown.md +41 -0
  232. package/src/ui/docs/content/mcp.md +44 -0
  233. package/src/ui/docs/content/menu.md +114 -0
  234. package/src/ui/docs/content/microcopy.md +83 -0
  235. package/src/ui/docs/content/page.md +34 -0
  236. package/src/ui/docs/content/pagination.md +99 -0
  237. package/src/ui/docs/content/popover.md +49 -0
  238. package/src/ui/docs/content/progress.md +69 -0
  239. package/src/ui/docs/content/queue.md +62 -0
  240. package/src/ui/docs/content/radio-group.md +77 -0
  241. package/src/ui/docs/content/resizable.md +86 -0
  242. package/src/ui/docs/content/router.md +56 -0
  243. package/src/ui/docs/content/runtime.md +77 -0
  244. package/src/ui/docs/content/scheduler.md +66 -0
  245. package/src/ui/docs/content/scroll-area.md +89 -0
  246. package/src/ui/docs/content/section-shell.md +121 -0
  247. package/src/ui/docs/content/select.md +342 -0
  248. package/src/ui/docs/content/separator.md +33 -0
  249. package/src/ui/docs/content/sidebar.md +38 -0
  250. package/src/ui/docs/content/skeleton.md +34 -0
  251. package/src/ui/docs/content/slider.md +64 -0
  252. package/src/ui/docs/content/spinner.md +37 -0
  253. package/src/ui/docs/content/split.md +33 -0
  254. package/src/ui/docs/content/storage.md +69 -0
  255. package/src/ui/docs/content/switch.md +69 -0
  256. package/src/ui/docs/content/table.md +102 -0
  257. package/src/ui/docs/content/tabs.md +94 -0
  258. package/src/ui/docs/content/testing.md +89 -0
  259. package/src/ui/docs/content/textarea.md +30 -0
  260. package/src/ui/docs/content/toast.md +67 -0
  261. package/src/ui/docs/content/toggle-group.md +81 -0
  262. package/src/ui/docs/content/toggle.md +72 -0
  263. package/src/ui/docs/content/tokens.md +171 -0
  264. package/src/ui/docs/content/tooltip.md +50 -0
  265. package/src/ui/docs/content/truncate.md +37 -0
  266. package/src/ui/docs/content/ui.md +40 -0
  267. package/src/ui/docs/content/upgrading.md +48 -0
  268. package/src/ui/docs/doc-client.tsx +214 -0
  269. package/src/ui/docs/doc.tsx +301 -0
  270. package/src/ui/docs/folder.tsx +149 -0
  271. package/src/ui/docs/index.ts +21 -0
  272. package/src/ui/docs/markdown.tsx +130 -0
  273. package/src/ui/docs/md-raw.d.ts +4 -0
  274. package/src/ui/docs/plugin.ts +104 -0
  275. package/src/ui/docs/registry.tsx +424 -0
  276. package/src/ui/docs/standalone.tsx +107 -0
  277. package/src/ui/drivers/react.tsx +627 -0
  278. package/src/ui/index.ts +92 -0
  279. package/src/ui/lib/cn.ts +10 -0
  280. package/src/ui/lib/zod-pt-br.ts +38 -0
  281. package/src/ui/meta.ts +412 -0
  282. package/src/ui/react.tsx +235 -0
  283. package/src/ui/router.ts +96 -0
  284. package/src/ui/theme.css +234 -0
  285. package/src/vite/design.ts +652 -0
  286. package/src/vite/index.ts +8 -0
@@ -0,0 +1,463 @@
1
+ /**
2
+ * gen-stubs — emite stubs TypeScript do tipo "ActionDef sem handler" pro
3
+ * frontend.
4
+ *
5
+ * Reconstrói o schema Zod do `input` (e `output` quando útil) a partir do
6
+ * JSON Schema serializado pelo runner, evitando que o consumer redeclare
7
+ * schemas só pra usar `useFormAction`. Cobre os tipos básicos (string,
8
+ * number, boolean, array, object, enum, const, union via anyOf/oneOf,
9
+ * default, optional via required[]). Refinements custom (.refine,
10
+ * .transform, .pipe) NÃO viajam via JSON Schema — emite warning no topo
11
+ * do arquivo pra deixar explícito que o schema reconstruído é approximação.
12
+ *
13
+ * Pra metadata extra que o frontend consome (label, icon, color,
14
+ * messages, fields, emits, invalidates, successStatus), copia direto do
15
+ * manifest. Closures (`authorize`, `invalidates: () => [...]`) viram
16
+ * `<function>` no manifest — emitidos como string literal com comment
17
+ * "// computado em runtime" pra avisar que o stub não roda essa lógica.
18
+ *
19
+ * Estrutura emitida (um arquivo por domínio raiz, achata subdomains):
20
+ *
21
+ * // AUTO-GERADO por @softize/opus gen. Não edite.
22
+ * // Refinements custom (.refine/.transform/.pipe) NÃO preservados.
23
+ * import { z } from 'zod'
24
+ * import { defineAction } from '@softize/opus/core'
25
+ *
26
+ * function clientOnly(): never { throw new Error('...') }
27
+ *
28
+ * export const ticketOpen = defineAction({ ... })
29
+ *
30
+ * Identifier do export: camelCase do nome (`ticket.open` → `ticketOpen`,
31
+ * `shipment.flag.create` → `shipmentFlagCreate`).
32
+ */
33
+
34
+ export function buildClientStubs(manifest) {
35
+ const out = []
36
+ for (const domain of manifest.domains) {
37
+ out.push({
38
+ filename: `${domain.name}.ts`,
39
+ content: renderDomainStubs(domain),
40
+ })
41
+ }
42
+ return out
43
+ }
44
+
45
+ function renderDomainStubs(domain) {
46
+ const actions = []
47
+ collectActions(domain, actions)
48
+
49
+ const lines = []
50
+ lines.push('// AUTO-GERADO por @softize/opus gen. Não edite.')
51
+ lines.push(`// Domínio raiz: ${domain.name}`)
52
+ lines.push(
53
+ '// Schemas Zod abaixo são reconstruções a partir do JSON Schema —',
54
+ )
55
+ lines.push(
56
+ '// refinements custom (.refine / .transform / .pipe) NÃO preservam.',
57
+ )
58
+ lines.push('')
59
+ lines.push("import { z } from 'zod'")
60
+ lines.push("import { defineAction } from '@softize/opus/core'")
61
+ lines.push('')
62
+ lines.push(
63
+ "function clientOnly(): never { throw new Error('[opus stub] handler chamado no cliente — use o fetchClient pra invocar via HTTP') }",
64
+ )
65
+ lines.push('')
66
+
67
+ for (const action of actions) {
68
+ lines.push(...renderActionStub(action))
69
+ lines.push('')
70
+ }
71
+ return lines.join('\n').trimEnd() + '\n'
72
+ }
73
+
74
+ function collectActions(domain, out) {
75
+ for (const action of domain.actions) out.push(action)
76
+ if (Array.isArray(domain.subdomains)) {
77
+ for (const sub of domain.subdomains) collectActions(sub, out)
78
+ }
79
+ }
80
+
81
+ function renderActionStub(action) {
82
+ const id = toCamelCase(action.name)
83
+ const lines = []
84
+ if (typeof action.summary === 'string' && action.summary.length > 0) {
85
+ lines.push(`/** ${action.summary.replace(/\*\//g, '* /')} */`)
86
+ }
87
+ lines.push(`export const ${id} = defineAction({`)
88
+ lines.push(` name: ${JSON.stringify(action.name)},`)
89
+ lines.push(` kind: ${JSON.stringify(action.kind)},`)
90
+
91
+ // Metadata leve antes do schema pra ficar parecido com o source.
92
+ appendStringProp(lines, 'label', action.label)
93
+ appendStringProp(lines, 'icon', action.icon)
94
+ appendStringProp(lines, 'color', action.color)
95
+ appendStringProp(lines, 'title', action.title)
96
+ appendStringProp(lines, 'summary', action.summary)
97
+ if (Array.isArray(action.tags) && action.tags.length > 0) {
98
+ lines.push(` tags: ${JSON.stringify(action.tags)},`)
99
+ }
100
+ if (
101
+ action.messages !== null &&
102
+ action.messages !== undefined &&
103
+ typeof action.messages === 'object'
104
+ ) {
105
+ lines.push(` messages: ${formatObjectLiteral(action.messages, ' ')},`)
106
+ }
107
+ if (typeof action.successStatus === 'number') {
108
+ lines.push(` successStatus: ${action.successStatus},`)
109
+ }
110
+ if (action.public === true) lines.push(' public: true,')
111
+ if (action.internal === true) lines.push(' internal: true,')
112
+ if (
113
+ (action.kind === 'simple' || action.kind === 'form') &&
114
+ action.background === true
115
+ ) {
116
+ lines.push(' background: { enabled: true },')
117
+ }
118
+ if (
119
+ (action.kind === 'simple' || action.kind === 'form') &&
120
+ Array.isArray(action.emits) &&
121
+ action.emits.length > 0
122
+ ) {
123
+ lines.push(` emits: ${JSON.stringify(action.emits)},`)
124
+ }
125
+ if (action.permission !== null && action.permission !== undefined) {
126
+ lines.push(` requires: ${JSON.stringify(action.permission)},`)
127
+ }
128
+
129
+ // input/output — Zod reconstruído.
130
+ const inputExpr = renderZodFromJsonSchema(action.input, /*indent*/ ' ')
131
+ lines.push(` input: ${inputExpr},`)
132
+ const outputExpr = renderZodFromJsonSchema(action.output, /*indent*/ ' ')
133
+ lines.push(` output: ${outputExpr} as never,`)
134
+
135
+ if (action.kind === 'form') {
136
+ const fields = stripNullsFromFields(action.fields)
137
+ if (Object.keys(fields).length > 0) {
138
+ lines.push(` fields: ${formatObjectLiteral(fields, ' ')},`)
139
+ } else {
140
+ lines.push(' fields: {} as never,')
141
+ }
142
+ }
143
+ if (action.kind === 'view') {
144
+ const projection = Array.isArray(action.projection) ? action.projection : []
145
+ lines.push(` projection: ${JSON.stringify(projection)},`)
146
+ }
147
+ if (action.kind === 'list') {
148
+ if (action.filters && Object.keys(action.filters).length > 0) {
149
+ const filters = stripNullsFromFields(action.filters)
150
+ lines.push(` filters: ${formatObjectLiteral(filters, ' ')},`)
151
+ }
152
+ }
153
+ if (Array.isArray(action.invalidates) && action.invalidates.length > 0) {
154
+ lines.push(` invalidates: ${JSON.stringify(action.invalidates)},`)
155
+ } else if (action.invalidates === '<function>') {
156
+ lines.push(' // invalidates: computado em runtime — não preservado no stub')
157
+ }
158
+
159
+ lines.push(' handler: clientOnly,')
160
+ lines.push('})')
161
+ return lines
162
+ }
163
+
164
+ function appendStringProp(lines, key, value) {
165
+ if (typeof value !== 'string' || value.length === 0) return
166
+ lines.push(` ${key}: ${JSON.stringify(value)},`)
167
+ }
168
+
169
+ /**
170
+ * Field/filter specs vêm do manifest com todos os slots em null.
171
+ * Remove os nulls e drops `hasShowWhen`/`hasRequireWhen` (são meta-flags
172
+ * do runner, não viajam pro stub).
173
+ */
174
+ function stripNullsFromFields(record) {
175
+ if (record === null || record === undefined || typeof record !== 'object') {
176
+ return {}
177
+ }
178
+ const out = {}
179
+ for (const [name, spec] of Object.entries(record)) {
180
+ if (spec === null || typeof spec !== 'object') continue
181
+ const clean = {}
182
+ for (const [k, v] of Object.entries(spec)) {
183
+ if (v === null || v === undefined) continue
184
+ if (k === 'hasShowWhen' || k === 'hasRequireWhen') continue
185
+ if (typeof v === 'boolean' && v === false) continue
186
+ clean[k] = v
187
+ }
188
+ out[name] = clean
189
+ }
190
+ return out
191
+ }
192
+
193
+ /**
194
+ * Serializa um objeto JS plano como literal TS indentado.
195
+ * Valores são serializados via JSON.stringify (seguro pra strings com
196
+ * aspas, booleans, numbers, arrays e objetos aninhados).
197
+ */
198
+ function formatObjectLiteral(obj, indent) {
199
+ const inner = indent + ' '
200
+ const entries = Object.entries(obj)
201
+ if (entries.length === 0) return '{}'
202
+ const lines = ['{']
203
+ for (const [k, v] of entries) {
204
+ lines.push(`${inner}${formatKey(k)}: ${formatValue(v, inner)},`)
205
+ }
206
+ lines.push(`${indent}}`)
207
+ return lines.join('\n')
208
+ }
209
+
210
+ function formatValue(value, indent) {
211
+ if (value === null || value === undefined) return 'null'
212
+ if (typeof value === 'string') return JSON.stringify(value)
213
+ if (typeof value === 'number' || typeof value === 'boolean') {
214
+ return JSON.stringify(value)
215
+ }
216
+ if (Array.isArray(value)) return JSON.stringify(value)
217
+ if (typeof value === 'object') return formatObjectLiteral(value, indent)
218
+ return JSON.stringify(value)
219
+ }
220
+
221
+ function formatKey(key) {
222
+ if (typeof key !== 'string') return JSON.stringify(String(key))
223
+ if (/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(key)) return key
224
+ return JSON.stringify(key)
225
+ }
226
+
227
+ // =============================================================================
228
+ // JSON Schema → Zod
229
+ // =============================================================================
230
+
231
+ /**
232
+ * Reconstrói expressão Zod a partir do JSON Schema serializado.
233
+ * Fallback pra `z.any()` quando shape não é reconhecido. NUNCA lança —
234
+ * pra evitar emitir arquivo broken; pior caso, devolve `z.any()`.
235
+ */
236
+ function renderZodFromJsonSchema(schema, indent) {
237
+ if (schema === null || schema === undefined) return 'z.any()'
238
+ if (typeof schema !== 'object') return 'z.any()'
239
+ if (schema.__unserializable === true) return 'z.any()'
240
+
241
+ // const X
242
+ if (Object.prototype.hasOwnProperty.call(schema, 'const')) {
243
+ const expr = `z.literal(${JSON.stringify(schema.const)})`
244
+ return wrapDefault(expr, schema)
245
+ }
246
+
247
+ // enum-only (sem type)
248
+ if (Array.isArray(schema.enum) && schema.type === undefined) {
249
+ const expr = renderEnum(schema.enum)
250
+ return wrapDefault(expr, schema)
251
+ }
252
+
253
+ // anyOf / oneOf → z.union
254
+ if (Array.isArray(schema.anyOf) || Array.isArray(schema.oneOf)) {
255
+ const branches = schema.anyOf ?? schema.oneOf
256
+ const rendered = branches.map((b) => renderZodFromJsonSchema(b, indent + ' '))
257
+ let expr
258
+ if (rendered.length === 1) {
259
+ expr = rendered[0]
260
+ } else if (rendered.length === 2) {
261
+ expr = `z.union([${rendered.join(', ')}])`
262
+ } else {
263
+ expr = `z.union([\n${indent} ${rendered.join(`,\n${indent} `)},\n${indent}])`
264
+ }
265
+ return wrapDefault(expr, schema)
266
+ }
267
+
268
+ // allOf → tentar merge object schemas; fallback pra z.any().
269
+ if (Array.isArray(schema.allOf)) {
270
+ // Heurística simples: se todos são objetos, mescla properties+required.
271
+ const merged = mergeAllOf(schema.allOf)
272
+ if (merged !== null) {
273
+ return renderZodFromJsonSchema(merged, indent)
274
+ }
275
+ return 'z.any()'
276
+ }
277
+
278
+ const t = schema.type
279
+ // type array (ex: ["string", "null"]) → union escalar.
280
+ if (Array.isArray(t)) {
281
+ const branches = t.map((tn) =>
282
+ renderZodFromJsonSchema({ ...schema, type: tn }, indent + ' '),
283
+ )
284
+ const expr = branches.length === 1 ? branches[0] : `z.union([${branches.join(', ')}])`
285
+ return wrapDefault(expr, schema)
286
+ }
287
+
288
+ if (t === 'string') {
289
+ let expr = renderStringSchema(schema)
290
+ return wrapDefault(expr, schema)
291
+ }
292
+
293
+ if (t === 'integer' || t === 'number') {
294
+ let expr = 'z.number()'
295
+ if (t === 'integer') expr += '.int()'
296
+ if (typeof schema.minimum === 'number') expr += `.min(${schema.minimum})`
297
+ if (typeof schema.maximum === 'number') expr += `.max(${schema.maximum})`
298
+ if (typeof schema.exclusiveMinimum === 'number') {
299
+ expr += `.gt(${schema.exclusiveMinimum})`
300
+ }
301
+ if (typeof schema.exclusiveMaximum === 'number') {
302
+ expr += `.lt(${schema.exclusiveMaximum})`
303
+ }
304
+ return wrapDefault(expr, schema)
305
+ }
306
+
307
+ if (t === 'boolean') {
308
+ return wrapDefault('z.boolean()', schema)
309
+ }
310
+
311
+ if (t === 'null') {
312
+ return wrapDefault('z.null()', schema)
313
+ }
314
+
315
+ if (t === 'array') {
316
+ const items = schema.items
317
+ const inner = renderZodFromJsonSchema(items, indent + ' ')
318
+ let expr = `z.array(${inner})`
319
+ if (typeof schema.minItems === 'number') expr += `.min(${schema.minItems})`
320
+ if (typeof schema.maxItems === 'number') expr += `.max(${schema.maxItems})`
321
+ return wrapDefault(expr, schema)
322
+ }
323
+
324
+ if (t === 'object') {
325
+ return renderObjectSchema(schema, indent)
326
+ }
327
+
328
+ // Sem type mas com properties → object implícito.
329
+ if (
330
+ typeof schema.properties === 'object' &&
331
+ schema.properties !== null
332
+ ) {
333
+ return renderObjectSchema({ ...schema, type: 'object' }, indent)
334
+ }
335
+
336
+ return 'z.any()'
337
+ }
338
+
339
+ function renderStringSchema(schema) {
340
+ // Casos especiais por format.
341
+ if (schema.format === 'date-time') {
342
+ let expr = 'z.string().datetime()'
343
+ return expr
344
+ }
345
+ let expr = 'z.string()'
346
+ if (typeof schema.minLength === 'number') {
347
+ expr += `.min(${schema.minLength})`
348
+ }
349
+ if (typeof schema.maxLength === 'number') {
350
+ expr += `.max(${schema.maxLength})`
351
+ }
352
+ if (typeof schema.pattern === 'string') {
353
+ // Regex preservada como `RegExp` literal. Escape de slashes resolvido
354
+ // por JSON.stringify + casting via `new RegExp`.
355
+ expr += `.regex(new RegExp(${JSON.stringify(schema.pattern)}))`
356
+ }
357
+ if (typeof schema.format === 'string') {
358
+ if (schema.format === 'email') expr += '.email()'
359
+ else if (schema.format === 'uri') expr += '.url()'
360
+ else if (schema.format === 'uuid') expr += '.uuid()'
361
+ // outros formats viram `as any` no zod, ignorados.
362
+ }
363
+ if (Array.isArray(schema.enum)) {
364
+ // type: 'string' + enum → z.enum
365
+ return renderEnum(schema.enum)
366
+ }
367
+ return expr
368
+ }
369
+
370
+ function renderObjectSchema(schema, indent) {
371
+ const props = schema.properties
372
+ if (props === null || typeof props !== 'object') {
373
+ return 'z.object({})'
374
+ }
375
+ const required = new Set(
376
+ Array.isArray(schema.required) ? schema.required : [],
377
+ )
378
+ const inner = indent + ' '
379
+ const lines = ['z.object({']
380
+ for (const [key, prop] of Object.entries(props)) {
381
+ let value = renderZodFromJsonSchema(prop, inner)
382
+ const isReq = required.has(key)
383
+ // Detecta nullable do JSON Schema (target openApi3 usa `nullable: true`).
384
+ if (prop && typeof prop === 'object' && prop.nullable === true) {
385
+ value += '.nullable()'
386
+ }
387
+ const hasDefault =
388
+ prop !== null &&
389
+ typeof prop === 'object' &&
390
+ Object.prototype.hasOwnProperty.call(prop, 'default')
391
+ // `.default()` já implica opcional no input; evita encadear `.optional()` redundante.
392
+ if (!isReq && !hasDefault) value += '.optional()'
393
+ lines.push(`${inner}${formatKey(key)}: ${value},`)
394
+ }
395
+ lines.push(`${indent}})`)
396
+ let expr = lines.join('\n')
397
+ // additionalProperties: true → .passthrough(); false (default no zod) já vem.
398
+ if (schema.additionalProperties === true) {
399
+ expr += '.passthrough()'
400
+ }
401
+ return wrapDefault(expr, schema)
402
+ }
403
+
404
+ /**
405
+ * Detecta enum de strings e emite `z.enum([...])`; fallback union de
406
+ * literais pra outros tipos.
407
+ */
408
+ function renderEnum(values) {
409
+ if (!Array.isArray(values) || values.length === 0) {
410
+ return 'z.never()'
411
+ }
412
+ const allStrings = values.every((v) => typeof v === 'string')
413
+ if (allStrings) {
414
+ // `as const` ajuda inferência mas zod aceita literal-array direto.
415
+ return `z.enum([${values.map((v) => JSON.stringify(v)).join(', ')}])`
416
+ }
417
+ const literals = values.map((v) => `z.literal(${JSON.stringify(v)})`)
418
+ if (literals.length === 1) return literals[0]
419
+ return `z.union([${literals.join(', ')}])`
420
+ }
421
+
422
+ function wrapDefault(expr, schema) {
423
+ if (!Object.prototype.hasOwnProperty.call(schema, 'default')) return expr
424
+ const def = schema.default
425
+ return `${expr}.default(${JSON.stringify(def)})`
426
+ }
427
+
428
+ /**
429
+ * Tenta achatar `allOf` mesclando properties+required de schemas-objeto.
430
+ * Retorna null se qualquer branch não for objeto.
431
+ */
432
+ function mergeAllOf(branches) {
433
+ if (!Array.isArray(branches) || branches.length === 0) return null
434
+ const merged = { type: 'object', properties: {}, required: [] }
435
+ for (const b of branches) {
436
+ if (b === null || typeof b !== 'object') return null
437
+ if (b.type !== 'object' && b.type !== undefined) return null
438
+ if (typeof b.properties === 'object' && b.properties !== null) {
439
+ Object.assign(merged.properties, b.properties)
440
+ }
441
+ if (Array.isArray(b.required)) {
442
+ for (const r of b.required) {
443
+ if (!merged.required.includes(r)) merged.required.push(r)
444
+ }
445
+ }
446
+ }
447
+ return merged
448
+ }
449
+
450
+ /**
451
+ * `ticket.open` → `ticketOpen`. `shipment.flag.create` → `shipmentFlagCreate`.
452
+ * Mantém prefixo do nome — não tem colisão em domínio raiz pq actions são
453
+ * globalmente únicas no `defineDomain`.
454
+ */
455
+ function toCamelCase(name) {
456
+ const parts = name.split('.')
457
+ if (parts.length === 0) return name
458
+ return parts
459
+ .map((p, i) =>
460
+ i === 0 ? p : p.charAt(0).toUpperCase() + p.slice(1),
461
+ )
462
+ .join('')
463
+ }