@softize/opus 8.6.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +1616 -0
- package/LICENSE +21 -0
- package/README.md +113 -0
- package/bin/cli.mjs +528 -0
- package/bin/lib/check.mjs +307 -0
- package/bin/lib/components.mjs +151 -0
- package/bin/lib/create.mjs +208 -0
- package/bin/lib/db-check-runner.mjs +86 -0
- package/bin/lib/db-migrate-runner.mjs +89 -0
- package/bin/lib/db-scaffold-runner.mjs +84 -0
- package/bin/lib/db.mjs +261 -0
- package/bin/lib/docs-include.mjs +48 -0
- package/bin/lib/gen-dicts.mjs +134 -0
- package/bin/lib/gen-docs.mjs +288 -0
- package/bin/lib/gen-manifest.mjs +102 -0
- package/bin/lib/gen-openapi.mjs +195 -0
- package/bin/lib/gen-runner.mjs +472 -0
- package/bin/lib/gen-stubs.mjs +463 -0
- package/bin/lib/gen.mjs +311 -0
- package/bin/lib/init.mjs +514 -0
- package/bin/lib/introspect.mjs +107 -0
- package/bin/lib/mcp.mjs +85 -0
- package/bin/lib/postinstall.mjs +56 -0
- package/docs/chat-event-protocol.md +85 -0
- package/docs/code-style.md +16 -0
- package/docs/data-layer.md +246 -0
- package/docs/ownership-vs-shadcn-lock.md +102 -0
- package/docs/protocol.md +2053 -0
- package/docs/releasing.md +110 -0
- package/docs/shellnav.md +131 -0
- package/package.json +338 -0
- package/registry/hooks/hooks.json +26 -0
- package/registry/hooks/link-memory-on-start.mjs +46 -0
- package/registry/hooks/opus-check-on-stop.mjs +114 -0
- package/registry/skills/create-action/SKILL.md +49 -0
- package/registry/skills/create-action/scaffold.mjs +122 -0
- package/registry/templates/app/_gitignore +3 -0
- package/registry/templates/app/_npmrc +1 -0
- package/registry/templates/app/_opus/_gitignore +5 -0
- package/registry/templates/app/_prettierrc.json +6 -0
- package/registry/templates/app/index.html +13 -0
- package/registry/templates/app/opus.config.ts +16 -0
- package/registry/templates/app/package.json +43 -0
- package/registry/templates/app/pnpm-workspace.yaml +11 -0
- package/registry/templates/app/public/favicon.svg +4 -0
- package/registry/templates/app/src/App.tsx +37 -0
- package/registry/templates/app/src/domains/tasks/actions/list.test.ts +34 -0
- package/registry/templates/app/src/domains/tasks/actions/list.ts +33 -0
- package/registry/templates/app/src/domains/tasks/index.ts +13 -0
- package/registry/templates/app/src/index.css +18 -0
- package/registry/templates/app/src/main.tsx +25 -0
- package/registry/templates/app/tsconfig.json +20 -0
- package/registry/templates/app/vite.config.ts +46 -0
- package/registry/templates/monorepo/_gitignore +3 -0
- package/registry/templates/monorepo/_npmrc +1 -0
- package/registry/templates/monorepo/package.json +9 -0
- package/registry/templates/monorepo/pnpm-workspace.yaml +14 -0
- package/src/ai/ask.ts +64 -0
- package/src/ai/drivers/anthropic.ts +309 -0
- package/src/ai/index.ts +17 -0
- package/src/audit/drivers/console.ts +117 -0
- package/src/audit/drivers/pg.ts +172 -0
- package/src/audit/index.ts +51 -0
- package/src/auth/drivers/better-auth.ts +103 -0
- package/src/auth/drivers/jwt.ts +188 -0
- package/src/auth/index.ts +9 -0
- package/src/client/drivers/fetch.ts +202 -0
- package/src/client/index.ts +22 -0
- package/src/core/actions.ts +110 -0
- package/src/core/audit.ts +239 -0
- package/src/core/contracts.ts +137 -0
- package/src/core/domain.ts +310 -0
- package/src/core/errors.ts +181 -0
- package/src/core/index.ts +174 -0
- package/src/core/logical-type.ts +31 -0
- package/src/core/reactions.ts +81 -0
- package/src/core/runtime.ts +1167 -0
- package/src/core/schedules.ts +41 -0
- package/src/core/types.ts +1356 -0
- package/src/data/drivers/kysely.ts +389 -0
- package/src/data/index.ts +10 -0
- package/src/data/readonly-pool.ts +160 -0
- package/src/dsl/eval.ts +136 -0
- package/src/dsl/index.ts +29 -0
- package/src/dsl/kysely.ts +230 -0
- package/src/dsl/loads.ts +123 -0
- package/src/dsl/parser.ts +423 -0
- package/src/dsl/types.ts +113 -0
- package/src/events/drivers/mitt.ts +70 -0
- package/src/events/index.ts +9 -0
- package/src/log/drivers/pino.ts +57 -0
- package/src/log/index.ts +9 -0
- package/src/mcp/index.ts +62 -0
- package/src/queue/drivers/bullmq.ts +190 -0
- package/src/queue/index.ts +9 -0
- package/src/scheduler/drivers/node-cron.ts +93 -0
- package/src/scheduler/every.ts +45 -0
- package/src/scheduler/index.ts +9 -0
- package/src/schema/drivers/zod.ts +765 -0
- package/src/schema/entity.ts +439 -0
- package/src/schema/format/locale.ts +144 -0
- package/src/schema/index.ts +65 -0
- package/src/schema/openapi.ts +302 -0
- package/src/schema/scaffold.ts +160 -0
- package/src/server/drivers/fastify.ts +224 -0
- package/src/server/drivers/node.ts +386 -0
- package/src/server/index.ts +142 -0
- package/src/storage/drivers/fs.ts +90 -0
- package/src/storage/drivers/s3.ts +117 -0
- package/src/storage/index.ts +27 -0
- package/src/testing/fake.ts +298 -0
- package/src/testing/index.ts +324 -0
- package/src/ui/components/patterns/action-form-card.tsx +48 -0
- package/src/ui/components/patterns/action-list-dialog.tsx +93 -0
- package/src/ui/components/patterns/app-shell.tsx +227 -0
- package/src/ui/components/patterns/confirm.tsx +226 -0
- package/src/ui/components/patterns/data-state.tsx +75 -0
- package/src/ui/components/patterns/form-dialog.tsx +64 -0
- package/src/ui/components/patterns/form.tsx +584 -0
- package/src/ui/components/patterns/list.tsx +1488 -0
- package/src/ui/components/patterns/page.tsx +46 -0
- package/src/ui/components/patterns/section-shell.tsx +246 -0
- package/src/ui/components/patterns/shell-nav.tsx +150 -0
- package/src/ui/components/patterns/sidebar.tsx +89 -0
- package/src/ui/components/patterns/split.tsx +93 -0
- package/src/ui/components/patterns/trigger.tsx +196 -0
- package/src/ui/components/patterns/view.tsx +84 -0
- package/src/ui/components/primitives/accordion.tsx +64 -0
- package/src/ui/components/primitives/alert-dialog.tsx +190 -0
- package/src/ui/components/primitives/alert.tsx +116 -0
- package/src/ui/components/primitives/aspect-ratio.tsx +9 -0
- package/src/ui/components/primitives/avatar.tsx +107 -0
- package/src/ui/components/primitives/badge.tsx +37 -0
- package/src/ui/components/primitives/breadcrumb.tsx +109 -0
- package/src/ui/components/primitives/button-group.tsx +83 -0
- package/src/ui/components/primitives/button.tsx +102 -0
- package/src/ui/components/primitives/calendar.tsx +218 -0
- package/src/ui/components/primitives/card.tsx +56 -0
- package/src/ui/components/primitives/carousel.tsx +239 -0
- package/src/ui/components/primitives/chat.tsx +407 -0
- package/src/ui/components/primitives/checkbox.tsx +30 -0
- package/src/ui/components/primitives/collapsible.tsx +31 -0
- package/src/ui/components/primitives/command.tsx +182 -0
- package/src/ui/components/primitives/composer.tsx +121 -0
- package/src/ui/components/primitives/copyable.tsx +50 -0
- package/src/ui/components/primitives/dialog.tsx +147 -0
- package/src/ui/components/primitives/drawer.tsx +141 -0
- package/src/ui/components/primitives/empty.tsx +104 -0
- package/src/ui/components/primitives/field.tsx +246 -0
- package/src/ui/components/primitives/icon-picker.tsx +180 -0
- package/src/ui/components/primitives/input-group.tsx +168 -0
- package/src/ui/components/primitives/input-otp.tsx +75 -0
- package/src/ui/components/primitives/input.tsx +72 -0
- package/src/ui/components/primitives/item.tsx +193 -0
- package/src/ui/components/primitives/kbd.tsx +28 -0
- package/src/ui/components/primitives/label.tsx +22 -0
- package/src/ui/components/primitives/markdown.tsx +35 -0
- package/src/ui/components/primitives/menu.tsx +255 -0
- package/src/ui/components/primitives/pagination.tsx +127 -0
- package/src/ui/components/primitives/popover.tsx +87 -0
- package/src/ui/components/primitives/progress.tsx +29 -0
- package/src/ui/components/primitives/radio-group.tsx +43 -0
- package/src/ui/components/primitives/resizable.tsx +51 -0
- package/src/ui/components/primitives/scroll-area.tsx +56 -0
- package/src/ui/components/primitives/select.tsx +479 -0
- package/src/ui/components/primitives/separator.tsx +26 -0
- package/src/ui/components/primitives/skeleton.tsx +13 -0
- package/src/ui/components/primitives/slider.tsx +61 -0
- package/src/ui/components/primitives/sonner.tsx +46 -0
- package/src/ui/components/primitives/spinner.tsx +29 -0
- package/src/ui/components/primitives/switch.tsx +33 -0
- package/src/ui/components/primitives/table.tsx +114 -0
- package/src/ui/components/primitives/tabs.tsx +104 -0
- package/src/ui/components/primitives/textarea.tsx +18 -0
- package/src/ui/components/primitives/toggle-group.tsx +81 -0
- package/src/ui/components/primitives/toggle.tsx +45 -0
- package/src/ui/components/primitives/tooltip.tsx +55 -0
- package/src/ui/components/primitives/truncate.tsx +49 -0
- package/src/ui/docs/DocBrowser.tsx +90 -0
- package/src/ui/docs/changelog.tsx +80 -0
- package/src/ui/docs/content/accordion.md +86 -0
- package/src/ui/docs/content/action-form-card.md +24 -0
- package/src/ui/docs/content/action-form-dialog.md +30 -0
- package/src/ui/docs/content/action-form.md +125 -0
- package/src/ui/docs/content/action-list-dialog.md +68 -0
- package/src/ui/docs/content/action-list.md +194 -0
- package/src/ui/docs/content/action-trigger.md +72 -0
- package/src/ui/docs/content/action-view.md +47 -0
- package/src/ui/docs/content/actions.md +138 -0
- package/src/ui/docs/content/ai.md +112 -0
- package/src/ui/docs/content/alert-dialog.md +73 -0
- package/src/ui/docs/content/alert.md +69 -0
- package/src/ui/docs/content/app-shell.md +155 -0
- package/src/ui/docs/content/aspect-ratio.md +66 -0
- package/src/ui/docs/content/audit.md +84 -0
- package/src/ui/docs/content/auth.md +70 -0
- package/src/ui/docs/content/avatar.md +94 -0
- package/src/ui/docs/content/badge.md +48 -0
- package/src/ui/docs/content/breadcrumb.md +87 -0
- package/src/ui/docs/content/button-group.md +71 -0
- package/src/ui/docs/content/button.md +60 -0
- package/src/ui/docs/content/calendar.md +62 -0
- package/src/ui/docs/content/card.md +49 -0
- package/src/ui/docs/content/carousel.md +85 -0
- package/src/ui/docs/content/chat.md +69 -0
- package/src/ui/docs/content/checkbox.md +75 -0
- package/src/ui/docs/content/cli.md +58 -0
- package/src/ui/docs/content/collapsible.md +64 -0
- package/src/ui/docs/content/command.md +56 -0
- package/src/ui/docs/content/composer.md +50 -0
- package/src/ui/docs/content/confirm.md +120 -0
- package/src/ui/docs/content/copyable.md +30 -0
- package/src/ui/docs/content/customization.md +110 -0
- package/src/ui/docs/content/cycle.md +34 -0
- package/src/ui/docs/content/data-state.md +47 -0
- package/src/ui/docs/content/data.md +99 -0
- package/src/ui/docs/content/dialog.md +60 -0
- package/src/ui/docs/content/drawer.md +55 -0
- package/src/ui/docs/content/empty.md +66 -0
- package/src/ui/docs/content/events.md +61 -0
- package/src/ui/docs/content/field.md +58 -0
- package/src/ui/docs/content/getting-started.md +109 -0
- package/src/ui/docs/content/icon-picker.md +51 -0
- package/src/ui/docs/content/input-group.md +78 -0
- package/src/ui/docs/content/input-otp.md +72 -0
- package/src/ui/docs/content/input.md +78 -0
- package/src/ui/docs/content/item.md +84 -0
- package/src/ui/docs/content/kbd.md +62 -0
- package/src/ui/docs/content/label.md +32 -0
- package/src/ui/docs/content/log.md +55 -0
- package/src/ui/docs/content/markdown.md +41 -0
- package/src/ui/docs/content/mcp.md +44 -0
- package/src/ui/docs/content/menu.md +114 -0
- package/src/ui/docs/content/microcopy.md +83 -0
- package/src/ui/docs/content/page.md +34 -0
- package/src/ui/docs/content/pagination.md +99 -0
- package/src/ui/docs/content/popover.md +49 -0
- package/src/ui/docs/content/progress.md +69 -0
- package/src/ui/docs/content/queue.md +62 -0
- package/src/ui/docs/content/radio-group.md +77 -0
- package/src/ui/docs/content/resizable.md +86 -0
- package/src/ui/docs/content/router.md +56 -0
- package/src/ui/docs/content/runtime.md +77 -0
- package/src/ui/docs/content/scheduler.md +66 -0
- package/src/ui/docs/content/scroll-area.md +89 -0
- package/src/ui/docs/content/section-shell.md +121 -0
- package/src/ui/docs/content/select.md +342 -0
- package/src/ui/docs/content/separator.md +33 -0
- package/src/ui/docs/content/sidebar.md +38 -0
- package/src/ui/docs/content/skeleton.md +34 -0
- package/src/ui/docs/content/slider.md +64 -0
- package/src/ui/docs/content/spinner.md +37 -0
- package/src/ui/docs/content/split.md +33 -0
- package/src/ui/docs/content/storage.md +69 -0
- package/src/ui/docs/content/switch.md +69 -0
- package/src/ui/docs/content/table.md +102 -0
- package/src/ui/docs/content/tabs.md +94 -0
- package/src/ui/docs/content/testing.md +89 -0
- package/src/ui/docs/content/textarea.md +30 -0
- package/src/ui/docs/content/toast.md +67 -0
- package/src/ui/docs/content/toggle-group.md +81 -0
- package/src/ui/docs/content/toggle.md +72 -0
- package/src/ui/docs/content/tokens.md +171 -0
- package/src/ui/docs/content/tooltip.md +50 -0
- package/src/ui/docs/content/truncate.md +37 -0
- package/src/ui/docs/content/ui.md +40 -0
- package/src/ui/docs/content/upgrading.md +48 -0
- package/src/ui/docs/doc-client.tsx +214 -0
- package/src/ui/docs/doc.tsx +301 -0
- package/src/ui/docs/folder.tsx +149 -0
- package/src/ui/docs/index.ts +21 -0
- package/src/ui/docs/markdown.tsx +130 -0
- package/src/ui/docs/md-raw.d.ts +4 -0
- package/src/ui/docs/plugin.ts +104 -0
- package/src/ui/docs/registry.tsx +424 -0
- package/src/ui/docs/standalone.tsx +107 -0
- package/src/ui/drivers/react.tsx +627 -0
- package/src/ui/index.ts +92 -0
- package/src/ui/lib/cn.ts +10 -0
- package/src/ui/lib/zod-pt-br.ts +38 -0
- package/src/ui/meta.ts +412 -0
- package/src/ui/react.tsx +235 -0
- package/src/ui/router.ts +96 -0
- package/src/ui/theme.css +234 -0
- package/src/vite/design.ts +652 -0
- package/src/vite/index.ts +8 -0
|
@@ -0,0 +1,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
|
+
}
|