@loj-lang/rdsl-compiler 0.6.1 → 0.6.2

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/dist/codegen.js CHANGED
@@ -565,7 +565,7 @@ function createCodegenSignature(value) {
565
565
  function generateModelTypes(model) {
566
566
  const generatedFields = generatedModelFields(model);
567
567
  const lines = [];
568
- lines.push(`// Generated by ReactDSL compiler v0.1.0`);
568
+ lines.push(`// Generated by Loj RDSL compiler v0.6.2`);
569
569
  lines.push(`// @source-node ${model.id}`);
570
570
  lines.push(``);
571
571
  // Generate TypeScript interface
@@ -640,7 +640,7 @@ function generateReadModelTypes(readModel) {
640
640
  const inputTypeName = readModelInputTypeName(readModel);
641
641
  const metaConstName = readModelMetaConstName(readModel);
642
642
  const lines = [];
643
- lines.push(`// Generated by ReactDSL compiler v0.1.0`);
643
+ lines.push(`// Generated by Loj RDSL compiler v0.6.2`);
644
644
  lines.push(`// @source-node ${readModel.id}`);
645
645
  lines.push(``);
646
646
  lines.push(`export interface ${inputTypeName} {`);
@@ -795,7 +795,7 @@ function generateListView(ir, resource, view, model) {
795
795
  rowActions.push(`{ label: ${messageLikeToRuntimeValueSource(listWorkflowLabel)}, href: (row) => ${workflowViewHrefExpression(resource.name, 'row', { returnToExpr: 'getCurrentAppHref()' })} }`);
796
796
  }
797
797
  const hasRowActions = rowActions.length > 0 || Boolean(deleteAction);
798
- lines.push(`// Generated by ReactDSL compiler v0.1.0`);
798
+ lines.push(`// Generated by Loj RDSL compiler v0.6.2`);
799
799
  lines.push(`// @source-node ${view.id}`);
800
800
  lines.push(``);
801
801
  lines.push(`import React, { useCallback } from 'react';`);
@@ -1272,6 +1272,14 @@ function generateEditView(ir, resource, view, model) {
1272
1272
  const newItemLabelMessage = view.messages?.newItemLabel ?? 'New';
1273
1273
  const workflowPreconditionMessage = resource.workflowMessages?.preconditionBlocked ?? 'Action blocked by workflow preconditions';
1274
1274
  const workflowInvalidTransitionMessage = resource.workflowMessages?.invalidTransition ?? 'Invalid workflow transition request';
1275
+ const fieldLabelMessages = [
1276
+ ...collectFormFieldDecoratorMessages(view.fields, 'label'),
1277
+ ...view.includes.flatMap((include) => collectFormFieldDecoratorMessages(include.fields, 'label')),
1278
+ ];
1279
+ const fieldPlaceholderMessages = [
1280
+ ...collectFormFieldDecoratorMessages(view.fields, 'placeholder'),
1281
+ ...view.includes.flatMap((include) => collectFormFieldDecoratorMessages(include.fields, 'placeholder')),
1282
+ ];
1275
1283
  const usesMessageResolver = someMessageLikesNeedRuntimeResolver([
1276
1284
  saveLabelMessage,
1277
1285
  saveAndContinueLabelMessage,
@@ -1286,6 +1294,8 @@ function generateEditView(ir, resource, view, model) {
1286
1294
  versionConflictFieldNamesLiteral !== '[]' ? reloadLatestMessage : undefined,
1287
1295
  hasWorkflow ? workflowPreconditionMessage : undefined,
1288
1296
  hasWorkflow ? workflowInvalidTransitionMessage : undefined,
1297
+ ...fieldLabelMessages,
1298
+ ...fieldPlaceholderMessages,
1289
1299
  ]);
1290
1300
  const lookupOptionsConstName = componentResourceOptionsConstName(componentName);
1291
1301
  const formTitleElementConstName = componentTitleElementConstName(componentName);
@@ -1344,7 +1354,7 @@ function generateEditView(ir, resource, view, model) {
1344
1354
  for (const compilation of includeLinkedDerivationCompilations.values()) {
1345
1355
  compilation?.helperImports.forEach((name) => derivationRuntimeImports.add(name));
1346
1356
  }
1347
- lines.push(`// Generated by ReactDSL compiler v0.1.0`);
1357
+ lines.push(`// Generated by Loj RDSL compiler v0.6.2`);
1348
1358
  lines.push(`// @source-node ${view.id}`);
1349
1359
  lines.push(``);
1350
1360
  lines.push(`import React, { useCallback } from 'react';`);
@@ -1536,9 +1546,10 @@ function generateEditView(ir, resource, view, model) {
1536
1546
  for (const binding of includeBindings) {
1537
1547
  const includeKey = camelCase(binding.fieldName);
1538
1548
  const existingItemsName = `${includeKey}ExistingItems`;
1549
+ const includeDefaultItem = includeFieldDefaultObjectLiteral(binding.targetModel, binding.fields);
1539
1550
  lines.push(` nextFormData.${binding.fieldName} = formEdits.${binding.fieldName} ?? (${existingItemsName}.length > 0`);
1540
1551
  lines.push(` ? ${existingItemsName}`);
1541
- lines.push(` : Array.from({ length: ${binding.minItems} }, () => ({})));`);
1552
+ lines.push(` : Array.from({ length: ${binding.minItems} }, () => (${includeDefaultItem})));`);
1542
1553
  }
1543
1554
  lines.push(` return nextFormData;`);
1544
1555
  lines.push(` }, [record, formEdits${includeBindings.map((binding) => `, ${camelCase(binding.fieldName)}ExistingItems`).join('')}]);`);
@@ -1705,6 +1716,7 @@ function generateEditView(ir, resource, view, model) {
1705
1716
  const handleAdd = `handleAdd${capitalize(includeKey)}`;
1706
1717
  const handleRemove = `handleRemove${capitalize(includeKey)}`;
1707
1718
  const handleChange = `handle${capitalize(includeKey)}FieldChange`;
1719
+ const includeDefaultItem = includeFieldDefaultObjectLiteral(binding.targetModel, binding.fields);
1708
1720
  const removeGuard = binding.mode === 'append-only'
1709
1721
  ? `currentItems.length <= ${binding.minItems} || Boolean(currentItems[index]?.id)`
1710
1722
  : `currentItems.length <= ${binding.minItems}`;
@@ -1712,7 +1724,7 @@ function generateEditView(ir, resource, view, model) {
1712
1724
  lines.push(` const ${handleAdd} = useCallback(() => {`);
1713
1725
  lines.push(` setFormEdits((prev) => ({`);
1714
1726
  lines.push(` ...prev,`);
1715
- lines.push(` ${binding.fieldName}: [...(prev.${binding.fieldName} ?? formData.${binding.fieldName}), {}],`);
1727
+ lines.push(` ${binding.fieldName}: [...(prev.${binding.fieldName} ?? formData.${binding.fieldName}), ${includeDefaultItem}],`);
1716
1728
  lines.push(` }));`);
1717
1729
  lines.push(` }, [formData.${binding.fieldName}]);`);
1718
1730
  lines.push(` const ${handleRemove} = useCallback((index: number) => {`);
@@ -1924,6 +1936,8 @@ function generateEditView(ir, resource, view, model) {
1924
1936
  const disabled = field.decorators.find(d => d.name === 'disabled');
1925
1937
  const fieldType = getFormFieldType(field, model);
1926
1938
  const modelField = model.fields.find(f => f.name === field.field);
1939
+ const labelMessage = formFieldLabelMessage(field);
1940
+ const placeholderMessage = formFieldPlaceholderMessage(field);
1927
1941
  let optionsAttr = '';
1928
1942
  if (modelField?.fieldType.type === 'enum') {
1929
1943
  optionsAttr = `options={${enumValuesExpression(modelMetaConstName(model), field.field)}} optionKeyPrefix={${enumKeyPrefixExpression(modelMetaConstName(model), field.field)}} optionLabels={${enumLabelsExpression(modelMetaConstName(model), field.field)}}`;
@@ -1937,13 +1951,15 @@ function generateEditView(ir, resource, view, model) {
1937
1951
  lines.push(` {/* @source-node ${field.id} */}`);
1938
1952
  lines.push(` {${fieldVisible} ? (`);
1939
1953
  lines.push(` <FormField`);
1940
- lines.push(` label="${columnLabel(field.field)}"`);
1954
+ lines.push(` label={${messageLikeToRuntimeTextSource(labelMessage)}}`);
1941
1955
  lines.push(` name="${field.field}"`);
1942
1956
  lines.push(` type="${fieldType}"`);
1943
1957
  lines.push(` value={formData.${field.field} ?? ''}`);
1944
1958
  lines.push(` onChange={(v: unknown) => handleFieldChange('${field.field}', v)}`);
1945
1959
  lines.push(` schema={${modelName}Schema.${field.field}}`);
1946
1960
  lines.push(` error={fieldErrors[${JSON.stringify(field.field)}] ?? null}`);
1961
+ if (placeholderMessage)
1962
+ lines.push(` placeholder={${messageLikeToRuntimeTextSource(placeholderMessage)}}`);
1947
1963
  if (optionsAttr)
1948
1964
  lines.push(` ${optionsAttr}`);
1949
1965
  lines.push(` disabled={${disabled ? 'true' : formFieldDisabledExpression(field, modelField, fieldEnabled, `linkedDerivedFieldNames.includes('${field.field}')`)}}`);
@@ -1992,6 +2008,8 @@ function generateEditView(ir, resource, view, model) {
1992
2008
  }
1993
2009
  const fieldType = getFormFieldType(field, binding.targetModel);
1994
2010
  const modelField = binding.targetModel.fields.find((candidate) => candidate.name === field.field);
2011
+ const labelMessage = formFieldLabelMessage(field);
2012
+ const placeholderMessage = formFieldPlaceholderMessage(field);
1995
2013
  let optionsAttr = '';
1996
2014
  if (modelField?.fieldType.type === 'enum') {
1997
2015
  optionsAttr = `options={${enumValuesExpression(modelMetaConstName(binding.targetModel), field.field)}} optionKeyPrefix={${enumKeyPrefixExpression(modelMetaConstName(binding.targetModel), field.field)}} optionLabels={${enumLabelsExpression(modelMetaConstName(binding.targetModel), field.field)}}`;
@@ -2005,12 +2023,14 @@ function generateEditView(ir, resource, view, model) {
2005
2023
  lines.push(` {/* @source-node ${field.id} */}`);
2006
2024
  lines.push(` {${fieldVisible} ? (`);
2007
2025
  lines.push(` <FormField`);
2008
- lines.push(` label="${columnLabel(field.field)}"`);
2026
+ lines.push(` label={${messageLikeToRuntimeTextSource(labelMessage)}}`);
2009
2027
  lines.push(` name="${field.field}"`);
2010
2028
  lines.push(` type="${fieldType}"`);
2011
2029
  lines.push(` value={item.${field.field} ?? ''}`);
2012
2030
  lines.push(` onChange={(v: unknown) => ${handleChange}(index, '${field.field}', v)}`);
2013
2031
  lines.push(` schema={${binding.targetModel.name}Schema.${field.field}}`);
2032
+ if (placeholderMessage)
2033
+ lines.push(` placeholder={${messageLikeToRuntimeTextSource(placeholderMessage)}}`);
2014
2034
  if (optionsAttr)
2015
2035
  lines.push(` ${optionsAttr}`);
2016
2036
  lines.push(` disabled={${formFieldDisabledExpression(field, modelField, fieldEnabled, `${includeKey}LinkedDerivedFieldNames.includes('${field.field}')`, existingItemDisabled)}}`);
@@ -2156,6 +2176,14 @@ function generateCreateView(ir, resource, view, model) {
2156
2176
  const newItemLabelMessage = view.messages?.newItemLabel ?? 'New';
2157
2177
  const workflowPreconditionMessage = resource.workflowMessages?.preconditionBlocked ?? 'Action blocked by workflow preconditions';
2158
2178
  const workflowInvalidTransitionMessage = resource.workflowMessages?.invalidTransition ?? 'Invalid workflow transition request';
2179
+ const fieldLabelMessages = [
2180
+ ...collectFormFieldDecoratorMessages(view.fields, 'label'),
2181
+ ...view.includes.flatMap((include) => collectFormFieldDecoratorMessages(include.fields, 'label')),
2182
+ ];
2183
+ const fieldPlaceholderMessages = [
2184
+ ...collectFormFieldDecoratorMessages(view.fields, 'placeholder'),
2185
+ ...view.includes.flatMap((include) => collectFormFieldDecoratorMessages(include.fields, 'placeholder')),
2186
+ ];
2159
2187
  const usesMessageResolver = someMessageLikesNeedRuntimeResolver([
2160
2188
  createLabelMessage,
2161
2189
  createAndContinueLabelMessage,
@@ -2168,6 +2196,8 @@ function generateCreateView(ir, resource, view, model) {
2168
2196
  versionConflictFieldNamesLiteral !== '[]' ? versionConflictMessage : undefined,
2169
2197
  hasWorkflow ? workflowPreconditionMessage : undefined,
2170
2198
  hasWorkflow ? workflowInvalidTransitionMessage : undefined,
2199
+ ...fieldLabelMessages,
2200
+ ...fieldPlaceholderMessages,
2171
2201
  ]);
2172
2202
  const seedParamAppliersConstName = createSeedFields.length > 0 ? staticComponentConstName(componentName, 'SeedParamAppliers') : null;
2173
2203
  const lookupOptionsConstName = componentResourceOptionsConstName(componentName);
@@ -2227,7 +2257,7 @@ function generateCreateView(ir, resource, view, model) {
2227
2257
  for (const compilation of includeLinkedDerivationCompilations.values()) {
2228
2258
  compilation?.helperImports.forEach((name) => derivationRuntimeImports.add(name));
2229
2259
  }
2230
- lines.push(`// Generated by ReactDSL compiler v0.1.0`);
2260
+ lines.push(`// Generated by Loj RDSL compiler v0.6.2`);
2231
2261
  lines.push(`// @source-node ${view.id}`);
2232
2262
  lines.push(``);
2233
2263
  lines.push(`import React, { useCallback } from 'react';`);
@@ -2391,8 +2421,16 @@ function generateCreateView(ir, resource, view, model) {
2391
2421
  if (hasWorkflow) {
2392
2422
  lines.push(` ${resource.workflow.program.field}: ${JSON.stringify(workflowInitialState(resource))} as ${modelName}['${resource.workflow.program.field}'],`);
2393
2423
  }
2424
+ for (const field of view.fields) {
2425
+ const modelField = model.fields.find((candidate) => candidate.name === field.field);
2426
+ const defaultValueLiteral = modelField ? fieldDefaultValueLiteral(modelField) : undefined;
2427
+ if (defaultValueLiteral !== undefined) {
2428
+ lines.push(` ${field.field}: ${defaultValueLiteral},`);
2429
+ }
2430
+ }
2394
2431
  for (const binding of includeBindings) {
2395
- lines.push(` ${binding.fieldName}: Array.from({ length: ${binding.minItems} }, () => ({})),`);
2432
+ const includeDefaultItem = includeFieldDefaultObjectLiteral(binding.targetModel, binding.fields);
2433
+ lines.push(` ${binding.fieldName}: Array.from({ length: ${binding.minItems} }, () => (${includeDefaultItem})),`);
2396
2434
  }
2397
2435
  lines.push(` };`);
2398
2436
  lines.push(` if (!searchParams) return initial;`);
@@ -2569,6 +2607,7 @@ function generateCreateView(ir, resource, view, model) {
2569
2607
  const handleAdd = `handleAdd${capitalize(includeKey)}`;
2570
2608
  const handleRemove = `handleRemove${capitalize(includeKey)}`;
2571
2609
  const handleChange = `handle${capitalize(includeKey)}FieldChange`;
2610
+ const includeDefaultItem = includeFieldDefaultObjectLiteral(binding.targetModel, binding.fields);
2572
2611
  const removeGuard = binding.mode === 'append-only'
2573
2612
  ? `prev.${binding.fieldName}.length <= ${binding.minItems} || Boolean(prev.${binding.fieldName}[index]?.id)`
2574
2613
  : `prev.${binding.fieldName}.length <= ${binding.minItems}`;
@@ -2576,7 +2615,7 @@ function generateCreateView(ir, resource, view, model) {
2576
2615
  lines.push(` const ${handleAdd} = useCallback(() => {`);
2577
2616
  lines.push(` setFormData((prev) => ({`);
2578
2617
  lines.push(` ...prev,`);
2579
- lines.push(` ${binding.fieldName}: [...prev.${binding.fieldName}, {}],`);
2618
+ lines.push(` ${binding.fieldName}: [...prev.${binding.fieldName}, ${includeDefaultItem}],`);
2580
2619
  lines.push(` }));`);
2581
2620
  lines.push(` }, []);`);
2582
2621
  lines.push(` const ${handleRemove} = useCallback((index: number) => {`);
@@ -2754,6 +2793,8 @@ function generateCreateView(ir, resource, view, model) {
2754
2793
  else {
2755
2794
  const fieldType = getFormFieldType(field, model);
2756
2795
  const modelField = model.fields.find(f => f.name === field.field);
2796
+ const labelMessage = formFieldLabelMessage(field);
2797
+ const placeholderMessage = formFieldPlaceholderMessage(field);
2757
2798
  let optionsAttr = '';
2758
2799
  if (modelField?.fieldType.type === 'enum') {
2759
2800
  optionsAttr = `options={${enumValuesExpression(modelMetaConstName(model), field.field)}} optionKeyPrefix={${enumKeyPrefixExpression(modelMetaConstName(model), field.field)}} optionLabels={${enumLabelsExpression(modelMetaConstName(model), field.field)}}`;
@@ -2767,13 +2808,15 @@ function generateCreateView(ir, resource, view, model) {
2767
2808
  lines.push(` {/* @source-node ${field.id} */}`);
2768
2809
  lines.push(` {${fieldVisible} ? (`);
2769
2810
  lines.push(` <FormField`);
2770
- lines.push(` label="${columnLabel(field.field)}"`);
2811
+ lines.push(` label={${messageLikeToRuntimeTextSource(labelMessage)}}`);
2771
2812
  lines.push(` name="${field.field}"`);
2772
2813
  lines.push(` type="${fieldType}"`);
2773
2814
  lines.push(` value={formData.${field.field} ?? ''}`);
2774
2815
  lines.push(` onChange={(v: unknown) => handleFieldChange('${field.field}', v)}`);
2775
2816
  lines.push(` schema={${modelName}Schema.${field.field}}`);
2776
2817
  lines.push(` error={fieldErrors[${JSON.stringify(field.field)}] ?? null}`);
2818
+ if (placeholderMessage)
2819
+ lines.push(` placeholder={${messageLikeToRuntimeTextSource(placeholderMessage)}}`);
2777
2820
  if (optionsAttr)
2778
2821
  lines.push(` ${optionsAttr}`);
2779
2822
  lines.push(` disabled={${formFieldDisabledExpression(field, modelField, fieldEnabled, `linkedDerivedFieldNames.includes('${field.field}')`)}}`);
@@ -2820,6 +2863,8 @@ function generateCreateView(ir, resource, view, model) {
2820
2863
  }
2821
2864
  const fieldType = getFormFieldType(field, binding.targetModel);
2822
2865
  const modelField = binding.targetModel.fields.find((candidate) => candidate.name === field.field);
2866
+ const labelMessage = formFieldLabelMessage(field);
2867
+ const placeholderMessage = formFieldPlaceholderMessage(field);
2823
2868
  let optionsAttr = '';
2824
2869
  if (modelField?.fieldType.type === 'enum') {
2825
2870
  optionsAttr = `options={${enumValuesExpression(modelMetaConstName(binding.targetModel), field.field)}} optionKeyPrefix={${enumKeyPrefixExpression(modelMetaConstName(binding.targetModel), field.field)}} optionLabels={${enumLabelsExpression(modelMetaConstName(binding.targetModel), field.field)}}`;
@@ -2833,12 +2878,14 @@ function generateCreateView(ir, resource, view, model) {
2833
2878
  lines.push(` {/* @source-node ${field.id} */}`);
2834
2879
  lines.push(` {${fieldVisible} ? (`);
2835
2880
  lines.push(` <FormField`);
2836
- lines.push(` label="${columnLabel(field.field)}"`);
2881
+ lines.push(` label={${messageLikeToRuntimeTextSource(labelMessage)}}`);
2837
2882
  lines.push(` name="${binding.fieldName}.${field.field}"`);
2838
2883
  lines.push(` type="${fieldType}"`);
2839
2884
  lines.push(` value={item.${field.field} ?? ''}`);
2840
2885
  lines.push(` onChange={(v: unknown) => ${handleChange}(index, '${field.field}', v)}`);
2841
2886
  lines.push(` schema={${binding.targetModel.name}Schema.${field.field}}`);
2887
+ if (placeholderMessage)
2888
+ lines.push(` placeholder={${messageLikeToRuntimeTextSource(placeholderMessage)}}`);
2842
2889
  if (optionsAttr) {
2843
2890
  lines.push(` ${optionsAttr}`);
2844
2891
  }
@@ -2885,7 +2932,7 @@ function generateWorkflowMetaModule(resource) {
2885
2932
  const workflowStateMetaLiteralSource = workflowStateMetaRuntimeLiteral(resource);
2886
2933
  const workflowStateMetaByNameLiteralSource = workflowStateMetaByNameRuntimeLiteral(resource);
2887
2934
  const workflowTransitionTargetsLiteralSource = workflowTransitionTargetsRuntimeLiteral(resource);
2888
- lines.push(`// Generated by ReactDSL compiler v0.1.0`);
2935
+ lines.push(`// Generated by Loj RDSL compiler v0.6.2`);
2889
2936
  lines.push(`// @source-node ${resource.id}`);
2890
2937
  lines.push(``);
2891
2938
  if (workflowRuntimeImports.size > 0) {
@@ -2980,7 +3027,7 @@ function generateReadView(ir, resource, view, model) {
2980
3027
  hasWorkflow ? workflowAdvanceToStepMessage : undefined,
2981
3028
  ...(hasWorkflow ? [workflowPreconditionMessage, workflowInvalidTransitionMessage] : []),
2982
3029
  ]);
2983
- lines.push(`// Generated by ReactDSL compiler v0.1.0`);
3030
+ lines.push(`// Generated by Loj RDSL compiler v0.6.2`);
2984
3031
  lines.push(`// @source-node ${view.id}`);
2985
3032
  lines.push(``);
2986
3033
  lines.push(`import React from 'react';`);
@@ -3324,7 +3371,7 @@ function generateWorkflowView(ir, resource, model) {
3324
3371
  : resource.views.list
3325
3372
  ? appLocalHrefExpression(JSON.stringify(`/${resource.name}`))
3326
3373
  : 'null';
3327
- lines.push(`// Generated by ReactDSL compiler v0.1.0`);
3374
+ lines.push(`// Generated by Loj RDSL compiler v0.6.2`);
3328
3375
  lines.push(`// @source-node ${resource.id}`);
3329
3376
  lines.push(``);
3330
3377
  lines.push(`import React from 'react';`);
@@ -3572,7 +3619,7 @@ function generateRelatedCollectionView(ir, resource, model, panel) {
3572
3619
  const relatedNotFoundMessage = 'Record not found';
3573
3620
  const relatedLoadingMessage = 'Loading...';
3574
3621
  const relatedEmptyMessage = 'No related records';
3575
- lines.push(`// Generated by ReactDSL compiler v0.1.0`);
3622
+ lines.push(`// Generated by Loj RDSL compiler v0.6.2`);
3576
3623
  lines.push(`// @source-node ${panel.panelId}`);
3577
3624
  lines.push(``);
3578
3625
  lines.push(`import React from 'react';`);
@@ -4146,7 +4193,7 @@ function generatePage(ir, page) {
4146
4193
  itemsName: binding.itemsName,
4147
4194
  });
4148
4195
  }
4149
- lines.push(`// Generated by ReactDSL compiler v0.1.0`);
4196
+ lines.push(`// Generated by Loj RDSL compiler v0.6.2`);
4150
4197
  lines.push(`// @source-node ${page.id}`);
4151
4198
  lines.push(``);
4152
4199
  lines.push(`import React from 'react';`);
@@ -5735,7 +5782,7 @@ function appendRelatedPanelsSectionComponent(lines, options) {
5735
5782
  // ─── Router ──────────────────────────────────────────────────────
5736
5783
  function generateRouter(ir) {
5737
5784
  const lines = [];
5738
- lines.push(`// Generated by ReactDSL compiler v0.1.0`);
5785
+ lines.push(`// Generated by Loj RDSL compiler v0.6.2`);
5739
5786
  lines.push(`// @source-node app.main.router`);
5740
5787
  lines.push(``);
5741
5788
  lines.push(`import React from 'react';`);
@@ -5960,7 +6007,7 @@ function generateAppEntry(ir) {
5960
6007
  || group.items.some((item) => messageLikeNeedsRuntimeResolver(item.label))));
5961
6008
  const primaryNavigationConstName = staticComponentConstName('App', 'PrimaryNavigation');
5962
6009
  const secondaryNavigationConstName = staticComponentConstName('App', 'SecondaryNavigation');
5963
- lines.push(`// Generated by ReactDSL compiler v0.1.0`);
6010
+ lines.push(`// Generated by Loj RDSL compiler v0.6.2`);
5964
6011
  lines.push(`// @source-node app.main`);
5965
6012
  lines.push(`// Prefer editing source .web.loj/.style.loj files or documented escape hatches instead of this generated file.`);
5966
6013
  lines.push(`// This is the application entry point.`);
@@ -8376,6 +8423,168 @@ function enumLabelsExpression(modelMetaName, fieldName) {
8376
8423
  function enumValuesExpression(modelMetaName, fieldName) {
8377
8424
  return `((${modelMetaName}.enumValues as Record<string, readonly string[] | undefined>)[${JSON.stringify(fieldName)}] ?? ([] as readonly string[]))`;
8378
8425
  }
8426
+ function collectFormFieldDecoratorMessages(fields, decoratorName) {
8427
+ return fields
8428
+ .map((field) => formFieldDecoratorMessageLike(field, decoratorName))
8429
+ .filter((message) => Boolean(message));
8430
+ }
8431
+ function formFieldLabelMessage(field) {
8432
+ return formFieldDecoratorMessageLike(field, 'label') ?? columnLabel(field.field);
8433
+ }
8434
+ function formFieldPlaceholderMessage(field) {
8435
+ return formFieldDecoratorMessageLike(field, 'placeholder');
8436
+ }
8437
+ function includeFieldDefaultObjectLiteral(model, fields) {
8438
+ const entries = fields.flatMap((field) => {
8439
+ const modelField = model.fields.find((candidate) => candidate.name === field.field);
8440
+ if (!modelField) {
8441
+ return [];
8442
+ }
8443
+ const defaultValueLiteral = fieldDefaultValueLiteral(modelField);
8444
+ if (defaultValueLiteral === undefined) {
8445
+ return [];
8446
+ }
8447
+ return [`${JSON.stringify(field.field)}: ${defaultValueLiteral}`];
8448
+ });
8449
+ if (entries.length === 0) {
8450
+ return '{}';
8451
+ }
8452
+ return `{ ${entries.join(', ')} }`;
8453
+ }
8454
+ function fieldDefaultValueLiteral(field) {
8455
+ const decorator = field.decorators.find((candidate) => candidate.name === 'default');
8456
+ if (!decorator?.args) {
8457
+ return undefined;
8458
+ }
8459
+ const raw = 'value' in decorator.args
8460
+ ? decorator.args.value
8461
+ : undefined;
8462
+ const value = normalizeDecoratorScalarValue(raw);
8463
+ if (value === undefined) {
8464
+ return undefined;
8465
+ }
8466
+ return JSON.stringify(value);
8467
+ }
8468
+ function formFieldDecoratorMessageLike(field, decoratorName) {
8469
+ const decorator = field.decorators.find((candidate) => candidate.name === decoratorName);
8470
+ if (!decorator?.args) {
8471
+ return undefined;
8472
+ }
8473
+ return normalizeDecoratorMessageLikeValue(decorator.args);
8474
+ }
8475
+ function normalizeDecoratorMessageLikeValue(args) {
8476
+ const directValue = normalizeDecoratorScalarValue(args.value);
8477
+ if (typeof directValue === 'string' && directValue.length > 0) {
8478
+ const descriptorFromJson = parseMessageLikeDescriptorJson(directValue);
8479
+ if (descriptorFromJson) {
8480
+ return descriptorFromJson;
8481
+ }
8482
+ return directValue;
8483
+ }
8484
+ const key = normalizeDecoratorStringValue(args.key);
8485
+ const defaultMessage = normalizeDecoratorStringValue(args.defaultMessage);
8486
+ let values;
8487
+ if (isPlainObject(args.values)) {
8488
+ values = Object.fromEntries(Object.entries(args.values)
8489
+ .map(([name, value]) => [name, normalizeDecoratorScalarValue(value)])
8490
+ .filter((entry) => entry[1] !== undefined));
8491
+ }
8492
+ if (!key && !defaultMessage && (!values || Object.keys(values).length === 0)) {
8493
+ return undefined;
8494
+ }
8495
+ const descriptor = {};
8496
+ if (key) {
8497
+ descriptor.key = key;
8498
+ }
8499
+ if (defaultMessage) {
8500
+ descriptor.defaultMessage = defaultMessage;
8501
+ }
8502
+ if (values && Object.keys(values).length > 0) {
8503
+ descriptor.values = values;
8504
+ }
8505
+ return descriptor;
8506
+ }
8507
+ function normalizeDecoratorStringValue(value) {
8508
+ const normalized = normalizeDecoratorScalarValue(value);
8509
+ if (typeof normalized !== 'string') {
8510
+ return undefined;
8511
+ }
8512
+ const trimmed = normalized.trim();
8513
+ return trimmed.length > 0 ? trimmed : undefined;
8514
+ }
8515
+ function normalizeDecoratorScalarValue(value) {
8516
+ if (value === null || typeof value === 'number' || typeof value === 'boolean') {
8517
+ return value;
8518
+ }
8519
+ if (typeof value !== 'string') {
8520
+ return undefined;
8521
+ }
8522
+ const trimmed = value.trim();
8523
+ if (!trimmed) {
8524
+ return undefined;
8525
+ }
8526
+ const quoted = trimmed.match(/^(['"])([\s\S]*)\1$/);
8527
+ if (quoted) {
8528
+ return quoted[2];
8529
+ }
8530
+ if (trimmed === 'true') {
8531
+ return true;
8532
+ }
8533
+ if (trimmed === 'false') {
8534
+ return false;
8535
+ }
8536
+ if (trimmed === 'null') {
8537
+ return null;
8538
+ }
8539
+ if (/^-?\d+(\.\d+)?$/.test(trimmed)) {
8540
+ const parsed = Number(trimmed);
8541
+ if (Number.isFinite(parsed)) {
8542
+ return parsed;
8543
+ }
8544
+ }
8545
+ return trimmed;
8546
+ }
8547
+ function isPlainObject(value) {
8548
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
8549
+ }
8550
+ function parseMessageLikeDescriptorJson(value) {
8551
+ const trimmed = value.trim();
8552
+ if (!trimmed.startsWith('{') || !trimmed.endsWith('}')) {
8553
+ return undefined;
8554
+ }
8555
+ let parsed;
8556
+ try {
8557
+ parsed = JSON.parse(trimmed);
8558
+ }
8559
+ catch {
8560
+ return undefined;
8561
+ }
8562
+ if (!isPlainObject(parsed)) {
8563
+ return undefined;
8564
+ }
8565
+ const key = normalizeDecoratorStringValue(parsed.key);
8566
+ const defaultMessage = normalizeDecoratorStringValue(parsed.defaultMessage);
8567
+ let values;
8568
+ if (isPlainObject(parsed.values)) {
8569
+ values = Object.fromEntries(Object.entries(parsed.values)
8570
+ .map(([name, raw]) => [name, normalizeDecoratorScalarValue(raw)])
8571
+ .filter((entry) => entry[1] !== undefined));
8572
+ }
8573
+ if (!key && !defaultMessage && (!values || Object.keys(values).length === 0)) {
8574
+ return undefined;
8575
+ }
8576
+ const descriptor = {};
8577
+ if (key) {
8578
+ descriptor.key = key;
8579
+ }
8580
+ if (defaultMessage) {
8581
+ descriptor.defaultMessage = defaultMessage;
8582
+ }
8583
+ if (values && Object.keys(values).length > 0) {
8584
+ descriptor.values = values;
8585
+ }
8586
+ return descriptor;
8587
+ }
8379
8588
  function fieldFormatExpression(modelMetaName, fieldName) {
8380
8589
  return `((${modelMetaName}.fieldFormats as Record<string, unknown>)[${JSON.stringify(fieldName)}] as any)`;
8381
8590
  }