@famgia/omnify-laravel 2.0.21 → 2.0.22

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
@@ -483,7 +483,8 @@ function expandCompoundType(propName, property, customTypes, options = {}) {
483
483
  return expanded;
484
484
  }
485
485
  function schemaToBlueprint(schema, allSchemas, options = {}) {
486
- const { customTypes = /* @__PURE__ */ new Map(), pluginEnums = /* @__PURE__ */ new Map(), locale } = options;
486
+ const { customTypes = /* @__PURE__ */ new Map(), pluginEnums = /* @__PURE__ */ new Map(), locale, excludeFKs } = options;
487
+ const schemaName = options.schemaName ?? schema.name;
487
488
  const compoundOptions = { locale, pluginEnums };
488
489
  const tableName = schema.options?.tableName ?? toTableName(schema.name);
489
490
  const columns = [];
@@ -553,7 +554,10 @@ function schemaToBlueprint(schema, allSchemas, options = {}) {
553
554
  if (!explicitColumnNames.has(fkResult.column.name)) {
554
555
  columns.push(fkResult.column);
555
556
  }
556
- foreignKeys.push(fkResult.foreignKey);
557
+ const fkKey = `${schemaName}.${propName}`;
558
+ if (!excludeFKs?.has(fkKey)) {
559
+ foreignKeys.push(fkResult.foreignKey);
560
+ }
557
561
  indexes.push(fkResult.index);
558
562
  }
559
563
  const polyResult = generatePolymorphicColumns(propName, property, allSchemas);
@@ -1220,6 +1224,143 @@ function extractDependencies(schema) {
1220
1224
  }
1221
1225
  return deps;
1222
1226
  }
