@postxl/schema 1.2.0 → 1.3.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.
@@ -4,6 +4,10 @@ export declare const standardFieldName_id: "id" & import("zod").$brand<"PXL.Fiel
4
4
  export declare const standardFieldName_createdAt: string & import("zod").$brand<"PXL.FieldName">;
5
5
  export declare const standardFieldName_updatedAt: string & import("zod").$brand<"PXL.FieldName">;
6
6
  export declare const standardFieldName_name: string & import("zod").$brand<"PXL.FieldName">;
7
+ export declare const standardFieldName_label: string & import("zod").$brand<"PXL.FieldName">;
8
+ export declare const standardFieldName_key: string & import("zod").$brand<"PXL.FieldName">;
9
+ export declare const standardFieldName_excelKey: string & import("zod").$brand<"PXL.FieldName">;
10
+ export declare const standardFieldName_slug: string & import("zod").$brand<"PXL.FieldName">;
7
11
  export declare const standardFieldNames: (string & import("zod").$brand<"PXL.FieldName">)[];
8
12
  export declare const standardField_id: FieldJSONInput;
9
13
  export declare const standardField_createdAt: FieldJSONInput;
@@ -33,12 +33,17 @@ var __importStar = (this && this.__importStar) || (function () {
33
33
  };
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
- exports.standardFields = exports.standardField_name = exports.standardField_updatedAt = exports.standardField_createdAt = exports.standardField_id = exports.standardFieldNames = exports.standardFieldName_name = exports.standardFieldName_updatedAt = exports.standardFieldName_createdAt = exports.standardFieldName_id = void 0;
36
+ exports.standardFields = exports.standardField_name = exports.standardField_updatedAt = exports.standardField_createdAt = exports.standardField_id = exports.standardFieldNames = exports.standardFieldName_slug = exports.standardFieldName_excelKey = exports.standardFieldName_key = exports.standardFieldName_label = exports.standardFieldName_name = exports.standardFieldName_updatedAt = exports.standardFieldName_createdAt = exports.standardFieldName_id = void 0;
37
37
  const Branded = __importStar(require("./shared/brands"));
38
38
  exports.standardFieldName_id = Branded.toFieldIdName('id');
39
39
  exports.standardFieldName_createdAt = Branded.toFieldName('createdAt');
40
40
  exports.standardFieldName_updatedAt = Branded.toFieldName('updatedAt');
41
41
  exports.standardFieldName_name = Branded.toFieldName('name');
42
+ exports.standardFieldName_label = Branded.toFieldName('label');
43
+ // Field names used as fallback for keyField resolution (in order: key, excelKey, slug, id)
44
+ exports.standardFieldName_key = Branded.toFieldName('key');
45
+ exports.standardFieldName_excelKey = Branded.toFieldName('excelKey');
46
+ exports.standardFieldName_slug = Branded.toFieldName('slug');
42
47
  exports.standardFieldNames = [
43
48
  exports.standardFieldName_id,
44
49
  exports.standardFieldName_createdAt,
@@ -13,6 +13,7 @@ export declare const zModelJSON: z.ZodPipe<z.ZodTransform<unknown, unknown>, z.Z
13
13
  standardFields: z.ZodOptional<z.ZodArray<z.ZodString>>;
14
14
  defaultField: z.ZodOptional<z.ZodString>;
15
15
  labelField: z.ZodOptional<z.ZodString>;
16
+ keyField: z.ZodOptional<z.ZodString>;
16
17
  indexes: z.ZodOptional<z.ZodArray<z.ZodArray<z.core.$ZodBranded<z.ZodString, "PXL.FieldName", "out">>>>;
17
18
  seed: z.ZodOptional<z.ZodArray<z.ZodAny>>;
18
19
  source: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
@@ -80,6 +80,12 @@ Example use case: default language, root region in region tree.`),
80
80
  .string()
81
81
  .optional().describe(`The name of the field that is used in the UI as the label of the entity.
82
82
  The label field must be a string field. If not provided, the \`name\` field is used as the label.`),
83
+ keyField: zod_1.z
84
+ //
85
+ .string()
86
+ .optional().describe(`The name of the field that is used as the key (unique identifier) for the entity.
87
+ The key field must be a scalar string field. If not provided, the fallback order is: \`key\`, \`excelKey\`, \`slug\`, then \`id\`.
88
+ The key field is used in navigation, Excel import, and other places where a human-readable identifier is needed.`),
83
89
  indexes: zod_1.z
84
90
  //
85
91
  .array(zod_1.z.array(brands_1.zFieldName).min(2, { error: 'Index must have at least 2 fields' }))
@@ -11,6 +11,7 @@ export declare function modelJSONTransformer(input: ModelJSON, ctx: z.Refinement
11
11
  databaseName: string & z.core.$brand<"PXL.DatabaseModelName">;
12
12
  defaultFieldName: (string & z.core.$brand<"PXL.FieldName">) | undefined;
13
13
  labelFieldName: (string & z.core.$brand<"PXL.FieldName">) | undefined;
14
+ keyFieldName: (string & z.core.$brand<"PXL.FieldName">) | undefined;
14
15
  isReadonly: boolean;
15
16
  excelName: string;
16
17
  description: string;
@@ -43,16 +44,18 @@ declare function addDefaults({ description, fields, standardFields, seed, faker,
43
44
  schema?: string | undefined;
44
45
  defaultField?: string | undefined;
45
46
  labelField?: string | undefined;
47
+ keyField?: string | undefined;
46
48
  source?: Record<string, unknown> | undefined;
47
49
  };
48
50
  type WithDefaults = ReturnType<typeof addDefaults>;
49
- declare function brandModel({ name, schema, databaseName, standardFields, defaultField, labelField, ...input }: WithDefaults): {
51
+ declare function brandModel({ name, schema, databaseName, standardFields, defaultField, labelField, keyField, ...input }: WithDefaults): {
50
52
  name: string & z.core.$brand<"PXL.ModelName">;
51
53
  schema: (string & z.core.$brand<"PXL.DatabaseSchemaName">) | undefined;
52
54
  databaseName: string & z.core.$brand<"PXL.DatabaseModelName">;
53
55
  standardFields: (string & z.core.$brand<"PXL.FieldName">)[];
54
56
  defaultFieldName: (string & z.core.$brand<"PXL.FieldName">) | undefined;
55
57
  labelFieldName: (string & z.core.$brand<"PXL.FieldName">) | undefined;
58
+ keyFieldName: (string & z.core.$brand<"PXL.FieldName">) | undefined;
56
59
  isReadonly: boolean;
57
60
  excelName: string;
58
61
  description: string;
@@ -80,6 +83,7 @@ declare function combineFields({ fields, standardFields, ...model }: BrandedMode
80
83
  databaseName: string & z.core.$brand<"PXL.DatabaseModelName">;
81
84
  defaultFieldName: (string & z.core.$brand<"PXL.FieldName">) | undefined;
82
85
  labelFieldName: (string & z.core.$brand<"PXL.FieldName">) | undefined;
86
+ keyFieldName: (string & z.core.$brand<"PXL.FieldName">) | undefined;
83
87
  isReadonly: boolean;
84
88
  excelName: string;
85
89
  description: string;
@@ -65,7 +65,7 @@ function addDefaults({ description, fields, standardFields, seed, faker, ...inpu
65
65
  },
66
66
  };
67
67
  }
68
- function brandModel({ name, schema, databaseName, standardFields, defaultField, labelField, ...input }) {
68
+ function brandModel({ name, schema, databaseName, standardFields, defaultField, labelField, keyField, ...input }) {
69
69
  return {
70
70
  ...input,
71
71
  name: (0, model_brands_1.toModelName)(name),
@@ -74,6 +74,7 @@ function brandModel({ name, schema, databaseName, standardFields, defaultField,
74
74
  standardFields: standardFields.map((f) => Field.toFieldName(f)),
75
75
  defaultFieldName: defaultField ? Field.toFieldName(defaultField) : undefined,
76
76
  labelFieldName: labelField ? Field.toFieldName(labelField) : undefined,
77
+ keyFieldName: keyField ? Field.toFieldName(keyField) : undefined,
77
78
  };
78
79
  }
79
80
  function combineFields({ fields, standardFields, ...model }, ctx) {
@@ -13,6 +13,13 @@ export type Model = {
13
13
  idField: Field.FieldId;
14
14
  defaultField: Field.FieldDatabaseNative | undefined;
15
15
  labelField: Field.FieldDatabaseNative;
16
+ /**
17
+ * The field used as the key/unique identifier for the entity.
18
+ * Used in navigation, Excel import, and other places where a human-readable identifier is needed.
19
+ * Must be a scalar string field. Resolved from: explicit keyField, or fallback to key, excelKey, slug, id (in that order).
20
+ * Always defined since id field is always present.
21
+ */
22
+ keyField: Field.FieldDatabaseNative;
16
23
  indexes: ModelIndex[];
17
24
  seed: Seed[];
18
25
  /**
@@ -23,6 +23,7 @@ export declare const zProjectSchemaJSON: z.ZodPipe<z.ZodTransform<unknown, unkno
23
23
  standardFields: z.ZodOptional<z.ZodArray<z.ZodString>>;
24
24
  defaultField: z.ZodOptional<z.ZodString>;
25
25
  labelField: z.ZodOptional<z.ZodString>;
26
+ keyField: z.ZodOptional<z.ZodString>;
26
27
  indexes: z.ZodOptional<z.ZodArray<z.ZodArray<z.core.$ZodBranded<z.ZodString, "PXL.FieldName", "out">>>>;
27
28
  seed: z.ZodOptional<z.ZodArray<z.ZodAny>>;
28
29
  source: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
@@ -28,6 +28,7 @@ export declare const zProjectSchema: z.ZodPipe<z.ZodPipe<z.ZodTransform<unknown,
28
28
  standardFields: z.ZodOptional<z.ZodArray<z.ZodString>>;
29
29
  defaultField: z.ZodOptional<z.ZodString>;
30
30
  labelField: z.ZodOptional<z.ZodString>;
31
+ keyField: z.ZodOptional<z.ZodString>;
31
32
  indexes: z.ZodOptional<z.ZodArray<z.ZodArray<z.core.$ZodBranded<z.ZodString, "PXL.FieldName", "out">>>>;
32
33
  seed: z.ZodOptional<z.ZodArray<z.ZodAny>>;
33
34
  source: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
@@ -73,6 +74,7 @@ export declare const zProjectSchema: z.ZodPipe<z.ZodPipe<z.ZodTransform<unknown,
73
74
  standardFields?: string[] | undefined;
74
75
  defaultField?: string | undefined;
75
76
  labelField?: string | undefined;
77
+ keyField?: string | undefined;
76
78
  indexes?: (string & z.core.$brand<"PXL.FieldName">)[][] | undefined;
77
79
  seed?: any[] | undefined;
78
80
  source?: Record<string, unknown> | undefined;
@@ -270,13 +270,15 @@ function transformModel(model, projectSchema, ctx) {
270
270
  const idField = extractIdField(fields, model, ctx);
271
271
  const defaultField = extractDefaultField(model, fields, ctx);
272
272
  const labelField = extractLabelField(model, fields, ctx);
273
- const { defaultFieldName, labelFieldName, ...remainingModel } = model;
273
+ const keyField = extractKeyField(model, fields, idField, ctx);
274
+ const { defaultFieldName, labelFieldName, keyFieldName, ...remainingModel } = model;
274
275
  return {
275
276
  ...remainingModel,
276
277
  databaseSchema: model.schema ?? projectSchema.defaultDatabaseSchema,
277
278
  fields,
278
279
  defaultField,
279
280
  labelField,
281
+ keyField,
280
282
  idField,
281
283
  };
282
284
  }
@@ -424,12 +426,14 @@ function extractLabelField({ labelFieldName, ...model }, fields, ctx) {
424
426
  return labelField;
425
427
  }
426
428
  function extractLabelFieldFromNameOrIdField({ name }, fields, ctx) {
427
- // If no label field is provided, ensure name field exists and use it
428
- const fallbackField = fields.get(Field.standardFieldName_name) ?? fields.get(Field.standardFieldName_id);
429
+ // If no label field is provided, try name field first, then label field, then id field
430
+ const fallbackField = fields.get(Field.standardFieldName_name) ??
431
+ fields.get(Field.standardFieldName_label) ??
432
+ fields.get(Field.standardFieldName_id);
429
433
  if (fallbackField === undefined) {
430
434
  ctx.addIssue({
431
435
  code: zod_1.z.ZodIssueCode.custom,
432
- message: `Model ${name} neither provides a \`${Field.standardFieldName_name}\` field nor has \`labelField\` property!`,
436
+ message: `Model ${name} neither provides a \`${Field.standardFieldName_name}\` or \`${Field.standardFieldName_label}\` field nor has \`labelField\` property!`,
433
437
  });
434
438
  return zod_1.z.NEVER;
435
439
  }
@@ -442,3 +446,53 @@ function extractLabelFieldFromNameOrIdField({ name }, fields, ctx) {
442
446
  }
443
447
  return fallbackField;
444
448
  }
449
+ function extractKeyField({ keyFieldName, ...model }, fields, idField, ctx) {
450
+ if (keyFieldName === undefined) {
451
+ return extractKeyFieldFromFallbacks(fields, idField);
452
+ }
453
+ const keyField = fields.get(keyFieldName);
454
+ if (keyField === undefined) {
455
+ ctx.addIssue({
456
+ code: zod_1.z.ZodIssueCode.custom,
457
+ message: `Key field ${keyFieldName} is not defined in model ${model.name}!`,
458
+ });
459
+ return zod_1.z.NEVER;
460
+ }
461
+ if (keyField.kind !== 'scalar' && keyField.kind !== 'id') {
462
+ ctx.addIssue({
463
+ code: zod_1.z.ZodIssueCode.custom,
464
+ message: `Key field ${keyFieldName} in model ${model.name} must be a scalar string field, but instead is of type ${Field.getFieldType(keyField)}!`,
465
+ });
466
+ return zod_1.z.NEVER;
467
+ }
468
+ const type = keyField.kind === 'scalar' ? keyField.type : keyField.unbrandedTypeName;
469
+ if (type !== 'string') {
470
+ ctx.addIssue({
471
+ code: zod_1.z.ZodIssueCode.custom,
472
+ message: `Key field ${keyFieldName} in model ${model.name} must be of type \`string\`, but instead is of type ${type}!`,
473
+ });
474
+ return zod_1.z.NEVER;
475
+ }
476
+ return keyField;
477
+ }
478
+ function extractKeyFieldFromFallbacks(fields, idField) {
479
+ // Fallback order: key, excelKey, slug, id
480
+ // Only consider string scalar fields for the fallbacks (except id which is always string)
481
+ const keyField = fields.get(Field.standardFieldName_key);
482
+ if (keyField !== undefined && isStringScalarField(keyField)) {
483
+ return keyField;
484
+ }
485
+ const excelKeyField = fields.get(Field.standardFieldName_excelKey);
486
+ if (excelKeyField !== undefined && isStringScalarField(excelKeyField)) {
487
+ return excelKeyField;
488
+ }
489
+ const slugField = fields.get(Field.standardFieldName_slug);
490
+ if (slugField !== undefined && isStringScalarField(slugField)) {
491
+ return slugField;
492
+ }
493
+ // id field is always present and is always a string
494
+ return idField;
495
+ }
496
+ function isStringScalarField(field) {
497
+ return field.kind === 'scalar' && field.type === 'string';
498
+ }
@@ -136,7 +136,7 @@
136
136
  ],
137
137
  "standardFields": ["id", "createdAt", "updatedAt"],
138
138
  "labelField": "title",
139
- "standardActions": ["create", "update", "upsert", "delete", "clone", "cloneMany"],
139
+ "standardActions": ["create", "update", "updateField", "upsert", "delete", "clone", "cloneMany"],
140
140
  "actions": {
141
141
  "publish": "Publish a post",
142
142
  "unpublish": "Unpublish a post"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@postxl/schema",
3
- "version": "1.2.0",
3
+ "version": "1.3.0",
4
4
  "description": "Decoders for PXL Schema definitions and validation for PXL code generation framework",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.js",
@@ -36,7 +36,7 @@
36
36
  "directory": "packages/schema"
37
37
  },
38
38
  "dependencies": {
39
- "@postxl/utils": "^1.2.0"
39
+ "@postxl/utils": "^1.3.1"
40
40
  },
41
41
  "devDependencies": {},
42
42
  "wallaby": {