@carlonicora/nextjs-jsonapi 1.7.6 → 1.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{BlockNoteEditor-CCOSI7TW.js → BlockNoteEditor-KSPPX6JO.js} +13 -13
- package/dist/{BlockNoteEditor-CCOSI7TW.js.map → BlockNoteEditor-KSPPX6JO.js.map} +1 -1
- package/dist/{BlockNoteEditor-KH7WWHBK.mjs → BlockNoteEditor-N534QVBR.mjs} +3 -3
- package/dist/{chunk-3HL4VFJ4.js → chunk-7Z7FEMEB.js} +56 -36
- package/dist/chunk-7Z7FEMEB.js.map +1 -0
- package/dist/{chunk-KFON36OE.js → chunk-B426TLJC.js} +358 -358
- package/dist/{chunk-KFON36OE.js.map → chunk-B426TLJC.js.map} +1 -1
- package/dist/{chunk-2O7ODHTG.mjs → chunk-CK5KLBZV.mjs} +21 -1
- package/dist/chunk-CK5KLBZV.mjs.map +1 -0
- package/dist/{chunk-2TCBJO4B.mjs → chunk-TLBZWOCU.mjs} +3 -3
- package/dist/client/index.js +3 -3
- package/dist/client/index.mjs +2 -2
- package/dist/components/index.js +3 -3
- package/dist/components/index.mjs +2 -2
- package/dist/contexts/index.js +3 -3
- package/dist/contexts/index.mjs +2 -2
- package/dist/core/index.d.mts +10 -0
- package/dist/core/index.d.ts +10 -0
- package/dist/core/index.js +2 -2
- package/dist/core/index.mjs +1 -1
- package/dist/index.js +2 -2
- package/dist/index.mjs +1 -1
- package/dist/scripts/generate-web-module/templates/components/editor.template.js +108 -4
- package/dist/scripts/generate-web-module/templates/components/editor.template.js.map +1 -1
- package/dist/scripts/generate-web-module/templates/data/interface.template.js +15 -2
- package/dist/scripts/generate-web-module/templates/data/interface.template.js.map +1 -1
- package/dist/scripts/generate-web-module/templates/data/model.template.js +32 -4
- package/dist/scripts/generate-web-module/templates/data/model.template.js.map +1 -1
- package/dist/scripts/generate-web-module/transformers/i18n-generator.d.ts.map +1 -1
- package/dist/scripts/generate-web-module/transformers/i18n-generator.js +14 -1
- package/dist/scripts/generate-web-module/transformers/i18n-generator.js.map +1 -1
- package/dist/scripts/generate-web-module/transformers/relationship-resolver.d.ts +5 -2
- package/dist/scripts/generate-web-module/transformers/relationship-resolver.d.ts.map +1 -1
- package/dist/scripts/generate-web-module/transformers/relationship-resolver.js +17 -7
- package/dist/scripts/generate-web-module/transformers/relationship-resolver.js.map +1 -1
- package/dist/scripts/generate-web-module/types/json-schema.interface.d.ts +1 -0
- package/dist/scripts/generate-web-module/types/json-schema.interface.d.ts.map +1 -1
- package/dist/scripts/generate-web-module/types/template-data.interface.d.ts +6 -0
- package/dist/scripts/generate-web-module/types/template-data.interface.d.ts.map +1 -1
- package/dist/scripts/generate-web-module/utils/i18n-updater.d.ts.map +1 -1
- package/dist/scripts/generate-web-module/utils/i18n-updater.js +3 -2
- package/dist/scripts/generate-web-module/utils/i18n-updater.js.map +1 -1
- package/dist/server/index.js +3 -3
- package/dist/server/index.mjs +1 -1
- package/package.json +1 -1
- package/scripts/generate-web-module/templates/components/editor.template.ts +115 -5
- package/scripts/generate-web-module/templates/data/interface.template.ts +18 -2
- package/scripts/generate-web-module/templates/data/model.template.ts +40 -6
- package/scripts/generate-web-module/transformers/i18n-generator.ts +16 -1
- package/scripts/generate-web-module/transformers/relationship-resolver.ts +18 -7
- package/scripts/generate-web-module/types/json-schema.interface.ts +1 -0
- package/scripts/generate-web-module/types/template-data.interface.ts +9 -0
- package/scripts/generate-web-module/utils/i18n-updater.ts +3 -2
- package/src/core/abstracts/AbstractApiData.ts +34 -0
- package/dist/chunk-2O7ODHTG.mjs.map +0 -1
- package/dist/chunk-3HL4VFJ4.js.map +0 -1
- /package/dist/{BlockNoteEditor-KH7WWHBK.mjs.map → BlockNoteEditor-N534QVBR.mjs.map} +0 -0
- /package/dist/{chunk-2TCBJO4B.mjs.map → chunk-TLBZWOCU.mjs.map} +0 -0
|
@@ -8,7 +8,7 @@ import { FrontendTemplateData, FrontendField, FrontendRelationship } from "../..
|
|
|
8
8
|
import { toCamelCase, pluralize, toPascalCase } from "../../transformers/name-transformer";
|
|
9
9
|
import { AUTHOR_VARIANT } from "../../types/field-mapping.types";
|
|
10
10
|
import { getFormFieldJsx } from "../../transformers/field-mapper";
|
|
11
|
-
import { getRelationshipFormJsx, getDefaultValueExpression, getPayloadMapping, isFoundationImport,
|
|
11
|
+
import { getRelationshipFormJsx, getDefaultValueExpression, getPayloadMapping, isFoundationImport, FOUNDATION_COMPONENTS_PACKAGE } from "../../transformers/relationship-resolver";
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
14
|
* Generate the editor component file content
|
|
@@ -156,7 +156,7 @@ function generateImports(data: FrontendTemplateData): string {
|
|
|
156
156
|
const componentName = rel.single ? `${rel.name}Selector` : `${rel.name}MultiSelector`;
|
|
157
157
|
if (rel.isFoundation) {
|
|
158
158
|
// Foundation entities use named imports from the package
|
|
159
|
-
imports.push(`import { ${componentName} } from "${
|
|
159
|
+
imports.push(`import { ${componentName} } from "${FOUNDATION_COMPONENTS_PACKAGE}";`);
|
|
160
160
|
} else {
|
|
161
161
|
imports.push(`import ${componentName} from "${rel.importPath}";`);
|
|
162
162
|
}
|
|
@@ -188,6 +188,21 @@ function generateImports(data: FrontendTemplateData): string {
|
|
|
188
188
|
componentImports.push("FormInput");
|
|
189
189
|
}
|
|
190
190
|
|
|
191
|
+
// Check if any relationship has boolean or date fields that need specific form components
|
|
192
|
+
const hasRelBooleanFields = relationships.some((rel) =>
|
|
193
|
+
rel.fields?.some((f) => f.type === "boolean")
|
|
194
|
+
);
|
|
195
|
+
const hasRelDateFields = relationships.some((rel) =>
|
|
196
|
+
rel.fields?.some((f) => f.type === "date" || f.type === "datetime")
|
|
197
|
+
);
|
|
198
|
+
|
|
199
|
+
if (hasRelBooleanFields) {
|
|
200
|
+
componentImports.push("FormCheckbox");
|
|
201
|
+
}
|
|
202
|
+
if (hasRelDateFields) {
|
|
203
|
+
componentImports.push("FormDatePicker");
|
|
204
|
+
}
|
|
205
|
+
|
|
191
206
|
imports.push(`import {
|
|
192
207
|
${componentImports.join(",\n ")},
|
|
193
208
|
} from "@carlonicora/nextjs-jsonapi/components";`);
|
|
@@ -288,9 +303,34 @@ function generateFormSchema(data: FrontendTemplateData): string {
|
|
|
288
303
|
schemaFields.push(` ${fieldId}: entityObjectSchema.optional(),`);
|
|
289
304
|
} else {
|
|
290
305
|
schemaFields.push(` ${fieldId}: entityObjectSchema.refine((data) => data.id && data.id.length > 0, {
|
|
291
|
-
message: t(\`
|
|
306
|
+
message: t(\`features.${names.camelCase}.relationships.${fieldId}.error\`),
|
|
292
307
|
}),`);
|
|
293
308
|
}
|
|
309
|
+
// Add relationship property fields to schema
|
|
310
|
+
if (rel.fields && rel.fields.length > 0) {
|
|
311
|
+
rel.fields.forEach((field) => {
|
|
312
|
+
const optional = rel.nullable ? ".optional()" : "";
|
|
313
|
+
switch (field.type) {
|
|
314
|
+
case "number":
|
|
315
|
+
schemaFields.push(` ${field.name}: z.number()${optional},`);
|
|
316
|
+
break;
|
|
317
|
+
case "boolean":
|
|
318
|
+
schemaFields.push(` ${field.name}: z.boolean()${optional},`);
|
|
319
|
+
break;
|
|
320
|
+
case "date":
|
|
321
|
+
case "datetime":
|
|
322
|
+
schemaFields.push(` ${field.name}: z.coerce.date()${optional},`);
|
|
323
|
+
break;
|
|
324
|
+
case "any":
|
|
325
|
+
schemaFields.push(` ${field.name}: z.any()${optional},`);
|
|
326
|
+
break;
|
|
327
|
+
case "string":
|
|
328
|
+
default:
|
|
329
|
+
schemaFields.push(` ${field.name}: z.string()${optional},`);
|
|
330
|
+
break;
|
|
331
|
+
}
|
|
332
|
+
});
|
|
333
|
+
}
|
|
294
334
|
} else {
|
|
295
335
|
schemaFields.push(` ${fieldId}: z.array(entityObjectSchema).optional(),`);
|
|
296
336
|
}
|
|
@@ -349,6 +389,28 @@ function generateDefaultValues(data: FrontendTemplateData): string {
|
|
|
349
389
|
defaults.push(` ${fieldId}: ${names.camelCase}?.${propertyName}
|
|
350
390
|
? { id: ${names.camelCase}.${propertyName}.id, name: ${names.camelCase}.${propertyName}.name }
|
|
351
391
|
: undefined,`);
|
|
392
|
+
// Add relationship property field defaults
|
|
393
|
+
if (rel.fields && rel.fields.length > 0) {
|
|
394
|
+
rel.fields.forEach((field) => {
|
|
395
|
+
switch (field.type) {
|
|
396
|
+
case "number":
|
|
397
|
+
defaults.push(` ${field.name}: ${names.camelCase}?.${propertyName}?.${field.name} ?? 0,`);
|
|
398
|
+
break;
|
|
399
|
+
case "boolean":
|
|
400
|
+
defaults.push(` ${field.name}: ${names.camelCase}?.${propertyName}?.${field.name} ?? false,`);
|
|
401
|
+
break;
|
|
402
|
+
case "date":
|
|
403
|
+
case "datetime":
|
|
404
|
+
case "any":
|
|
405
|
+
defaults.push(` ${field.name}: ${names.camelCase}?.${propertyName}?.${field.name},`);
|
|
406
|
+
break;
|
|
407
|
+
case "string":
|
|
408
|
+
default:
|
|
409
|
+
defaults.push(` ${field.name}: ${names.camelCase}?.${propertyName}?.${field.name} ?? "",`);
|
|
410
|
+
break;
|
|
411
|
+
}
|
|
412
|
+
});
|
|
413
|
+
}
|
|
352
414
|
} else {
|
|
353
415
|
defaults.push(` ${fieldId}: ${names.camelCase}?.${pluralPropertyName}
|
|
354
416
|
? ${names.camelCase}.${pluralPropertyName}.map((item) => ({ id: item.id, name: item.name }))
|
|
@@ -394,6 +456,12 @@ function generateOnSubmit(data: FrontendTemplateData): string {
|
|
|
394
456
|
|
|
395
457
|
if (rel.single) {
|
|
396
458
|
payloadFields.push(` ${payloadKey}: values.${fieldId}?.id,`);
|
|
459
|
+
// Add relationship property fields to payload
|
|
460
|
+
if (rel.fields && rel.fields.length > 0) {
|
|
461
|
+
rel.fields.forEach((field) => {
|
|
462
|
+
payloadFields.push(` ${field.name}: values.${field.name},`);
|
|
463
|
+
});
|
|
464
|
+
}
|
|
397
465
|
} else {
|
|
398
466
|
payloadFields.push(` ${payloadKey}: values.${fieldId} ? values.${fieldId}.map((item) => item.id) : [],`);
|
|
399
467
|
}
|
|
@@ -484,9 +552,51 @@ function generateFormFields(data: FrontendTemplateData): string {
|
|
|
484
552
|
formElements.push(` <${rel.name}Selector
|
|
485
553
|
id="${fieldId}"
|
|
486
554
|
form={form}
|
|
487
|
-
label={t(\`
|
|
488
|
-
placeholder={t(\`
|
|
555
|
+
label={t(\`features.${names.camelCase}.relationships.${fieldId}.label\`)}
|
|
556
|
+
placeholder={t(\`features.${names.camelCase}.relationships.${fieldId}.placeholder\`)}${!rel.nullable ? "\n isRequired" : ""}
|
|
557
|
+
/>`);
|
|
558
|
+
// Add form inputs for relationship property fields
|
|
559
|
+
if (rel.fields && rel.fields.length > 0) {
|
|
560
|
+
rel.fields.forEach((field) => {
|
|
561
|
+
const isRequired = !rel.nullable;
|
|
562
|
+
switch (field.type) {
|
|
563
|
+
case "number":
|
|
564
|
+
formElements.push(` <FormInput
|
|
565
|
+
form={form}
|
|
566
|
+
id="${field.name}"
|
|
567
|
+
name={t(\`features.${names.camelCase}.relationships.${fieldId}.fields.${field.name}.label\`)}
|
|
568
|
+
placeholder={t(\`features.${names.camelCase}.relationships.${fieldId}.fields.${field.name}.placeholder\`)}
|
|
569
|
+
type="number"${isRequired ? "\n isRequired" : ""}
|
|
570
|
+
/>`);
|
|
571
|
+
break;
|
|
572
|
+
case "boolean":
|
|
573
|
+
formElements.push(` <FormCheckbox
|
|
574
|
+
form={form}
|
|
575
|
+
id="${field.name}"
|
|
576
|
+
name={t(\`features.${names.camelCase}.relationships.${fieldId}.fields.${field.name}.label\`)}
|
|
489
577
|
/>`);
|
|
578
|
+
break;
|
|
579
|
+
case "date":
|
|
580
|
+
case "datetime":
|
|
581
|
+
formElements.push(` <FormDatePicker
|
|
582
|
+
form={form}
|
|
583
|
+
id="${field.name}"
|
|
584
|
+
name={t(\`features.${names.camelCase}.relationships.${fieldId}.fields.${field.name}.label\`)}${isRequired ? "\n isRequired" : ""}
|
|
585
|
+
/>`);
|
|
586
|
+
break;
|
|
587
|
+
case "string":
|
|
588
|
+
case "any":
|
|
589
|
+
default:
|
|
590
|
+
formElements.push(` <FormInput
|
|
591
|
+
form={form}
|
|
592
|
+
id="${field.name}"
|
|
593
|
+
name={t(\`features.${names.camelCase}.relationships.${fieldId}.fields.${field.name}.label\`)}
|
|
594
|
+
placeholder={t(\`features.${names.camelCase}.relationships.${fieldId}.fields.${field.name}.placeholder\`)}${isRequired ? "\n isRequired" : ""}
|
|
595
|
+
/>`);
|
|
596
|
+
break;
|
|
597
|
+
}
|
|
598
|
+
});
|
|
599
|
+
}
|
|
490
600
|
} else {
|
|
491
601
|
formElements.push(` <${rel.name}MultiSelector
|
|
492
602
|
id="${fieldId}"
|
|
@@ -74,13 +74,21 @@ function generateInputType(data: FrontendTemplateData): string {
|
|
|
74
74
|
fieldLines.push(` ${field.name}${optional}: ${field.tsType};`);
|
|
75
75
|
});
|
|
76
76
|
|
|
77
|
-
// Add relationship IDs
|
|
77
|
+
// Add relationship IDs and relationship property fields
|
|
78
78
|
relationships.forEach((rel) => {
|
|
79
79
|
const effectiveName = rel.variant || rel.name;
|
|
80
80
|
if (rel.single) {
|
|
81
81
|
const key = `${toCamelCase(effectiveName)}Id`;
|
|
82
82
|
const optional = rel.nullable ? "?" : "";
|
|
83
83
|
fieldLines.push(` ${key}${optional}: string;`);
|
|
84
|
+
|
|
85
|
+
// Add relationship property fields to input type (match relationship optionality)
|
|
86
|
+
if (rel.fields && rel.fields.length > 0) {
|
|
87
|
+
rel.fields.forEach((field) => {
|
|
88
|
+
const fieldOptional = rel.nullable ? "?" : "";
|
|
89
|
+
fieldLines.push(` ${field.name}${fieldOptional}: ${field.tsType};`);
|
|
90
|
+
});
|
|
91
|
+
}
|
|
84
92
|
} else {
|
|
85
93
|
const key = `${toCamelCase(rel.name)}Ids`;
|
|
86
94
|
fieldLines.push(` ${key}?: string[];`);
|
|
@@ -120,7 +128,15 @@ function generateInterface(data: FrontendTemplateData): string {
|
|
|
120
128
|
const effectiveName = rel.variant || rel.name;
|
|
121
129
|
if (rel.single) {
|
|
122
130
|
const propertyName = toCamelCase(effectiveName);
|
|
123
|
-
|
|
131
|
+
|
|
132
|
+
// Build return type - use intersection if relationship has fields
|
|
133
|
+
let baseType = rel.interfaceName;
|
|
134
|
+
if (rel.fields && rel.fields.length > 0) {
|
|
135
|
+
const metaFields = rel.fields.map(f => `${f.name}?: ${f.tsType}`).join("; ");
|
|
136
|
+
baseType = `${rel.interfaceName} & { ${metaFields} }`;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const type = rel.nullable ? `(${baseType}) | undefined` : baseType;
|
|
124
140
|
getterLines.push(` get ${propertyName}(): ${type};`);
|
|
125
141
|
} else {
|
|
126
142
|
const propertyName = pluralize(toCamelCase(rel.name));
|
|
@@ -91,7 +91,13 @@ function generatePrivateFields(data: FrontendTemplateData): string {
|
|
|
91
91
|
const effectiveName = rel.variant || rel.name;
|
|
92
92
|
if (rel.single) {
|
|
93
93
|
const propName = toCamelCase(effectiveName);
|
|
94
|
-
|
|
94
|
+
// Use intersection type if relationship has fields
|
|
95
|
+
let typeDecl = rel.interfaceName;
|
|
96
|
+
if (rel.fields && rel.fields.length > 0) {
|
|
97
|
+
const metaFields = rel.fields.map((f) => `${f.name}?: ${f.tsType}`).join("; ");
|
|
98
|
+
typeDecl = `${rel.interfaceName} & { ${metaFields} }`;
|
|
99
|
+
}
|
|
100
|
+
lines.push(` private _${propName}?: ${typeDecl};`);
|
|
95
101
|
} else {
|
|
96
102
|
const propName = pluralize(toCamelCase(rel.name));
|
|
97
103
|
lines.push(` private _${propName}?: ${rel.interfaceName}[];`);
|
|
@@ -127,12 +133,20 @@ function generateGetters(data: FrontendTemplateData): string {
|
|
|
127
133
|
const effectiveName = rel.variant || rel.name;
|
|
128
134
|
if (rel.single) {
|
|
129
135
|
const propName = toCamelCase(effectiveName);
|
|
136
|
+
|
|
137
|
+
// Build return type - use intersection if relationship has fields
|
|
138
|
+
let baseType = rel.interfaceName;
|
|
139
|
+
if (rel.fields && rel.fields.length > 0) {
|
|
140
|
+
const metaFields = rel.fields.map((f) => `${f.name}?: ${f.tsType}`).join("; ");
|
|
141
|
+
baseType = `${rel.interfaceName} & { ${metaFields} }`;
|
|
142
|
+
}
|
|
143
|
+
|
|
130
144
|
if (rel.nullable) {
|
|
131
|
-
lines.push(` get ${propName}(): ${
|
|
145
|
+
lines.push(` get ${propName}(): (${baseType}) | undefined {
|
|
132
146
|
return this._${propName};
|
|
133
147
|
}`);
|
|
134
148
|
} else {
|
|
135
|
-
lines.push(` get ${propName}(): ${
|
|
149
|
+
lines.push(` get ${propName}(): ${baseType} {
|
|
136
150
|
if (this._${propName} === undefined) throw new Error("JsonApi error: ${data.names.camelCase} ${propName} is missing");
|
|
137
151
|
return this._${propName};
|
|
138
152
|
}`);
|
|
@@ -188,9 +202,18 @@ function generateRehydrateMethod(data: FrontendTemplateData): string {
|
|
|
188
202
|
if (rel.single) {
|
|
189
203
|
const propName = toCamelCase(effectiveName);
|
|
190
204
|
const relationshipKey = effectiveName.toLowerCase();
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
)
|
|
205
|
+
|
|
206
|
+
// Use _readIncludedWithMeta for relationships with fields
|
|
207
|
+
if (rel.fields && rel.fields.length > 0) {
|
|
208
|
+
const metaType = `{ ${rel.fields.map((f) => `${f.name}?: ${f.tsType}`).join("; ")} }`;
|
|
209
|
+
lines.push(
|
|
210
|
+
` this._${propName} = this._readIncludedWithMeta<${rel.interfaceName}, ${metaType}>(data, "${relationshipKey}", Modules.${rel.name});`,
|
|
211
|
+
);
|
|
212
|
+
} else {
|
|
213
|
+
lines.push(
|
|
214
|
+
` this._${propName} = this._readIncluded(data, "${relationshipKey}", Modules.${rel.name}) as ${rel.interfaceName}${rel.nullable ? " | undefined" : ""};`,
|
|
215
|
+
);
|
|
216
|
+
}
|
|
194
217
|
} else {
|
|
195
218
|
const propName = pluralize(toCamelCase(rel.name));
|
|
196
219
|
const relationshipKey = pluralize(rel.name.toLowerCase());
|
|
@@ -268,6 +291,17 @@ function generateCreateJsonApiMethod(data: FrontendTemplateData): string {
|
|
|
268
291
|
lines.push(` type: Modules.${rel.name}.name,`);
|
|
269
292
|
lines.push(` id: data.${payloadKey},`);
|
|
270
293
|
lines.push(` },`);
|
|
294
|
+
|
|
295
|
+
// Add meta for relationship fields
|
|
296
|
+
if (rel.fields && rel.fields.length > 0) {
|
|
297
|
+
lines.push(` meta: {`);
|
|
298
|
+
rel.fields.forEach((field, i) => {
|
|
299
|
+
const comma = i < rel.fields!.length - 1 ? "," : "";
|
|
300
|
+
lines.push(` ${field.name}: data.${field.name}${comma}`);
|
|
301
|
+
});
|
|
302
|
+
lines.push(` },`);
|
|
303
|
+
}
|
|
304
|
+
|
|
271
305
|
lines.push(` };`);
|
|
272
306
|
lines.push(` }`);
|
|
273
307
|
} else {
|
|
@@ -43,6 +43,18 @@ export function generateI18nKeys(
|
|
|
43
43
|
error: `${toTitleCase(effectiveName)} is required`,
|
|
44
44
|
list: pluralize(toTitleCase(rel.name)),
|
|
45
45
|
};
|
|
46
|
+
|
|
47
|
+
// Add fields for relationship edge properties
|
|
48
|
+
if (rel.fields && rel.fields.length > 0) {
|
|
49
|
+
relationshipKeys[effectiveKey].fields = {};
|
|
50
|
+
rel.fields.forEach((field) => {
|
|
51
|
+
relationshipKeys[effectiveKey].fields![field.name] = {
|
|
52
|
+
label: toTitleCase(field.name),
|
|
53
|
+
placeholder: `Enter ${toTitleCase(field.name).toLowerCase()}`,
|
|
54
|
+
error: `${toTitleCase(field.name)} is required`,
|
|
55
|
+
};
|
|
56
|
+
});
|
|
57
|
+
}
|
|
46
58
|
});
|
|
47
59
|
|
|
48
60
|
// Generate type keys
|
|
@@ -68,6 +80,9 @@ export function generateI18nKeys(
|
|
|
68
80
|
* @returns Object structure for en.json
|
|
69
81
|
*/
|
|
70
82
|
export function buildI18nMessages(i18nKeys: I18nKeySet): Record<string, any> {
|
|
83
|
+
// Use proper pluralization and lowercase for types key
|
|
84
|
+
const pluralLowercaseKey = pluralize(i18nKeys.moduleName).toLowerCase();
|
|
85
|
+
|
|
71
86
|
return {
|
|
72
87
|
features: {
|
|
73
88
|
[i18nKeys.moduleName]: {
|
|
@@ -76,7 +91,7 @@ export function buildI18nMessages(i18nKeys: I18nKeySet): Record<string, any> {
|
|
|
76
91
|
},
|
|
77
92
|
},
|
|
78
93
|
types: {
|
|
79
|
-
[
|
|
94
|
+
[pluralLowercaseKey]: i18nKeys.type.icuPlural,
|
|
80
95
|
},
|
|
81
96
|
};
|
|
82
97
|
}
|
|
@@ -6,14 +6,18 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import { JsonRelationshipDefinition } from "../types/json-schema.interface";
|
|
9
|
-
import { FrontendRelationship, RelationshipServiceMethod } from "../types/template-data.interface";
|
|
9
|
+
import { FrontendRelationship, FrontendField, RelationshipServiceMethod } from "../types/template-data.interface";
|
|
10
10
|
import { AUTHOR_VARIANT, AUTHOR_ZOD_SCHEMA, ENTITY_ZOD_SCHEMA } from "../types/field-mapping.types";
|
|
11
11
|
import { toCamelCase, toKebabCase, pluralize, toPascalCase } from "./name-transformer";
|
|
12
|
+
import { mapFields } from "./field-mapper";
|
|
12
13
|
|
|
13
14
|
/**
|
|
14
|
-
* Foundation package
|
|
15
|
+
* Foundation package constants for web imports
|
|
16
|
+
* Components (selectors) come from /components
|
|
17
|
+
* Data (interfaces, services) come from /core
|
|
15
18
|
*/
|
|
16
|
-
export const
|
|
19
|
+
export const FOUNDATION_COMPONENTS_PACKAGE = "@carlonicora/nextjs-jsonapi/components";
|
|
20
|
+
export const FOUNDATION_CORE_PACKAGE = "@carlonicora/nextjs-jsonapi/core";
|
|
17
21
|
|
|
18
22
|
/**
|
|
19
23
|
* Check if a directory represents a foundation import (from the package)
|
|
@@ -78,9 +82,9 @@ export function resolveRelationship(rel: JsonRelationshipDefinition): FrontendRe
|
|
|
78
82
|
|
|
79
83
|
if (isFoundationImport(rel.directory)) {
|
|
80
84
|
// Foundation entities import from the package
|
|
81
|
-
importPath =
|
|
82
|
-
interfaceImportPath =
|
|
83
|
-
serviceImportPath =
|
|
85
|
+
importPath = FOUNDATION_COMPONENTS_PACKAGE; // Selectors from /components
|
|
86
|
+
interfaceImportPath = FOUNDATION_CORE_PACKAGE; // Interfaces from /core
|
|
87
|
+
serviceImportPath = FOUNDATION_CORE_PACKAGE; // Services from /core
|
|
84
88
|
} else {
|
|
85
89
|
// Feature entities use local paths
|
|
86
90
|
const webDirectory = mapDirectoryToWebPath(rel.directory);
|
|
@@ -89,6 +93,12 @@ export function resolveRelationship(rel: JsonRelationshipDefinition): FrontendRe
|
|
|
89
93
|
serviceImportPath = `@/features/${webDirectory}/${modelKebab}/data/${rel.name}Service`;
|
|
90
94
|
}
|
|
91
95
|
|
|
96
|
+
// Map relationship fields (only for single relationships)
|
|
97
|
+
let fields: FrontendField[] | undefined;
|
|
98
|
+
if (rel.single && rel.fields && rel.fields.length > 0) {
|
|
99
|
+
fields = mapFields(rel.fields, toCamelCase(rel.name));
|
|
100
|
+
}
|
|
101
|
+
|
|
92
102
|
return {
|
|
93
103
|
name: rel.name,
|
|
94
104
|
variant: rel.variant,
|
|
@@ -106,6 +116,7 @@ export function resolveRelationship(rel: JsonRelationshipDefinition): FrontendRe
|
|
|
106
116
|
serviceImportPath,
|
|
107
117
|
interfaceName: `${rel.name}Interface`,
|
|
108
118
|
modelKebab,
|
|
119
|
+
fields,
|
|
109
120
|
};
|
|
110
121
|
}
|
|
111
122
|
|
|
@@ -161,7 +172,7 @@ export function getSelectorImports(relationships: FrontendRelationship[]): strin
|
|
|
161
172
|
relationships.forEach((rel) => {
|
|
162
173
|
if (isFoundationImport(rel.directory)) {
|
|
163
174
|
// Foundation entities use named imports from the package
|
|
164
|
-
imports.add(`import { ${rel.selectorComponent} } from "${
|
|
175
|
+
imports.add(`import { ${rel.selectorComponent} } from "${FOUNDATION_COMPONENTS_PACKAGE}";`);
|
|
165
176
|
} else {
|
|
166
177
|
imports.add(`import ${rel.selectorComponent} from "${rel.importPath}";`);
|
|
167
178
|
}
|
|
@@ -26,6 +26,7 @@ export interface JsonRelationshipDefinition {
|
|
|
26
26
|
relationshipName: string; // Backend-specific, ignored in frontend
|
|
27
27
|
toNode: boolean; // Backend-specific, ignored in frontend
|
|
28
28
|
nullable: boolean;
|
|
29
|
+
fields?: JsonFieldDefinition[]; // Relationship property fields (stored on edges)
|
|
29
30
|
}
|
|
30
31
|
|
|
31
32
|
/**
|
|
@@ -58,6 +58,7 @@ export interface FrontendRelationship {
|
|
|
58
58
|
serviceImportPath: string; // Full import path for service
|
|
59
59
|
interfaceName: string; // e.g., "UserInterface"
|
|
60
60
|
modelKebab: string; // e.g., "user"
|
|
61
|
+
fields?: FrontendField[]; // Relationship property fields (stored on edges)
|
|
61
62
|
}
|
|
62
63
|
|
|
63
64
|
/**
|
|
@@ -80,6 +81,14 @@ export interface I18nKeySet {
|
|
|
80
81
|
placeholder: string;
|
|
81
82
|
error: string;
|
|
82
83
|
list: string;
|
|
84
|
+
fields?: Record<
|
|
85
|
+
string,
|
|
86
|
+
{
|
|
87
|
+
label: string;
|
|
88
|
+
placeholder: string;
|
|
89
|
+
error: string;
|
|
90
|
+
}
|
|
91
|
+
>;
|
|
83
92
|
}
|
|
84
93
|
>;
|
|
85
94
|
type: {
|
|
@@ -75,8 +75,9 @@ export function updateI18n(
|
|
|
75
75
|
messages.types = {};
|
|
76
76
|
}
|
|
77
77
|
const typesKey = Object.keys(moduleMessages.types)[0];
|
|
78
|
-
|
|
79
|
-
|
|
78
|
+
const lowercasePluralKey = names.pluralCamel.toLowerCase();
|
|
79
|
+
if (typesKey && !messages.types[lowercasePluralKey]) {
|
|
80
|
+
messages.types[lowercasePluralKey] = moduleMessages.types[typesKey];
|
|
80
81
|
}
|
|
81
82
|
|
|
82
83
|
if (dryRun) {
|
|
@@ -115,6 +115,40 @@ export abstract class AbstractApiData implements ApiDataInterface {
|
|
|
115
115
|
}) as T;
|
|
116
116
|
}
|
|
117
117
|
|
|
118
|
+
/**
|
|
119
|
+
* Read included relationship data and augment with relationship meta properties.
|
|
120
|
+
* Used for single relationships (one-to-one) that have edge properties.
|
|
121
|
+
*
|
|
122
|
+
* @param data - Hydrated JSON:API data
|
|
123
|
+
* @param type - Relationship type key (e.g., "guide")
|
|
124
|
+
* @param dataType - Module reference for rehydration
|
|
125
|
+
* @returns Related object augmented with meta properties, or undefined
|
|
126
|
+
*/
|
|
127
|
+
protected _readIncludedWithMeta<T extends ApiDataInterface, M extends Record<string, any>>(
|
|
128
|
+
data: JsonApiHydratedDataInterface,
|
|
129
|
+
type: string,
|
|
130
|
+
dataType: ApiRequestDataTypeInterface,
|
|
131
|
+
): (T & M) | undefined {
|
|
132
|
+
// Get the base related object using existing logic
|
|
133
|
+
const related = this._readIncluded<T>(data, type, dataType);
|
|
134
|
+
|
|
135
|
+
// Only works for single relationships (not arrays)
|
|
136
|
+
if (!related || Array.isArray(related)) {
|
|
137
|
+
return undefined;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Extract relationship meta from JSON:API data
|
|
141
|
+
const relationshipMeta = data.jsonApi.relationships?.[type]?.meta;
|
|
142
|
+
|
|
143
|
+
// If no meta, return the related object as-is
|
|
144
|
+
if (!relationshipMeta) {
|
|
145
|
+
return related as T & M;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Augment the object with meta properties
|
|
149
|
+
return Object.assign(related, relationshipMeta) as T & M;
|
|
150
|
+
}
|
|
151
|
+
|
|
118
152
|
dehydrate(): JsonApiHydratedDataInterface {
|
|
119
153
|
return {
|
|
120
154
|
jsonApi: this._jsonApi,
|