1227
+ function detectCircularDependencies(schemas) {
1228
+ const circularFKs = /* @__PURE__ */ new Set();
1229
+ const graph = /* @__PURE__ */ new Map();
1230
+ for (const schema of Object.values(schemas)) {
1231
+ if (schema.kind === "enum" || !schema.properties) continue;
1232
+ const refs = /* @__PURE__ */ new Map();
1233
+ for (const [propName, property] of Object.entries(schema.properties)) {
1234
+ if (property.type !== "Association") continue;
1235
+ const assocProp = property;
1236
+ if ((assocProp.relation === "ManyToOne" || assocProp.relation === "OneToOne") && !assocProp.mappedBy && assocProp.target && assocProp.target !== schema.name) {
1237
+ refs.set(assocProp.target, propName);
1238
+ }
1239
+ }
1240
+ if (refs.size > 0) {
1241
+ graph.set(schema.name, refs);
1242
+ }
1243
+ }
1244
+ for (const [schemaA, refsA] of graph) {
1245
+ for (const [targetB, propNameA] of refsA) {
1246
+ if (targetB === schemaA) continue;
1247
+ const refsB = graph.get(targetB);
1248
+ if (refsB && refsB.has(schemaA)) {
1249
+ if (schemaA > targetB) {
1250
+ circularFKs.add(`${schemaA}.${propNameA}`);
1251
+ } else {
1252
+ const propNameB = refsB.get(schemaA);
1253
+ circularFKs.add(`${targetB}.${propNameB}`);
1254
+ }
1255
+ }
1256
+ }
1257
+ }
1258
+ return circularFKs;
1259
+ }
1260
+ function extractDeferredFKs(schemas, circularFKs) {
1261
+ const deferred = [];
1262
+ for (const schema of Object.values(schemas)) {
1263
+ if (schema.kind === "enum" || !schema.properties) continue;
1264
+ const tableName = schema.options?.tableName ?? toTableName(schema.name);
1265
+ for (const [propName, property] of Object.entries(schema.properties)) {
1266
+ const key = `${schema.name}.${propName}`;
1267
+ if (!circularFKs.has(key)) continue;
1268
+ const assocProp = property;
1269
+ if (!assocProp.target) continue;
1270
+ const targetSchema = schemas[assocProp.target];
1271
+ const targetTable = targetSchema?.options?.tableName ?? toTableName(assocProp.target);
1272
+ const columnName = toColumnName(propName) + "_id";
1273
+ deferred.push({
1274
+ tableName,
1275
+ columnName,
1276
+ targetTable,
1277
+ onDelete: assocProp.onDelete,
1278
+ onUpdate: assocProp.onUpdate
1279
+ });
1280
+ }
1281
+ }
1282
+ return deferred;
1283
+ }
1284
+ function generateDeferredFKMigration(deferredFKs, options = {}) {
1285
+ if (deferredFKs.length === 0) return null;
1286
+ const timestamp = options.timestamp ?? generateTimestamp();
1287
+ const fileName = `${timestamp}_add_circular_foreign_keys.php`;
1288
+ const connection = options.connection ? `
1289
+ protected $connection = '${options.connection}';
1290
+ ` : "";
1291
+ const byTable = /* @__PURE__ */ new Map();
1292
+ for (const fk of deferredFKs) {
1293
+ const existing = byTable.get(fk.tableName) ?? [];
1294
+ existing.push(fk);
1295
+ byTable.set(fk.tableName, existing);
1296
+ }
1297
+ const upStatements = [];
1298
+ const downStatements = [];
1299
+ for (const [tableName, fks] of byTable) {
1300
+ const fkLines = fks.map((fk) => {
1301
+ let line = ` $table->foreign('${fk.columnName}')->references('id')->on('${fk.targetTable}')`;
1302
+ if (fk.onDelete) line += `->onDelete('${fk.onDelete}')`;
1303
+ if (fk.onUpdate) line += `->onUpdate('${fk.onUpdate}')`;
1304
+ return line + ";";
1305
+ });
1306
+ upStatements.push(` Schema::table('${tableName}', function (Blueprint $table) {
1307
+ ${fkLines.join("\n")}
1308
+ });`);
1309
+ const dropLines = fks.map(
1310
+ (fk) => ` $table->dropForeign(['${fk.columnName}']);`
1311
+ );
1312
+ downStatements.push(` Schema::table('${tableName}', function (Blueprint $table) {
1313
+ ${dropLines.join("\n")}
1314
+ });`);
1315
+ }
1316
+ const content = `<?php
1317
+
1318
+ /**
1319
+ * \u26A0\uFE0F DO NOT EDIT THIS FILE! \u26A0\uFE0F
1320
+ * \u3053\u306E\u30D5\u30A1\u30A4\u30EB\u3092\u7DE8\u96C6\u3057\u306A\u3044\u3067\u304F\u3060\u3055\u3044\uFF01
1321
+ * KH\xD4NG \u0110\u01AF\u1EE2C S\u1EECA FILE N\xC0Y!
1322
+ *
1323
+ * This file is AUTO-GENERATED by Omnify.
1324
+ * Any manual changes will be OVERWRITTEN on next generation.
1325
+ *
1326
+ * This migration adds foreign key constraints for circular dependencies.
1327
+ * These constraints are deferred to run after all tables are created.
1328
+ *
1329
+ * @generated by @famgia/omnify-laravel
1330
+ */
1331
+
1332
+ use Illuminate\\Database\\Migrations\\Migration;
1333
+ use Illuminate\\Database\\Schema\\Blueprint;
1334
+ use Illuminate\\Support\\Facades\\Schema;
1335
+
1336
+ return new class extends Migration
1337
+ {${connection}
1338
+ /**
1339
+ * Run the migrations.
1340
+ */
1341
+ public function up(): void
1342
+ {
1343
+ ${upStatements.join("\n\n")}
1344
+ }
1345
+
1346
+ /**
1347
+ * Reverse the migrations.
1348
+ */
1349
+ public function down(): void
1350
+ {
1351
+ ${downStatements.join("\n\n")}
1352
+ }
1353
+ };
1354
+ `;
1355
+ const tables = [...byTable.keys()];
1356
+ return {
1357
+ fileName,
1358
+ className: "AddCircularForeignKeys",
1359
+ content,
1360
+ tables,
1361
+ type: "alter"
1362
+ };
1363
+ }
1223
1364
  function topologicalSort(schemas) {
1224
1365
  const schemaList = Object.values(schemas).filter((s) => s.kind !== "enum");
1225
1366
  const sorted = [];
@@ -1253,6 +1394,7 @@ function generateMigrations(schemas, options = {}) {
1253
1394
  const migrations = [];
1254
1395
  const pivotTablesGenerated = /* @__PURE__ */ new Set();
1255
1396
  let timestampOffset = 0;
1397
+ const circularFKs = detectCircularDependencies(schemas);
1256
1398
  const sortedSchemas = topologicalSort(schemas);
1257
1399
  const baseTimestamp = options.timestamp ?? generateTimestamp();
1258
1400
  for (const schema of sortedSchemas) {
@@ -1261,7 +1403,9 @@ function generateMigrations(schemas, options = {}) {
1261
1403
  const blueprint = schemaToBlueprint(schema, schemas, {
1262
1404
  customTypes: options.customTypes,
1263
1405
  pluginEnums: options.pluginEnums,
1264
- locale: options.locale
1406
+ locale: options.locale,
1407
+ excludeFKs: circularFKs,
1408
+ schemaName: schema.name
1265
1409
  });
1266
1410
  const migration = generateCreateMigration(blueprint, {
1267
1411
  ...options,
@@ -1301,6 +1445,19 @@ function generateMigrations(schemas, options = {}) {
1301
1445
  });
1302
1446
  }
1303
1447
  }
1448
+ if (circularFKs.size > 0) {
1449
+ const deferredFKs = extractDeferredFKs(schemas, circularFKs);
1450
+ if (deferredFKs.length > 0) {
1451
+ const offsetTimestamp = incrementTimestamp(baseTimestamp, timestampOffset);
1452
+ const deferredMigration = generateDeferredFKMigration(deferredFKs, {
1453
+ ...options,
1454
+ timestamp: offsetTimestamp
1455
+ });
1456
+ if (deferredMigration) {
1457
+ migrations.push(deferredMigration);
1458
+ }
1459
+ }
1460
+ }
1304
1461
  return migrations;
1305
1462
  }
1306
1463
  function incrementTimestamp(timestamp, seconds) {
@@ -1390,8 +1547,16 @@ function generateTimestamp2() {
1390
1547
  const seconds = String(now.getSeconds()).padStart(2, "0");
1391
1548
  return `${year}_${month}_${day}_${hours}${minutes}${seconds}`;
1392
1549
  }
1550
+ function shouldSkipAssociationColumn(prop) {
1551
+ if (prop.type !== "Association") return false;
1552
+ if (isAssociationWithFkColumn(prop)) return false;
1553
+ return true;
1554
+ }
1393
1555
  function formatAddColumn(columnName, prop) {
1394
1556
  const lines = [];
1557
+ if (shouldSkipAssociationColumn(prop)) {
1558
+ return lines;
1559
+ }
1395
1560
  if (isAssociationWithFkColumn(prop)) {
1396
1561
  const fkColumn = getAssociationFkColumnName(columnName);
1397
1562
  let code2 = `$table->unsignedBigInteger('${fkColumn}')`;
@@ -1431,6 +1596,9 @@ function formatAddColumn(columnName, prop) {
1431
1596
  }
1432
1597
  function formatDropColumn(columnName, prop) {
1433
1598
  const lines = [];
1599
+ if (prop && shouldSkipAssociationColumn(prop)) {
1600
+ return lines;
1601
+ }
1434
1602
  if (prop && isAssociationWithFkColumn(prop)) {
1435
1603
  const fkColumn = getAssociationFkColumnName(columnName);
1436
1604
  lines.push(`$table->dropForeign(['${fkColumn}']);`);
@@ -3080,9 +3248,26 @@ ${providerLine}
3080
3248
  }
3081
3249
 
3082
3250
  // src/factory/generator.ts
3251
+ function deriveFactoryNamespace(modelNamespace) {
3252
+ if (modelNamespace === "App\\Models") {
3253
+ return "Database\\Factories";
3254
+ }
3255
+ if (modelNamespace.endsWith("\\Models")) {
3256
+ const base = modelNamespace.slice(0, -7);
3257
+ return `${base}\\Database\\Factories`;
3258
+ }
3259
+ const parts = modelNamespace.split("\\");
3260
+ if (parts.length > 1 && parts[parts.length - 1] === "Models") {
3261
+ parts.pop();
3262
+ return [...parts, "Database", "Factories"].join("\\");
3263
+ }
3264
+ return "Database\\Factories";
3265
+ }
3083
3266
  function resolveOptions2(options) {
3267
+ const modelNamespace = options?.modelNamespace ?? "App\\Models";
3084
3268
  return {
3085
- modelNamespace: options?.modelNamespace ?? "App\\Models",
3269
+ modelNamespace,
3270
+ factoryNamespace: options?.factoryNamespace ?? deriveFactoryNamespace(modelNamespace),
3086
3271
  factoryPath: options?.factoryPath ?? "database/factories",
3087
3272
  fakerLocale: options?.fakerLocale ?? "en_US",
3088
3273
  customTypes: options?.customTypes ?? /* @__PURE__ */ new Map(),
@@ -3095,16 +3280,18 @@ function resolveSchemaOptions2(schema, globalOptions) {
3095
3280
  return globalOptions;
3096
3281
  }
3097
3282
  const base = pkgOutput.base;
3283
+ const modelNamespace = pkgOutput.modelsNamespace;
3098
3284
  return {
3099
3285
  ...globalOptions,
3100
- modelNamespace: pkgOutput.modelsNamespace,
3286
+ modelNamespace,
3287
+ factoryNamespace: pkgOutput.factoriesNamespace ?? deriveFactoryNamespace(modelNamespace),
3101
3288
  factoryPath: `${base}/${pkgOutput.factoriesPath ?? "database/factories"}`
3102
3289
  };
3103
3290
  }
3104
3291
  function getStubContent2() {
3105
3292
  return `<?php
3106
3293
 
3107
- namespace Database\\Factories;
3294
+ namespace {{FACTORY_NAMESPACE}};
3108
3295
 
3109
3296
  use {{MODEL_NAMESPACE}}\\{{MODEL_NAME}};
3110
3297
  use Illuminate\\Database\\Eloquent\\Factories\\Factory;
@@ -3401,6 +3588,7 @@ function generateFactory(schema, schemas, options, stubContent) {
3401
3588
  }
3402
3589
  }
3403
3590
  let content = stubContent;
3591
+ content = content.replace(/\{\{FACTORY_NAMESPACE\}\}/g, options.factoryNamespace);
3404
3592
  content = content.replace(/\{\{MODEL_NAMESPACE\}\}/g, options.modelNamespace);
3405
3593
  content = content.replace(/\{\{MODEL_NAME\}\}/g, modelName);
3406
3594
  const uniqueImports = [...new Set(imports)];
@@ -5026,9 +5214,10 @@ function laravelPlugin(options) {
5026
5214
  name: "laravel-migrations",
5027
5215
  description: "Generate Laravel migration files",
5028
5216
  generate: async (ctx) => {
5217
+ const baseTimestamp = resolved.timestamp ?? generateTimestamp();
5029
5218
  const migrationOptions = {
5030
5219
  connection: resolved.connection,
5031
- timestamp: resolved.timestamp,
5220
+ timestamp: baseTimestamp,
5032
5221
  customTypes: ctx.customTypes,
5033
5222
  pluginEnums: ctx.pluginEnums
5034
5223
  };
@@ -5050,6 +5239,7 @@ function laravelPlugin(options) {
5050
5239
  if (ctx.changes.length === 0) {
5051
5240
  return outputs;
5052
5241
  }
5242
+ let timestampOffset = 0;
5053
5243
  const addedSchemaNames = new Set(
5054
5244
  ctx.changes.filter((c) => c.changeType === "added").map((c) => c.schemaName)
5055
5245
  );
@@ -5074,15 +5264,17 @@ function laravelPlugin(options) {
5074
5264
  schemaName: migration.schemaName
5075
5265
  }
5076
5266
  });
5267
+ timestampOffset++;
5077
5268
  }
5078
5269
  }
5079
5270
  const alterChanges = ctx.changes.filter(
5080
5271
  (c) => c.changeType === "modified" || c.changeType === "removed"
5081
5272
  );
5082
5273
  if (alterChanges.length > 0) {
5274
+ const alterTimestamp = timestampOffset > 0 ? incrementTimestamp(baseTimestamp, timestampOffset) : baseTimestamp;
5083
5275
  const alterMigrations = generateMigrationsFromChanges(
5084
5276
  alterChanges,
5085
- migrationOptions
5277
+ { ...migrationOptions, timestamp: alterTimestamp }
5086
5278
  );
5087
5279
  for (const migration of alterMigrations) {
5088
5280
  outputs.push({