@softize/opus 15.2.2 → 16.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +51 -18
- package/bin/lib/check.mjs +98 -15
- package/bin/lib/copy.mjs +811 -314
- package/bin/lib/gen-manifest.mjs +24 -23
- package/bin/lib/gen-runner.mjs +188 -148
- package/docs/adr/0010-page-header-owns-page-chrome.md +3 -4
- package/docs/adr/0011-page-shell-coordinates-persistent-page-chrome.md +5 -3
- package/docs/adr/0012-modal-header-only-names-the-surface.md +45 -0
- package/docs/adr/0013-presentation-is-a-portable-action-oriented-artifact.md +92 -0
- package/docs/code-style.md +24 -19
- package/package.json +5 -1
- package/registry/skills/build-opus-ui/references/ui-patterns.md +43 -26
- package/src/core/presentation.ts +512 -0
- package/src/presentation/index.ts +1 -0
- package/src/ui/components/patterns/action-list-dialog.tsx +26 -9
- package/src/ui/components/patterns/confirm.tsx +34 -29
- package/src/ui/components/patterns/form-dialog.tsx +20 -8
- package/src/ui/components/patterns/list.tsx +26 -9
- package/src/ui/components/patterns/page.tsx +99 -139
- package/src/ui/components/patterns/presentation.tsx +316 -0
- package/src/ui/components/patterns/sidebar.tsx +3 -3
- package/src/ui/components/patterns/trigger.tsx +38 -17
- package/src/ui/components/primitives/button-group.tsx +53 -43
- package/src/ui/components/primitives/command.tsx +30 -72
- package/src/ui/components/primitives/dialog.tsx +23 -89
- package/src/ui/components/primitives/drawer.tsx +8 -34
- package/src/ui/docs/content/action-form-dialog.md +12 -10
- package/src/ui/docs/content/action-list-dialog.md +22 -17
- package/src/ui/docs/content/button.md +52 -35
- package/src/ui/docs/content/communication.md +26 -26
- package/src/ui/docs/content/dialog.md +173 -154
- package/src/ui/docs/content/drawer.md +12 -11
- package/src/ui/docs/content/page.md +72 -91
- package/src/ui/docs/content/presentation.md +158 -0
- package/src/ui/docs/registry.tsx +6 -0
- package/src/ui/meta.ts +8 -2
- package/src/ui/react.tsx +10 -3
package/bin/lib/gen-manifest.mjs
CHANGED
|
@@ -12,11 +12,12 @@
|
|
|
12
12
|
// histórico do git é o "quando". `opusVersion` fica (muda só com upgrade da base).
|
|
13
13
|
export function buildManifest(payload) {
|
|
14
14
|
return {
|
|
15
|
-
version:
|
|
15
|
+
version: "1",
|
|
16
16
|
opusVersion: payload.opusVersion,
|
|
17
17
|
seeds: payload.seeds ?? [],
|
|
18
|
+
presentations: payload.presentations ?? [],
|
|
18
19
|
domains: payload.domains.map(shapeDomain),
|
|
19
|
-
}
|
|
20
|
+
};
|
|
20
21
|
}
|
|
21
22
|
|
|
22
23
|
function shapeDomain(d) {
|
|
@@ -32,7 +33,7 @@ function shapeDomain(d) {
|
|
|
32
33
|
reactions: d.reactions,
|
|
33
34
|
schedules: d.schedules,
|
|
34
35
|
subdomains: d.subdomains.map(shapeDomain),
|
|
35
|
-
}
|
|
36
|
+
};
|
|
36
37
|
}
|
|
37
38
|
|
|
38
39
|
function shapeAction(a) {
|
|
@@ -61,24 +62,24 @@ function shapeAction(a) {
|
|
|
61
62
|
messages: a.messages,
|
|
62
63
|
examples: a.examples,
|
|
63
64
|
errors: a.errors,
|
|
65
|
+
};
|
|
66
|
+
if (a.kind === "simple" || a.kind === "form") {
|
|
67
|
+
out.emits = a.emits ?? [];
|
|
68
|
+
out.background = a.background === true;
|
|
64
69
|
}
|
|
65
|
-
if (a.kind ===
|
|
66
|
-
|
|
67
|
-
out.
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
out.
|
|
72
|
-
out.sort = a.sort
|
|
73
|
-
out.paginate = a.paginate
|
|
74
|
-
out.text = a.text
|
|
75
|
-
out.periods = a.periods
|
|
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;
|
|
76
77
|
}
|
|
77
|
-
if (a.kind ===
|
|
78
|
-
out.projection = a.projection ?? []
|
|
79
|
-
out.expand = a.expand
|
|
78
|
+
if (a.kind === "view") {
|
|
79
|
+
out.projection = a.projection ?? [];
|
|
80
|
+
out.expand = a.expand;
|
|
80
81
|
}
|
|
81
|
-
return out
|
|
82
|
+
return out;
|
|
82
83
|
}
|
|
83
84
|
|
|
84
85
|
/**
|
|
@@ -86,16 +87,16 @@ function shapeAction(a) {
|
|
|
86
87
|
* cada). Usado pra gerar OpenAPI a partir do manifest.
|
|
87
88
|
*/
|
|
88
89
|
export function flattenManifestActions(manifest) {
|
|
89
|
-
const out = []
|
|
90
|
-
for (const domain of manifest.domains) walk(domain, out)
|
|
91
|
-
return out
|
|
90
|
+
const out = [];
|
|
91
|
+
for (const domain of manifest.domains) walk(domain, out);
|
|
92
|
+
return out;
|
|
92
93
|
}
|
|
93
94
|
|
|
94
95
|
function walk(domain, out) {
|
|
95
96
|
for (const action of domain.actions) {
|
|
96
|
-
out.push({ ...action, domainName: domain.name })
|
|
97
|
+
out.push({ ...action, domainName: domain.name });
|
|
97
98
|
}
|
|
98
99
|
if (Array.isArray(domain.subdomains)) {
|
|
99
|
-
for (const sub of domain.subdomains) walk(sub, out)
|
|
100
|
+
for (const sub of domain.subdomains) walk(sub, out);
|
|
100
101
|
}
|
|
101
102
|
}
|
package/bin/lib/gen-runner.mjs
CHANGED
|
@@ -25,7 +25,8 @@
|
|
|
25
25
|
* sourceConfigDir: string, // dirname absoluto do opus.config.ts
|
|
26
26
|
* output: string, // pasta de saída declarada no config
|
|
27
27
|
* domains: SerializedDomain[],
|
|
28
|
-
* seeds: SerializedSeed[]
|
|
28
|
+
* seeds: SerializedSeed[],
|
|
29
|
+
* presentations: PresentationDefinition[]
|
|
29
30
|
* }
|
|
30
31
|
*
|
|
31
32
|
* SerializedDomain:
|
|
@@ -61,92 +62,113 @@
|
|
|
61
62
|
* stubs poderem indicar que existe lógica.
|
|
62
63
|
*/
|
|
63
64
|
|
|
64
|
-
import path from
|
|
65
|
-
import { pathToFileURL, fileURLToPath } from
|
|
65
|
+
import path from "node:path";
|
|
66
|
+
import { pathToFileURL, fileURLToPath } from "node:url";
|
|
66
67
|
|
|
67
|
-
const __filename = fileURLToPath(import.meta.url)
|
|
68
|
-
const __dirname = path.dirname(__filename)
|
|
69
|
-
const PACKAGE_ROOT = path.resolve(__dirname,
|
|
68
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
69
|
+
const __dirname = path.dirname(__filename);
|
|
70
|
+
const PACKAGE_ROOT = path.resolve(__dirname, "..", "..");
|
|
70
71
|
|
|
71
72
|
// =============================================================================
|
|
72
73
|
// Entrada
|
|
73
74
|
// =============================================================================
|
|
74
75
|
|
|
75
76
|
async function main() {
|
|
76
|
-
const configPath = process.argv[2]
|
|
77
|
-
if (typeof configPath !==
|
|
78
|
-
return emitError(
|
|
77
|
+
const configPath = process.argv[2];
|
|
78
|
+
if (typeof configPath !== "string" || configPath.length === 0) {
|
|
79
|
+
return emitError("Caminho do config ausente (argv[2])");
|
|
79
80
|
}
|
|
80
81
|
|
|
81
|
-
const configAbs = path.resolve(configPath)
|
|
82
|
-
const configUrl = pathToFileURL(configAbs).href
|
|
82
|
+
const configAbs = path.resolve(configPath);
|
|
83
|
+
const configUrl = pathToFileURL(configAbs).href;
|
|
83
84
|
|
|
84
85
|
// Importa opus (a partir do PACKAGE_ROOT, não do consumer) — precisamos
|
|
85
86
|
// do `flattenDomain` + helpers internos pra serializar.
|
|
86
87
|
const opusCoreUrl = pathToFileURL(
|
|
87
|
-
path.join(PACKAGE_ROOT,
|
|
88
|
-
).href
|
|
89
|
-
const { flattenDomain } = await import(opusCoreUrl)
|
|
88
|
+
path.join(PACKAGE_ROOT, "src", "core", "index.ts"),
|
|
89
|
+
).href;
|
|
90
|
+
const { flattenDomain } = await import(opusCoreUrl);
|
|
90
91
|
|
|
91
92
|
const opusSeedUrl = pathToFileURL(
|
|
92
|
-
path.join(PACKAGE_ROOT,
|
|
93
|
-
).href
|
|
93
|
+
path.join(PACKAGE_ROOT, "src", "seed", "index.ts"),
|
|
94
|
+
).href;
|
|
94
95
|
const { checkSeedRegistry, isSeedDefinition, publicSeedDefinition } =
|
|
95
|
-
await import(opusSeedUrl)
|
|
96
|
+
await import(opusSeedUrl);
|
|
97
|
+
|
|
98
|
+
const opusPresentationUrl = pathToFileURL(
|
|
99
|
+
path.join(PACKAGE_ROOT, "src", "presentation", "index.ts"),
|
|
100
|
+
).href;
|
|
101
|
+
const { validatePresentations } = await import(opusPresentationUrl);
|
|
96
102
|
|
|
97
103
|
// Acessar logical type meta dos dicts.
|
|
98
104
|
const opusSchemaUrl = pathToFileURL(
|
|
99
|
-
path.join(PACKAGE_ROOT,
|
|
100
|
-
).href
|
|
101
|
-
const opusSchema = await import(opusSchemaUrl)
|
|
105
|
+
path.join(PACKAGE_ROOT, "src", "schema", "index.ts"),
|
|
106
|
+
).href;
|
|
107
|
+
const opusSchema = await import(opusSchemaUrl);
|
|
102
108
|
|
|
103
109
|
// Pra serializar Zod → JSON Schema reusa o caminho que `toOpenAPISpec`
|
|
104
110
|
// usa. Importação direta do npm pq é dep regular da opus.
|
|
105
|
-
const { zodToJsonSchema } = await import(
|
|
111
|
+
const { zodToJsonSchema } = await import("zod-to-json-schema");
|
|
106
112
|
|
|
107
|
-
const pkgUrl = pathToFileURL(path.join(PACKAGE_ROOT,
|
|
108
|
-
const pkg = await import(pkgUrl, { with: { type:
|
|
113
|
+
const pkgUrl = pathToFileURL(path.join(PACKAGE_ROOT, "package.json")).href;
|
|
114
|
+
const pkg = await import(pkgUrl, { with: { type: "json" } }).then(
|
|
109
115
|
(m) => m.default,
|
|
110
|
-
)
|
|
116
|
+
);
|
|
111
117
|
|
|
112
118
|
// Import dinâmico do config do consumer.
|
|
113
|
-
const mod = await import(configUrl)
|
|
114
|
-
const cfg = mod.default ?? mod.config ?? mod
|
|
115
|
-
if (cfg === undefined || cfg === null || typeof cfg !==
|
|
119
|
+
const mod = await import(configUrl);
|
|
120
|
+
const cfg = mod.default ?? mod.config ?? mod;
|
|
121
|
+
if (cfg === undefined || cfg === null || typeof cfg !== "object") {
|
|
116
122
|
return emitError(
|
|
117
123
|
`Config em ${configAbs} não exporta um objeto default ou named "config"`,
|
|
118
|
-
)
|
|
124
|
+
);
|
|
119
125
|
}
|
|
120
126
|
|
|
121
127
|
if (!Array.isArray(cfg.domains)) {
|
|
122
128
|
return emitError(
|
|
123
129
|
`Config em ${configAbs} precisa de \`domains: DomainConfig[]\` no export default`,
|
|
124
|
-
)
|
|
130
|
+
);
|
|
125
131
|
}
|
|
126
132
|
|
|
127
|
-
const outputDir = typeof cfg.output ===
|
|
133
|
+
const outputDir = typeof cfg.output === "string" ? cfg.output : ".gen";
|
|
128
134
|
|
|
129
135
|
const ctx = {
|
|
130
136
|
flattenDomain,
|
|
131
137
|
toJsonSchema: (schema) =>
|
|
132
|
-
zodToJsonSchema(schema, { target:
|
|
138
|
+
zodToJsonSchema(schema, { target: "openApi3", $refStrategy: "none" }),
|
|
133
139
|
sourceConfigDir: path.dirname(configAbs),
|
|
134
|
-
}
|
|
140
|
+
};
|
|
135
141
|
|
|
136
|
-
const domains = cfg.domains.map((d) => serializeDomain(d, ctx))
|
|
142
|
+
const domains = cfg.domains.map((d) => serializeDomain(d, ctx));
|
|
143
|
+
if (cfg.presentations !== undefined && !Array.isArray(cfg.presentations)) {
|
|
144
|
+
throw new Error(
|
|
145
|
+
"config.presentations inválido — use um array de declarações registradas",
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
const actionRegistry = Object.fromEntries(
|
|
149
|
+
cfg.domains
|
|
150
|
+
.flatMap((domain) => collectDomainActions(domain))
|
|
151
|
+
.map((action) => [action.name, action]),
|
|
152
|
+
);
|
|
153
|
+
const presentations = validatePresentations(
|
|
154
|
+
cfg.presentations ?? [],
|
|
155
|
+
actionRegistry,
|
|
156
|
+
);
|
|
137
157
|
if (cfg.seeds !== undefined && !Array.isArray(cfg.seeds)) {
|
|
138
|
-
throw new Error(
|
|
158
|
+
throw new Error(
|
|
159
|
+
"config.seeds inválido — use um array de declarações registradas",
|
|
160
|
+
);
|
|
139
161
|
}
|
|
140
|
-
const registeredSeeds = cfg.seeds ?? []
|
|
141
|
-
const seedCheck = checkSeedRegistry(registeredSeeds)
|
|
162
|
+
const registeredSeeds = cfg.seeds ?? [];
|
|
163
|
+
const seedCheck = checkSeedRegistry(registeredSeeds);
|
|
142
164
|
if (!seedCheck.ok) {
|
|
143
165
|
throw new Error(
|
|
144
|
-
`config.seeds inválido — ${seedCheck.diagnostics.map((diagnostic) => diagnostic.message).join(
|
|
145
|
-
)
|
|
166
|
+
`config.seeds inválido — ${seedCheck.diagnostics.map((diagnostic) => diagnostic.message).join("; ")}`,
|
|
167
|
+
);
|
|
146
168
|
}
|
|
147
169
|
const seeds = registeredSeeds.map((seed) =>
|
|
148
170
|
serializeSeed(seed, isSeedDefinition, publicSeedDefinition),
|
|
149
|
-
)
|
|
171
|
+
);
|
|
150
172
|
|
|
151
173
|
const payload = {
|
|
152
174
|
opusVersion: pkg.version,
|
|
@@ -154,16 +176,27 @@ async function main() {
|
|
|
154
176
|
output: outputDir,
|
|
155
177
|
domains,
|
|
156
178
|
seeds,
|
|
157
|
-
|
|
179
|
+
presentations,
|
|
180
|
+
};
|
|
158
181
|
|
|
159
|
-
emit({ ok: true, payload })
|
|
182
|
+
emit({ ok: true, payload });
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function collectDomainActions(domain) {
|
|
186
|
+
const actions = Array.from(iterateActions(domain.actions));
|
|
187
|
+
for (const subdomain of Array.isArray(domain.subdomains)
|
|
188
|
+
? domain.subdomains
|
|
189
|
+
: []) {
|
|
190
|
+
actions.push(...collectDomainActions(subdomain));
|
|
191
|
+
}
|
|
192
|
+
return actions;
|
|
160
193
|
}
|
|
161
194
|
|
|
162
195
|
function serializeSeed(seed, isSeedDefinition, publicSeedDefinition) {
|
|
163
196
|
if (!isSeedDefinition(seed)) {
|
|
164
|
-
throw new Error(
|
|
197
|
+
throw new Error("config.seeds contém uma declaração inválida");
|
|
165
198
|
}
|
|
166
|
-
return publicSeedDefinition(seed)
|
|
199
|
+
return publicSeedDefinition(seed);
|
|
167
200
|
}
|
|
168
201
|
|
|
169
202
|
// =============================================================================
|
|
@@ -171,7 +204,7 @@ function serializeSeed(seed, isSeedDefinition, publicSeedDefinition) {
|
|
|
171
204
|
// =============================================================================
|
|
172
205
|
|
|
173
206
|
function serializeDomain(domain, ctx) {
|
|
174
|
-
const entityList = serializeEntities(domain.entities)
|
|
207
|
+
const entityList = serializeEntities(domain.entities);
|
|
175
208
|
return {
|
|
176
209
|
name: domain.name,
|
|
177
210
|
description: nullable(domain.description),
|
|
@@ -192,7 +225,7 @@ function serializeDomain(domain, ctx) {
|
|
|
192
225
|
subdomains: Array.isArray(domain.subdomains)
|
|
193
226
|
? domain.subdomains.map((s) => serializeDomain(s, ctx))
|
|
194
227
|
: [],
|
|
195
|
-
}
|
|
228
|
+
};
|
|
196
229
|
}
|
|
197
230
|
|
|
198
231
|
function serializeDataProducts(source) {
|
|
@@ -243,13 +276,18 @@ function serializeDataProducts(source) {
|
|
|
243
276
|
/** Serializa as entidades (`defineEntity`) com campos + docs — a fonte que vai pro
|
|
244
277
|
* manifest/lente. Ignora valores que não parecem EntityConfig (name+fields). */
|
|
245
278
|
function serializeEntities(src) {
|
|
246
|
-
if (src === undefined || src === null || typeof src !==
|
|
247
|
-
const out = []
|
|
279
|
+
if (src === undefined || src === null || typeof src !== "object") return [];
|
|
280
|
+
const out = [];
|
|
248
281
|
for (const [key, ent] of Object.entries(src)) {
|
|
249
|
-
if (
|
|
250
|
-
|
|
282
|
+
if (
|
|
283
|
+
ent === null ||
|
|
284
|
+
typeof ent !== "object" ||
|
|
285
|
+
typeof ent.fields !== "object"
|
|
286
|
+
)
|
|
287
|
+
continue;
|
|
288
|
+
const fields = [];
|
|
251
289
|
for (const [fname, f] of Object.entries(ent.fields)) {
|
|
252
|
-
const col = f?.column ?? {}
|
|
290
|
+
const col = f?.column ?? {};
|
|
253
291
|
fields.push({
|
|
254
292
|
name: fname,
|
|
255
293
|
logicalType: f?.meta?.logicalType ?? null,
|
|
@@ -257,70 +295,68 @@ function serializeEntities(src) {
|
|
|
257
295
|
pk: col.pk === true,
|
|
258
296
|
references: col.references ?? null,
|
|
259
297
|
doc: col.doc ?? null,
|
|
260
|
-
})
|
|
298
|
+
});
|
|
261
299
|
}
|
|
262
300
|
out.push({
|
|
263
|
-
name: typeof ent.name ===
|
|
264
|
-
description: typeof ent.description ===
|
|
265
|
-
table: typeof ent.table ===
|
|
301
|
+
name: typeof ent.name === "string" ? ent.name : key,
|
|
302
|
+
description: typeof ent.description === "string" ? ent.description : null,
|
|
303
|
+
table: typeof ent.table === "string" ? ent.table : null,
|
|
266
304
|
fields,
|
|
267
305
|
relations: ent.relations ?? null,
|
|
268
|
-
})
|
|
306
|
+
});
|
|
269
307
|
}
|
|
270
|
-
return out
|
|
308
|
+
return out;
|
|
271
309
|
}
|
|
272
310
|
|
|
273
311
|
function serializeDicts(dicts) {
|
|
274
|
-
if (dicts === undefined || dicts === null || typeof dicts !==
|
|
275
|
-
return {}
|
|
312
|
+
if (dicts === undefined || dicts === null || typeof dicts !== "object") {
|
|
313
|
+
return {};
|
|
276
314
|
}
|
|
277
|
-
const out = {}
|
|
315
|
+
const out = {};
|
|
278
316
|
for (const [name, dict] of Object.entries(dicts)) {
|
|
279
|
-
if (dict === null || typeof dict !==
|
|
317
|
+
if (dict === null || typeof dict !== "object") continue;
|
|
280
318
|
// DictType expõe `meta.params.entries`. Fallback: tenta `.keys()` +
|
|
281
319
|
// `.metaFor(k)` caso seja shape custom.
|
|
282
|
-
const meta = dict.meta
|
|
320
|
+
const meta = dict.meta;
|
|
283
321
|
if (
|
|
284
322
|
meta !== undefined &&
|
|
285
323
|
meta !== null &&
|
|
286
|
-
meta.logicalType ===
|
|
324
|
+
meta.logicalType === "dict" &&
|
|
287
325
|
meta.params !== undefined
|
|
288
326
|
) {
|
|
289
|
-
const params = meta.params
|
|
290
|
-
const keys = Array.isArray(params.keys) ? params.keys : []
|
|
327
|
+
const params = meta.params;
|
|
328
|
+
const keys = Array.isArray(params.keys) ? params.keys : [];
|
|
291
329
|
const entries =
|
|
292
330
|
params.entries !== undefined && params.entries !== null
|
|
293
331
|
? params.entries
|
|
294
|
-
: {}
|
|
295
|
-
const values = {}
|
|
332
|
+
: {};
|
|
333
|
+
const values = {};
|
|
296
334
|
for (const k of keys) {
|
|
297
|
-
values[k] = entries[k] ?? null
|
|
335
|
+
values[k] = entries[k] ?? null;
|
|
298
336
|
}
|
|
299
337
|
// `doc` = entendimento do vocabulário inteiro (o que esse dict representa);
|
|
300
338
|
// `presentation` = papel de apresentação declarado (ADR 0003) — null quando ausente.
|
|
301
339
|
out[name] = {
|
|
302
340
|
keys,
|
|
303
341
|
values,
|
|
304
|
-
doc: typeof params.doc ===
|
|
305
|
-
presentation:
|
|
306
|
-
|
|
307
|
-
|
|
342
|
+
doc: typeof params.doc === "string" ? params.doc : null,
|
|
343
|
+
presentation:
|
|
344
|
+
typeof params.presentation === "string" ? params.presentation : null,
|
|
345
|
+
};
|
|
346
|
+
continue;
|
|
308
347
|
}
|
|
309
348
|
// Fallback: dict-like com `keys()` + `metaFor()`.
|
|
310
|
-
if (
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
for (const k of keys) values[k] = dict.metaFor(k)
|
|
317
|
-
out[name] = { keys, values }
|
|
318
|
-
continue
|
|
349
|
+
if (typeof dict.keys === "function" && typeof dict.metaFor === "function") {
|
|
350
|
+
const keys = dict.keys();
|
|
351
|
+
const values = {};
|
|
352
|
+
for (const k of keys) values[k] = dict.metaFor(k);
|
|
353
|
+
out[name] = { keys, values };
|
|
354
|
+
continue;
|
|
319
355
|
}
|
|
320
356
|
// Não-reconhecível: registra placeholder pra não silenciar.
|
|
321
|
-
out[name] = { keys: [], values: {}, unknownShape: true }
|
|
357
|
+
out[name] = { keys: [], values: {}, unknownShape: true };
|
|
322
358
|
}
|
|
323
|
-
return out
|
|
359
|
+
return out;
|
|
324
360
|
}
|
|
325
361
|
|
|
326
362
|
function serializeAction(action, ctx) {
|
|
@@ -351,43 +387,43 @@ function serializeAction(action, ctx) {
|
|
|
351
387
|
examples: Array.isArray(action.examples) ? action.examples : [],
|
|
352
388
|
errors: Array.isArray(action.errors) ? action.errors : [],
|
|
353
389
|
sourceModule: lookupSourceModule(action, ctx),
|
|
354
|
-
}
|
|
390
|
+
};
|
|
355
391
|
|
|
356
|
-
if (action.kind ===
|
|
357
|
-
base.emits = Array.isArray(action.emits) ? action.emits : []
|
|
392
|
+
if (action.kind === "simple" || action.kind === "form") {
|
|
393
|
+
base.emits = Array.isArray(action.emits) ? action.emits : [];
|
|
358
394
|
base.background =
|
|
359
395
|
action.background !== undefined &&
|
|
360
396
|
action.background !== null &&
|
|
361
|
-
action.background.enabled === true
|
|
397
|
+
action.background.enabled === true;
|
|
362
398
|
}
|
|
363
399
|
|
|
364
|
-
if (action.kind ===
|
|
365
|
-
base.fields = serializeFields(action.fields)
|
|
400
|
+
if (action.kind === "form") {
|
|
401
|
+
base.fields = serializeFields(action.fields);
|
|
366
402
|
}
|
|
367
403
|
|
|
368
|
-
if (action.kind ===
|
|
369
|
-
base.filters = serializeFilters(action.filters)
|
|
370
|
-
base.sort = action.sort ?? null
|
|
371
|
-
base.paginate = action.paginate ?? null
|
|
372
|
-
base.text = action.text ?? null
|
|
373
|
-
base.periods = Array.isArray(action.periods) ? action.periods : []
|
|
404
|
+
if (action.kind === "list") {
|
|
405
|
+
base.filters = serializeFilters(action.filters);
|
|
406
|
+
base.sort = action.sort ?? null;
|
|
407
|
+
base.paginate = action.paginate ?? null;
|
|
408
|
+
base.text = action.text ?? null;
|
|
409
|
+
base.periods = Array.isArray(action.periods) ? action.periods : [];
|
|
374
410
|
}
|
|
375
411
|
|
|
376
|
-
if (action.kind ===
|
|
377
|
-
base.projection = Array.isArray(action.projection) ? action.projection : []
|
|
378
|
-
base.expand = action.expand ?? null
|
|
412
|
+
if (action.kind === "view") {
|
|
413
|
+
base.projection = Array.isArray(action.projection) ? action.projection : [];
|
|
414
|
+
base.expand = action.expand ?? null;
|
|
379
415
|
}
|
|
380
416
|
|
|
381
|
-
return base
|
|
417
|
+
return base;
|
|
382
418
|
}
|
|
383
419
|
|
|
384
420
|
function serializeFields(fields) {
|
|
385
|
-
if (fields === undefined || fields === null || typeof fields !==
|
|
386
|
-
return {}
|
|
421
|
+
if (fields === undefined || fields === null || typeof fields !== "object") {
|
|
422
|
+
return {};
|
|
387
423
|
}
|
|
388
|
-
const out = {}
|
|
424
|
+
const out = {};
|
|
389
425
|
for (const [name, spec] of Object.entries(fields)) {
|
|
390
|
-
if (spec === null || typeof spec !==
|
|
426
|
+
if (spec === null || typeof spec !== "object") continue;
|
|
391
427
|
out[name] = {
|
|
392
428
|
label: spec.label ?? null,
|
|
393
429
|
placeholder: spec.placeholder ?? null,
|
|
@@ -399,21 +435,25 @@ function serializeFields(fields) {
|
|
|
399
435
|
order: spec.order ?? null,
|
|
400
436
|
widget: spec.widget ?? null,
|
|
401
437
|
options: spec.options ?? null,
|
|
402
|
-
hasShowWhen: typeof spec.showWhen ===
|
|
403
|
-
hasRequireWhen: typeof spec.requireWhen ===
|
|
438
|
+
hasShowWhen: typeof spec.showWhen === "function",
|
|
439
|
+
hasRequireWhen: typeof spec.requireWhen === "function",
|
|
404
440
|
aiDescription: spec.aiDescription ?? null,
|
|
405
|
-
}
|
|
441
|
+
};
|
|
406
442
|
}
|
|
407
|
-
return out
|
|
443
|
+
return out;
|
|
408
444
|
}
|
|
409
445
|
|
|
410
446
|
function serializeFilters(filters) {
|
|
411
|
-
if (
|
|
412
|
-
|
|
447
|
+
if (
|
|
448
|
+
filters === undefined ||
|
|
449
|
+
filters === null ||
|
|
450
|
+
typeof filters !== "object"
|
|
451
|
+
) {
|
|
452
|
+
return {};
|
|
413
453
|
}
|
|
414
|
-
const out = {}
|
|
454
|
+
const out = {};
|
|
415
455
|
for (const [name, spec] of Object.entries(filters)) {
|
|
416
|
-
if (spec === null || typeof spec !==
|
|
456
|
+
if (spec === null || typeof spec !== "object") continue;
|
|
417
457
|
out[name] = {
|
|
418
458
|
label: spec.label ?? null,
|
|
419
459
|
placeholder: spec.placeholder ?? null,
|
|
@@ -426,9 +466,9 @@ function serializeFilters(filters) {
|
|
|
426
466
|
depends: spec.depends ?? null,
|
|
427
467
|
options: spec.options ?? null,
|
|
428
468
|
aiDescription: spec.aiDescription ?? null,
|
|
429
|
-
}
|
|
469
|
+
};
|
|
430
470
|
}
|
|
431
|
-
return out
|
|
471
|
+
return out;
|
|
432
472
|
}
|
|
433
473
|
|
|
434
474
|
function serializeReaction(reaction) {
|
|
@@ -437,13 +477,13 @@ function serializeReaction(reaction) {
|
|
|
437
477
|
on: reaction.on,
|
|
438
478
|
description: nullable(reaction.description),
|
|
439
479
|
tags: Array.isArray(reaction.tags) ? reaction.tags : [],
|
|
440
|
-
handler:
|
|
480
|
+
handler: "<function>",
|
|
441
481
|
retry: reaction.retry ?? null,
|
|
442
482
|
timeout: reaction.timeout ?? null,
|
|
443
483
|
concurrency: reaction.concurrency ?? null,
|
|
444
|
-
hasDedup: typeof reaction.dedup ===
|
|
445
|
-
hasAuthorize: typeof reaction.authorize ===
|
|
446
|
-
}
|
|
484
|
+
hasDedup: typeof reaction.dedup === "function",
|
|
485
|
+
hasAuthorize: typeof reaction.authorize === "function",
|
|
486
|
+
};
|
|
447
487
|
}
|
|
448
488
|
|
|
449
489
|
function serializeSchedule(sched) {
|
|
@@ -457,8 +497,8 @@ function serializeSchedule(sched) {
|
|
|
457
497
|
description: nullable(sched.description),
|
|
458
498
|
tags: Array.isArray(sched.tags) ? sched.tags : [],
|
|
459
499
|
input:
|
|
460
|
-
typeof sched.input ===
|
|
461
|
-
}
|
|
500
|
+
typeof sched.input === "function" ? "<function>" : (sched.input ?? null),
|
|
501
|
+
};
|
|
462
502
|
}
|
|
463
503
|
|
|
464
504
|
// =============================================================================
|
|
@@ -466,16 +506,16 @@ function serializeSchedule(sched) {
|
|
|
466
506
|
// =============================================================================
|
|
467
507
|
|
|
468
508
|
function* iterateActions(actions) {
|
|
469
|
-
if (actions === undefined) return
|
|
509
|
+
if (actions === undefined) return;
|
|
470
510
|
if (Array.isArray(actions)) {
|
|
471
|
-
for (const a of actions) yield a
|
|
472
|
-
return
|
|
511
|
+
for (const a of actions) yield a;
|
|
512
|
+
return;
|
|
473
513
|
}
|
|
474
514
|
for (const value of Object.values(actions)) {
|
|
475
515
|
if (Array.isArray(value)) {
|
|
476
|
-
for (const a of value) yield a
|
|
516
|
+
for (const a of value) yield a;
|
|
477
517
|
} else {
|
|
478
|
-
yield value
|
|
518
|
+
yield value;
|
|
479
519
|
}
|
|
480
520
|
}
|
|
481
521
|
}
|
|
@@ -483,36 +523,36 @@ function* iterateActions(actions) {
|
|
|
483
523
|
function derivePermission(action) {
|
|
484
524
|
// Projeção FIEL do `requires`: lista sai lista (semântica ANY) — projetar só a
|
|
485
525
|
// 1ª string faria a spec mentir por omissão pra quem lê o manifest.
|
|
486
|
-
const req = action.requires
|
|
487
|
-
if (typeof req ===
|
|
526
|
+
const req = action.requires;
|
|
527
|
+
if (typeof req === "string") return req;
|
|
488
528
|
if (Array.isArray(req)) {
|
|
489
|
-
const strs = req.filter((r) => typeof r ===
|
|
490
|
-
return strs.length > 0 ? strs : null
|
|
529
|
+
const strs = req.filter((r) => typeof r === "string");
|
|
530
|
+
return strs.length > 0 ? strs : null;
|
|
491
531
|
}
|
|
492
|
-
return null
|
|
532
|
+
return null;
|
|
493
533
|
}
|
|
494
534
|
|
|
495
535
|
function serializeAuthorize(authorize) {
|
|
496
|
-
if (authorize === undefined || authorize === null) return null
|
|
536
|
+
if (authorize === undefined || authorize === null) return null;
|
|
497
537
|
// DSL: pode chegar como string em frameworks (versão futura). Hoje é fn.
|
|
498
|
-
if (typeof authorize ===
|
|
499
|
-
if (typeof authorize ===
|
|
500
|
-
return null
|
|
538
|
+
if (typeof authorize === "string") return authorize;
|
|
539
|
+
if (typeof authorize === "function") return "<function>";
|
|
540
|
+
return null;
|
|
501
541
|
}
|
|
502
542
|
|
|
503
543
|
function serializeInvalidates(inv) {
|
|
504
|
-
if (inv === undefined || inv === null) return null
|
|
505
|
-
if (Array.isArray(inv)) return inv
|
|
506
|
-
if (typeof inv ===
|
|
507
|
-
return null
|
|
544
|
+
if (inv === undefined || inv === null) return null;
|
|
545
|
+
if (Array.isArray(inv)) return inv;
|
|
546
|
+
if (typeof inv === "function") return "<function>";
|
|
547
|
+
return null;
|
|
508
548
|
}
|
|
509
549
|
|
|
510
550
|
function safeToJsonSchema(schema, ctx) {
|
|
511
|
-
if (schema === undefined || schema === null) return null
|
|
551
|
+
if (schema === undefined || schema === null) return null;
|
|
512
552
|
try {
|
|
513
|
-
return ctx.toJsonSchema(schema)
|
|
553
|
+
return ctx.toJsonSchema(schema);
|
|
514
554
|
} catch (err) {
|
|
515
|
-
return { __unserializable: true, reason: err.message }
|
|
555
|
+
return { __unserializable: true, reason: err.message };
|
|
516
556
|
}
|
|
517
557
|
}
|
|
518
558
|
|
|
@@ -520,13 +560,13 @@ function lookupSourceModule(_action, _ctx) {
|
|
|
520
560
|
// Não há mecanismo confiável pra extrair o source module a partir do
|
|
521
561
|
// ActionDef em runtime (ESM não expõe). Reservamos o campo pra futura
|
|
522
562
|
// integração com `import.meta.url` no defineAction.
|
|
523
|
-
return null
|
|
563
|
+
return null;
|
|
524
564
|
}
|
|
525
565
|
|
|
526
566
|
function nullable(value) {
|
|
527
|
-
if (value === undefined || value === null) return null
|
|
528
|
-
if (typeof value ===
|
|
529
|
-
return value
|
|
567
|
+
if (value === undefined || value === null) return null;
|
|
568
|
+
if (typeof value === "string" && value.length === 0) return null;
|
|
569
|
+
return value;
|
|
530
570
|
}
|
|
531
571
|
|
|
532
572
|
// =============================================================================
|
|
@@ -534,14 +574,14 @@ function nullable(value) {
|
|
|
534
574
|
// =============================================================================
|
|
535
575
|
|
|
536
576
|
function emit(payload) {
|
|
537
|
-
process.stdout.write(JSON.stringify(payload) +
|
|
577
|
+
process.stdout.write(JSON.stringify(payload) + "\n");
|
|
538
578
|
}
|
|
539
579
|
|
|
540
580
|
function emitError(message) {
|
|
541
|
-
emit({ ok: false, error: message })
|
|
542
|
-
process.exit(1)
|
|
581
|
+
emit({ ok: false, error: message });
|
|
582
|
+
process.exit(1);
|
|
543
583
|
}
|
|
544
584
|
|
|
545
585
|
main().catch((err) => {
|
|
546
|
-
emitError(`runner crash: ${err.stack ?? err.message ?? String(err)}`)
|
|
547
|
-
})
|
|
586
|
+
emitError(`runner crash: ${err.stack ?? err.message ?? String(err)}`);
|
|
587
|
+
});
|