@loj-lang/rdsl-compiler 0.5.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 (78) hide show
  1. package/README.md +23 -0
  2. package/dist/cache-signature.d.ts +2 -0
  3. package/dist/cache-signature.d.ts.map +1 -0
  4. package/dist/cache-signature.js +17 -0
  5. package/dist/cache-signature.js.map +1 -0
  6. package/dist/codegen.d.ts +37 -0
  7. package/dist/codegen.d.ts.map +1 -0
  8. package/dist/codegen.js +6394 -0
  9. package/dist/codegen.js.map +1 -0
  10. package/dist/dependency-graph.d.ts +23 -0
  11. package/dist/dependency-graph.d.ts.map +1 -0
  12. package/dist/dependency-graph.js +516 -0
  13. package/dist/dependency-graph.js.map +1 -0
  14. package/dist/expr.d.ts +24 -0
  15. package/dist/expr.d.ts.map +1 -0
  16. package/dist/expr.js +359 -0
  17. package/dist/expr.js.map +1 -0
  18. package/dist/flow-proof.d.ts +68 -0
  19. package/dist/flow-proof.d.ts.map +1 -0
  20. package/dist/flow-proof.js +487 -0
  21. package/dist/flow-proof.js.map +1 -0
  22. package/dist/host-files.d.ts +27 -0
  23. package/dist/host-files.d.ts.map +1 -0
  24. package/dist/host-files.js +441 -0
  25. package/dist/host-files.js.map +1 -0
  26. package/dist/index.d.ts +120 -0
  27. package/dist/index.d.ts.map +1 -0
  28. package/dist/index.js +948 -0
  29. package/dist/index.js.map +1 -0
  30. package/dist/ir.d.ts +451 -0
  31. package/dist/ir.d.ts.map +1 -0
  32. package/dist/ir.js +13 -0
  33. package/dist/ir.js.map +1 -0
  34. package/dist/manifest.d.ts +104 -0
  35. package/dist/manifest.d.ts.map +1 -0
  36. package/dist/manifest.js +635 -0
  37. package/dist/manifest.js.map +1 -0
  38. package/dist/node-inspect.d.ts +23 -0
  39. package/dist/node-inspect.d.ts.map +1 -0
  40. package/dist/node-inspect.js +475 -0
  41. package/dist/node-inspect.js.map +1 -0
  42. package/dist/normalize.d.ts +101 -0
  43. package/dist/normalize.d.ts.map +1 -0
  44. package/dist/normalize.js +1771 -0
  45. package/dist/normalize.js.map +1 -0
  46. package/dist/page-table-block.d.ts +69 -0
  47. package/dist/page-table-block.d.ts.map +1 -0
  48. package/dist/page-table-block.js +241 -0
  49. package/dist/page-table-block.js.map +1 -0
  50. package/dist/parser.d.ts +262 -0
  51. package/dist/parser.d.ts.map +1 -0
  52. package/dist/parser.js +1335 -0
  53. package/dist/parser.js.map +1 -0
  54. package/dist/project-paths.d.ts +6 -0
  55. package/dist/project-paths.d.ts.map +1 -0
  56. package/dist/project-paths.js +84 -0
  57. package/dist/project-paths.js.map +1 -0
  58. package/dist/relation-projection.d.ts +60 -0
  59. package/dist/relation-projection.d.ts.map +1 -0
  60. package/dist/relation-projection.js +121 -0
  61. package/dist/relation-projection.js.map +1 -0
  62. package/dist/rules-proof.d.ts +95 -0
  63. package/dist/rules-proof.d.ts.map +1 -0
  64. package/dist/rules-proof.js +537 -0
  65. package/dist/rules-proof.js.map +1 -0
  66. package/dist/source-files.d.ts +9 -0
  67. package/dist/source-files.d.ts.map +1 -0
  68. package/dist/source-files.js +27 -0
  69. package/dist/source-files.js.map +1 -0
  70. package/dist/style-proof.d.ts +70 -0
  71. package/dist/style-proof.d.ts.map +1 -0
  72. package/dist/style-proof.js +640 -0
  73. package/dist/style-proof.js.map +1 -0
  74. package/dist/validator.d.ts +51 -0
  75. package/dist/validator.d.ts.map +1 -0
  76. package/dist/validator.js +2487 -0
  77. package/dist/validator.js.map +1 -0
  78. package/package.json +40 -0
