arkormx 0.2.2 → 0.2.4

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.d.mts CHANGED
@@ -27,11 +27,22 @@ interface SchemaIndex {
27
27
  columns: string[];
28
28
  name?: string;
29
29
  }
30
+ type SchemaForeignKeyAction = 'cascade' | 'restrict' | 'setNull' | 'noAction' | 'setDefault';
31
+ interface SchemaForeignKey {
32
+ column: string;
33
+ referencesTable: string;
34
+ referencesColumn: string;
35
+ onDelete?: SchemaForeignKeyAction;
36
+ relationAlias?: string;
37
+ inverseRelationAlias?: string;
38
+ fieldAlias?: string;
39
+ }
30
40
  interface SchemaTableCreateOperation {
31
41
  type: 'createTable';
32
42
  table: string;
33
43
  columns: SchemaColumn[];
34
44
  indexes: SchemaIndex[];
45
+ foreignKeys: SchemaForeignKey[];
35
46
  }
36
47
  interface SchemaTableAlterOperation {
37
48
  type: 'alterTable';
@@ -39,6 +50,7 @@ interface SchemaTableAlterOperation {
39
50
  addColumns: SchemaColumn[];
40
51
  dropColumns: string[];
41
52
  addIndexes: SchemaIndex[];
53
+ addForeignKeys: SchemaForeignKey[];
42
54
  }
43
55
  interface SchemaTableDropOperation {
44
56
  type: 'dropTable';
@@ -2286,6 +2298,60 @@ declare class SeedCommand extends Command<CliApp> {
2286
2298
  private loadSeederClassesFromFile;
2287
2299
  }
2288
2300
  //#endregion
2301
+ //#region src/database/ForeignKeyBuilder.d.ts
2302
+ /**
2303
+ * The ForeignKeyBuilder class provides a fluent interface for defining
2304
+ * foreign key constraints in a migration. It allows you to specify
2305
+ * the referenced table and column, as well as actions to take on
2306
+ * delete and aliases for the relation.
2307
+ *
2308
+ * @author Legacy (3m1n3nc3)
2309
+ * @since 0.2.2
2310
+ */
2311
+ declare class ForeignKeyBuilder {
2312
+ private readonly foreignKey;
2313
+ constructor(foreignKey: SchemaForeignKey);
2314
+ /**
2315
+ * Defines the referenced table and column for this foreign key constraint.
2316
+ *
2317
+ * @param table
2318
+ * @param column
2319
+ * @returns
2320
+ */
2321
+ references(table: string, column: string): this;
2322
+ /**
2323
+ * Defines the action to take when a referenced record is deleted, such
2324
+ * as "CASCADE", "SET NULL", or "RESTRICT".
2325
+ *
2326
+ * @param action
2327
+ * @returns
2328
+ */
2329
+ onDelete(action: SchemaForeignKeyAction): this;
2330
+ /**
2331
+ * Defines an alias for the relation represented by this foreign key, which
2332
+ * can be used in the ORM for more intuitive access to related models.
2333
+ *
2334
+ * @param name
2335
+ * @returns
2336
+ */
2337
+ alias(name: string): this;
2338
+ /**
2339
+ * Defines an alias for the inverse relation represented by this foreign key.
2340
+ *
2341
+ * @param name
2342
+ * @returns
2343
+ */
2344
+ inverseAlias(name: string): this;
2345
+ /**
2346
+ * Defines an alias for the foreign key field itself, which can be
2347
+ * used in the ORM for more intuitive access to the foreign key value.
2348
+ *
2349
+ * @param fieldName
2350
+ * @returns
2351
+ */
2352
+ as(fieldName: string): this;
2353
+ }
2354
+ //#endregion
2289
2355
  //#region src/database/TableBuilder.d.ts
2290
2356
  /**
2291
2357
  * The TableBuilder class provides a fluent interface for defining
@@ -2298,6 +2364,7 @@ declare class TableBuilder {
2298
2364
  private readonly columns;
2299
2365
  private readonly dropColumnNames;
2300
2366
  private readonly indexes;
2367
+ private readonly foreignKeys;
2301
2368
  private latestColumnName;
2302
2369
  /**
2303
2370
  * Defines a primary key column in the table.
@@ -2475,6 +2542,21 @@ declare class TableBuilder {
2475
2542
  * @returns The current TableBuilder instance for chaining.
2476
2543
  */
2477
2544
  index(columns?: string | string[], name?: string): this;
2545
+ /**
2546
+ * Defines a foreign key relation for an existing column.
2547
+ *
2548
+ * @param column The local foreign key column name.
2549
+ * @returns A fluent foreign key builder.
2550
+ */
2551
+ foreignKey(column: string): ForeignKeyBuilder;
2552
+ /**
2553
+ * Defines a foreign key relation for a column, using a
2554
+ * conventional naming pattern.
2555
+ *
2556
+ * @param column
2557
+ * @returns
2558
+ */
2559
+ foreign(column?: string): ForeignKeyBuilder;
2478
2560
  /**
2479
2561
  * Returns a deep copy of the defined columns for the table.
2480
2562
  *
@@ -2493,6 +2575,12 @@ declare class TableBuilder {
2493
2575
  * @returns
2494
2576
  */
2495
2577
  getIndexes(): SchemaIndex[];
2578
+ /**
2579
+ * Returns a deep copy of the defined foreign keys for the table.
2580
+ *
2581
+ * @returns
2582
+ */
2583
+ getForeignKeys(): SchemaForeignKey[];
2496
2584
  /**
2497
2585
  * Defines a column in the table with the given name.
2498
2586
  *
@@ -2683,10 +2771,57 @@ declare const buildFieldLine: (column: SchemaColumn) => string;
2683
2771
  /**
2684
2772
  * Build a Prisma model-level @@index definition line.
2685
2773
  *
2686
- * @param index
2774
+ * @param index The schema index definition to convert to a Prisma \@\@index line.
2687
2775
  * @returns
2688
2776
  */
2689
2777
  declare const buildIndexLine: (index: SchemaIndex) => string;
2778
+ /**
2779
+ * Derive a relation field name from a foreign key column name by applying
2780
+ * common conventions, such as removing "Id" suffixes and converting to camelCase.
2781
+ *
2782
+ * @param columnName The name of the foreign key column.
2783
+ * @returns The derived relation field name.
2784
+ */
2785
+ declare const deriveRelationFieldName: (columnName: string) => string;
2786
+ /**
2787
+ * Derive a relation name for the inverse side of a relation based on the
2788
+ * source and target model names, using an explicit alias if provided or a
2789
+ * convention of combining the target model name with the last segment of
2790
+ * the source model name.
2791
+ *
2792
+ * @param sourceModelName The name of the source model in the relation.
2793
+ * @param targetModelName The name of the target model in the relation.
2794
+ * @param explicitAlias An optional explicit alias for the inverse relation.
2795
+ * @returns The derived or explicit inverse relation alias.
2796
+ */
2797
+ declare const deriveInverseRelationAlias: (sourceModelName: string, targetModelName: string, explicitAlias?: string) => string;
2798
+ declare const deriveCollectionFieldName: (modelName: string) => string;
2799
+ /**
2800
+ * Format a SchemaForeignKeyAction value as a Prisma onDelete action string.
2801
+ *
2802
+ * @param action The foreign key action to format.
2803
+ * @returns The corresponding Prisma onDelete action string.
2804
+ */
2805
+ declare const formatRelationAction: (action: SchemaForeignKeyAction) => string;
2806
+ /**
2807
+ * Build a Prisma relation field line based on a SchemaForeignKey
2808
+ * definition, including relation name and onDelete action.
2809
+ *
2810
+ * @param foreignKey The foreign key definition to convert to a relation line.
2811
+ * @returns The corresponding Prisma schema line for the relation field.
2812
+ */
2813
+ declare const buildRelationLine: (foreignKey: SchemaForeignKey) => string;
2814
+ /**
2815
+ * Build a Prisma relation field line for the inverse side of a relation, based
2816
+ * on the source and target model names and the foreign key definition, using
2817
+ * naming conventions and any explicit inverse alias provided.
2818
+ *
2819
+ * @param sourceModelName The name of the source model in the relation.
2820
+ * @param targetModelName The name of the target model in the relation.
2821
+ * @param foreignKey The foreign key definition for the relation.
2822
+ * @returns The Prisma schema line for the inverse relation field.
2823
+ */
2824
+ declare const buildInverseRelationLine: (sourceModelName: string, targetModelName: string, foreignKey: SchemaForeignKey) => string;
2690
2825
  /**
2691
2826
  * Build a Prisma model block string based on a SchemaTableCreateOperation, including
2692
2827
  * all fields and any necessary mapping.
@@ -2945,4 +3080,4 @@ declare class URLDriver {
2945
3080
  url(page: number): string;
2946
3081
  }
2947
3082
  //#endregion
2948
- export { ArkormCollection, ArkormException, CliApp, InitCommand, InlineFactory, LengthAwarePaginator, MIGRATION_BRAND, MakeFactoryCommand, MakeMigrationCommand, MakeModelCommand, MakeSeederCommand, MigrateCommand, Migration, Model, ModelFactory, ModelNotFoundException, ModelsSyncCommand, PRISMA_MODEL_REGEX, Paginator, PrismaDelegateMap, QueryBuilder, SEEDER_BRAND, SchemaBuilder, SeedCommand, Seeder, SeederCallArgument, SeederConstructor, SeederInput, TableBuilder, URLDriver, applyAlterTableOperation, applyCreateTableOperation, applyDropTableOperation, applyMigrationToPrismaSchema, applyOperationsToPrismaSchema, buildFieldLine, buildIndexLine, buildMigrationSource, buildModelBlock, configureArkormRuntime, createMigrationTimestamp, createPrismaAdapter, createPrismaDelegateMap, defineConfig, defineFactory, ensureArkormConfigLoading, escapeRegex, findModelBlock, formatDefaultValue, generateMigrationFile, getDefaultStubsPath, getMigrationPlan, getRuntimePaginationURLDriverFactory, getRuntimePrismaClient, getUserConfig, inferDelegateName, isDelegateLike, loadArkormConfig, pad, resetArkormRuntimeForTests, resolveCast, resolveMigrationClassName, resolvePrismaType, runMigrationWithPrisma, runPrismaCommand, toMigrationFileSlug, toModelName };
3083
+ export { ArkormCollection, ArkormException, CliApp, ForeignKeyBuilder, InitCommand, InlineFactory, LengthAwarePaginator, MIGRATION_BRAND, MakeFactoryCommand, MakeMigrationCommand, MakeModelCommand, MakeSeederCommand, MigrateCommand, Migration, Model, ModelFactory, ModelNotFoundException, ModelsSyncCommand, PRISMA_MODEL_REGEX, Paginator, PrismaDelegateMap, QueryBuilder, SEEDER_BRAND, SchemaBuilder, SeedCommand, Seeder, SeederCallArgument, SeederConstructor, SeederInput, TableBuilder, URLDriver, applyAlterTableOperation, applyCreateTableOperation, applyDropTableOperation, applyMigrationToPrismaSchema, applyOperationsToPrismaSchema, buildFieldLine, buildIndexLine, buildInverseRelationLine, buildMigrationSource, buildModelBlock, buildRelationLine, configureArkormRuntime, createMigrationTimestamp, createPrismaAdapter, createPrismaDelegateMap, defineConfig, defineFactory, deriveCollectionFieldName, deriveInverseRelationAlias, deriveRelationFieldName, ensureArkormConfigLoading, escapeRegex, findModelBlock, formatDefaultValue, formatRelationAction, generateMigrationFile, getDefaultStubsPath, getMigrationPlan, getRuntimePaginationURLDriverFactory, getRuntimePrismaClient, getUserConfig, inferDelegateName, isDelegateLike, loadArkormConfig, pad, resetArkormRuntimeForTests, resolveCast, resolveMigrationClassName, resolvePrismaType, runMigrationWithPrisma, runPrismaCommand, toMigrationFileSlug, toModelName };
package/dist/index.mjs CHANGED
@@ -88,6 +88,79 @@ var ArkormException = class extends Error {
88
88
  }
89
89
  };
90
90
 
91
+ //#endregion
92
+ //#region src/database/ForeignKeyBuilder.ts
93
+ /**
94
+ * The ForeignKeyBuilder class provides a fluent interface for defining
95
+ * foreign key constraints in a migration. It allows you to specify
96
+ * the referenced table and column, as well as actions to take on
97
+ * delete and aliases for the relation.
98
+ *
99
+ * @author Legacy (3m1n3nc3)
100
+ * @since 0.2.2
101
+ */
102
+ var ForeignKeyBuilder = class {
103
+ foreignKey;
104
+ constructor(foreignKey) {
105
+ this.foreignKey = foreignKey;
106
+ }
107
+ /**
108
+ * Defines the referenced table and column for this foreign key constraint.
109
+ *
110
+ * @param table
111
+ * @param column
112
+ * @returns
113
+ */
114
+ references(table, column) {
115
+ this.foreignKey.referencesTable = table;
116
+ this.foreignKey.referencesColumn = column;
117
+ return this;
118
+ }
119
+ /**
120
+ * Defines the action to take when a referenced record is deleted, such
121
+ * as "CASCADE", "SET NULL", or "RESTRICT".
122
+ *
123
+ * @param action
124
+ * @returns
125
+ */
126
+ onDelete(action) {
127
+ this.foreignKey.onDelete = action;
128
+ return this;
129
+ }
130
+ /**
131
+ * Defines an alias for the relation represented by this foreign key, which
132
+ * can be used in the ORM for more intuitive access to related models.
133
+ *
134
+ * @param name
135
+ * @returns
136
+ */
137
+ alias(name) {
138
+ this.foreignKey.relationAlias = name;
139
+ return this;
140
+ }
141
+ /**
142
+ * Defines an alias for the inverse relation represented by this foreign key.
143
+ *
144
+ * @param name
145
+ * @returns
146
+ */
147
+ inverseAlias(name) {
148
+ this.foreignKey.inverseRelationAlias = name;
149
+ return this;
150
+ }
151
+ /**
152
+ * Defines an alias for the foreign key field itself, which can be
153
+ * used in the ORM for more intuitive access to the foreign key value.
154
+ *
155
+ * @param fieldName
156
+ * @returns
157
+ */
158
+ as(fieldName) {
159
+ this.foreignKey.fieldAlias = fieldName;
160
+ return this;
161
+ }
162
+ };
163
+
91
164
  //#endregion
92
165
  //#region src/database/TableBuilder.ts
93
166
  /**
@@ -101,6 +174,7 @@ var TableBuilder = class {
101
174
  columns = [];
102
175
  dropColumnNames = [];
103
176
  indexes = [];
177
+ foreignKeys = [];
104
178
  latestColumnName;
105
179
  /**
106
180
  * Defines a primary key column in the table.
@@ -343,6 +417,32 @@ var TableBuilder = class {
343
417
  return this;
344
418
  }
345
419
  /**
420
+ * Defines a foreign key relation for an existing column.
421
+ *
422
+ * @param column The local foreign key column name.
423
+ * @returns A fluent foreign key builder.
424
+ */
425
+ foreignKey(column) {
426
+ const entry = {
427
+ column,
428
+ referencesTable: "",
429
+ referencesColumn: "id"
430
+ };
431
+ this.foreignKeys.push(entry);
432
+ return new ForeignKeyBuilder(entry);
433
+ }
434
+ /**
435
+ * Defines a foreign key relation for a column, using a
436
+ * conventional naming pattern.
437
+ *
438
+ * @param column
439
+ * @returns
440
+ */
441
+ foreign(column) {
442
+ const columnName = this.resolveColumn(column).name;
443
+ return this.foreignKey(columnName + (column ? "" : "Id"));
444
+ }
445
+ /**
346
446
  * Returns a deep copy of the defined columns for the table.
347
447
  *
348
448
  * @returns
@@ -370,6 +470,14 @@ var TableBuilder = class {
370
470
  }));
371
471
  }
372
472
  /**
473
+ * Returns a deep copy of the defined foreign keys for the table.
474
+ *
475
+ * @returns
476
+ */
477
+ getForeignKeys() {
478
+ return this.foreignKeys.map((foreignKey) => ({ ...foreignKey }));
479
+ }
480
+ /**
373
481
  * Defines a column in the table with the given name.
374
482
  *
375
483
  * @param name The name of the column.
@@ -432,7 +540,8 @@ var SchemaBuilder = class {
432
540
  type: "createTable",
433
541
  table,
434
542
  columns: builder.getColumns(),
435
- indexes: builder.getIndexes()
543
+ indexes: builder.getIndexes(),
544
+ foreignKeys: builder.getForeignKeys()
436
545
  });
437
546
  return this;
438
547
  }
@@ -451,7 +560,8 @@ var SchemaBuilder = class {
451
560
  table,
452
561
  addColumns: builder.getColumns(),
453
562
  dropColumns: builder.getDropColumns(),
454
- addIndexes: builder.getIndexes()
563
+ addIndexes: builder.getIndexes(),
564
+ addForeignKeys: builder.getForeignKeys()
455
565
  });
456
566
  return this;
457
567
  }
@@ -481,7 +591,8 @@ var SchemaBuilder = class {
481
591
  indexes: operation.indexes.map((index) => ({
482
592
  ...index,
483
593
  columns: [...index.columns]
484
- }))
594
+ })),
595
+ foreignKeys: operation.foreignKeys.map((foreignKey) => ({ ...foreignKey }))
485
596
  };
486
597
  if (operation.type === "alterTable") return {
487
598
  ...operation,
@@ -490,7 +601,8 @@ var SchemaBuilder = class {
490
601
  addIndexes: operation.addIndexes.map((index) => ({
491
602
  ...index,
492
603
  columns: [...index.columns]
493
- }))
604
+ })),
605
+ addForeignKeys: operation.addForeignKeys.map((foreignKey) => ({ ...foreignKey }))
494
606
  };
495
607
  return { ...operation };
496
608
  });
@@ -575,13 +687,139 @@ const buildFieldLine = (column) => {
575
687
  /**
576
688
  * Build a Prisma model-level @@index definition line.
577
689
  *
578
- * @param index
690
+ * @param index The schema index definition to convert to a Prisma \@\@index line.
579
691
  * @returns
580
692
  */
581
693
  const buildIndexLine = (index) => {
582
694
  return ` @@index([${index.columns.join(", ")}]${typeof index.name === "string" && index.name.trim().length > 0 ? `, name: "${index.name.replace(/"/g, "\\\"")}"` : ""})`;
583
695
  };
