@famgia/omnify-core 0.0.13 → 0.0.16

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/index.cjs CHANGED
@@ -47,7 +47,7 @@ __export(index_exports, {
47
47
  createVersionStore: () => createVersionStore,
48
48
  duplicateSchemaError: () => duplicateSchemaError,
49
49
  ensureFileSchema: () => ensureFileSchema,
50
- err: () => import_omnify_types.err,
50
+ err: () => import_omnify_types3.err,
51
51
  expandProperty: () => expandProperty,
52
52
  expandSchema: () => expandSchema,
53
53
  expandSchemaProperties: () => expandSchemaProperties,
@@ -88,7 +88,7 @@ __export(index_exports, {
88
88
  missingConfigFieldError: () => missingConfigFieldError,
89
89
  missingFieldError: () => missingFieldError,
90
90
  notImplementedError: () => notImplementedError,
91
- ok: () => import_omnify_types.ok,
91
+ ok: () => import_omnify_types3.ok,
92
92
  outputWriteError: () => outputWriteError,
93
93
  parseJsonSchema: () => parseJsonSchema,
94
94
  parseYamlSchema: () => parseYamlSchema,
@@ -110,7 +110,7 @@ __export(index_exports, {
110
110
  yamlSyntaxError: () => yamlSyntaxError
111
111
  });
112
112
  module.exports = __toCommonJS(index_exports);
113
- var import_omnify_types = require("@famgia/omnify-types");
113
+ var import_omnify_types3 = require("@famgia/omnify-types");
114
114
 
115
115
  // src/errors/omnify-error.ts
116
116
  var OmnifyError = class _OmnifyError extends Error {
@@ -553,6 +553,7 @@ function notImplementedError(feature) {
553
553
  var fs = __toESM(require("fs/promises"), 1);
554
554
  var path = __toESM(require("path"), 1);
555
555
  var import_js_yaml = __toESM(require("js-yaml"), 1);
556
+ var import_omnify_types = require("@famgia/omnify-types");
556
557
  function fileNameToSchemaName(fileName) {
557
558
  const baseName = path.basename(fileName, path.extname(fileName));
558
559
  if (!baseName.includes("-") && !baseName.includes("_")) {
@@ -639,7 +640,7 @@ function buildSchemaDefinition(data) {
639
640
  if (data.kind !== void 0) {
640
641
  schema.kind = data.kind;
641
642
  }
642
- if (data.displayName !== void 0 && typeof data.displayName === "string") {
643
+ if (data.displayName !== void 0 && (0, import_omnify_types.isLocalizedString)(data.displayName)) {
643
644
  schema.displayName = data.displayName;
644
645
  }
645
646
  if (data.titleIndex !== void 0 && typeof data.titleIndex === "string") {
@@ -665,10 +666,13 @@ function buildSchemaDefinition(data) {
665
666
  }
666
667
  }
667
668
  if (data.properties !== void 0 && typeof data.properties === "object") {
668
- const properties = buildProperties(data.properties);
669
+ const { properties, invalidProperties } = buildProperties(data.properties);
669
670
  if (Object.keys(properties).length > 0) {
670
671
  schema.properties = properties;
671
672
  }
673
+ if (invalidProperties.length > 0) {
674
+ schema._invalidProperties = invalidProperties;
675
+ }
672
676
  }
673
677
  if (data.values !== void 0 && Array.isArray(data.values)) {
674
678
  schema.values = data.values;
@@ -717,12 +721,25 @@ function buildSchemaOptions(data) {
717
721
  }
718
722
  function buildProperties(data) {
719
723
  const properties = {};
724
+ const invalidProperties = [];
720
725
  for (const [name, value] of Object.entries(data)) {
721
- if (value !== void 0 && typeof value === "object" && value !== null) {
726
+ if (value === void 0 || value === null) {
727
+ continue;
728
+ }
729
+ if (typeof value === "object") {
722
730
  properties[name] = buildPropertyDefinition(value);
731
+ } else {
732
+ invalidProperties.push({
733
+ propertyName: name,
734
+ value,
735
+ expectedFormat: `Property '${name}' must be an object with 'type' field. Example:
736
+ ${name}:
737
+ type: String
738
+ nullable: true`
739
+ });
723
740
  }
724
741
  }
725
- return properties;
742
+ return { properties, invalidProperties };
726
743
  }
727
744
  var VALID_PROPERTY_FIELDS = /* @__PURE__ */ new Set([
728
745
  "type",
@@ -771,7 +788,7 @@ function buildPropertyDefinition(data) {
771
788
  if (unknownFields.length > 0) {
772
789
  prop._unknownFields = unknownFields;
773
790
  }
774
- if (data.displayName !== void 0 && typeof data.displayName === "string") {
791
+ if (data.displayName !== void 0 && (0, import_omnify_types.isLocalizedString)(data.displayName)) {
775
792
  prop.displayName = data.displayName;
776
793
  }
777
794
  if (data.nullable !== void 0 && typeof data.nullable === "boolean") {
@@ -783,7 +800,7 @@ function buildPropertyDefinition(data) {
783
800
  if (data.unique !== void 0 && typeof data.unique === "boolean") {
784
801
  prop.unique = data.unique;
785
802
  }
786
- if (data.description !== void 0 && typeof data.description === "string") {
803
+ if (data.description !== void 0 && (0, import_omnify_types.isLocalizedString)(data.description)) {
787
804
  prop.description = data.description;
788
805
  }
789
806
  if (data.renamedFrom !== void 0 && typeof data.renamedFrom === "string") {
@@ -1060,6 +1077,9 @@ async function ensureFileSchema(schemas, schemasDir, autoCreate = false) {
1060
1077
  };
1061
1078
  }
1062
1079
 
1080
+ // src/validation/validator.ts
1081
+ var import_omnify_types2 = require("@famgia/omnify-types");
1082
+
1063
1083
  // src/validation/types/base.ts
1064
1084
  var BASE_PROPERTY_FIELDS = [
1065
1085
  "type",
@@ -1153,6 +1173,30 @@ var TypeRegistry = class {
1153
1173
  getAllTypeNames() {
1154
1174
  return [...this.types.keys()];
1155
1175
  }
1176
+ /**
1177
+ * Get all valid fields across ALL types.
1178
+ * Used to determine if a field is valid for any type.
1179
+ */
1180
+ getAllValidFields() {
1181
+ const allFields = /* @__PURE__ */ new Set();
1182
+ for (const type of this.types.values()) {
1183
+ for (const field of type.validFields) {
1184
+ allFields.add(field);
1185
+ }
1186
+ }
1187
+ return allFields;
1188
+ }
1189
+ /**
1190
+ * Check if a field is valid for any registered type.
1191
+ */
1192
+ isFieldValidForAnyType(field) {
1193
+ for (const type of this.types.values()) {
1194
+ if (type.validFields.includes(field)) {
1195
+ return true;
1196
+ }
1197
+ }
1198
+ return false;
1199
+ }
1156
1200
  /**
1157
1201
  * Get count of registered types.
1158
1202
  */
@@ -2206,6 +2250,25 @@ function validateUnknownOptionsFields(schema, filePath) {
2206
2250
  }
2207
2251
  return errors;
2208
2252
  }
2253
+ function validatePropertyFormat(schema, filePath) {
2254
+ const errors = [];
2255
+ const invalidProperties = schema._invalidProperties;
2256
+ if (!invalidProperties || invalidProperties.length === 0) {
2257
+ return errors;
2258
+ }
2259
+ for (const invalid of invalidProperties) {
2260
+ const valueType = typeof invalid.value;
2261
+ const valuePreview = valueType === "string" ? `"${invalid.value}"` : String(invalid.value);
2262
+ errors.push(
2263
+ validationError(
2264
+ `Property '${invalid.propertyName}' has invalid format: got ${valueType} (${valuePreview})`,
2265
+ buildLocation7(filePath),
2266
+ invalid.expectedFormat
2267
+ )
2268
+ );
2269
+ }
2270
+ return errors;
2271
+ }
2209
2272
  function validateDbCompatibility(propertyName, property, filePath, databaseDriver) {
2210
2273
  const errors = [];
2211
2274
  const warnings = [];
@@ -2232,16 +2295,52 @@ function validateDbCompatibility(propertyName, property, filePath, databaseDrive
2232
2295
  }
2233
2296
  return { errors, warnings };
2234
2297
  }
2235
- function validateUnknownFields(propertyName, property, filePath) {
2298
+ function validateUnknownFieldsDetailed(propertyName, property, filePath, customTypeDefinitions) {
2236
2299
  const errors = [];
2237
- const unknownFields = property._unknownFields;
2238
- if (!unknownFields || unknownFields.length === 0) {
2239
- return errors;
2300
+ const warnings = [];
2301
+ if (!property.type) {
2302
+ return { errors, warnings };
2240
2303
  }
2241
- for (const field of unknownFields) {
2242
- let suggestion = `Valid property fields: type, displayName, nullable, default, unique, description, length, unsigned, precision, scale, enum, relation, target, onDelete, onUpdate, joinTable`;
2243
- if (field === "required") {
2244
- suggestion = `'required' is not a valid field. Properties are required by default.
2304
+ let validFields;
2305
+ const isBuiltIn = typeRegistry.has(property.type);
2306
+ if (isBuiltIn) {
2307
+ validFields = typeRegistry.getValidFields(property.type);
2308
+ } else {
2309
+ const customTypeDef = customTypeDefinitions?.get(property.type);
2310
+ if (customTypeDef?.validFields) {
2311
+ validFields = [...BASE_PROPERTY_FIELDS, ...customTypeDef.validFields];
2312
+ } else {
2313
+ validFields = [...BASE_PROPERTY_FIELDS];
2314
+ }
2315
+ }
2316
+ const validFieldsSet = new Set(validFields);
2317
+ for (const field of Object.keys(property)) {
2318
+ if (field.startsWith("_")) continue;
2319
+ if (validFieldsSet.has(field)) continue;
2320
+ const isValidForOtherType = typeRegistry.isFieldValidForAnyType(field);
2321
+ if (isValidForOtherType) {
2322
+ errors.push(
2323
+ validationError(
2324
+ `Property '${propertyName}' of type '${property.type}' has invalid field '${field}'`,
2325
+ buildLocation7(filePath),
2326
+ `'${field}' is not valid for type '${property.type}'. Valid fields: ${validFields.join(", ")}`
2327
+ )
2328
+ );
2329
+ } else {
2330
+ warnings.push(
2331
+ validationError(
2332
+ `Property '${propertyName}' has unknown field '${field}'`,
2333
+ buildLocation7(filePath),
2334
+ getSuggestionForUnknownField(field)
2335
+ )
2336
+ );
2337
+ }
2338
+ }
2339
+ return { errors, warnings };
2340
+ }
2341
+ function getSuggestionForUnknownField(field) {
2342
+ if (field === "required") {
2343
+ return `'required' is not a valid field. Properties are required by default.
2245
2344
  Use 'nullable: true' to make a field optional.
2246
2345
  Example:
2247
2346
  email:
@@ -2251,10 +2350,12 @@ Example:
2251
2350
  phone:
2252
2351
  type: String
2253
2352
  nullable: true # optional field`;
2254
- } else if (field === "hidden") {
2255
- suggestion = `'hidden' is not a standard field. For Laravel, hidden fields are configured in the Model class.`;
2256
- } else if (field === "foreignKey") {
2257
- suggestion = `'foreignKey' is not needed. Omnify auto-generates foreign keys from Association properties.
2353
+ }
2354
+ if (field === "hidden") {
2355
+ return `'hidden' is not a standard field. For Laravel, hidden fields are configured in the Model class.`;
2356
+ }
2357
+ if (field === "foreignKey") {
2358
+ return `'foreignKey' is not needed. Omnify auto-generates foreign keys from Association properties.
2258
2359
  The foreign key name is derived from the property name + '_id'.
2259
2360
  Example:
2260
2361
  author:
@@ -2262,16 +2363,8 @@ Example:
2262
2363
  relation: ManyToOne
2263
2364
  target: User
2264
2365
  # Creates author_id column automatically`;
2265
- }
2266
- errors.push(
2267
- validationError(
2268
- `Property '${propertyName}' has unknown field '${field}'`,
2269
- buildLocation7(filePath),
2270
- suggestion
2271
- )
2272
- );
2273
2366
  }
2274
- return errors;
2367
+ return `Valid property fields: type, displayName, nullable, default, unique, description, length, unsigned, precision, scale, enum, relation, target, onDelete, onUpdate, joinTable`;
2275
2368
  }
2276
2369
  function validatePropertyType(propertyName, property, filePath, customTypes = []) {
2277
2370
  const errors = [];
@@ -2527,6 +2620,62 @@ function validateEnumSchema(schema, filePath) {
2527
2620
  }
2528
2621
  return errors;
2529
2622
  }
2623
+ function validateLocalizedString(fieldName, value, filePath, localeConfig, context) {
2624
+ const warnings = [];
2625
+ if (value === void 0 || !(0, import_omnify_types2.isLocaleMap)(value)) {
2626
+ return { warnings };
2627
+ }
2628
+ if (!localeConfig) {
2629
+ return { warnings };
2630
+ }
2631
+ const configuredLocales = new Set(localeConfig.locales);
2632
+ const usedLocales = Object.keys(value);
2633
+ for (const locale of usedLocales) {
2634
+ if (!configuredLocales.has(locale)) {
2635
+ const contextStr = context ? ` (property '${context}')` : "";
2636
+ warnings.push(
2637
+ validationError(
2638
+ `${fieldName}${contextStr} uses locale '${locale}' which is not in configured locales`,
2639
+ buildLocation7(filePath),
2640
+ `Configured locales: ${localeConfig.locales.join(", ")}. Add '${locale}' to your locale configuration or remove it from the schema.`
2641
+ )
2642
+ );
2643
+ }
2644
+ }
2645
+ return { warnings };
2646
+ }
2647
+ function validateLocalizedStrings(schema, localeConfig) {
2648
+ const warnings = [];
2649
+ const schemaDisplayNameResult = validateLocalizedString(
2650
+ "displayName",
2651
+ schema.displayName,
2652
+ schema.filePath,
2653
+ localeConfig
2654
+ );
2655
+ warnings.push(...schemaDisplayNameResult.warnings);
2656
+ if (schema.properties) {
2657
+ for (const [propName, property] of Object.entries(schema.properties)) {
2658
+ const propDisplayNameResult = validateLocalizedString(
2659
+ "displayName",
2660
+ property.displayName,
2661
+ schema.filePath,
2662
+ localeConfig,
2663
+ propName
2664
+ );
2665
+ warnings.push(...propDisplayNameResult.warnings);
2666
+ const propDescription = property.description;
2667
+ const propDescriptionResult = validateLocalizedString(
2668
+ "description",
2669
+ propDescription,
2670
+ schema.filePath,
2671
+ localeConfig,
2672
+ propName
2673
+ );
2674
+ warnings.push(...propDescriptionResult.warnings);
2675
+ }
2676
+ }
2677
+ return warnings;
2678
+ }
2530
2679
  function validateSchema(schema, options = {}) {
2531
2680
  const errors = [];
2532
2681
  const warnings = [];
@@ -2535,6 +2684,8 @@ function validateSchema(schema, options = {}) {
2535
2684
  errors.push(...unknownSchemaFieldErrors);
2536
2685
  const unknownOptionsErrors = validateUnknownOptionsFields(schema, schema.filePath);
2537
2686
  errors.push(...unknownOptionsErrors);
2687
+ const propertyFormatErrors = validatePropertyFormat(schema, schema.filePath);
2688
+ errors.push(...propertyFormatErrors);
2538
2689
  const propErrors = validateProperties(schema, schema.filePath, customTypes);
2539
2690
  errors.push(...propErrors);
2540
2691
  const optErrors = validateOptions(schema, schema.filePath);
@@ -2556,13 +2707,21 @@ function validateSchema(schema, options = {}) {
2556
2707
  }
2557
2708
  if (schema.properties) {
2558
2709
  for (const [name, property] of Object.entries(schema.properties)) {
2559
- const unknownFieldErrors = validateUnknownFields(name, property, schema.filePath);
2560
- errors.push(...unknownFieldErrors);
2710
+ const unknownFieldResult = validateUnknownFieldsDetailed(
2711
+ name,
2712
+ property,
2713
+ schema.filePath,
2714
+ options.customTypeDefinitions
2715
+ );
2716
+ errors.push(...unknownFieldResult.errors);
2717
+ warnings.push(...unknownFieldResult.warnings);
2561
2718
  const dbCompat = validateDbCompatibility(name, property, schema.filePath, options.databaseDriver);
2562
2719
  errors.push(...dbCompat.errors);
2563
2720
  warnings.push(...dbCompat.warnings);
2564
2721
  }
2565
2722
  }
2723
+ const localizedStringWarnings = validateLocalizedStrings(schema, options.localeConfig);
2724
+ warnings.push(...localizedStringWarnings);
2566
2725
  return {
2567
2726
  schemaName: schema.name,
2568
2727
  valid: errors.length === 0,
@@ -2652,8 +2811,10 @@ var GeneratorRunner = class {
2652
2811
  }
2653
2812
  /**
2654
2813
  * Run all generators in topological order.
2814
+ * @param schemas - The schemas to generate from
2815
+ * @param changes - Schema changes detected from lock file comparison
2655
2816
  */
2656
- async runAll(schemas) {
2817
+ async runAll(schemas, changes) {
2657
2818
  const errors = [];
2658
2819
  const outputs = [];
2659
2820
  const outputsByGenerator = /* @__PURE__ */ new Map();
@@ -2681,10 +2842,12 @@ var GeneratorRunner = class {
2681
2842
  this.options.logger.debug(`Running generator: ${name}`);
2682
2843
  const ctx = {
2683
2844
  schemas,
2845
+ changes,
2684
2846
  pluginConfig,
2685
2847
  cwd: this.options.cwd,
2686
2848
  logger: this.options.logger,
2687
- previousOutputs
2849
+ previousOutputs,
2850
+ customTypes: this.options.customTypes ?? /* @__PURE__ */ new Map()
2688
2851
  };
2689
2852
  const result = await definition.generate(ctx);
2690
2853
  const generatorOutputs = Array.isArray(result) ? result : [result];
@@ -3036,17 +3199,24 @@ var PluginManager = class {
3036
3199
  /**
3037
3200
  * Runs all registered generators in topological order.
3038
3201
  * @param schemas - The schemas to generate from
3202
+ * @param changes - Schema changes detected from lock file comparison
3039
3203
  * @returns Result with all generated outputs
3040
3204
  */
3041
- async runGenerators(schemas) {
3205
+ async runGenerators(schemas, changes) {
3206
+ const customTypes = /* @__PURE__ */ new Map();
3207
+ for (const [name, registeredType] of this._types) {
3208
+ const { pluginName, pluginVersion, ...typeDefinition } = registeredType;
3209
+ customTypes.set(name, typeDefinition);
3210
+ }
3042
3211
  const runner = new GeneratorRunner({
3043
3212
  cwd: this._cwd,
3044
- logger: this._logger
3213
+ logger: this._logger,
3214
+ customTypes
3045
3215
  });
3046
3216
  for (const gen of this._generators.values()) {
3047
3217
  runner.register(gen);
3048
3218
  }
3049
- return runner.runAll(schemas);
3219
+ return runner.runAll(schemas, changes);
3050
3220
  }
3051
3221
  /**
3052
3222
  * Clears all registered plugins, types, and generators.