package/dist/parser.js ADDED
@@ -0,0 +1,1335 @@
1
+ /**
2
+ * ReactDSL Parser
3
+ *
4
+ * Parses .rdsl files (strict YAML subset) into a raw AST.
5
+ *
6
+ * Restrictions vs full YAML:
7
+ * - No anchors/aliases
8
+ * - No merge keys
9
+ * - No custom tags
10
+ * - No implicit bool/date coercions
11
+ */
12
+ import { parseDocument, LineCounter, isAlias, isMap, isSeq, isPair, isScalar } from 'yaml';
13
+ import { CANONICAL_RDSL_SOURCE_SUFFIX, describeRdslSourceSuffixes, } from './source-files.js';
14
+ // ─── Decorator Parser ────────────────────────────────────────────
15
+ const DECORATOR_REGEX = /@(\w+)(?:\(([^)]*)\))?/g;
16
+ export function parseDecorators(input) {
17
+ const decorators = [];
18
+ let baseName = input;
19
+ // Extract all @decorator(...) patterns
20
+ const firstAt = input.indexOf('@');
21
+ if (firstAt > 0) {
22
+ baseName = input.substring(0, firstAt).trim();
23
+ const decoratorPart = input.substring(firstAt);
24
+ let match;
25
+ DECORATOR_REGEX.lastIndex = 0;
26
+ while ((match = DECORATOR_REGEX.exec(decoratorPart)) !== null) {
27
+ decorators.push({
28
+ name: match[1],
29
+ args: match[2] || undefined,
30
+ });
31
+ }
32
+ }
33
+ else if (firstAt === 0) {
34
+ // Entire string is decorators (shouldn't happen for fields, but handle gracefully)
35
+ baseName = '';
36
+ let match;
37
+ DECORATOR_REGEX.lastIndex = 0;
38
+ while ((match = DECORATOR_REGEX.exec(input)) !== null) {
39
+ decorators.push({
40
+ name: match[1],
41
+ args: match[2] || undefined,
42
+ });
43
+ }
44
+ }
45
+ return { baseName, decorators };
46
+ }
47
+ // ─── Field Type Parser ───────────────────────────────────────────
48
+ export function parseFieldType(expr) {
49
+ const enumMatch = expr.match(/^enum\(([^)]+)\)$/);
50
+ if (enumMatch) {
51
+ const values = enumMatch[1].split(',').map(v => v.trim());
52
+ return { typeName: 'enum', enumValues: values };
53
+ }
54
+ return { typeName: expr.trim() };
55
+ }
56
+ // ─── Effect Parser ───────────────────────────────────────────────
57
+ export function parseEffect(entry) {
58
+ if (typeof entry === 'object' && entry !== null) {
59
+ const obj = entry;
60
+ const keys = Object.keys(obj);
61
+ if (keys.length === 1) {
62
+ const type = keys[0];
63
+ const value = obj[type];
64
+ if (type === 'toast' && value && typeof value === 'object' && !Array.isArray(value)) {
65
+ return {
66
+ type,
67
+ value: parseToastDescriptorObject(value),
68
+ };
69
+ }
70
+ return { type, value: String(value) };
71
+ }
72
+ }
73
+ return null;
74
+ }
75
+ // ─── Column/Field Entry Parser ───────────────────────────────────
76
+ export function parseColumnEntry(entry, sourceSpan) {
77
+ if (typeof entry === 'string') {
78
+ const { baseName, decorators } = parseDecorators(entry);
79
+ return { field: baseName, decorators, sourceSpan };
80
+ }
81
+ // Shouldn't normally happen in valid DSL, but handle object form
82
+ if (typeof entry === 'object' && entry !== null) {
83
+ const obj = entry;
84
+ return {
85
+ field: String(obj['field'] || ''),
86
+ decorators: [],
87
+ sourceSpan,
88
+ };
89
+ }
90
+ return { field: String(entry), decorators: [], sourceSpan };
91
+ }
92
+ export function parseFormFieldEntry(entry, fileName, lineCounter) {
93
+ if (isScalar(entry)) {
94
+ const value = String(entry.value);
95
+ if (value.includes('@')) {
96
+ return parseColumnEntry(value, getNodeSpan(entry, fileName, lineCounter));
97
+ }
98
+ return value;
99
+ }
100
+ if (isMap(entry)) {
101
+ const map = entry;
102
+ const fieldValue = getMapValue(map, 'field');
103
+ const rawField = fieldValue === undefined
104
+ ? ''
105
+ : String(isScalar(fieldValue) ? fieldValue.value : fieldValue);
106
+ const parsed = parseColumnEntry(rawField, getNodeSpan(entry, fileName, lineCounter));
107
+ const rules = parseRules(map, fileName, lineCounter);
108
+ return {
109
+ ...parsed,
110
+ rules,
111
+ sourceSpan: getNodeSpan(entry, fileName, lineCounter),
112
+ };
113
+ }
114
+ return String(entry);
115
+ }
116
+ // ─── Action Entry Parser ─────────────────────────────────────────
117
+ export function parseActionEntry(entry, sourceSpan) {
118
+ if (typeof entry === 'string') {
119
+ const { baseName, decorators } = parseDecorators(entry);
120
+ return { name: baseName, decorators, sourceSpan };
121
+ }
122
+ return { name: String(entry), decorators: [], sourceSpan };
123
+ }
124
+ export function parse(source, fileName = `app${CANONICAL_RDSL_SOURCE_SUFFIX}`) {
125
+ const errors = [];
126
+ const ast = {
127
+ imports: [],
128
+ models: [],
129
+ resources: [],
130
+ readModels: [],
131
+ pages: [],
132
+ };
133
+ let doc;
134
+ const lineCounter = new LineCounter();
135
+ const normalizedSource = preprocessRelationTypeExprs(source);
136
+ try {
137
+ doc = parseDocument(normalizedSource, {
138
+ // Strict subset: reject YAML features we don't support
139
+ merge: false,
140
+ uniqueKeys: true,
141
+ lineCounter,
142
+ });
143
+ }
144
+ catch (err) {
145
+ errors.push({
146
+ message: `YAML parse error: ${err instanceof Error ? err.message : String(err)}`,
147
+ });
148
+ return { ast, errors };
149
+ }
150
+ // Check for YAML-level errors
151
+ for (const err of doc.errors) {
152
+ errors.push({ message: err.message, line: err.pos?.[0] });
153
+ }
154
+ const root = doc.contents;
155
+ detectUnsupportedYamlFeatures(root, errors);
156
+ if (errors.length > 0)
157
+ return { ast, errors };
158
+ if (!isMap(root)) {
159
+ errors.push({ message: 'Root document must be a YAML mapping' });
160
+ return { ast, errors };
161
+ }
162
+ // Walk top-level keys
163
+ for (const pair of root.items) {
164
+ if (!isPair(pair) || !isScalar(pair.key))
165
+ continue;
166
+ const key = String(pair.key.value);
167
+ if (key === 'app') {
168
+ ast.app = parseAppBlock(pair.value, fileName, lineCounter, getPairSpan(pair, fileName, lineCounter));
169
+ }
170
+ else if (key === 'compiler') {
171
+ ast.compiler = parseCompilerBlock(pair.value, fileName, lineCounter, errors, getPairSpan(pair, fileName, lineCounter));
172
+ }
173
+ else if (key === 'imports') {
174
+ ast.imports = parseImportsBlock(pair.value, fileName, lineCounter, errors, getPairSpan(pair, fileName, lineCounter));
175
+ }
176
+ else if (key.startsWith('model ')) {
177
+ const modelName = key.substring(6).trim();
178
+ const model = parseModelBlock(modelName, pair.value, fileName, lineCounter, getPairSpan(pair, fileName, lineCounter));
179
+ if (model)
180
+ ast.models.push(model);
181
+ }
182
+ else if (key.startsWith('resource ')) {
183
+ const resourceName = key.substring(9).trim();
184
+ const resource = parseResourceBlock(resourceName, pair.value, fileName, lineCounter, errors, getPairSpan(pair, fileName, lineCounter));
185
+ if (resource)
186
+ ast.resources.push(resource);
187
+ }
188
+ else if (key.startsWith('readModel ')) {
189
+ const readModelName = key.substring('readModel '.length).trim();
190
+ const readModel = parseReadModelBlock(readModelName, pair.value, fileName, lineCounter, errors, getPairSpan(pair, fileName, lineCounter));
191
+ if (readModel)
192
+ ast.readModels.push(readModel);
193
+ }
194
+ else if (key.startsWith('page ')) {
195
+ const pageName = key.substring(5).trim();
196
+ const page = parsePageBlock(pageName, pair.value, fileName, lineCounter, getPairSpan(pair, fileName, lineCounter));
197
+ if (page)
198
+ ast.pages.push(page);
199
+ }
200
+ else {
201
+ errors.push({ message: `Unknown top-level key: "${key}"` });
202
+ }
203
+ }
204
+ return { ast, errors };
205
+ }
206
+ function preprocessRelationTypeExprs(source) {
207
+ return source.replace(/^(\s*[A-Za-z_][A-Za-z0-9_]*\s*:\s*)(hasMany\([^#\n]*\)(?:\s+@\w+(?:\([^)]*\))?)*)((?:\s+#.*)?)$/gm, (_match, prefix, expr, suffix) => {
208
+ const trimmed = expr.trim();
209
+ if (trimmed.startsWith('"') || trimmed.startsWith('\'')) {
210
+ return `${prefix}${expr}${suffix}`;
211
+ }
212
+ const escaped = trimmed
213
+ .replace(/\\/g, '\\\\')
214
+ .replace(/"/g, '\\"');
215
+ return `${prefix}"${escaped}"${suffix}`;
216
+ });
217
+ }
218
+ function detectUnsupportedYamlFeatures(node, errors) {
219
+ if (!node || typeof node !== 'object')
220
+ return;
221
+ if (isAlias(node)) {
222
+ errors.push({ message: `YAML aliases are not supported in ${describeRdslSourceSuffixes()} files` });
223
+ return;
224
+ }
225
+ const maybeAnchored = node;
226
+ if (maybeAnchored.anchor) {
227
+ errors.push({ message: `YAML anchors are not supported in ${describeRdslSourceSuffixes()} files` });
228
+ }
229
+ if (isPair(node)) {
230
+ const pair = node;
231
+ detectUnsupportedYamlFeatures(pair.key, errors);
232
+ detectUnsupportedYamlFeatures(pair.value, errors);
233
+ return;
234
+ }
235
+ if (isMap(node) || isSeq(node)) {
236
+ const coll = node;
237
+ for (const item of coll.items) {
238
+ detectUnsupportedYamlFeatures(item, errors);
239
+ }
240
+ }
241
+ }
242
+ // ─── Block Parsers ───────────────────────────────────────────────
243
+ function getScalarValue(node) {
244
+ if (isScalar(node))
245
+ return String(node.value);
246
+ return undefined;
247
+ }
248
+ function getMapValue(map, key) {
249
+ const pair = getMapPair(map, key);
250
+ return pair?.value;
251
+ }
252
+ function getMapPair(map, key) {
253
+ for (const pair of map.items) {
254
+ if (isPair(pair) && isScalar(pair.key) && String(pair.key.value) === key) {
255
+ return pair;
256
+ }
257
+ }
258
+ return undefined;
259
+ }
260
+ function getNodeSpan(node, fileName, lineCounter) {
261
+ if (!node || typeof node !== 'object')
262
+ return undefined;
263
+ const rangedNode = node;
264
+ if (!rangedNode.range)
265
+ return undefined;
266
+ return rangeToSourceSpan(rangedNode.range, fileName, lineCounter);
267
+ }
268
+ function getPairSpan(pair, fileName, lineCounter) {
269
+ if (!pair)
270
+ return undefined;
271
+ const keyRange = isScalar(pair.key) || (pair.key && typeof pair.key === 'object' && 'range' in pair.key)
272
+ ? pair.key.range
273
+ : undefined;
274
+ const valueRange = pair.value && typeof pair.value === 'object' && 'range' in pair.value
275
+ ? pair.value.range
276
+ : undefined;
277
+ if (keyRange && valueRange) {
278
+ return rangeToSourceSpan([keyRange[0], valueRange[1]], fileName, lineCounter);
279
+ }
280
+ if (valueRange) {
281
+ return rangeToSourceSpan(valueRange, fileName, lineCounter);
282
+ }
283
+ if (keyRange) {
284
+ return rangeToSourceSpan(keyRange, fileName, lineCounter);
285
+ }
286
+ return undefined;
287
+ }
288
+ function rangeToSourceSpan(range, file, lineCounter) {
289
+ const start = lineCounter.linePos(range[0]);
290
+ const end = lineCounter.linePos(range[1]);
291
+ return {
292
+ file,
293
+ startLine: start.line,
294
+ startCol: start.col,
295
+ endLine: end.line,
296
+ endCol: end.col,
297
+ };
298
+ }
299
+ function parseAppBlock(node, fileName, lineCounter, sourceSpan) {
300
+ const app = { name: 'Untitled', sourceSpan };
301
+ if (!isMap(node))
302
+ return app;
303
+ const map = node;
304
+ const name = getMapValue(map, 'name');
305
+ if (name !== undefined)
306
+ app.name = String(isScalar(name) ? name.value : name);
307
+ const theme = getMapValue(map, 'theme');
308
+ if (theme !== undefined)
309
+ app.theme = String(isScalar(theme) ? theme.value : theme);
310
+ const auth = getMapValue(map, 'auth');
311
+ if (auth !== undefined)
312
+ app.auth = String(isScalar(auth) ? auth.value : auth);
313
+ const style = getMapValue(map, 'style');
314
+ if (style !== undefined)
315
+ app.style = String(isScalar(style) ? style.value : style);
316
+ const seo = getMapValue(map, 'seo');
317
+ if (isMap(seo)) {
318
+ app.seo = parseAppSeoBlock(seo, fileName, lineCounter);
319
+ }
320
+ // Parse navigation
321
+ const nav = getMapValue(map, 'navigation');
322
+ if (isSeq(nav)) {
323
+ app.navigation = [];
324
+ for (const item of nav.items) {
325
+ if (isMap(item)) {
326
+ const group = parseNavGroup(item, fileName, lineCounter);
327
+ if (group)
328
+ app.navigation.push(group);
329
+ }
330
+ }
331
+ }
332
+ return app;
333
+ }
334
+ function parseAppSeoBlock(map, fileName, lineCounter) {
335
+ const seo = {
336
+ sourceSpan: getNodeSpan(map, fileName, lineCounter),
337
+ };
338
+ const siteName = getMapValue(map, 'siteName');
339
+ if (siteName !== undefined)
340
+ seo.siteName = parseMessageLikeNode(siteName, fileName, lineCounter);
341
+ const defaultTitle = getMapValue(map, 'defaultTitle');
342
+ if (defaultTitle !== undefined)
343
+ seo.defaultTitle = parseMessageLikeNode(defaultTitle, fileName, lineCounter);
344
+ const titleTemplate = getMapValue(map, 'titleTemplate');
345
+ if (titleTemplate !== undefined)
346
+ seo.titleTemplate = parseMessageLikeNode(titleTemplate, fileName, lineCounter);
347
+ const defaultDescription = getMapValue(map, 'defaultDescription');
348
+ if (defaultDescription !== undefined)
349
+ seo.defaultDescription = parseMessageLikeNode(defaultDescription, fileName, lineCounter);
350
+ const defaultImage = getMapValue(map, 'defaultImage');
351
+ if (defaultImage !== undefined)
352
+ seo.defaultImage = String(isScalar(defaultImage) ? defaultImage.value : defaultImage);
353
+ const favicon = getMapValue(map, 'favicon');
354
+ if (favicon !== undefined)
355
+ seo.favicon = String(isScalar(favicon) ? favicon.value : favicon);
356
+ return seo;
357
+ }
358
+ function parsePageSeoBlock(map, fileName, lineCounter) {
359
+ const seo = {
360
+ sourceSpan: getNodeSpan(map, fileName, lineCounter),
361
+ };
362
+ const description = getMapValue(map, 'description');
363
+ if (description !== undefined)
364
+ seo.description = parseMessageLikeNode(description, fileName, lineCounter);
365
+ const canonicalPath = getMapValue(map, 'canonicalPath');
366
+ if (canonicalPath !== undefined)
367
+ seo.canonicalPath = String(isScalar(canonicalPath) ? canonicalPath.value : canonicalPath);
368
+ const image = getMapValue(map, 'image');
369
+ if (image !== undefined)
370
+ seo.image = String(isScalar(image) ? image.value : image);
371
+ const noIndex = getMapValue(map, 'noIndex');
372
+ if (isScalar(noIndex)) {
373
+ seo.noIndex = Boolean(noIndex.value);
374
+ }
375
+ else if (typeof noIndex === 'boolean') {
376
+ seo.noIndex = noIndex;
377
+ }
378
+ return seo;
379
+ }
380
+ function parseCompilerBlock(node, fileName, lineCounter, errors, sourceSpan) {
381
+ const compiler = { sourceSpan };
382
+ if (!isMap(node))
383
+ return compiler;
384
+ const map = node;
385
+ for (const pair of map.items) {
386
+ if (!isPair(pair) || !isScalar(pair.key))
387
+ continue;
388
+ const key = String(pair.key.value);
389
+ if (key !== 'target') {
390
+ const span = getPairSpan(pair, fileName, lineCounter);
391
+ errors.push({
392
+ message: `Unknown compiler key: "${key}"`,
393
+ line: span?.startLine,
394
+ col: span?.startCol,
395
+ });
396
+ continue;
397
+ }
398
+ if (pair.value !== undefined) {
399
+ compiler.target = String(isScalar(pair.value) ? pair.value.value : pair.value);
400
+ }
401
+ }
402
+ return compiler;
403
+ }
404
+ function parseImportsBlock(node, fileName, lineCounter, errors, sourceSpan) {
405
+ if (!isSeq(node)) {
406
+ errors.push({
407
+ message: `imports must be a YAML sequence of relative ${describeRdslSourceSuffixes()} file paths`,
408
+ line: sourceSpan?.startLine,
409
+ col: sourceSpan?.startCol,
410
+ });
411
+ return [];
412
+ }
413
+ const imports = [];
414
+ for (const item of node.items) {
415
+ const value = getScalarValue(item);
416
+ const itemSpan = getNodeSpan(item, fileName, lineCounter);
417
+ if (value === undefined || value.length === 0) {
418
+ errors.push({
419
+ message: 'imports entries must be scalar file paths',
420
+ line: itemSpan?.startLine,
421
+ col: itemSpan?.startCol,
422
+ });
423
+ continue;
424
+ }
425
+ imports.push(value);
426
+ }
427
+ return imports;
428
+ }
429
+ function parseNavGroup(map, fileName, lineCounter) {
430
+ const groupVal = getMapValue(map, 'group');
431
+ if (!groupVal)
432
+ return null;
433
+ const parsedGroup = parseMessageLikeNode(groupVal, fileName, lineCounter);
434
+ if (parsedGroup === undefined) {
435
+ return null;
436
+ }
437
+ const group = {
438
+ group: parsedGroup,
439
+ items: [],
440
+ sourceSpan: getNodeSpan(map, fileName, lineCounter),
441
+ };
442
+ const visibleIf = getMapValue(map, 'visibleIf');
443
+ if (visibleIf !== undefined)
444
+ group.visibleIf = String(isScalar(visibleIf) ? visibleIf.value : visibleIf);
445
+ const items = getMapValue(map, 'items');
446
+ if (isSeq(items)) {
447
+ for (const item of items.items) {
448
+ if (isMap(item)) {
449
+ const navItem = parseNavItem(item, fileName, lineCounter);
450
+ if (navItem)
451
+ group.items.push(navItem);
452
+ }
453
+ }
454
+ }
455
+ return group;
456
+ }
457
+ function parseNavItem(map, fileName, lineCounter) {
458
+ const label = getMapValue(map, 'label');
459
+ const target = getMapValue(map, 'target');
460
+ if (!label || !target)
461
+ return null;
462
+ const parsedLabel = parseMessageLikeNode(label, fileName, lineCounter);
463
+ if (parsedLabel === undefined) {
464
+ return null;
465
+ }
466
+ const item = {
467
+ label: parsedLabel,
468
+ target: String(isScalar(target) ? target.value : target),
469
+ sourceSpan: getNodeSpan(map, fileName, lineCounter),
470
+ };
471
+ const icon = getMapValue(map, 'icon');
472
+ if (icon !== undefined)
473
+ item.icon = String(isScalar(icon) ? icon.value : icon);
474
+ return item;
475
+ }
476
+ function parseModelBlock(name, node, fileName, lineCounter, sourceSpan) {
477
+ const model = { name, fields: [], sourceSpan };
478
+ if (!isMap(node))
479
+ return model;
480
+ const map = node;
481
+ for (const pair of map.items) {
482
+ if (!isPair(pair) || !isScalar(pair.key))
483
+ continue;
484
+ const fieldName = String(pair.key.value);
485
+ const rawType = String(isScalar(pair.value) ? pair.value.value : pair.value);
486
+ const { baseName: typeExpr, decorators } = parseDecorators(rawType);
487
+ const field = {
488
+ name: fieldName,
489
+ typeExpr: typeExpr || rawType,
490
+ decorators,
491
+ sourceSpan: getPairSpan(pair, fileName, lineCounter),
492
+ };
493
+ model.fields.push(field);
494
+ }
495
+ return model;
496
+ }
497
+ function parseResourceBlock(name, node, fileName, lineCounter, errors, sourceSpan) {
498
+ if (!isMap(node))
499
+ return null;
500
+ const map = node;
501
+ const resource = {
502
+ name,
503
+ model: '',
504
+ api: '',
505
+ sourceSpan,
506
+ };
507
+ const model = getMapValue(map, 'model');
508
+ if (model !== undefined)
509
+ resource.model = String(isScalar(model) ? model.value : model);
510
+ const api = getMapValue(map, 'api');
511
+ if (api !== undefined)
512
+ resource.api = String(isScalar(api) ? api.value : api);
513
+ const workflowPair = getMapPair(map, 'workflow');
514
+ if (workflowPair) {
515
+ if (isScalar(workflowPair.value)) {
516
+ resource.workflow = String(workflowPair.value.value);
517
+ resource.workflowSourceSpan = getPairSpan(workflowPair, fileName, lineCounter);
518
+ }
519
+ else if (isMap(workflowPair.value)) {
520
+ const workflowMap = workflowPair.value;
521
+ const source = getMapValue(workflowMap, 'source');
522
+ if (source !== undefined) {
523
+ resource.workflow = String(isScalar(source) ? source.value : source);
524
+ }
525
+ else {
526
+ errors.push({
527
+ message: 'resource workflow map must declare source',
528
+ line: getPairSpan(workflowPair, fileName, lineCounter)?.startLine,
529
+ col: getPairSpan(workflowPair, fileName, lineCounter)?.startCol,
530
+ });
531
+ }
532
+ const style = getMapValue(workflowMap, 'style');
533
+ if (style !== undefined) {
534
+ resource.workflowStyle = String(isScalar(style) ? style.value : style);
535
+ }
536
+ resource.workflowSourceSpan = getPairSpan(workflowPair, fileName, lineCounter);
537
+ }
538
+ else {
539
+ errors.push({
540
+ message: 'resource workflow must be a scalar @flow(...) link or a mapping with source/style',
541
+ line: getPairSpan(workflowPair, fileName, lineCounter)?.startLine,
542
+ col: getPairSpan(workflowPair, fileName, lineCounter)?.startCol,
543
+ });
544
+ }
545
+ }
546
+ // Parse list view
547
+ const listPair = getMapPair(map, 'list');
548
+ if (isMap(listPair?.value))
549
+ resource.list = parseListView(listPair.value, fileName, lineCounter, getPairSpan(listPair, fileName, lineCounter));
550
+ // Parse edit view
551
+ const editPair = getMapPair(map, 'edit');
552
+ if (isMap(editPair?.value))
553
+ resource.edit = parseEditView(editPair.value, fileName, lineCounter, errors, getPairSpan(editPair, fileName, lineCounter));
554
+ // Parse create view
555
+ const createPair = getMapPair(map, 'create');
556
+ if (isMap(createPair?.value))
557
+ resource.create = parseCreateView(createPair.value, fileName, lineCounter, errors, getPairSpan(createPair, fileName, lineCounter));
558
+ // Parse read view
559
+ const readPair = getMapPair(map, 'read');
560
+ if (isMap(readPair?.value))
561
+ resource.read = parseReadView(readPair.value, fileName, lineCounter, getPairSpan(readPair, fileName, lineCounter));
562
+ return resource;
563
+ }
564
+ function parseFieldMapBlock(ownerLabel, node, fileName, lineCounter, errors, sourceSpan) {
565
+ if (!isMap(node)) {
566
+ errors.push({
567
+ message: `${ownerLabel} must be a YAML mapping`,
568
+ line: sourceSpan?.startLine,
569
+ col: sourceSpan?.startCol,
570
+ });
571
+ return undefined;
572
+ }
573
+ const fields = [];
574
+ for (const pair of node.items) {
575
+ if (!isPair(pair) || !isScalar(pair.key))
576
+ continue;
577
+ const fieldName = String(pair.key.value);
578
+ const rawType = isScalar(pair.value) ? String(pair.value.value) : undefined;
579
+ const pairSpan = getPairSpan(pair, fileName, lineCounter);
580
+ if (!rawType) {
581
+ errors.push({
582
+ message: `${ownerLabel} field "${fieldName}" must have a scalar type expression`,
583
+ line: pairSpan?.startLine,
584
+ col: pairSpan?.startCol,
585
+ });
586
+ continue;
587
+ }
588
+ const { baseName: typeExpr, decorators } = parseDecorators(rawType);
589
+ fields.push({
590
+ name: fieldName,
591
+ typeExpr: typeExpr || rawType,
592
+ decorators,
593
+ sourceSpan: pairSpan,
594
+ });
595
+ }
596
+ return fields;
597
+ }
598
+ function parseReadModelBlock(name, node, fileName, lineCounter, errors, sourceSpan) {
599
+ if (!isMap(node)) {
600
+ errors.push({
601
+ message: `readModel ${name} must be a YAML mapping`,
602
+ line: sourceSpan?.startLine,
603
+ col: sourceSpan?.startCol,
604
+ });
605
+ return null;
606
+ }
607
+ const map = node;
608
+ const readModel = {
609
+ name,
610
+ inputs: [],
611
+ result: [],
612
+ sourceSpan,
613
+ };
614
+ const api = getMapValue(map, 'api');
615
+ if (api !== undefined) {
616
+ readModel.api = String(isScalar(api) ? api.value : api);
617
+ }
618
+ const rules = getMapValue(map, 'rules');
619
+ if (rules !== undefined) {
620
+ readModel.rules = String(isScalar(rules) ? rules.value : rules);
621
+ }
622
+ const inputsPair = getMapPair(map, 'inputs');
623
+ if (inputsPair) {
624
+ readModel.inputs = parseFieldMapBlock(`readModel ${name} inputs`, inputsPair.value, fileName, lineCounter, errors, getPairSpan(inputsPair, fileName, lineCounter)) ?? [];
625
+ }
626
+ const resultPair = getMapPair(map, 'result');
627
+ if (resultPair) {
628
+ readModel.result = parseFieldMapBlock(`readModel ${name} result`, resultPair.value, fileName, lineCounter, errors, getPairSpan(resultPair, fileName, lineCounter)) ?? [];
629
+ }
630
+ const listPair = getMapPair(map, 'list');
631
+ if (isMap(listPair?.value)) {
632
+ readModel.list = parseReadModelListView(listPair.value, fileName, lineCounter, getPairSpan(listPair, fileName, lineCounter));
633
+ }
634
+ return readModel;
635
+ }
636
+ function parseReadModelListView(map, fileName, lineCounter, sourceSpan) {
637
+ const view = { sourceSpan };
638
+ const columns = getMapValue(map, 'columns');
639
+ if (isSeq(columns)) {
640
+ view.columns = columns.items.map((item) => {
641
+ const value = isScalar(item) ? String(item.value) : String(item);
642
+ return parseColumnEntry(value, getNodeSpan(item, fileName, lineCounter));
643
+ });
644
+ }
645
+ const groupBy = getMapValue(map, 'groupBy');
646
+ if (isSeq(groupBy)) {
647
+ view.groupBy = groupBy.items.map((item) => String(isScalar(item) ? item.value : item));
648
+ }
649
+ const pivotBy = getMapValue(map, 'pivotBy');
650
+ if (pivotBy !== undefined) {
651
+ view.pivotBy = String(isScalar(pivotBy) ? pivotBy.value : pivotBy);
652
+ }
653
+ const pagination = getMapValue(map, 'pagination');
654
+ if (isMap(pagination)) {
655
+ const paginationMap = pagination;
656
+ const size = getMapValue(paginationMap, 'size');
657
+ const style = getMapValue(paginationMap, 'style');
658
+ view.pagination = {
659
+ size: size !== undefined ? Number(isScalar(size) ? size.value : size) : undefined,
660
+ style: style !== undefined ? String(isScalar(style) ? style.value : style) : undefined,
661
+ };
662
+ }
663
+ return view;
664
+ }
665
+ function parseListView(map, fileName, lineCounter, sourceSpan) {
666
+ const view = { sourceSpan };
667
+ const title = getMapValue(map, 'title');
668
+ if (title !== undefined)
669
+ view.title = parseMessageLikeNode(title, fileName, lineCounter);
670
+ const style = getMapValue(map, 'style');
671
+ if (style !== undefined)
672
+ view.style = String(isScalar(style) ? style.value : style);
673
+ // Filters — array of strings
674
+ const filters = getMapValue(map, 'filters');
675
+ if (isSeq(filters)) {
676
+ view.filters = filters.items.map((i) => String(isScalar(i) ? i.value : i));
677
+ }
678
+ // Columns — array of decorated strings
679
+ const columns = getMapValue(map, 'columns');
680
+ if (isSeq(columns)) {
681
+ view.columns = columns.items.map((i) => {
682
+ const val = isScalar(i) ? String(i.value) : String(i);
683
+ return parseColumnEntry(val, getNodeSpan(i, fileName, lineCounter));
684
+ });
685
+ }
686
+ // Actions
687
+ const actions = getMapValue(map, 'actions');
688
+ if (isSeq(actions)) {
689
+ view.actions = actions.items.map((i) => {
690
+ const val = isScalar(i) ? String(i.value) : String(i);
691
+ return parseActionEntry(val, getNodeSpan(i, fileName, lineCounter));
692
+ });
693
+ }
694
+ // Pagination
695
+ const pagination = getMapValue(map, 'pagination');
696
+ if (isMap(pagination)) {
697
+ const pMap = pagination;
698
+ const size = getMapValue(pMap, 'size');
699
+ const style = getMapValue(pMap, 'style');
700
+ view.pagination = {
701
+ size: size !== undefined ? Number(isScalar(size) ? size.value : size) : undefined,
702
+ style: style !== undefined ? String(isScalar(style) ? style.value : style) : undefined,
703
+ };
704
+ }
705
+ // Rules
706
+ view.rules = parseRules(map, fileName, lineCounter);
707
+ return view;
708
+ }
709
+ function parseEditView(map, fileName, lineCounter, errors, sourceSpan) {
710
+ const view = { sourceSpan };
711
+ const style = getMapValue(map, 'style');
712
+ if (style !== undefined)
713
+ view.style = String(isScalar(style) ? style.value : style);
714
+ const fields = getMapValue(map, 'fields');
715
+ if (isSeq(fields)) {
716
+ view.fields = fields.items.map((item) => parseFormFieldEntry(item, fileName, lineCounter));
717
+ }
718
+ const includes = getMapValue(map, 'includes');
719
+ if (isSeq(includes)) {
720
+ view.includes = includes.items
721
+ .map((item) => parseCreateInclude(item, fileName, lineCounter))
722
+ .filter((entry) => Boolean(entry));
723
+ }
724
+ view.rules = parseViewRules(map, fileName, lineCounter);
725
+ const onSuccess = getMapValue(map, 'onSuccess');
726
+ if (isSeq(onSuccess)) {
727
+ view.onSuccess = parseEffectList(onSuccess, fileName, lineCounter, errors);
728
+ }
729
+ return view;
730
+ }
731
+ function parseCreateView(map, fileName, lineCounter, errors, sourceSpan) {
732
+ const view = { sourceSpan };
733
+ const style = getMapValue(map, 'style');
734
+ if (style !== undefined)
735
+ view.style = String(isScalar(style) ? style.value : style);
736
+ const fields = getMapValue(map, 'fields');
737
+ if (isSeq(fields)) {
738
+ view.fields = fields.items.map((item) => parseFormFieldEntry(item, fileName, lineCounter));
739
+ }
740
+ const includes = getMapValue(map, 'includes');
741
+ if (isSeq(includes)) {
742
+ view.includes = includes.items
743
+ .map((item) => parseCreateInclude(item, fileName, lineCounter))
744
+ .filter((entry) => Boolean(entry));
745
+ }
746
+ view.rules = parseViewRules(map, fileName, lineCounter);
747
+ const onSuccess = getMapValue(map, 'onSuccess');
748
+ if (isSeq(onSuccess)) {
749
+ view.onSuccess = parseEffectList(onSuccess, fileName, lineCounter, errors);
750
+ }
751
+ return view;
752
+ }
753
+ function parseCreateInclude(node, fileName, lineCounter) {
754
+ if (!isMap(node)) {
755
+ return null;
756
+ }
757
+ const map = node;
758
+ const field = getMapValue(map, 'field');
759
+ if (!field) {
760
+ return null;
761
+ }
762
+ const include = {
763
+ field: String(isScalar(field) ? field.value : field),
764
+ sourceSpan: getNodeSpan(map, fileName, lineCounter),
765
+ };
766
+ const minItems = getMapValue(map, 'minItems');
767
+ if (minItems !== undefined) {
768
+ include.minItems = Number(isScalar(minItems) ? minItems.value : minItems);
769
+ }
770
+ const rules = getMapValue(map, 'rules');
771
+ if (rules !== undefined) {
772
+ include.rules = String(isScalar(rules) ? rules.value : rules);
773
+ }
774
+ const fields = getMapValue(map, 'fields');
775
+ if (isSeq(fields)) {
776
+ include.fields = fields.items.map((item) => parseFormFieldEntry(item, fileName, lineCounter));
777
+ }
778
+ return include;
779
+ }
780
+ function parseReadView(map, fileName, lineCounter, sourceSpan) {
781
+ const view = { sourceSpan };
782
+ const title = getMapValue(map, 'title');
783
+ if (title !== undefined)
784
+ view.title = parseMessageLikeNode(title, fileName, lineCounter);
785
+ const style = getMapValue(map, 'style');
786
+ if (style !== undefined)
787
+ view.style = String(isScalar(style) ? style.value : style);
788
+ const fields = getMapValue(map, 'fields');
789
+ if (isSeq(fields)) {
790
+ view.fields = fields.items.map((i) => {
791
+ const val = isScalar(i) ? String(i.value) : String(i);
792
+ return parseColumnEntry(val, getNodeSpan(i, fileName, lineCounter));
793
+ });
794
+ }
795
+ const related = getMapValue(map, 'related');
796
+ if (isSeq(related)) {
797
+ view.related = related.items.map((i) => String(isScalar(i) ? i.value : i));
798
+ }
799
+ return view;
800
+ }
801
+ function parseRules(map, fileName, lineCounter) {
802
+ const rulesPair = getMapPair(map, 'rules');
803
+ if (!isMap(rulesPair?.value)) {
804
+ // Fallback: check for top-level rule fields
805
+ const result = {};
806
+ let found = false;
807
+ for (const key of ['visibleIf', 'enabledIf', 'allowIf', 'enforce']) {
808
+ const val = getMapValue(map, key);
809
+ if (val !== undefined) {
810
+ result[key] = String(isScalar(val) ? val.value : val);
811
+ found = true;
812
+ }
813
+ }
814
+ return found ? result : undefined;
815
+ }
816
+ const rMap = rulesPair.value;
817
+ const result = {
818
+ sourceSpan: getPairSpan(rulesPair, fileName, lineCounter),
819
+ };
820
+ for (const key of ['visibleIf', 'enabledIf', 'allowIf', 'enforce']) {
821
+ const val = getMapValue(rMap, key);
822
+ if (val !== undefined) {
823
+ result[key] = String(isScalar(val) ? val.value : val);
824
+ }
825
+ }
826
+ return result;
827
+ }
828
+ function parseViewRules(map, fileName, lineCounter) {
829
+ const rulesPair = getMapPair(map, 'rules');
830
+ if (isScalar(rulesPair?.value)) {
831
+ return String(rulesPair.value.value);
832
+ }
833
+ return parseRules(map, fileName, lineCounter);
834
+ }
835
+ function parseEffectList(seq, fileName, lineCounter, errors) {
836
+ const effects = [];
837
+ for (const item of seq.items) {
838
+ if (isMap(item)) {
839
+ const map = item;
840
+ for (const pair of map.items) {
841
+ if (isPair(pair) && isScalar(pair.key)) {
842
+ const type = String(pair.key.value);
843
+ const parsedValue = parseEffectValue(type, pair.value, fileName, lineCounter, errors);
844
+ if (parsedValue === undefined) {
845
+ continue;
846
+ }
847
+ effects.push({
848
+ type,
849
+ value: parsedValue,
850
+ sourceSpan: getPairSpan(pair, fileName, lineCounter),
851
+ });
852
+ }
853
+ }
854
+ }
855
+ else if (isScalar(item)) {
856
+ // Simple string effect like "toast: saved"
857
+ const val = String(item.value);
858
+ const colonIdx = val.indexOf(':');
859
+ if (colonIdx > 0) {
860
+ effects.push({
861
+ type: val.substring(0, colonIdx).trim(),
862
+ value: val.substring(colonIdx + 1).trim(),
863
+ sourceSpan: getNodeSpan(item, fileName, lineCounter),
864
+ });
865
+ }
866
+ }
867
+ }
868
+ return effects;
869
+ }
870
+ function parseEffectValue(type, node, fileName, lineCounter, errors) {
871
+ if (type === 'toast' && isMap(node)) {
872
+ return parseToastDescriptor(node, fileName, lineCounter, errors, getNodeSpan(node, fileName, lineCounter));
873
+ }
874
+ if (isScalar(node)) {
875
+ return String(node.value);
876
+ }
877
+ errors.push({
878
+ message: `Effect "${type}" must use a scalar value${type === 'toast' ? ' or a supported toast descriptor object' : ''}`,
879
+ line: getNodeSpan(node, fileName, lineCounter)?.startLine,
880
+ col: getNodeSpan(node, fileName, lineCounter)?.startCol,
881
+ });
882
+ return undefined;
883
+ }
884
+ function parseToastDescriptor(map, fileName, lineCounter, errors, sourceSpan) {
885
+ const descriptor = { sourceSpan };
886
+ for (const pair of map.items) {
887
+ if (!isPair(pair) || !isScalar(pair.key)) {
888
+ continue;
889
+ }
890
+ const key = String(pair.key.value);
891
+ if (key === 'key') {
892
+ if (isScalar(pair.value)) {
893
+ descriptor.key = String(pair.value.value);
894
+ }
895
+ else {
896
+ errors.push({
897
+ message: 'toast.key must be a scalar string',
898
+ line: getPairSpan(pair, fileName, lineCounter)?.startLine,
899
+ col: getPairSpan(pair, fileName, lineCounter)?.startCol,
900
+ });
901
+ }
902
+ continue;
903
+ }
904
+ if (key === 'defaultMessage') {
905
+ if (isScalar(pair.value)) {
906
+ descriptor.defaultMessage = String(pair.value.value);
907
+ }
908
+ else {
909
+ errors.push({
910
+ message: 'toast.defaultMessage must be a scalar string',
911
+ line: getPairSpan(pair, fileName, lineCounter)?.startLine,
912
+ col: getPairSpan(pair, fileName, lineCounter)?.startCol,
913
+ });
914
+ }
915
+ continue;
916
+ }
917
+ if (key === 'values') {
918
+ if (isMap(pair.value)) {
919
+ descriptor.values = parseToastValues(pair.value, fileName, lineCounter, errors);
920
+ }
921
+ else {
922
+ errors.push({
923
+ message: 'toast.values must be a mapping of literal values or { ref: ... } objects',
924
+ line: getPairSpan(pair, fileName, lineCounter)?.startLine,
925
+ col: getPairSpan(pair, fileName, lineCounter)?.startCol,
926
+ });
927
+ }
928
+ continue;
929
+ }
930
+ errors.push({
931
+ message: `Unknown toast descriptor key: "${key}"`,
932
+ line: getPairSpan(pair, fileName, lineCounter)?.startLine,
933
+ col: getPairSpan(pair, fileName, lineCounter)?.startCol,
934
+ });
935
+ }
936
+ return descriptor;
937
+ }
938
+ function parseToastValues(map, fileName, lineCounter, errors) {
939
+ const values = {};
940
+ for (const pair of map.items) {
941
+ if (!isPair(pair) || !isScalar(pair.key)) {
942
+ continue;
943
+ }
944
+ const name = String(pair.key.value);
945
+ if (isScalar(pair.value)) {
946
+ const scalarValue = pair.value.value;
947
+ if (typeof scalarValue === 'string'
948
+ || typeof scalarValue === 'number'
949
+ || typeof scalarValue === 'boolean'
950
+ || scalarValue === null) {
951
+ values[name] = scalarValue;
952
+ }
953
+ else {
954
+ values[name] = String(scalarValue);
955
+ }
956
+ continue;
957
+ }
958
+ if (isMap(pair.value)) {
959
+ const refPair = getMapPair(pair.value, 'ref');
960
+ if (pair.value.items.length === 1 && refPair && isScalar(refPair.value)) {
961
+ values[name] = {
962
+ ref: String(refPair.value.value),
963
+ sourceSpan: getPairSpan(refPair, fileName, lineCounter),
964
+ };
965
+ }
966
+ else {
967
+ errors.push({
968
+ message: `toast.values.${name} must be a scalar or { ref: <path> }`,
969
+ line: getPairSpan(pair, fileName, lineCounter)?.startLine,
970
+ col: getPairSpan(pair, fileName, lineCounter)?.startCol,
971
+ });
972
+ }
973
+ continue;
974
+ }
975
+ errors.push({
976
+ message: `toast.values.${name} must be a scalar or { ref: <path> }`,
977
+ line: getPairSpan(pair, fileName, lineCounter)?.startLine,
978
+ col: getPairSpan(pair, fileName, lineCounter)?.startCol,
979
+ });
980
+ }
981
+ return values;
982
+ }
983
+ function parseToastDescriptorObject(value) {
984
+ const descriptor = {};
985
+ if (value.key !== undefined) {
986
+ descriptor.key = String(value.key);
987
+ }
988
+ if (value.defaultMessage !== undefined) {
989
+ descriptor.defaultMessage = String(value.defaultMessage);
990
+ }
991
+ if (value.values && typeof value.values === 'object' && !Array.isArray(value.values)) {
992
+ descriptor.values = Object.fromEntries(Object.entries(value.values).map(([key, entry]) => {
993
+ if (entry && typeof entry === 'object' && !Array.isArray(entry) && 'ref' in entry) {
994
+ return [key, { ref: String(entry.ref) }];
995
+ }
996
+ return [key, entry];
997
+ }));
998
+ }
999
+ return descriptor;
1000
+ }
1001
+ function parseMessageLikeNode(node, fileName, lineCounter) {
1002
+ if (isScalar(node)) {
1003
+ return String(node.value);
1004
+ }
1005
+ if (isMap(node)) {
1006
+ return parseUiMessageDescriptor(node, fileName, lineCounter, getNodeSpan(node, fileName, lineCounter));
1007
+ }
1008
+ return undefined;
1009
+ }
1010
+ function parseMessageLikeObject(value) {
1011
+ if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
1012
+ return String(value);
1013
+ }
1014
+ if (value && typeof value === 'object' && !Array.isArray(value)) {
1015
+ return parseUiMessageDescriptorObject(value);
1016
+ }
1017
+ return undefined;
1018
+ }
1019
+ function parseUiMessageDescriptor(map, _fileName, _lineCounter, sourceSpan) {
1020
+ const descriptor = { sourceSpan };
1021
+ for (const pair of map.items) {
1022
+ if (!isPair(pair) || !isScalar(pair.key)) {
1023
+ continue;
1024
+ }
1025
+ const key = String(pair.key.value);
1026
+ if (key === 'key' || key === 'defaultMessage') {
1027
+ if (isScalar(pair.value)) {
1028
+ descriptor[key] = String(pair.value.value);
1029
+ }
1030
+ continue;
1031
+ }
1032
+ if (key === 'values' && isMap(pair.value)) {
1033
+ descriptor.values = Object.fromEntries(pair.value.items.flatMap((entry) => {
1034
+ if (!isPair(entry) || !isScalar(entry.key) || !isScalar(entry.value)) {
1035
+ return [];
1036
+ }
1037
+ const value = entry.value.value;
1038
+ if (typeof value === 'string'
1039
+ || typeof value === 'number'
1040
+ || typeof value === 'boolean'
1041
+ || value === null) {
1042
+ return [[String(entry.key.value), value]];
1043
+ }
1044
+ return [];
1045
+ }));
1046
+ }
1047
+ }
1048
+ return descriptor;
1049
+ }
1050
+ function parseUiMessageDescriptorObject(value) {
1051
+ const descriptor = {};
1052
+ if (value.key !== undefined) {
1053
+ descriptor.key = String(value.key);
1054
+ }
1055
+ if (value.defaultMessage !== undefined) {
1056
+ descriptor.defaultMessage = String(value.defaultMessage);
1057
+ }
1058
+ if (value.values && typeof value.values === 'object' && !Array.isArray(value.values)) {
1059
+ descriptor.values = Object.fromEntries(Object.entries(value.values).flatMap(([key, entry]) => {
1060
+ if (typeof entry === 'string'
1061
+ || typeof entry === 'number'
1062
+ || typeof entry === 'boolean'
1063
+ || entry === null) {
1064
+ return [[key, entry]];
1065
+ }
1066
+ return [];
1067
+ }));
1068
+ }
1069
+ return descriptor;
1070
+ }
1071
+ function parsePageBlock(name, node, fileName, lineCounter, sourceSpan) {
1072
+ if (!isMap(node))
1073
+ return null;
1074
+ const map = node;
1075
+ const page = {
1076
+ name,
1077
+ title: '',
1078
+ type: 'dashboard',
1079
+ blocks: [],
1080
+ sourceSpan,
1081
+ };
1082
+ const title = getMapValue(map, 'title');
1083
+ if (title !== undefined)
1084
+ page.title = parseMessageLikeNode(title, fileName, lineCounter) ?? '';
1085
+ const type = getMapValue(map, 'type');
1086
+ if (type !== undefined)
1087
+ page.type = String(isScalar(type) ? type.value : type);
1088
+ const path = getMapValue(map, 'path');
1089
+ if (path !== undefined)
1090
+ page.path = String(isScalar(path) ? path.value : path);
1091
+ const layout = getMapValue(map, 'layout');
1092
+ if (layout !== undefined)
1093
+ page.layout = String(isScalar(layout) ? layout.value : layout);
1094
+ const style = getMapValue(map, 'style');
1095
+ if (style !== undefined)
1096
+ page.style = String(isScalar(style) ? style.value : style);
1097
+ const seo = getMapValue(map, 'seo');
1098
+ if (isMap(seo)) {
1099
+ page.seo = parsePageSeoBlock(seo, fileName, lineCounter);
1100
+ }
1101
+ const actions = getMapValue(map, 'actions');
1102
+ if (isSeq(actions)) {
1103
+ page.actions = actions.items
1104
+ .map((entry) => parsePageAction(entry, fileName, lineCounter))
1105
+ .filter((entry) => Boolean(entry));
1106
+ }
1107
+ const blocks = getMapValue(map, 'blocks');
1108
+ if (isSeq(blocks)) {
1109
+ for (const item of blocks.items) {
1110
+ if (isMap(item)) {
1111
+ const bMap = item;
1112
+ const block = {
1113
+ type: '',
1114
+ title: '',
1115
+ sourceSpan: getNodeSpan(bMap, fileName, lineCounter),
1116
+ };
1117
+ const bType = getMapValue(bMap, 'type');
1118
+ if (bType !== undefined)
1119
+ block.type = String(isScalar(bType) ? bType.value : bType);
1120
+ const bTitle = getMapValue(bMap, 'title');
1121
+ if (bTitle !== undefined)
1122
+ block.title = parseMessageLikeNode(bTitle, fileName, lineCounter) ?? '';
1123
+ const bStyle = getMapValue(bMap, 'style');
1124
+ if (bStyle !== undefined)
1125
+ block.style = String(isScalar(bStyle) ? bStyle.value : bStyle);
1126
+ const bData = getMapValue(bMap, 'data');
1127
+ if (bData !== undefined)
1128
+ block.data = String(isScalar(bData) ? bData.value : bData);
1129
+ const bQueryState = getMapValue(bMap, 'queryState');
1130
+ if (bQueryState !== undefined) {
1131
+ block.queryState = String(isScalar(bQueryState) ? bQueryState.value : bQueryState);
1132
+ }
1133
+ const bSelectionState = getMapValue(bMap, 'selectionState');
1134
+ if (bSelectionState !== undefined) {
1135
+ block.selectionState = String(isScalar(bSelectionState) ? bSelectionState.value : bSelectionState);
1136
+ }
1137
+ const bDateNavigation = getMapValue(bMap, 'dateNavigation');
1138
+ if (isScalar(bDateNavigation)) {
1139
+ block.dateNavigation = {
1140
+ field: String(bDateNavigation.value),
1141
+ sourceSpan: getNodeSpan(bMap, fileName, lineCounter),
1142
+ };
1143
+ }
1144
+ else if (isMap(bDateNavigation)) {
1145
+ const dateNavigationMap = bDateNavigation;
1146
+ const field = getMapValue(dateNavigationMap, 'field');
1147
+ const prevLabel = getMapValue(dateNavigationMap, 'prevLabel');
1148
+ const nextLabel = getMapValue(dateNavigationMap, 'nextLabel');
1149
+ block.dateNavigation = {
1150
+ field: field === undefined ? '' : String(isScalar(field) ? field.value : field),
1151
+ prevLabel: prevLabel === undefined ? undefined : parseMessageLikeNode(prevLabel, fileName, lineCounter),
1152
+ nextLabel: nextLabel === undefined ? undefined : parseMessageLikeNode(nextLabel, fileName, lineCounter),
1153
+ sourceSpan: getNodeSpan(dateNavigationMap, fileName, lineCounter),
1154
+ };
1155
+ }
1156
+ // Escape hatch tier 3: custom block
1157
+ const bCustom = getMapValue(bMap, 'custom');
1158
+ if (bCustom !== undefined)
1159
+ block.custom = String(isScalar(bCustom) ? bCustom.value : bCustom);
1160
+ const bRowActions = getMapValue(bMap, 'rowActions');
1161
+ if (isSeq(bRowActions)) {
1162
+ block.rowActions = bRowActions.items
1163
+ .map((entry) => parseDashboardRowAction(entry, fileName, lineCounter))
1164
+ .filter((entry) => Boolean(entry));
1165
+ }
1166
+ page.blocks.push(block);
1167
+ }
1168
+ }
1169
+ }
1170
+ return page;
1171
+ }
1172
+ function parsePageActionSeedValue(node, fileName, lineCounter) {
1173
+ if (isScalar(node)) {
1174
+ const value = node.value;
1175
+ if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
1176
+ return value;
1177
+ }
1178
+ return String(value);
1179
+ }
1180
+ if (!isMap(node)) {
1181
+ return undefined;
1182
+ }
1183
+ const map = node;
1184
+ const input = getMapValue(map, 'input');
1185
+ if (isScalar(input)) {
1186
+ return {
1187
+ input: String(input.value),
1188
+ sourceSpan: getNodeSpan(map, fileName, lineCounter),
1189
+ };
1190
+ }
1191
+ const selection = getMapValue(map, 'selection');
1192
+ if (isScalar(selection)) {
1193
+ return {
1194
+ selection: String(selection.value),
1195
+ sourceSpan: getNodeSpan(map, fileName, lineCounter),
1196
+ };
1197
+ }
1198
+ return undefined;
1199
+ }
1200
+ function parseDashboardRowSeedValue(node, fileName, lineCounter) {
1201
+ if (isScalar(node)) {
1202
+ const value = node.value;
1203
+ if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
1204
+ return value;
1205
+ }
1206
+ return String(value);
1207
+ }
1208
+ if (!isMap(node)) {
1209
+ return undefined;
1210
+ }
1211
+ const map = node;
1212
+ const row = getMapValue(map, 'row');
1213
+ if (isScalar(row)) {
1214
+ return {
1215
+ row: String(row.value),
1216
+ sourceSpan: getNodeSpan(map, fileName, lineCounter),
1217
+ };
1218
+ }
1219
+ const input = getMapValue(map, 'input');
1220
+ if (isScalar(input)) {
1221
+ return {
1222
+ input: String(input.value),
1223
+ sourceSpan: getNodeSpan(map, fileName, lineCounter),
1224
+ };
1225
+ }
1226
+ return undefined;
1227
+ }
1228
+ function parsePageAction(node, fileName, lineCounter) {
1229
+ if (!isMap(node)) {
1230
+ return null;
1231
+ }
1232
+ const map = node;
1233
+ const createPair = getMapPair(map, 'create');
1234
+ if (!createPair) {
1235
+ return null;
1236
+ }
1237
+ if (isScalar(createPair.value)) {
1238
+ return {
1239
+ create: {
1240
+ resource: String(createPair.value.value),
1241
+ sourceSpan: getPairSpan(createPair, fileName, lineCounter),
1242
+ },
1243
+ sourceSpan: getNodeSpan(map, fileName, lineCounter),
1244
+ };
1245
+ }
1246
+ if (!isMap(createPair.value)) {
1247
+ return null;
1248
+ }
1249
+ const createMap = createPair.value;
1250
+ const resource = getMapValue(createMap, 'resource');
1251
+ if (!isScalar(resource)) {
1252
+ return null;
1253
+ }
1254
+ const create = {
1255
+ resource: String(resource.value),
1256
+ sourceSpan: getPairSpan(createPair, fileName, lineCounter),
1257
+ };
1258
+ const label = getMapValue(createMap, 'label');
1259
+ if (label !== undefined) {
1260
+ create.label = parseMessageLikeNode(label, fileName, lineCounter);
1261
+ }
1262
+ const seed = getMapValue(createMap, 'seed');
1263
+ if (isMap(seed)) {
1264
+ create.seed = {};
1265
+ for (const pair of seed.items) {
1266
+ if (!isPair(pair) || !isScalar(pair.key)) {
1267
+ continue;
1268
+ }
1269
+ const key = String(pair.key.value);
1270
+ const value = parsePageActionSeedValue(pair.value, fileName, lineCounter);
1271
+ if (value !== undefined) {
1272
+ create.seed[key] = value;
1273
+ }
1274
+ }
1275
+ }
1276
+ return {
1277
+ create,
1278
+ sourceSpan: getNodeSpan(map, fileName, lineCounter),
1279
+ };
1280
+ }
1281
+ function parseDashboardRowAction(node, fileName, lineCounter) {
1282
+ if (!isMap(node)) {
1283
+ return null;
1284
+ }
1285
+ const map = node;
1286
+ const createPair = getMapPair(map, 'create');
1287
+ if (!createPair) {
1288
+ return null;
1289
+ }
1290
+ if (isScalar(createPair.value)) {
1291
+ return {
1292
+ create: {
1293
+ resource: String(createPair.value.value),
1294
+ sourceSpan: getPairSpan(createPair, fileName, lineCounter),
1295
+ },
1296
+ sourceSpan: getNodeSpan(map, fileName, lineCounter),
1297
+ };
1298
+ }
1299
+ if (!isMap(createPair.value)) {
1300
+ return null;
1301
+ }
1302
+ const createMap = createPair.value;
1303
+ const resource = getMapValue(createMap, 'resource');
1304
+ if (!isScalar(resource)) {
1305
+ return null;
1306
+ }
1307
+ const create = {
1308
+ resource: String(resource.value),
1309
+ sourceSpan: getPairSpan(createPair, fileName, lineCounter),
1310
+ };
1311
+ const label = getMapValue(createMap, 'label');
1312
+ if (label !== undefined) {
1313
+ create.label = parseMessageLikeNode(label, fileName, lineCounter);
1314
+ }
1315
+ const seed = getMapValue(createMap, 'seed');
1316
+ if (isMap(seed)) {
1317
+ const entries = {};
1318
+ for (const pair of seed.items) {
1319
+ if (!isPair(pair) || !isScalar(pair.key)) {
1320
+ continue;
1321
+ }
1322
+ const fieldName = String(pair.key.value);
1323
+ const parsedValue = parseDashboardRowSeedValue(pair.value, fileName, lineCounter);
1324
+ if (parsedValue !== undefined) {
1325
+ entries[fieldName] = parsedValue;
1326
+ }
1327
+ }
1328
+ create.seed = entries;
1329
+ }
1330
+ return {
1331
+ create,
1332
+ sourceSpan: getNodeSpan(map, fileName, lineCounter),
1333
+ };
1334
+ }
1335
+ //# sourceMappingURL=parser.js.map