@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,195 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* gen-openapi — gera OpenAPI 3.1 a partir do manifest serializado.
|
|
3
|
+
*
|
|
4
|
+
* Não reusa `src/schema/openapi.ts` direto pq aquele assume `ActionDef`
|
|
5
|
+
* vivo (com Zod schemas instanciados). Aqui já temos JSON Schema serializado
|
|
6
|
+
* pelo runner. Replicamos a convenção de rota e o envelope `ok/data/meta`
|
|
7
|
+
* pra manter contrato consistente.
|
|
8
|
+
*
|
|
9
|
+
* Convenção (mesma de src/schema/openapi.ts):
|
|
10
|
+
* simple/form → POST /api/{name-com-slashes}
|
|
11
|
+
* list/view → GET /api/{name-com-slashes}
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { flattenManifestActions } from './gen-manifest.mjs'
|
|
15
|
+
|
|
16
|
+
export function buildOpenAPI(manifest, options = {}) {
|
|
17
|
+
const {
|
|
18
|
+
info = {
|
|
19
|
+
title: 'Generated by @softize/opus',
|
|
20
|
+
version: manifest.opusVersion,
|
|
21
|
+
},
|
|
22
|
+
servers,
|
|
23
|
+
apiPrefix = '/api',
|
|
24
|
+
} = options
|
|
25
|
+
|
|
26
|
+
const paths = {}
|
|
27
|
+
for (const action of flattenManifestActions(manifest)) {
|
|
28
|
+
if (action.internal === true) continue
|
|
29
|
+
const { path, method } = routeFor(action, apiPrefix)
|
|
30
|
+
const op = buildOperation(action, method)
|
|
31
|
+
paths[path] = paths[path] ?? {}
|
|
32
|
+
paths[path][method] = op
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const spec = {
|
|
36
|
+
openapi: '3.1.0',
|
|
37
|
+
info,
|
|
38
|
+
paths,
|
|
39
|
+
components: { schemas: {} },
|
|
40
|
+
}
|
|
41
|
+
if (Array.isArray(servers) && servers.length > 0) spec.servers = servers
|
|
42
|
+
return spec
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function routeFor(action, apiPrefix) {
|
|
46
|
+
const segments = action.name.split('.').map(encodeURIComponent)
|
|
47
|
+
const path = `${apiPrefix}/${segments.join('/')}`
|
|
48
|
+
const method =
|
|
49
|
+
action.kind === 'simple' || action.kind === 'form' ? 'post' : 'get'
|
|
50
|
+
return { path, method }
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function buildOperation(action, method) {
|
|
54
|
+
const op = {
|
|
55
|
+
operationId: action.name,
|
|
56
|
+
responses: buildResponses(action),
|
|
57
|
+
}
|
|
58
|
+
if (typeof action.summary === 'string') op.summary = action.summary
|
|
59
|
+
if (typeof action.description === 'string') op.description = action.description
|
|
60
|
+
if (Array.isArray(action.tags) && action.tags.length > 0) op.tags = action.tags
|
|
61
|
+
|
|
62
|
+
const inputSchema = action.input
|
|
63
|
+
|
|
64
|
+
if (method === 'post' && inputSchema !== null) {
|
|
65
|
+
op.requestBody = {
|
|
66
|
+
required: true,
|
|
67
|
+
content: { 'application/json': { schema: inputSchema } },
|
|
68
|
+
}
|
|
69
|
+
} else if (method === 'get') {
|
|
70
|
+
const params = buildQueryParams(inputSchema)
|
|
71
|
+
if (params.length > 0) op.parameters = params
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (action.public !== true) {
|
|
75
|
+
op.security = [{ bearerAuth: [] }]
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return op
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function buildResponses(action) {
|
|
82
|
+
const successStatus = String(action.successStatus ?? 200)
|
|
83
|
+
return {
|
|
84
|
+
[successStatus]: {
|
|
85
|
+
description: 'Success',
|
|
86
|
+
content: {
|
|
87
|
+
'application/json': {
|
|
88
|
+
schema: wrapEnvelope(action.output, action.kind),
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
'4XX': errorResponse('Client error', action.errors),
|
|
93
|
+
'5XX': errorResponse('Server error'),
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function wrapEnvelope(outputSchema, kind) {
|
|
98
|
+
const data =
|
|
99
|
+
kind === 'list'
|
|
100
|
+
? {
|
|
101
|
+
type: 'object',
|
|
102
|
+
properties: {
|
|
103
|
+
items: { type: 'array', items: outputSchema ?? {} },
|
|
104
|
+
cursor: {
|
|
105
|
+
type: 'object',
|
|
106
|
+
properties: {
|
|
107
|
+
next: { type: ['string', 'null'] },
|
|
108
|
+
prev: { type: ['string', 'null'] },
|
|
109
|
+
},
|
|
110
|
+
required: ['next'],
|
|
111
|
+
},
|
|
112
|
+
total: { type: 'integer' },
|
|
113
|
+
},
|
|
114
|
+
required: ['items', 'cursor'],
|
|
115
|
+
}
|
|
116
|
+
: (outputSchema ?? {})
|
|
117
|
+
|
|
118
|
+
return {
|
|
119
|
+
type: 'object',
|
|
120
|
+
properties: {
|
|
121
|
+
ok: { const: true },
|
|
122
|
+
data,
|
|
123
|
+
meta: metaSchema(),
|
|
124
|
+
},
|
|
125
|
+
required: ['ok', 'data', 'meta'],
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function errorResponse(description, errors) {
|
|
130
|
+
const codes =
|
|
131
|
+
Array.isArray(errors) && errors.length > 0
|
|
132
|
+
? { enum: errors.map((e) => e.code) }
|
|
133
|
+
: { type: 'string' }
|
|
134
|
+
return {
|
|
135
|
+
description,
|
|
136
|
+
content: {
|
|
137
|
+
'application/json': {
|
|
138
|
+
schema: {
|
|
139
|
+
type: 'object',
|
|
140
|
+
properties: {
|
|
141
|
+
ok: { const: false },
|
|
142
|
+
error: {
|
|
143
|
+
type: 'object',
|
|
144
|
+
properties: {
|
|
145
|
+
code: codes,
|
|
146
|
+
category: { type: 'string' },
|
|
147
|
+
message: { type: 'string' },
|
|
148
|
+
severity: { enum: ['warning', 'error', 'fatal'] },
|
|
149
|
+
retriable: { type: 'boolean' },
|
|
150
|
+
},
|
|
151
|
+
required: ['code', 'category', 'message', 'severity', 'retriable'],
|
|
152
|
+
},
|
|
153
|
+
meta: metaSchema(),
|
|
154
|
+
},
|
|
155
|
+
required: ['ok', 'error', 'meta'],
|
|
156
|
+
},
|
|
157
|
+
},
|
|
158
|
+
},
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function metaSchema() {
|
|
163
|
+
return {
|
|
164
|
+
type: 'object',
|
|
165
|
+
properties: {
|
|
166
|
+
actionId: { type: 'string' },
|
|
167
|
+
action: { type: 'string' },
|
|
168
|
+
durationMs: { type: 'number' },
|
|
169
|
+
requestId: { type: 'string' },
|
|
170
|
+
cached: { type: 'boolean' },
|
|
171
|
+
},
|
|
172
|
+
required: ['actionId', 'action', 'durationMs'],
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function buildQueryParams(inputSchema) {
|
|
177
|
+
if (
|
|
178
|
+
inputSchema === null ||
|
|
179
|
+
typeof inputSchema !== 'object' ||
|
|
180
|
+
typeof inputSchema.properties !== 'object' ||
|
|
181
|
+
inputSchema.properties === null
|
|
182
|
+
) {
|
|
183
|
+
return []
|
|
184
|
+
}
|
|
185
|
+
const props = inputSchema.properties
|
|
186
|
+
const required = new Set(
|
|
187
|
+
Array.isArray(inputSchema.required) ? inputSchema.required : [],
|
|
188
|
+
)
|
|
189
|
+
return Object.entries(props).map(([name, schema]) => ({
|
|
190
|
+
name,
|
|
191
|
+
in: 'query',
|
|
192
|
+
required: required.has(name),
|
|
193
|
+
schema,
|
|
194
|
+
}))
|
|
195
|
+
}
|
|
@@ -0,0 +1,472 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* gen-runner — subprocess executado via `tsx` pra carregar e serializar
|
|
4
|
+
* o `opus.config.ts` do consumer.
|
|
5
|
+
*
|
|
6
|
+
* Por que subprocess: o CLI principal roda como `.mjs` puro (Node sem
|
|
7
|
+
* loader TS). Pra avaliar TypeScript do consumer com `import`, precisamos
|
|
8
|
+
* de `tsx` no caminho. Spawn isolado mantém o CLI puro e evita poluir o
|
|
9
|
+
* process do usuário.
|
|
10
|
+
*
|
|
11
|
+
* Protocolo:
|
|
12
|
+
* Entrada via argv:
|
|
13
|
+
* [0] node
|
|
14
|
+
* [1] gen-runner.mjs
|
|
15
|
+
* [2] config path absoluto (.ts/.mts/.mjs/.js)
|
|
16
|
+
*
|
|
17
|
+
* Saída:
|
|
18
|
+
* stdout: única linha JSON com `{ ok: true, payload }` ou
|
|
19
|
+
* `{ ok: false, error }`. Tudo que não for essa linha é debug
|
|
20
|
+
* (vai pra stderr).
|
|
21
|
+
*
|
|
22
|
+
* `payload` shape (ver gen-manifest.mjs pra consumidor):
|
|
23
|
+
* {
|
|
24
|
+
* opusVersion: string,
|
|
25
|
+
* sourceConfigDir: string, // dirname absoluto do opus.config.ts
|
|
26
|
+
* output: string, // pasta de saída declarada no config
|
|
27
|
+
* domains: SerializedDomain[]
|
|
28
|
+
* }
|
|
29
|
+
*
|
|
30
|
+
* SerializedDomain:
|
|
31
|
+
* {
|
|
32
|
+
* name, dicts, actions, reactions, schedules, subdomains,
|
|
33
|
+
* hasRepository, hasService, hasModels
|
|
34
|
+
* }
|
|
35
|
+
*
|
|
36
|
+
* SerializedAction (todos os ActionDef preservam suas strings):
|
|
37
|
+
* {
|
|
38
|
+
* name, kind, summary, description, label, title, icon, color, tags,
|
|
39
|
+
* permission: string | string[] | null, // requires fiel (lista = ANY)
|
|
40
|
+
* authorize: '<function>' | string?, // DSL string preservada
|
|
41
|
+
* input: JSONSchema, // zod-to-json-schema
|
|
42
|
+
* output: JSONSchema, // zod-to-json-schema
|
|
43
|
+
* fields?: Record<string, FieldSpecSerialized>,
|
|
44
|
+
* filters?, sort?, paginate?, text?, periods?, // search
|
|
45
|
+
* projection?, expand?, // view
|
|
46
|
+
* background?: boolean,
|
|
47
|
+
* emits?: string[],
|
|
48
|
+
* invalidates?: string[] | '<function>',
|
|
49
|
+
* successStatus?: number,
|
|
50
|
+
* public?: boolean,
|
|
51
|
+
* internal?: boolean,
|
|
52
|
+
* rateLimit?: { window, max },
|
|
53
|
+
* confirm?: ConfirmSpec,
|
|
54
|
+
* messages?, examples?, errors?,
|
|
55
|
+
* sourceModule?: string | null // path absoluto onde a action foi declarada (best-effort)
|
|
56
|
+
* }
|
|
57
|
+
*
|
|
58
|
+
* Closures não serializáveis (authorize fn, handler, etc) viram a string
|
|
59
|
+
* literal `"<function>"`. Mantém o nome do campo no payload pra docs e
|
|
60
|
+
* stubs poderem indicar que existe lógica.
|
|
61
|
+
*/
|
|
62
|
+
|
|
63
|
+
import path from 'node:path'
|
|
64
|
+
import { pathToFileURL, fileURLToPath } from 'node:url'
|
|
65
|
+
|
|
66
|
+
const __filename = fileURLToPath(import.meta.url)
|
|
67
|
+
const __dirname = path.dirname(__filename)
|
|
68
|
+
const PACKAGE_ROOT = path.resolve(__dirname, '..', '..')
|
|
69
|
+
|
|
70
|
+
// =============================================================================
|
|
71
|
+
// Entrada
|
|
72
|
+
// =============================================================================
|
|
73
|
+
|
|
74
|
+
async function main() {
|
|
75
|
+
const configPath = process.argv[2]
|
|
76
|
+
if (typeof configPath !== 'string' || configPath.length === 0) {
|
|
77
|
+
return emitError('Missing config path argv[2]')
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const configAbs = path.resolve(configPath)
|
|
81
|
+
const configUrl = pathToFileURL(configAbs).href
|
|
82
|
+
|
|
83
|
+
// Importa opus (a partir do PACKAGE_ROOT, não do consumer) — precisamos
|
|
84
|
+
// do `flattenDomain` + helpers internos pra serializar.
|
|
85
|
+
const opusCoreUrl = pathToFileURL(
|
|
86
|
+
path.join(PACKAGE_ROOT, 'src', 'core', 'index.ts'),
|
|
87
|
+
).href
|
|
88
|
+
const { flattenDomain } = await import(opusCoreUrl)
|
|
89
|
+
|
|
90
|
+
// Acessar logical type meta dos dicts.
|
|
91
|
+
const opusSchemaUrl = pathToFileURL(
|
|
92
|
+
path.join(PACKAGE_ROOT, 'src', 'schema', 'index.ts'),
|
|
93
|
+
).href
|
|
94
|
+
const opusSchema = await import(opusSchemaUrl)
|
|
95
|
+
|
|
96
|
+
// Pra serializar Zod → JSON Schema reusa o caminho que `toOpenAPISpec`
|
|
97
|
+
// usa. Importação direta do npm pq é dep regular da opus.
|
|
98
|
+
const { zodToJsonSchema } = await import('zod-to-json-schema')
|
|
99
|
+
|
|
100
|
+
const pkgUrl = pathToFileURL(path.join(PACKAGE_ROOT, 'package.json')).href
|
|
101
|
+
const pkg = await import(pkgUrl, { with: { type: 'json' } }).then(
|
|
102
|
+
(m) => m.default,
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
// Import dinâmico do config do consumer.
|
|
106
|
+
const mod = await import(configUrl)
|
|
107
|
+
const cfg = mod.default ?? mod.config ?? mod
|
|
108
|
+
if (cfg === undefined || cfg === null || typeof cfg !== 'object') {
|
|
109
|
+
return emitError(
|
|
110
|
+
`Config em ${configAbs} não exporta um objeto default ou named "config"`,
|
|
111
|
+
)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (!Array.isArray(cfg.domains)) {
|
|
115
|
+
return emitError(
|
|
116
|
+
`Config em ${configAbs} precisa de \`domains: DomainConfig[]\` no export default`,
|
|
117
|
+
)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const outputDir = typeof cfg.output === 'string' ? cfg.output : '.gen'
|
|
121
|
+
|
|
122
|
+
const ctx = {
|
|
123
|
+
flattenDomain,
|
|
124
|
+
toJsonSchema: (schema) =>
|
|
125
|
+
zodToJsonSchema(schema, { target: 'openApi3', $refStrategy: 'none' }),
|
|
126
|
+
sourceConfigDir: path.dirname(configAbs),
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const domains = cfg.domains.map((d) => serializeDomain(d, ctx))
|
|
130
|
+
|
|
131
|
+
const payload = {
|
|
132
|
+
opusVersion: pkg.version,
|
|
133
|
+
sourceConfigDir: ctx.sourceConfigDir,
|
|
134
|
+
output: outputDir,
|
|
135
|
+
domains,
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
emit({ ok: true, payload })
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// =============================================================================
|
|
142
|
+
// Serialização
|
|
143
|
+
// =============================================================================
|
|
144
|
+
|
|
145
|
+
function serializeDomain(domain, ctx) {
|
|
146
|
+
// `entities` é o nome preferido; `models` é o legado (alias). Lê os dois.
|
|
147
|
+
const entitySrc = domain.entities ?? domain.models
|
|
148
|
+
const entityList = serializeEntities(entitySrc)
|
|
149
|
+
return {
|
|
150
|
+
name: domain.name,
|
|
151
|
+
description: nullable(domain.description),
|
|
152
|
+
hasRepository: domain.repository !== undefined,
|
|
153
|
+
hasService: domain.service !== undefined,
|
|
154
|
+
// Compat: nomes ainda expostos; `entities` traz a estrutura+doc completa.
|
|
155
|
+
hasModels: entityList.length > 0,
|
|
156
|
+
modelNames: entityList.map((e) => e.name),
|
|
157
|
+
entities: entityList,
|
|
158
|
+
dicts: serializeDicts(domain.dicts),
|
|
159
|
+
actions: Array.from(iterateActions(domain.actions), (a) =>
|
|
160
|
+
serializeAction(a, ctx),
|
|
161
|
+
),
|
|
162
|
+
reactions: Array.isArray(domain.reactions)
|
|
163
|
+
? domain.reactions.map(serializeReaction)
|
|
164
|
+
: [],
|
|
165
|
+
schedules: Array.isArray(domain.schedules)
|
|
166
|
+
? domain.schedules.map(serializeSchedule)
|
|
167
|
+
: [],
|
|
168
|
+
subdomains: Array.isArray(domain.subdomains)
|
|
169
|
+
? domain.subdomains.map((s) => serializeDomain(s, ctx))
|
|
170
|
+
: [],
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/** Serializa as entidades (`defineEntity`) com campos + docs — a fonte que vai pro
|
|
175
|
+
* manifest/lente. Ignora valores que não parecem EntityConfig (name+fields). */
|
|
176
|
+
function serializeEntities(src) {
|
|
177
|
+
if (src === undefined || src === null || typeof src !== 'object') return []
|
|
178
|
+
const out = []
|
|
179
|
+
for (const [key, ent] of Object.entries(src)) {
|
|
180
|
+
if (ent === null || typeof ent !== 'object' || typeof ent.fields !== 'object') continue
|
|
181
|
+
const fields = []
|
|
182
|
+
for (const [fname, f] of Object.entries(ent.fields)) {
|
|
183
|
+
const col = f?.column ?? {}
|
|
184
|
+
fields.push({
|
|
185
|
+
name: fname,
|
|
186
|
+
logicalType: f?.meta?.logicalType ?? null,
|
|
187
|
+
nullable: col.nullable === true,
|
|
188
|
+
pk: col.pk === true,
|
|
189
|
+
references: col.references ?? null,
|
|
190
|
+
doc: col.doc ?? null,
|
|
191
|
+
})
|
|
192
|
+
}
|
|
193
|
+
out.push({
|
|
194
|
+
name: typeof ent.name === 'string' ? ent.name : key,
|
|
195
|
+
description: typeof ent.description === 'string' ? ent.description : null,
|
|
196
|
+
table: typeof ent.table === 'string' ? ent.table : null,
|
|
197
|
+
fields,
|
|
198
|
+
relations: ent.relations ?? null,
|
|
199
|
+
})
|
|
200
|
+
}
|
|
201
|
+
return out
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function serializeDicts(dicts) {
|
|
205
|
+
if (dicts === undefined || dicts === null || typeof dicts !== 'object') {
|
|
206
|
+
return {}
|
|
207
|
+
}
|
|
208
|
+
const out = {}
|
|
209
|
+
for (const [name, dict] of Object.entries(dicts)) {
|
|
210
|
+
if (dict === null || typeof dict !== 'object') continue
|
|
211
|
+
// DictType expõe `meta.params.entries`. Fallback: tenta `.keys()` +
|
|
212
|
+
// `.metaFor(k)` caso seja shape custom.
|
|
213
|
+
const meta = dict.meta
|
|
214
|
+
if (
|
|
215
|
+
meta !== undefined &&
|
|
216
|
+
meta !== null &&
|
|
217
|
+
meta.logicalType === 'dict' &&
|
|
218
|
+
meta.params !== undefined
|
|
219
|
+
) {
|
|
220
|
+
const params = meta.params
|
|
221
|
+
const keys = Array.isArray(params.keys) ? params.keys : []
|
|
222
|
+
const entries =
|
|
223
|
+
params.entries !== undefined && params.entries !== null
|
|
224
|
+
? params.entries
|
|
225
|
+
: {}
|
|
226
|
+
const values = {}
|
|
227
|
+
for (const k of keys) {
|
|
228
|
+
values[k] = entries[k] ?? null
|
|
229
|
+
}
|
|
230
|
+
// `doc` = entendimento do vocabulário inteiro (o que esse dict representa).
|
|
231
|
+
out[name] = { keys, values, doc: typeof params.doc === 'string' ? params.doc : null }
|
|
232
|
+
continue
|
|
233
|
+
}
|
|
234
|
+
// Fallback: dict-like com `keys()` + `metaFor()`.
|
|
235
|
+
if (
|
|
236
|
+
typeof dict.keys === 'function' &&
|
|
237
|
+
typeof dict.metaFor === 'function'
|
|
238
|
+
) {
|
|
239
|
+
const keys = dict.keys()
|
|
240
|
+
const values = {}
|
|
241
|
+
for (const k of keys) values[k] = dict.metaFor(k)
|
|
242
|
+
out[name] = { keys, values }
|
|
243
|
+
continue
|
|
244
|
+
}
|
|
245
|
+
// Não-reconhecível: registra placeholder pra não silenciar.
|
|
246
|
+
out[name] = { keys: [], values: {}, unknownShape: true }
|
|
247
|
+
}
|
|
248
|
+
return out
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function serializeAction(action, ctx) {
|
|
252
|
+
const base = {
|
|
253
|
+
name: action.name,
|
|
254
|
+
kind: action.kind,
|
|
255
|
+
summary: nullable(action.summary),
|
|
256
|
+
description: nullable(action.description),
|
|
257
|
+
label: nullable(action.label),
|
|
258
|
+
title: nullable(action.title),
|
|
259
|
+
icon: nullable(action.icon),
|
|
260
|
+
color: nullable(action.color),
|
|
261
|
+
tags: Array.isArray(action.tags) ? action.tags : [],
|
|
262
|
+
permission: derivePermission(action),
|
|
263
|
+
authorize: serializeAuthorize(action.authorize),
|
|
264
|
+
public: action.public === true,
|
|
265
|
+
internal: action.internal === true,
|
|
266
|
+
successStatus: action.successStatus,
|
|
267
|
+
input: safeToJsonSchema(action.input, ctx),
|
|
268
|
+
output: safeToJsonSchema(action.output, ctx),
|
|
269
|
+
invalidates: serializeInvalidates(action.invalidates),
|
|
270
|
+
confirm: action.confirm ?? null,
|
|
271
|
+
rateLimit:
|
|
272
|
+
action.rateLimit !== undefined
|
|
273
|
+
? { window: action.rateLimit.window, max: action.rateLimit.max }
|
|
274
|
+
: null,
|
|
275
|
+
messages: action.messages ?? null,
|
|
276
|
+
examples: Array.isArray(action.examples) ? action.examples : [],
|
|
277
|
+
errors: Array.isArray(action.errors) ? action.errors : [],
|
|
278
|
+
sourceModule: lookupSourceModule(action, ctx),
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
if (action.kind === 'simple' || action.kind === 'form') {
|
|
282
|
+
base.emits = Array.isArray(action.emits) ? action.emits : []
|
|
283
|
+
base.background =
|
|
284
|
+
action.background !== undefined &&
|
|
285
|
+
action.background !== null &&
|
|
286
|
+
action.background.enabled === true
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
if (action.kind === 'form') {
|
|
290
|
+
base.fields = serializeFields(action.fields)
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
if (action.kind === 'list') {
|
|
294
|
+
base.filters = serializeFilters(action.filters)
|
|
295
|
+
base.sort = action.sort ?? null
|
|
296
|
+
base.paginate = action.paginate ?? null
|
|
297
|
+
base.text = action.text ?? null
|
|
298
|
+
base.periods = Array.isArray(action.periods) ? action.periods : []
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
if (action.kind === 'view') {
|
|
302
|
+
base.projection = Array.isArray(action.projection) ? action.projection : []
|
|
303
|
+
base.expand = action.expand ?? null
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
return base
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
function serializeFields(fields) {
|
|
310
|
+
if (fields === undefined || fields === null || typeof fields !== 'object') {
|
|
311
|
+
return {}
|
|
312
|
+
}
|
|
313
|
+
const out = {}
|
|
314
|
+
for (const [name, spec] of Object.entries(fields)) {
|
|
315
|
+
if (spec === null || typeof spec !== 'object') continue
|
|
316
|
+
out[name] = {
|
|
317
|
+
label: spec.label ?? null,
|
|
318
|
+
placeholder: spec.placeholder ?? null,
|
|
319
|
+
hint: spec.hint ?? null,
|
|
320
|
+
default: spec.default ?? null,
|
|
321
|
+
mask: spec.mask ?? null,
|
|
322
|
+
depends: spec.depends ?? null,
|
|
323
|
+
group: spec.group ?? null,
|
|
324
|
+
order: spec.order ?? null,
|
|
325
|
+
widget: spec.widget ?? null,
|
|
326
|
+
options: spec.options ?? null,
|
|
327
|
+
hasShowWhen: typeof spec.showWhen === 'function',
|
|
328
|
+
hasRequireWhen: typeof spec.requireWhen === 'function',
|
|
329
|
+
aiDescription: spec.aiDescription ?? null,
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
return out
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
function serializeFilters(filters) {
|
|
336
|
+
if (filters === undefined || filters === null || typeof filters !== 'object') {
|
|
337
|
+
return {}
|
|
338
|
+
}
|
|
339
|
+
const out = {}
|
|
340
|
+
for (const [name, spec] of Object.entries(filters)) {
|
|
341
|
+
if (spec === null || typeof spec !== 'object') continue
|
|
342
|
+
out[name] = {
|
|
343
|
+
label: spec.label ?? null,
|
|
344
|
+
placeholder: spec.placeholder ?? null,
|
|
345
|
+
type: spec.type ?? null,
|
|
346
|
+
multiple: spec.multiple === true,
|
|
347
|
+
mode: spec.mode ?? null,
|
|
348
|
+
operators: Array.isArray(spec.operators) ? spec.operators : null,
|
|
349
|
+
path: spec.path ?? null,
|
|
350
|
+
section: spec.section ?? null,
|
|
351
|
+
depends: spec.depends ?? null,
|
|
352
|
+
options: spec.options ?? null,
|
|
353
|
+
aiDescription: spec.aiDescription ?? null,
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
return out
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
function serializeReaction(reaction) {
|
|
360
|
+
return {
|
|
361
|
+
name: reaction.name,
|
|
362
|
+
on: reaction.on,
|
|
363
|
+
description: nullable(reaction.description),
|
|
364
|
+
tags: Array.isArray(reaction.tags) ? reaction.tags : [],
|
|
365
|
+
handler: '<function>',
|
|
366
|
+
retry: reaction.retry ?? null,
|
|
367
|
+
timeout: reaction.timeout ?? null,
|
|
368
|
+
concurrency: reaction.concurrency ?? null,
|
|
369
|
+
hasDedup: typeof reaction.dedup === 'function',
|
|
370
|
+
hasAuthorize: typeof reaction.authorize === 'function',
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
function serializeSchedule(sched) {
|
|
375
|
+
return {
|
|
376
|
+
name: sched.name,
|
|
377
|
+
action: sched.action,
|
|
378
|
+
cron: nullable(sched.cron),
|
|
379
|
+
every: nullable(sched.every),
|
|
380
|
+
timezone: nullable(sched.timezone),
|
|
381
|
+
enabled: sched.enabled !== false,
|
|
382
|
+
description: nullable(sched.description),
|
|
383
|
+
tags: Array.isArray(sched.tags) ? sched.tags : [],
|
|
384
|
+
input:
|
|
385
|
+
typeof sched.input === 'function' ? '<function>' : (sched.input ?? null),
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// =============================================================================
|
|
390
|
+
// Helpers
|
|
391
|
+
// =============================================================================
|
|
392
|
+
|
|
393
|
+
function* iterateActions(actions) {
|
|
394
|
+
if (actions === undefined) return
|
|
395
|
+
if (Array.isArray(actions)) {
|
|
396
|
+
for (const a of actions) yield a
|
|
397
|
+
return
|
|
398
|
+
}
|
|
399
|
+
for (const value of Object.values(actions)) {
|
|
400
|
+
if (Array.isArray(value)) {
|
|
401
|
+
for (const a of value) yield a
|
|
402
|
+
} else {
|
|
403
|
+
yield value
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
function derivePermission(action) {
|
|
409
|
+
// Projeção FIEL do `requires`: lista sai lista (semântica ANY) — projetar só a
|
|
410
|
+
// 1ª string faria a spec mentir por omissão pra quem lê o manifest.
|
|
411
|
+
const req = action.requires
|
|
412
|
+
if (typeof req === 'string') return req
|
|
413
|
+
if (Array.isArray(req)) {
|
|
414
|
+
const strs = req.filter((r) => typeof r === 'string')
|
|
415
|
+
return strs.length > 0 ? strs : null
|
|
416
|
+
}
|
|
417
|
+
return null
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
function serializeAuthorize(authorize) {
|
|
421
|
+
if (authorize === undefined || authorize === null) return null
|
|
422
|
+
// DSL: pode chegar como string em frameworks (versão futura). Hoje é fn.
|
|
423
|
+
if (typeof authorize === 'string') return authorize
|
|
424
|
+
if (typeof authorize === 'function') return '<function>'
|
|
425
|
+
return null
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
function serializeInvalidates(inv) {
|
|
429
|
+
if (inv === undefined || inv === null) return null
|
|
430
|
+
if (Array.isArray(inv)) return inv
|
|
431
|
+
if (typeof inv === 'function') return '<function>'
|
|
432
|
+
return null
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
function safeToJsonSchema(schema, ctx) {
|
|
436
|
+
if (schema === undefined || schema === null) return null
|
|
437
|
+
try {
|
|
438
|
+
return ctx.toJsonSchema(schema)
|
|
439
|
+
} catch (err) {
|
|
440
|
+
return { __unserializable: true, reason: err.message }
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
function lookupSourceModule(_action, _ctx) {
|
|
445
|
+
// Não há mecanismo confiável pra extrair o source module a partir do
|
|
446
|
+
// ActionDef em runtime (ESM não expõe). Reservamos o campo pra futura
|
|
447
|
+
// integração com `import.meta.url` no defineAction.
|
|
448
|
+
return null
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
function nullable(value) {
|
|
452
|
+
if (value === undefined || value === null) return null
|
|
453
|
+
if (typeof value === 'string' && value.length === 0) return null
|
|
454
|
+
return value
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
// =============================================================================
|
|
458
|
+
// IPC
|
|
459
|
+
// =============================================================================
|
|
460
|
+
|
|
461
|
+
function emit(payload) {
|
|
462
|
+
process.stdout.write(JSON.stringify(payload) + '\n')
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
function emitError(message) {
|
|
466
|
+
emit({ ok: false, error: message })
|
|
467
|
+
process.exit(1)
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
main().catch((err) => {
|
|
471
|
+
emitError(`runner crash: ${err.stack ?? err.message ?? String(err)}`)
|
|
472
|
+
})
|