584
696
  /**
697
+ * Derive a relation field name from a foreign key column name by applying
698
+ * common conventions, such as removing "Id" suffixes and converting to camelCase.
699
+ *
700
+ * @param columnName The name of the foreign key column.
701
+ * @returns The derived relation field name.
702
+ */
703
+ const deriveRelationFieldName = (columnName) => {
704
+ const trimmed = columnName.trim();
705
+ if (!trimmed) return "relation";
706
+ if (trimmed.endsWith("Id") && trimmed.length > 2) {
707
+ const root = trimmed.slice(0, -2);
708
+ return `${root.charAt(0).toLowerCase()}${root.slice(1)}`;
709
+ }
710
+ if (trimmed.endsWith("_id") && trimmed.length > 3) return trimmed.slice(0, -3).replace(/_([a-zA-Z0-9])/g, (_, letter) => letter.toUpperCase());
711
+ return `${trimmed.charAt(0).toLowerCase()}${trimmed.slice(1)}`;
712
+ };
713
+ const pascalWords = (value) => {
714
+ return value.match(/[A-Z][a-z0-9]*/g) ?? [value];
715
+ };
716
+ /**
717
+ * Derive a relation name for the inverse side of a relation based on the
718
+ * source and target model names, using an explicit alias if provided or a
719
+ * convention of combining the target model name with the last segment of
720
+ * the source model name.
721
+ *
722
+ * @param sourceModelName The name of the source model in the relation.
723
+ * @param targetModelName The name of the target model in the relation.
724
+ * @param explicitAlias An optional explicit alias for the inverse relation.
725
+ * @returns The derived or explicit inverse relation alias.
726
+ */
727
+ const deriveInverseRelationAlias = (sourceModelName, targetModelName, explicitAlias) => {
728
+ if (explicitAlias && explicitAlias.trim().length > 0) return explicitAlias.trim();
729
+ const sourceWords = pascalWords(sourceModelName);
730
+ return `${sourceWords[sourceWords.length - 1] ?? sourceModelName}${targetModelName}`;
731
+ };
732
+ const deriveCollectionFieldName = (modelName) => {
733
+ if (!modelName) return "items";
734
+ const camel = `${modelName.charAt(0).toLowerCase()}${modelName.slice(1)}`;
735
+ if (camel.endsWith("s")) return `${camel}es`;
736
+ return `${camel}s`;
737
+ };
738
+ /**
739
+ * Format a SchemaForeignKeyAction value as a Prisma onDelete action string.
740
+ *
741
+ * @param action The foreign key action to format.
742
+ * @returns The corresponding Prisma onDelete action string.
743
+ */
744
+ const formatRelationAction = (action) => {
745
+ if (action === "cascade") return "Cascade";
746
+ if (action === "restrict") return "Restrict";
747
+ if (action === "setNull") return "SetNull";
748
+ if (action === "setDefault") return "SetDefault";
749
+ return "NoAction";
750
+ };
751
+ /**
752
+ * Build a Prisma relation field line based on a SchemaForeignKey
753
+ * definition, including relation name and onDelete action.
754
+ *
755
+ * @param foreignKey The foreign key definition to convert to a relation line.
756
+ * @returns The corresponding Prisma schema line for the relation field.
757
+ */
758
+ const buildRelationLine = (foreignKey) => {
759
+ if (!foreignKey.referencesTable.trim()) throw new ArkormException(`Foreign key [${foreignKey.column}] must define a referenced table.`);
760
+ if (!foreignKey.referencesColumn.trim()) throw new ArkormException(`Foreign key [${foreignKey.column}] must define a referenced column.`);
761
+ const fieldName = foreignKey.fieldAlias?.trim() || deriveRelationFieldName(foreignKey.column);
762
+ const targetModel = toModelName(foreignKey.referencesTable);
763
+ const relationName = foreignKey.relationAlias?.trim();
764
+ const relationPrefix = relationName ? `@relation("${relationName.replace(/"/g, "\\\"")}", ` : "@relation(";
765
+ const onDelete = foreignKey.onDelete ? `, onDelete: ${formatRelationAction(foreignKey.onDelete)}` : "";
766
+ return ` ${fieldName} ${targetModel} ${relationPrefix}fields: [${foreignKey.column}], references: [${foreignKey.referencesColumn}]${onDelete})`;
767
+ };
768
+ /**
769
+ * Build a Prisma relation field line for the inverse side of a relation, based
770
+ * on the source and target model names and the foreign key definition, using
771
+ * naming conventions and any explicit inverse alias provided.
772
+ *
773
+ * @param sourceModelName The name of the source model in the relation.
774
+ * @param targetModelName The name of the target model in the relation.
775
+ * @param foreignKey The foreign key definition for the relation.
776
+ * @returns The Prisma schema line for the inverse relation field.
777
+ */
778
+ const buildInverseRelationLine = (sourceModelName, targetModelName, foreignKey) => {
779
+ return ` ${deriveCollectionFieldName(sourceModelName)} ${sourceModelName}[] @relation("${deriveInverseRelationAlias(sourceModelName, targetModelName, foreignKey.inverseRelationAlias).replace(/"/g, "\\\"")}")`;
780
+ };
781
+ /**
782
+ * Inject a line into the body of a Prisma model block if it does not already
783
+ * exist, using a provided existence check function to determine if the line
784
+ * is already present.
785
+ *
786
+ * @param bodyLines The lines of the model block body to modify.
787
+ * @param line The line to inject if it does not already exist.
788
+ * @param exists A function that checks if a given line already exists in the body.
789
+ * @returns
790
+ */
791
+ const injectLineIntoModelBody = (bodyLines, line, exists) => {
792
+ if (bodyLines.some(exists)) return bodyLines;
793
+ const insertIndex = Math.max(1, bodyLines.length - 1);
794
+ bodyLines.splice(insertIndex, 0, line);
795
+ return bodyLines;
796
+ };
797
+ /**
798
+ * Apply inverse relation definitions to a Prisma schema string based on the
799
+ * foreign keys defined in a create or alter table operation, ensuring that
800
+ * related models have corresponding relation fields for bi-directional navigation.
801
+ *
802
+ * @param schema The Prisma schema string to modify.
803
+ * @param sourceModelName The name of the source model in the relation.
804
+ * @param foreignKeys An array of foreign key definitions to process.
805
+ * @returns The updated Prisma schema string with inverse relations applied.
806
+ */
807
+ const applyInverseRelations = (schema, sourceModelName, foreignKeys) => {
808
+ let nextSchema = schema;
809
+ for (const foreignKey of foreignKeys) {
810
+ const targetModel = findModelBlock(nextSchema, foreignKey.referencesTable);
811
+ if (!targetModel) continue;
812
+ const inverseLine = buildInverseRelationLine(sourceModelName, targetModel.modelName, foreignKey);
813
+ const targetBodyLines = targetModel.block.split("\n");
814
+ const fieldName = deriveCollectionFieldName(sourceModelName);
815
+ const fieldRegex = new RegExp(`^\\s*${escapeRegex(fieldName)}\\s+`);
816
+ injectLineIntoModelBody(targetBodyLines, inverseLine, (line) => fieldRegex.test(line));
817
+ const updatedTarget = targetBodyLines.join("\n");
818
+ nextSchema = `${nextSchema.slice(0, targetModel.start)}${updatedTarget}${nextSchema.slice(targetModel.end)}`;
819
+ }
820
+ return nextSchema;
821
+ };
822
+ /**
585
823
  * Build a Prisma model block string based on a SchemaTableCreateOperation, including
586
824
  * all fields and any necessary mapping.
587
825
  *
@@ -592,12 +830,14 @@ const buildModelBlock = (operation) => {
592
830
  const modelName = toModelName(operation.table);
593
831
  const mapped = operation.table !== modelName.toLowerCase();
594
832
  const fields = operation.columns.map(buildFieldLine);
833
+ const relations = (operation.foreignKeys ?? []).map(buildRelationLine);
595
834
  const metadata = [...(operation.indexes ?? []).map(buildIndexLine), ...mapped ? [` @@map("${str(operation.table).snake()}")`] : []];
596
835
  return `model ${modelName} {\n${(metadata.length > 0 ? [
597
836
  ...fields,
837
+ ...relations,
598
838
  "",
599
839
  ...metadata
600
- ] : fields).join("\n")}\n}`;
840
+ ] : [...fields, ...relations]).join("\n")}\n}`;
601
841
  };
602
842
  /**
603
843
  * Find the Prisma model block in a schema string that corresponds to a given
@@ -647,7 +887,7 @@ const findModelBlock = (schema, table) => {
647
887
  const applyCreateTableOperation = (schema, operation) => {
648
888
  if (findModelBlock(schema, operation.table)) throw new ArkormException(`Prisma model for table [${operation.table}] already exists.`);
649
889
  const block = buildModelBlock(operation);
650
- return `${schema.trimEnd()}\n\n${block}\n`;
890
+ return applyInverseRelations(`${schema.trimEnd()}\n\n${block}\n`, toModelName(operation.table), operation.foreignKeys ?? []);
651
891
  };
652
892
  /**
653
893
  * Apply an alter table operation to a Prisma schema string, modifying the model
@@ -684,8 +924,13 @@ const applyAlterTableOperation = (schema, operation) => {
684
924
  const insertIndex = Math.max(1, bodyLines.length - 1);
685
925
  bodyLines.splice(insertIndex, 0, indexLine);
686
926
  });
927
+ for (const foreignKey of operation.addForeignKeys ?? []) {
928
+ const relationLine = buildRelationLine(foreignKey);
929
+ const relationRegex = new RegExp(`^\\s*${escapeRegex(foreignKey.fieldAlias?.trim() || deriveRelationFieldName(foreignKey.column))}\\s+`);
930
+ injectLineIntoModelBody(bodyLines, relationLine, (line) => relationRegex.test(line));
931
+ }
687
932
  block = bodyLines.join("\n");
688
- return `${schema.slice(0, model.start)}${block}${schema.slice(model.end)}`;
933
+ return applyInverseRelations(`${schema.slice(0, model.start)}${block}${schema.slice(model.end)}`, model.modelName, operation.addForeignKeys ?? []);
689
934
  };
690
935
  /**
691
936
  * Apply a drop table operation to a Prisma schema string, removing the model block
@@ -1807,7 +2052,7 @@ var MigrateCommand = class extends Command {
1807
2052
  const migrationsDir = this.app.resolveRuntimeDirectoryPath(configuredMigrationsDir);
1808
2053
  if (!existsSync$1(migrationsDir)) return void this.error(`Error: Migrations directory not found: ${this.app.formatPathForLog(configuredMigrationsDir)}`);
1809
2054
  const schemaPath = this.option("schema") ? resolve(String(this.option("schema"))) : join$1(process.cwd(), "prisma", "schema.prisma");
1810
- const classes = this.option("all") ? await this.loadAllMigrations(migrationsDir) : (await this.loadNamedMigration(migrationsDir, this.argument("name"))).filter(([cls]) => cls !== void 0);
2055
+ const classes = this.option("all") || !this.argument("name") ? await this.loadAllMigrations(migrationsDir) : (await this.loadNamedMigration(migrationsDir, this.argument("name"))).filter(([cls]) => cls !== void 0);
1811
2056
  if (classes.length === 0) return void this.error("Error: No migration classes found to run.");
1812
2057
  for (const [MigrationClassItem] of classes) await applyMigrationToPrismaSchema(MigrationClassItem, {
1813
2058
  schemaPath,
@@ -4842,4 +5087,4 @@ var Model = class Model {
4842
5087
  };
4843
5088
 
4844
5089
  //#endregion
4845
- export { ArkormCollection, ArkormException, CliApp, InitCommand, InlineFactory, LengthAwarePaginator, MIGRATION_BRAND, MakeFactoryCommand, MakeMigrationCommand, MakeModelCommand, MakeSeederCommand, MigrateCommand, Migration, Model, ModelFactory, ModelNotFoundException, ModelsSyncCommand, PRISMA_MODEL_REGEX, Paginator, QueryBuilder, SEEDER_BRAND, SchemaBuilder, SeedCommand, Seeder, TableBuilder, URLDriver, applyAlterTableOperation, applyCreateTableOperation, applyDropTableOperation, applyMigrationToPrismaSchema, applyOperationsToPrismaSchema, buildFieldLine, buildIndexLine, buildMigrationSource, buildModelBlock, configureArkormRuntime, createMigrationTimestamp, createPrismaAdapter, createPrismaDelegateMap, defineConfig, defineFactory, ensureArkormConfigLoading, escapeRegex, findModelBlock, formatDefaultValue, generateMigrationFile, getDefaultStubsPath, getMigrationPlan, getRuntimePaginationURLDriverFactory, getRuntimePrismaClient, getUserConfig, inferDelegateName, isDelegateLike, loadArkormConfig, pad, resetArkormRuntimeForTests, resolveCast, resolveMigrationClassName, resolvePrismaType, runMigrationWithPrisma, runPrismaCommand, toMigrationFileSlug, toModelName };
5090
+ export { ArkormCollection, ArkormException, CliApp, ForeignKeyBuilder, InitCommand, InlineFactory, LengthAwarePaginator, MIGRATION_BRAND, MakeFactoryCommand, MakeMigrationCommand, MakeModelCommand, MakeSeederCommand, MigrateCommand, Migration, Model, ModelFactory, ModelNotFoundException, ModelsSyncCommand, PRISMA_MODEL_REGEX, Paginator, QueryBuilder, SEEDER_BRAND, SchemaBuilder, SeedCommand, Seeder, TableBuilder, URLDriver, applyAlterTableOperation, applyCreateTableOperation, applyDropTableOperation, applyMigrationToPrismaSchema, applyOperationsToPrismaSchema, buildFieldLine, buildIndexLine, buildInverseRelationLine, buildMigrationSource, buildModelBlock, buildRelationLine, configureArkormRuntime, createMigrationTimestamp, createPrismaAdapter, createPrismaDelegateMap, defineConfig, defineFactory, deriveCollectionFieldName, deriveInverseRelationAlias, deriveRelationFieldName, ensureArkormConfigLoading, escapeRegex, findModelBlock, formatDefaultValue, formatRelationAction, generateMigrationFile, getDefaultStubsPath, getMigrationPlan, getRuntimePaginationURLDriverFactory, getRuntimePrismaClient, getUserConfig, inferDelegateName, isDelegateLike, loadArkormConfig, pad, resetArkormRuntimeForTests, resolveCast, resolveMigrationClassName, resolvePrismaType, runMigrationWithPrisma, runPrismaCommand, toMigrationFileSlug, toModelName };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "arkormx",
3
- "version": "0.2.2",
3
+ "version": "0.2.4",
4
4
  "description": "Modern TypeScript-first ORM for Node.js.",
5
5
  "keywords": [
6
6
  "orm",