@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,134 @@
1
+ /**
2
+ * gen-dicts — emite módulos TS por domínio com `t.dict({...})`
3
+ * reconstruído a partir do manifest.
4
+ *
5
+ * Por quê: o `manifest.json` carrega `dicts: { Name: { keys, values } }`,
6
+ * mas o frontend precisa do código TS pra usar `dict.has() / .metaFor() /
7
+ * .options()` com inferência de tipo. Sem isso o consumer mantinha cópia
8
+ * manual sincronizada à mão — bug-source clássico.
9
+ *
10
+ * Estrutura emitida (um arquivo por domínio raiz, achata subdomains numa
11
+ * mesma saída pra refletir o agrupamento dos outros emits):
12
+ *
13
+ * // AUTO-GERADO por @softize/opus gen. Não edite.
14
+ * // Domínio raiz: ticket
15
+ *
16
+ * import { t } from '@softize/opus/schema/zod'
17
+ *
18
+ * export const TicketStatus = t.dict({
19
+ * open: { label: 'Aberto', color: 'info' },
20
+ * ...
21
+ * })
22
+ *
23
+ * Edge cases:
24
+ * - Domínio sem dicts → não emite arquivo.
25
+ * - Dict com `unknownShape: true` (vinda do runner pra dict-like
26
+ * irreconhecível) → emite `t.dict({}) as never` com warning no topo.
27
+ * - Valores em entries serializados via JSON.stringify pra escape correto.
28
+ */
29
+
30
+ export function buildDictStubs(manifest) {
31
+ const out = []
32
+ for (const domain of manifest.domains) {
33
+ const dicts = collectDicts(domain)
34
+ if (dicts.length === 0) continue
35
+ out.push({
36
+ filename: `${domain.name}.ts`,
37
+ content: renderDomainDicts(domain.name, dicts),
38
+ })
39
+ }
40
+ return out
41
+ }
42
+
43
+ /**
44
+ * Agrupa dicts do domínio raiz + subdomains em uma única lista.
45
+ * Nome do export = chave declarada no domínio (`TicketStatus`, etc).
46
+ * Conflitos entre subdomínios são improváveis (dicts moram em namespaces
47
+ * separados na config) mas se acontecerem, ganha o primeiro encontrado.
48
+ */
49
+ function collectDicts(domain) {
50
+ const seen = new Set()
51
+ const out = []
52
+ walk(domain)
53
+ function walk(d) {
54
+ if (d.dicts !== null && typeof d.dicts === 'object') {
55
+ for (const [name, dict] of Object.entries(d.dicts)) {
56
+ if (seen.has(name)) continue
57
+ seen.add(name)
58
+ out.push({ name, dict })
59
+ }
60
+ }
61
+ if (Array.isArray(d.subdomains)) {
62
+ for (const sub of d.subdomains) walk(sub)
63
+ }
64
+ }
65
+ return out
66
+ }
67
+
68
+ function renderDomainDicts(domainName, dicts) {
69
+ const lines = []
70
+ lines.push('// AUTO-GERADO por @softize/opus gen. Não edite.')
71
+ lines.push(`// Domínio raiz: ${domainName}`)
72
+ lines.push('')
73
+ lines.push("import { t } from '@softize/opus/schema/zod'")
74
+ lines.push('')
75
+ for (const { name, dict } of dicts) {
76
+ lines.push(...renderDict(name, dict))
77
+ lines.push('')
78
+ }
79
+ return lines.join('\n').trimEnd() + '\n'
80
+ }
81
+
82
+ function renderDict(name, dict) {
83
+ const lines = []
84
+ if (dict.unknownShape === true) {
85
+ lines.push(
86
+ `// AVISO: dict "${name}" tinha shape irreconhecível no runner — emit vazio.`,
87
+ )
88
+ lines.push(`export const ${name} = t.dict({} as Record<string, { label: string }>)`)
89
+ return lines
90
+ }
91
+ const keys = Array.isArray(dict.keys) ? dict.keys : []
92
+ const values = dict.values ?? {}
93
+ if (keys.length === 0) {
94
+ lines.push(
95
+ `// AVISO: dict "${name}" não tinha chaves no manifest — emit vazio.`,
96
+ )
97
+ lines.push(`export const ${name} = t.dict({} as Record<string, { label: string }>)`)
98
+ return lines
99
+ }
100
+ lines.push(`export const ${name} = t.dict({`)
101
+ for (const key of keys) {
102
+ const entry = values[key]
103
+ lines.push(` ${formatKey(key)}: ${renderEntry(entry)},`)
104
+ }
105
+ lines.push('})')
106
+ return lines
107
+ }
108
+
109
+ /**
110
+ * Emite a chave como identifier nu (`open`) quando for válido em TS,
111
+ * senão como literal string (`'foo-bar'`).
112
+ */
113
+ function formatKey(key) {
114
+ if (typeof key !== 'string') return JSON.stringify(String(key))
115
+ if (/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(key)) return key
116
+ return JSON.stringify(key)
117
+ }
118
+
119
+ function renderEntry(entry) {
120
+ if (entry === null || entry === undefined || typeof entry !== 'object') {
121
+ return '{ label: "" }'
122
+ }
123
+ const parts = []
124
+ // `label` primeiro pra preservar leitura.
125
+ if (Object.prototype.hasOwnProperty.call(entry, 'label')) {
126
+ parts.push(`label: ${JSON.stringify(entry.label ?? '')}`)
127
+ }
128
+ for (const [k, v] of Object.entries(entry)) {
129
+ if (k === 'label') continue
130
+ parts.push(`${formatKey(k)}: ${JSON.stringify(v)}`)
131
+ }
132
+ if (parts.length === 0) parts.push('label: ""')
133
+ return `{ ${parts.join(', ')} }`
134
+ }
@@ -0,0 +1,288 @@
1
+ /**
2
+ * gen-docs — emite Markdown human-readable por domínio.
3
+ *
4
+ * Um arquivo por domínio raiz (subdomains entram embaixo do raiz pra
5
+ * preservar agrupamento). Estrutura:
6
+ *
7
+ * # Domínio: <name>
8
+ * ## Dicts
9
+ * ## Actions
10
+ * ## Reactions
11
+ * ## Schedules
12
+ * ## Subdomínios
13
+ * ### <subname>
14
+ *
15
+ * Foco em legibilidade — não é spec, é overview pra humano. Schema cru
16
+ * fica no manifest; aqui só descrição amigável.
17
+ */
18
+
19
+ export function buildDocs(manifest) {
20
+ const out = []
21
+ for (const domain of manifest.domains) {
22
+ out.push({
23
+ filename: `${domain.name}.md`,
24
+ content: renderDomain(domain, 1),
25
+ })
26
+ }
27
+ return out
28
+ }
29
+
30
+ function renderDomain(domain, level) {
31
+ const heading = '#'.repeat(level)
32
+ const sub = '#'.repeat(level + 1)
33
+ const lines = []
34
+ lines.push(`${heading} Domínio: ${domain.name}`)
35
+ lines.push('')
36
+
37
+ if (Object.keys(domain.dicts).length > 0) {
38
+ lines.push(`${sub} Dicts`)
39
+ lines.push('')
40
+ for (const [name, dict] of Object.entries(domain.dicts)) {
41
+ lines.push(...renderDict(name, dict, level + 2))
42
+ }
43
+ }
44
+
45
+ if (domain.actions.length > 0) {
46
+ lines.push(`${sub} Actions`)
47
+ lines.push('')
48
+ for (const action of domain.actions) {
49
+ lines.push(...renderAction(action, level + 2))
50
+ }
51
+ }
52
+
53
+ if (domain.reactions.length > 0) {
54
+ lines.push(`${sub} Reactions`)
55
+ lines.push('')
56
+ for (const reaction of domain.reactions) {
57
+ lines.push(...renderReaction(reaction, level + 2))
58
+ }
59
+ }
60
+
61
+ if (domain.schedules.length > 0) {
62
+ lines.push(`${sub} Schedules`)
63
+ lines.push('')
64
+ for (const schedule of domain.schedules) {
65
+ lines.push(...renderSchedule(schedule, level + 2))
66
+ }
67
+ }
68
+
69
+ if (domain.subdomains.length > 0) {
70
+ lines.push(`${sub} Subdomínios`)
71
+ lines.push('')
72
+ for (const subdomain of domain.subdomains) {
73
+ lines.push(renderDomain(subdomain, level + 2))
74
+ }
75
+ }
76
+
77
+ return lines.join('\n').trimEnd() + '\n'
78
+ }
79
+
80
+ function renderDict(name, dict, level) {
81
+ const heading = '#'.repeat(level)
82
+ const lines = []
83
+ lines.push(`${heading} ${name}`)
84
+ lines.push('')
85
+ // Tabela: detecta colunas extras (color, icon, order, ...) dinamicamente
86
+ // a partir das chaves de qualquer entry.
87
+ const allCols = new Set(['label'])
88
+ for (const v of Object.values(dict.values)) {
89
+ if (v === null || typeof v !== 'object') continue
90
+ for (const k of Object.keys(v)) allCols.add(k)
91
+ }
92
+ const cols = ['value', ...allCols]
93
+ lines.push(`| ${cols.join(' | ')} |`)
94
+ lines.push(`| ${cols.map(() => '---').join(' | ')} |`)
95
+ for (const key of dict.keys) {
96
+ const entry = dict.values[key]
97
+ const row = cols.map((col) => {
98
+ if (col === 'value') return key
99
+ if (entry === null || typeof entry !== 'object') return ''
100
+ const v = entry[col]
101
+ if (v === undefined || v === null) return ''
102
+ return String(v)
103
+ })
104
+ lines.push(`| ${row.join(' | ')} |`)
105
+ }
106
+ lines.push('')
107
+ return lines
108
+ }
109
+
110
+ function renderAction(action, level) {
111
+ const heading = '#'.repeat(level)
112
+ const lines = []
113
+ lines.push(`${heading} ${action.name} (${action.kind})`)
114
+ lines.push('')
115
+ if (typeof action.summary === 'string') {
116
+ lines.push(`> ${action.summary}`)
117
+ lines.push('')
118
+ } else if (typeof action.description === 'string') {
119
+ lines.push(`> ${action.description.split('\n')[0]}`)
120
+ lines.push('')
121
+ }
122
+
123
+ const bullets = []
124
+ if (Array.isArray(action.permission)) {
125
+ // requires em lista = ANY: qualquer uma das permissões alcança a action.
126
+ bullets.push(`**Permission:** ${action.permission.map((p) => `\`${p}\``).join(' | ')} (qualquer uma)`)
127
+ } else if (action.permission !== null) {
128
+ bullets.push(`**Permission:** \`${action.permission}\``)
129
+ } else if (action.public === true) {
130
+ bullets.push('**Permission:** público')
131
+ }
132
+ bullets.push(`**HTTP:** \`${httpFor(action)}\``)
133
+ if (Array.isArray(action.emits) && action.emits.length > 0) {
134
+ bullets.push(`**Emite:** ${action.emits.map((e) => `\`${e}\``).join(', ')}`)
135
+ }
136
+ if (Array.isArray(action.invalidates) && action.invalidates.length > 0) {
137
+ bullets.push(
138
+ `**Invalida:** ${action.invalidates.map((e) => `\`${e}\``).join(', ')}`,
139
+ )
140
+ } else if (action.invalidates === '<function>') {
141
+ bullets.push('**Invalida:** (computado em runtime)')
142
+ }
143
+ if (action.background === true) bullets.push('**Background:** sim')
144
+ if (typeof action.successStatus === 'number') {
145
+ bullets.push(`**Status sucesso:** \`${action.successStatus}\``)
146
+ }
147
+ for (const b of bullets) lines.push(`- ${b}`)
148
+ if (bullets.length > 0) lines.push('')
149
+
150
+ if (action.input !== null) {
151
+ lines.push('**Input:**')
152
+ lines.push('')
153
+ lines.push(...renderSchemaShape(action.input))
154
+ lines.push('')
155
+ }
156
+
157
+ if (action.output !== null) {
158
+ lines.push('**Output:**')
159
+ lines.push('')
160
+ lines.push(...renderSchemaShape(action.output))
161
+ lines.push('')
162
+ }
163
+
164
+ if (action.kind === 'form' && action.fields && Object.keys(action.fields).length > 0) {
165
+ lines.push('**Campos:**')
166
+ lines.push('')
167
+ for (const [name, spec] of Object.entries(action.fields)) {
168
+ const label = typeof spec.label === 'string' ? ` — ${spec.label}` : ''
169
+ const widget =
170
+ typeof spec.widget === 'string' ? ` (widget: \`${spec.widget}\`)` : ''
171
+ lines.push(`- \`${name}\`${label}${widget}`)
172
+ }
173
+ lines.push('')
174
+ }
175
+
176
+ if (action.kind === 'list' && action.filters && Object.keys(action.filters).length > 0) {
177
+ lines.push('**Filtros:**')
178
+ lines.push('')
179
+ for (const [name, spec] of Object.entries(action.filters)) {
180
+ const label = typeof spec.label === 'string' ? ` — ${spec.label}` : ''
181
+ lines.push(`- \`${name}\` (${spec.type ?? 'text'})${label}`)
182
+ }
183
+ lines.push('')
184
+ }
185
+
186
+ return lines
187
+ }
188
+
189
+ function renderReaction(reaction, level) {
190
+ const heading = '#'.repeat(level)
191
+ const lines = []
192
+ lines.push(`${heading} ${reaction.name}`)
193
+ lines.push('')
194
+ const ons = Array.isArray(reaction.on) ? reaction.on : [reaction.on]
195
+ lines.push(`- **Escuta:** ${ons.map((o) => `\`${o}\``).join(', ')}`)
196
+ if (typeof reaction.description === 'string') {
197
+ lines.push(`- **Descrição:** ${reaction.description}`)
198
+ }
199
+ if (reaction.retry !== null) {
200
+ lines.push(`- **Retry:** ${JSON.stringify(reaction.retry)}`)
201
+ }
202
+ lines.push('')
203
+ return lines
204
+ }
205
+
206
+ function renderSchedule(sched, level) {
207
+ const heading = '#'.repeat(level)
208
+ const lines = []
209
+ lines.push(`${heading} ${sched.name}`)
210
+ lines.push('')
211
+ lines.push(`- **Action:** \`${sched.action}\``)
212
+ if (typeof sched.cron === 'string') lines.push(`- **Cron:** \`${sched.cron}\``)
213
+ if (typeof sched.every === 'string') lines.push(`- **Every:** \`${sched.every}\``)
214
+ if (typeof sched.timezone === 'string') {
215
+ lines.push(`- **Timezone:** \`${sched.timezone}\``)
216
+ }
217
+ if (sched.enabled === false) lines.push('- **Status:** desabilitado')
218
+ if (typeof sched.description === 'string') {
219
+ lines.push(`- **Descrição:** ${sched.description}`)
220
+ }
221
+ lines.push('')
222
+ return lines
223
+ }
224
+
225
+ /**
226
+ * Render leve do shape de um JSON Schema — só top level, não recursivo.
227
+ * Pra schema completo, o consumidor vai pro manifest.
228
+ */
229
+ function renderSchemaShape(schema) {
230
+ if (schema === null || typeof schema !== 'object') return ['_(vazio)_']
231
+ if (schema.__unserializable === true) {
232
+ return [`_(schema não-serializável: ${schema.reason})_`]
233
+ }
234
+ if (schema.type !== 'object' || typeof schema.properties !== 'object') {
235
+ // Schema escalar ou union — emite raw curto.
236
+ const oneLine = JSON.stringify(schema)
237
+ if (oneLine.length <= 100) return ['```json', oneLine, '```']
238
+ return [
239
+ '```json',
240
+ JSON.stringify(schema, null, 2),
241
+ '```',
242
+ ]
243
+ }
244
+ const required = new Set(
245
+ Array.isArray(schema.required) ? schema.required : [],
246
+ )
247
+ const lines = []
248
+ for (const [name, prop] of Object.entries(schema.properties)) {
249
+ const isReq = required.has(name)
250
+ const type = describeProp(prop)
251
+ lines.push(`- \`${name}${isReq ? '' : '?'}: ${type}\``)
252
+ }
253
+ return lines
254
+ }
255
+
256
+ function describeProp(prop) {
257
+ if (prop === null || typeof prop !== 'object') return 'unknown'
258
+ if (Array.isArray(prop.enum)) {
259
+ return `enum<${prop.enum.join(', ')}>`
260
+ }
261
+ if (Array.isArray(prop.type)) {
262
+ return prop.type.join('|')
263
+ }
264
+ if (typeof prop.type === 'string') {
265
+ if (prop.type === 'array') {
266
+ return `array<${describeProp(prop.items)}>`
267
+ }
268
+ if (prop.type === 'string' && typeof prop.format === 'string') {
269
+ return `string<${prop.format}>`
270
+ }
271
+ const constraints = []
272
+ if (typeof prop.minLength === 'number') constraints.push(`min=${prop.minLength}`)
273
+ if (typeof prop.maxLength === 'number') constraints.push(`max=${prop.maxLength}`)
274
+ if (typeof prop.minimum === 'number') constraints.push(`>=${prop.minimum}`)
275
+ if (typeof prop.maximum === 'number') constraints.push(`<=${prop.maximum}`)
276
+ const suffix = constraints.length > 0 ? ` (${constraints.join(', ')})` : ''
277
+ return `${prop.type}${suffix}`
278
+ }
279
+ if (Array.isArray(prop.oneOf) || Array.isArray(prop.anyOf)) return 'union'
280
+ return 'object'
281
+ }
282
+
283
+ function httpFor(action) {
284
+ const method =
285
+ action.kind === 'simple' || action.kind === 'form' ? 'POST' : 'GET'
286
+ const path = `/api/${action.name.split('.').join('/')}`
287
+ return `${method} ${path}`
288
+ }
@@ -0,0 +1,102 @@
1
+ /**
2
+ * gen-manifest — transforma o payload do gen-runner no manifest.json
3
+ * público que vai ser emitido.
4
+ *
5
+ * O payload do runner já vem quase no formato certo (ver gen-runner.mjs
6
+ * pra spec). Aqui é só re-shape leve + adicionar headers (`version`,
7
+ * `generatedAt`, `opusVersion`).
8
+ */
9
+
10
+ // SEM `generatedAt`: o manifest é commitado (build artifact estilo lockfile) e o
11
+ // check de frescura compara bytes — um timestamp tornaria todo run diferente. O
12
+ // histórico do git é o "quando". `opusVersion` fica (muda só com upgrade da base).
13
+ export function buildManifest(payload) {
14
+ return {
15
+ version: '1',
16
+ opusVersion: payload.opusVersion,
17
+ domains: payload.domains.map(shapeDomain),
18
+ }
19
+ }
20
+
21
+ function shapeDomain(d) {
22
+ return {
23
+ name: d.name,
24
+ description: d.description ?? null,
25
+ dicts: d.dicts,
26
+ repository: d.hasRepository,
27
+ service: d.hasService,
28
+ // `entities` = a fonte estrutural+negócio (campos + docs). `models` = nomes
29
+ // (legado, removido no PR de rename). Ambos por ora pra não quebrar a lente.
30
+ entities: d.entities ?? [],
31
+ models: d.modelNames,
32
+ actions: d.actions.map(shapeAction),
33
+ reactions: d.reactions,
34
+ schedules: d.schedules,
35
+ subdomains: d.subdomains.map(shapeDomain),
36
+ }
37
+ }
38
+
39
+ function shapeAction(a) {
40
+ // Já vem no formato certo do runner; cópia rasa preserva ordem das chaves
41
+ // pra ficar consistente entre runs.
42
+ const out = {
43
+ name: a.name,
44
+ kind: a.kind,
45
+ summary: a.summary,
46
+ description: a.description,
47
+ label: a.label,
48
+ title: a.title,
49
+ icon: a.icon,
50
+ color: a.color,
51
+ tags: a.tags,
52
+ permission: a.permission,
53
+ authorize: a.authorize,
54
+ public: a.public,
55
+ internal: a.internal,
56
+ successStatus: a.successStatus,
57
+ input: a.input,
58
+ output: a.output,
59
+ invalidates: a.invalidates,
60
+ confirm: a.confirm,
61
+ rateLimit: a.rateLimit,
62
+ messages: a.messages,
63
+ examples: a.examples,
64
+ errors: a.errors,
65
+ }
66
+ if (a.kind === 'simple' || a.kind === 'form') {
67
+ out.emits = a.emits ?? []
68
+ out.background = a.background === true
69
+ }
70
+ if (a.kind === 'form') out.fields = a.fields ?? {}
71
+ if (a.kind === 'list') {
72
+ out.filters = a.filters ?? {}
73
+ out.sort = a.sort
74
+ out.paginate = a.paginate
75
+ out.text = a.text
76
+ out.periods = a.periods
77
+ }
78
+ if (a.kind === 'view') {
79
+ out.projection = a.projection ?? []
80
+ out.expand = a.expand
81
+ }
82
+ return out
83
+ }
84
+
85
+ /**
86
+ * Achata um manifest em uma lista plana de actions (com domínio raiz pra
87
+ * cada). Usado pra gerar OpenAPI a partir do manifest.
88
+ */
89
+ export function flattenManifestActions(manifest) {
90
+ const out = []
91
+ for (const domain of manifest.domains) walk(domain, out)
92
+ return out
93
+ }
94
+
95
+ function walk(domain, out) {
96
+ for (const action of domain.actions) {
97
+ out.push({ ...action, domainName: domain.name })
98
+ }
99
+ if (Array.isArray(domain.subdomains)) {
100
+ for (const sub of domain.subdomains) walk(sub, out)
101
+ }
102
+ }