@softize/opus 15.2.2 → 16.1.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.
Files changed (37) hide show
  1. package/CHANGELOG.md +60 -18
  2. package/bin/lib/check.mjs +98 -15
  3. package/bin/lib/copy.mjs +811 -314
  4. package/bin/lib/gen-manifest.mjs +24 -23
  5. package/bin/lib/gen-runner.mjs +188 -148
  6. package/docs/adr/0010-page-header-owns-page-chrome.md +3 -4
  7. package/docs/adr/0011-page-shell-coordinates-persistent-page-chrome.md +5 -3
  8. package/docs/adr/0012-modal-header-only-names-the-surface.md +45 -0
  9. package/docs/adr/0013-presentation-is-a-portable-action-oriented-artifact.md +92 -0
  10. package/docs/code-style.md +24 -19
  11. package/package.json +5 -1
  12. package/registry/skills/build-opus-ui/references/ui-patterns.md +43 -26
  13. package/src/core/presentation.ts +512 -0
  14. package/src/presentation/index.ts +1 -0
  15. package/src/ui/components/patterns/action-list-dialog.tsx +26 -9
  16. package/src/ui/components/patterns/confirm.tsx +34 -29
  17. package/src/ui/components/patterns/form-dialog.tsx +20 -8
  18. package/src/ui/components/patterns/list.tsx +26 -9
  19. package/src/ui/components/patterns/page.tsx +99 -139
  20. package/src/ui/components/patterns/presentation.tsx +539 -0
  21. package/src/ui/components/patterns/sidebar.tsx +3 -3
  22. package/src/ui/components/patterns/trigger.tsx +38 -17
  23. package/src/ui/components/primitives/button-group.tsx +53 -43
  24. package/src/ui/components/primitives/command.tsx +30 -72
  25. package/src/ui/components/primitives/dialog.tsx +23 -89
  26. package/src/ui/components/primitives/drawer.tsx +8 -34
  27. package/src/ui/docs/content/action-form-dialog.md +12 -10
  28. package/src/ui/docs/content/action-list-dialog.md +22 -17
  29. package/src/ui/docs/content/button.md +52 -35
  30. package/src/ui/docs/content/communication.md +26 -26
  31. package/src/ui/docs/content/dialog.md +173 -154
  32. package/src/ui/docs/content/drawer.md +12 -11
  33. package/src/ui/docs/content/page.md +72 -91
  34. package/src/ui/docs/content/presentation.md +188 -0
  35. package/src/ui/docs/registry.tsx +6 -0
  36. package/src/ui/meta.ts +8 -2
  37. package/src/ui/react.tsx +16 -3
@@ -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: '1',
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 === 'simple' || a.kind === 'form') {
66
- out.emits = a.emits ?? []
67
- out.background = a.background === true
68
- }
69
- if (a.kind === 'form') out.fields = a.fields ?? {}
70
- if (a.kind === 'list') {
71
- out.filters = a.filters ?? {}
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 === 'view') {
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
  }
@@ -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 'node:path'
65
- import { pathToFileURL, fileURLToPath } from 'node:url'
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 !== 'string' || configPath.length === 0) {
78
- return emitError('Caminho do config ausente (argv[2])')
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, 'src', 'core', 'index.ts'),
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, 'src', 'seed', 'index.ts'),
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, 'src', 'schema', 'index.ts'),
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('zod-to-json-schema')
111
+ const { zodToJsonSchema } = await import("zod-to-json-schema");
106
112
 
107
- const pkgUrl = pathToFileURL(path.join(PACKAGE_ROOT, 'package.json')).href
108
- const pkg = await import(pkgUrl, { with: { type: 'json' } }).then(
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 !== 'object') {
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 === 'string' ? cfg.output : '.gen'
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: 'openApi3', $refStrategy: 'none' }),
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('config.seeds inválido — use um array de declarações registradas')
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('config.seeds contém uma declaração inválida')
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 !== 'object') return []
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 (ent === null || typeof ent !== 'object' || typeof ent.fields !== 'object') continue
250
- const fields = []
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 === 'string' ? ent.name : key,
264
- description: typeof ent.description === 'string' ? ent.description : null,
265
- table: typeof ent.table === 'string' ? ent.table : null,
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 !== 'object') {
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 !== 'object') continue
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 === 'dict' &&
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 === 'string' ? params.doc : null,
305
- presentation: typeof params.presentation === 'string' ? params.presentation : null,
306
- }
307
- continue
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
- typeof dict.keys === 'function' &&
312
- typeof dict.metaFor === 'function'
313
- ) {
314
- const keys = dict.keys()
315
- const values = {}
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 === 'simple' || action.kind === 'form') {
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 === 'form') {
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 === 'list') {
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 === 'view') {
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 !== 'object') {
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 !== 'object') continue
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 === 'function',
403
- hasRequireWhen: typeof spec.requireWhen === 'function',
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 (filters === undefined || filters === null || typeof filters !== 'object') {
412
- return {}
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 !== 'object') continue
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: '<function>',
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 === 'function',
445
- hasAuthorize: typeof reaction.authorize === 'function',
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 === 'function' ? '<function>' : (sched.input ?? null),
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 === 'string') return 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 === 'string')
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 === 'string') return authorize
499
- if (typeof authorize === 'function') return '<function>'
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 === 'function') return '<function>'
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 === 'string' && value.length === 0) return null
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) + '\n')
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
+ });