ormbridge 0.14.0 → 0.15.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.
Files changed (69) hide show
  1. package/README.md +21 -21
  2. package/dist/cliRunner.js +2 -2
  3. package/dist/cliRunner.js.map +1 -1
  4. package/dist/config.d.ts +1 -1
  5. package/dist/emitters/sequelize.d.ts +16 -3
  6. package/dist/emitters/sequelize.js +270 -45
  7. package/dist/emitters/sequelize.js.map +1 -1
  8. package/dist/emitters/sequelize7.d.ts +68 -0
  9. package/dist/emitters/sequelize7.js +199 -0
  10. package/dist/emitters/sequelize7.js.map +1 -0
  11. package/dist/emitters/sequelizeText.d.ts +12 -0
  12. package/dist/emitters/sequelizeText.js +37 -0
  13. package/dist/emitters/sequelizeText.js.map +1 -0
  14. package/dist/formats.d.ts +2 -2
  15. package/dist/formats.js +4 -3
  16. package/dist/formats.js.map +1 -1
  17. package/dist/index.d.ts +2 -2
  18. package/dist/index.js +1 -1
  19. package/dist/index.js.map +1 -1
  20. package/dist/io.js +44 -6
  21. package/dist/io.js.map +1 -1
  22. package/dist/parsers/efcore.js +60 -3
  23. package/dist/parsers/efcore.js.map +1 -1
  24. package/dist/parsers/efcoreAnnotations.js +1 -1
  25. package/dist/parsers/efcoreAnnotations.js.map +1 -1
  26. package/dist/parsers/efcoreBuild.d.ts +1 -0
  27. package/dist/parsers/efcoreBuild.js +15 -4
  28. package/dist/parsers/efcoreBuild.js.map +1 -1
  29. package/dist/parsers/efcoreEntities.js +90 -22
  30. package/dist/parsers/efcoreEntities.js.map +1 -1
  31. package/dist/parsers/efcoreMigrationHistory.d.ts +17 -0
  32. package/dist/parsers/efcoreMigrationHistory.js +105 -0
  33. package/dist/parsers/efcoreMigrationHistory.js.map +1 -0
  34. package/dist/parsers/efcoreMigrationReplay.d.ts +25 -0
  35. package/dist/parsers/efcoreMigrationReplay.js +790 -0
  36. package/dist/parsers/efcoreMigrationReplay.js.map +1 -0
  37. package/dist/parsers/efcoreMigrationSchema.d.ts +14 -0
  38. package/dist/parsers/efcoreMigrationSchema.js +550 -0
  39. package/dist/parsers/efcoreMigrationSchema.js.map +1 -0
  40. package/dist/parsers/efcoreMigrationState.d.ts +82 -0
  41. package/dist/parsers/efcoreMigrationState.js +104 -0
  42. package/dist/parsers/efcoreMigrationState.js.map +1 -0
  43. package/dist/parsers/efcoreModel.d.ts +18 -2
  44. package/dist/parsers/efcoreOwned.d.ts +12 -0
  45. package/dist/parsers/efcoreOwned.js +139 -0
  46. package/dist/parsers/efcoreOwned.js.map +1 -0
  47. package/dist/parsers/efcoreSnapshot.d.ts +85 -0
  48. package/dist/parsers/efcoreSnapshot.js +791 -0
  49. package/dist/parsers/efcoreSnapshot.js.map +1 -0
  50. package/dist/parsers/efcoreSteps.js +28 -3
  51. package/dist/parsers/efcoreSteps.js.map +1 -1
  52. package/dist/parsers/sequelize.js +37 -10
  53. package/dist/parsers/sequelize.js.map +1 -1
  54. package/dist/parsers/sequelizeAssociations.js +13 -2
  55. package/dist/parsers/sequelizeAssociations.js.map +1 -1
  56. package/dist/parsers/sequelizeCollect.d.ts +7 -2
  57. package/dist/parsers/sequelizeCollect.js +120 -9
  58. package/dist/parsers/sequelizeCollect.js.map +1 -1
  59. package/dist/parsers/sequelizeMigrationState.d.ts +75 -0
  60. package/dist/parsers/sequelizeMigrationState.js +330 -0
  61. package/dist/parsers/sequelizeMigrationState.js.map +1 -0
  62. package/dist/parsers/sequelizeMigrations.d.ts +14 -0
  63. package/dist/parsers/sequelizeMigrations.js +1123 -0
  64. package/dist/parsers/sequelizeMigrations.js.map +1 -0
  65. package/dist/parsers/sequelizeModel.d.ts +19 -2
  66. package/dist/styles.d.ts +3 -3
  67. package/dist/styles.js +4 -4
  68. package/dist/styles.js.map +1 -1
  69. package/package.json +1 -1
@@ -4,14 +4,25 @@ import { underscore } from '../parsers/sequelizeInflection.js';
4
4
  import { prismaOnlyWarnings } from '../prismaOnlyConstructs.js';
5
5
  import { normalizeSchema } from '../transforms.js';
6
6
  import { COCKROACH_PREFIX, cockroachInvertible, cockroachSequentialWarning, isCockroach, } from './cockroach.js';
7
+ import { writeV7Index, writeV7ModelFile, } from './sequelize7.js';
8
+ import { compact, IDENTIFIER_PATTERN, INDENT, MAX_LINE_WIDTH, objectLines, quote, } from './sequelizeText.js';
7
9
  export const SEQUELIZE_STYLES = ['init', 'define'];
10
+ export const SEQUELIZE_GENERATIONS = [
11
+ 'v6',
12
+ 'v7',
13
+ ];
14
+ export const SEQUELIZE_OUTPUT_STYLES = [
15
+ ...SEQUELIZE_STYLES,
16
+ ...SEQUELIZE_GENERATIONS,
17
+ ];
8
18
  /** True when a value is one of the Sequelize `--style` values. */
9
19
  export function isSequelizeStyle(value) {
10
20
  return SEQUELIZE_STYLES.includes(value);
11
21
  }
12
- const INDENT = ' ';
13
- const MAX_LINE_WIDTH = 80;
14
- const IDENTIFIER_PATTERN = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
22
+ /** True when a value is a Sequelize 6 layout or a generation (`v6`, `v7`). */
23
+ export function isSequelizeOutputStyle(value) {
24
+ return SEQUELIZE_OUTPUT_STYLES.includes(value);
25
+ }
15
26
  const CREATED_FIELD_PATTERN = /^(created_?at|created|date_?created|createdon)$/i;
16
27
  /** Names a generated model file declares or imports, which a class cannot take. */
17
28
  const RESERVED_CLASS_NAMES = new Set([
@@ -32,6 +43,24 @@ const RESERVED_CLASS_NAMES = new Set([
32
43
  'Boolean',
33
44
  'Models',
34
45
  ]);
46
+ /** Names the Sequelize 7 model files import as well, which a class cannot take there. */
47
+ const V7_RESERVED_CLASS_NAMES = new Set([
48
+ 'Attribute',
49
+ 'AutoIncrement',
50
+ 'BelongsTo',
51
+ 'BelongsToMany',
52
+ 'ColumnName',
53
+ 'CreatedAt',
54
+ 'Default',
55
+ 'HasMany',
56
+ 'HasOne',
57
+ 'NotNull',
58
+ 'PrimaryKey',
59
+ 'Table',
60
+ 'Unique',
61
+ 'UpdatedAt',
62
+ 'sql',
63
+ ]);
35
64
  /** Words an attribute cannot be called on a Sequelize instance without shadowing a member of `Model`. */
36
65
  const RESERVED_ATTRIBUTES = new Set([
37
66
  'constructor',
@@ -82,7 +111,9 @@ const RANGE_SUBTYPES = {
82
111
  // ---------------------------------------------------------------------------
83
112
  export function emitSequelize(schema, options) {
84
113
  const prepared = options.naming === 'normalize' ? normalizeSchema(schema) : schema;
85
- const style = options.style ?? 'init';
114
+ const style = options.style === undefined || options.style === 'v6'
115
+ ? 'init'
116
+ : options.style;
86
117
  const context = {
87
118
  schema: prepared,
88
119
  options,
@@ -98,7 +129,10 @@ export function emitSequelize(schema, options) {
98
129
  if (options.provider === 'mongodb') {
99
130
  context.warnings.push('Provider "mongodb": Sequelize is relational (it has no MongoDB dialect; use Mongoose); PostgreSQL column types were written.');
100
131
  }
101
- if (context.cockroach) {
132
+ if (context.cockroach && style === 'v7') {
133
+ context.warnings.push(`${COCKROACH_PREFIX} Sequelize 7 has no CockroachDB dialect or patch package, so the models use the PostgreSQL dialect (@sequelize/postgres) and may meet differences it does not handle; hstore, range types and multi-dimensional arrays (which CockroachDB lacks) are written as JSONB or TEXT.`);
134
+ }
135
+ else if (context.cockroach) {
102
136
  context.warnings.push(`${COCKROACH_PREFIX} the models use the "postgres" dialect through the sequelize-cockroachdb package (Sequelize 6): DataTypes.INTEGER creates a 64-bit INT8 column there, and hstore, range types and multi-dimensional arrays (which CockroachDB lacks) are written as JSONB or TEXT.`);
103
137
  }
104
138
  const files = {};
@@ -110,8 +144,9 @@ export function emitSequelize(schema, options) {
110
144
  const associations = {
111
145
  byOwner: new Map(),
112
146
  ordered: [],
147
+ v7: new Map(),
113
148
  };
114
- const extension = style === 'init' ? 'ts' : 'js';
149
+ const extension = style === 'define' ? 'js' : 'ts';
115
150
  const rendered = new Map();
116
151
  for (const model of prepared.models) {
117
152
  context.usesLiteral = false;
@@ -126,12 +161,19 @@ export function emitSequelize(schema, options) {
126
161
  files[`${plan.fileBase}.${extension}`] =
127
162
  style === 'init'
128
163
  ? writeInitFile(context, plan, result, associations)
129
- : writeDefineFile(plan, result, associations);
164
+ : style === 'v7'
165
+ ? writeV7ModelFile(v7ModelOf(plan, result, associations), (className) => fileOf(context, className))
166
+ : writeDefineFile(plan, result, associations);
130
167
  }
131
168
  files[`index.${extension}`] =
132
169
  style === 'init'
133
170
  ? writeInitIndex(context, associations)
134
- : writeDefineIndex(context);
171
+ : style === 'v7'
172
+ ? writeV7Index(context.schema.models.map((model) => {
173
+ const plan = planOf(context, model);
174
+ return { className: plan.className, fileBase: plan.fileBase };
175
+ }), v7DialectOf(context), v7Notes(context))
176
+ : writeDefineIndex(context);
135
177
  if (context.sequentialKeys.length > 0) {
136
178
  context.warnings.push(cockroachSequentialWarning(context.sequentialKeys, 'autoIncrement creates a SERIAL column, which CockroachDB fills with unique_rowid() (64-bit values above 2^53 that a JavaScript number cannot hold exactly; sequelize-cockroachdb returns them as strings, so those keys and the foreign keys that point at them are typed string)'));
137
179
  }
@@ -174,16 +216,6 @@ function dialectOf(provider) {
174
216
  // ---------------------------------------------------------------------------
175
217
  // Small helpers
176
218
  // ---------------------------------------------------------------------------
177
- function quote(value) {
178
- const escaped = value
179
- .replace(/\\/g, '\\\\')
180
- .replace(/\n/g, '\\n')
181
- .replace(/\r/g, '\\r');
182
- if (value.includes("'") && !value.includes('"')) {
183
- return `"${escaped}"`;
184
- }
185
- return `'${escaped.replace(/'/g, "\\'")}'`;
186
- }
187
219
  function uniqueName(baseName, used) {
188
220
  let candidate = baseName;
189
221
  let suffix = 2;
@@ -204,24 +236,6 @@ function identifier(context, owner, raw) {
204
236
  context.warnings.push(`${owner}: "${raw}" is not a valid JavaScript identifier; it was written as "${result}".`);
205
237
  return result;
206
238
  }
207
- function compact(entries) {
208
- return entries.filter((entry) => entry !== undefined);
209
- }
210
- /** `{ a, b }` on one line when it fits after `prefix`, otherwise one entry per line. */
211
- function objectLines(prefix, entries, indent, suffix) {
212
- const inline = `${indent}${prefix}{ ${entries.join(', ')} }${suffix}`;
213
- if (entries.length === 0) {
214
- return [`${indent}${prefix}{}${suffix}`];
215
- }
216
- if (inline.length <= MAX_LINE_WIDTH) {
217
- return [inline];
218
- }
219
- return [
220
- `${indent}${prefix}{`,
221
- ...entries.map((entry) => `${indent}${INDENT}${entry},`),
222
- `${indent}}${suffix}`,
223
- ];
224
- }
225
239
  function isIntegerKey(field) {
226
240
  return field.type === 'int' || field.type === 'bigInt';
227
241
  }
@@ -295,15 +309,18 @@ function chooseUnderscored(context, model, attrs) {
295
309
  }
296
310
  function planModels(context) {
297
311
  const fileBases = new Set(['index']);
298
- const classNames = new Set(RESERVED_CLASS_NAMES);
312
+ const reserved = context.style === 'v7'
313
+ ? new Set([...RESERVED_CLASS_NAMES, ...V7_RESERVED_CLASS_NAMES])
314
+ : RESERVED_CLASS_NAMES;
315
+ const classNames = new Set(reserved);
299
316
  for (const model of context.schema.models) {
300
317
  // Sequelize model names are PascalCase; `legacy_order` is written as `LegacyOrder`.
301
318
  const pascal = /^[A-Z][A-Za-z0-9]*$/.test(model.name)
302
319
  ? model.name
303
320
  : toPascalCase(model.name);
304
321
  const rawClass = identifier(context, model.name, pascal === '' ? model.name : pascal);
305
- const className = uniqueName(RESERVED_CLASS_NAMES.has(rawClass) ? `${rawClass}Model` : rawClass, classNames);
306
- if (RESERVED_CLASS_NAMES.has(rawClass)) {
322
+ const className = uniqueName(reserved.has(rawClass) ? `${rawClass}Model` : rawClass, classNames);
323
+ if (reserved.has(rawClass)) {
307
324
  context.warnings.push(`${model.name}: "${rawClass}" is a name the generated files use, so the class and the model were named "${className}".`);
308
325
  }
309
326
  const base = toCamelCase(className) || 'model';
@@ -419,6 +436,10 @@ function typeSpecOf(context, label, field, keyLike) {
419
436
  context.warnings.push(`${label}: ${dialectName(context.dialect)} has no array column type; the array was written as ${jsonType.startsWith('DataTypes.JSON') ? 'JSON' : 'TEXT'}.`);
420
437
  return { expression: jsonType, ts };
421
438
  }
439
+ /** True for SQLite under Sequelize 7, whose dialect has no BIGINT, DECIMAL or CHAR type. */
440
+ function v7Sqlite(context) {
441
+ return context.style === 'v7' && context.dialect === 'sqlite';
442
+ }
422
443
  function dialectName(dialect) {
423
444
  switch (dialect) {
424
445
  case 'mysql':
@@ -493,10 +514,16 @@ function scalarTypeSpec(context, label, field, keyLike) {
493
514
  : 'number',
494
515
  };
495
516
  case 'bigInt':
496
- return { expression: 'DataTypes.BIGINT', ts: 'string' };
517
+ return v7Sqlite(context)
518
+ ? { expression: 'DataTypes.INTEGER', ts: 'number' }
519
+ : { expression: 'DataTypes.BIGINT', ts: 'string' };
497
520
  case 'float':
498
521
  return { expression: 'DataTypes.DOUBLE', ts: 'number' };
499
522
  case 'decimal': {
523
+ if (v7Sqlite(context)) {
524
+ context.warnings.push(`${label}: the Sequelize 7 SQLite dialect has no DECIMAL type; the column was written as DOUBLE, which is not exact.`);
525
+ return { expression: 'DataTypes.DOUBLE', ts: 'number' };
526
+ }
500
527
  const precision = field.maxDigits;
501
528
  const scale = field.decimalPlaces;
502
529
  const args = precision === undefined
@@ -523,7 +550,9 @@ function scalarTypeSpec(context, label, field, keyLike) {
523
550
  return { expression: 'DataTypes.BLOB', ts: 'Buffer' };
524
551
  case 'duration':
525
552
  context.warnings.push(`${label}: Sequelize has no interval type; the duration was written as BIGINT (microseconds).`);
526
- return { expression: 'DataTypes.BIGINT', ts: 'string' };
553
+ return v7Sqlite(context)
554
+ ? { expression: 'DataTypes.INTEGER', ts: 'number' }
555
+ : { expression: 'DataTypes.BIGINT', ts: 'string' };
527
556
  case 'ipAddress':
528
557
  return dialect === 'postgres'
529
558
  ? { expression: 'DataTypes.INET', ts: 'string' }
@@ -617,7 +646,7 @@ function defaultOf(context, label, field) {
617
646
  }
618
647
  case 'dbExpression':
619
648
  context.usesLiteral = true;
620
- return `Sequelize.literal(${quote(defaultExpression(context, value.expression))})`;
649
+ return `${context.style === 'v7' ? 'sql' : 'Sequelize'}.literal(${quote(defaultExpression(context, value.expression))})`;
621
650
  case 'clientGenerated':
622
651
  context.warnings.push(`${label}: the ${value.generator}() default is generated by Prisma Client; Sequelize has no equivalent and the default was dropped.`);
623
652
  return undefined;
@@ -751,9 +780,10 @@ function fieldAttribute(context, plan, field) {
751
780
  };
752
781
  }
753
782
  /** The columns Sequelize adds for `timestamps`: required date-times that it fills in itself. */
754
- function implicitTimestamp(name) {
783
+ function implicitTimestamp(name, timestamp) {
755
784
  return {
756
785
  name,
786
+ timestamp,
757
787
  type: 'DataTypes.DATE',
758
788
  entries: ['allowNull: false'],
759
789
  ts: 'Date',
@@ -925,7 +955,7 @@ function renderModel(context, plan, associations) {
925
955
  for (const field of model.fields) {
926
956
  const kind = timestampKinds.get(field);
927
957
  if (kind !== undefined && isImplicitTimestamp(plan, field, kind)) {
928
- attributes.push(implicitTimestamp(plan.attrs.get(field.name) ?? field.name));
958
+ attributes.push(implicitTimestamp(plan.attrs.get(field.name) ?? field.name, kind));
929
959
  continue;
930
960
  }
931
961
  attributes.push(fieldAttribute(context, plan, field));
@@ -944,7 +974,7 @@ function renderModel(context, plan, associations) {
944
974
  const idAttribute = attributes.find((attribute) => attribute.name === 'id');
945
975
  if (idAttribute === undefined) {
946
976
  removeId = true;
947
- context.warnings.push(`${model.name}: the model has no primary key; Sequelize would add an "id" column, so removeAttribute('id') was written. Most Sequelize features need a key.`);
977
+ context.warnings.push(`${model.name}: the model has no primary key; Sequelize would add an "id" column, so ${context.style === 'v7' ? 'noPrimaryKey: true' : "removeAttribute('id')"} was written. Most Sequelize features need a key.`);
948
978
  }
949
979
  else {
950
980
  idAttribute.entries = [
@@ -1047,6 +1077,15 @@ function renderRelation(context, plan, relation, attributes, associations) {
1047
1077
  attributes.push(attribute);
1048
1078
  const keyOption = `foreignKey: ${quote(attribute.name)}`;
1049
1079
  const toKey = relation.toField === undefined ? undefined : key.attr;
1080
+ if (context.style === 'v7') {
1081
+ addRelationV7(associations, plan, targetPlan, relation, {
1082
+ alias,
1083
+ inverse,
1084
+ key: attribute.name,
1085
+ targetKey: toKey,
1086
+ });
1087
+ return;
1088
+ }
1050
1089
  addAssociation(associations, {
1051
1090
  owner: plan.className,
1052
1091
  method: 'belongsTo',
@@ -1091,6 +1130,40 @@ function renderManyToMany(context, plan, targetPlan, relation, alias, inverse, a
1091
1130
  const targetColumn = `${isSelf ? 'to_' : ''}${toSnakeCase(target.name)}_id`;
1092
1131
  // The join table the other emitters derive; Sequelize creates it itself.
1093
1132
  const table = quote(`${model.tableName}_${relation.name}`);
1133
+ if (context.style === 'v7') {
1134
+ let otherSide = inverse;
1135
+ if (otherSide === undefined && isSelf) {
1136
+ // Sequelize 7 refuses a self-referencing many-to-many whose two sides do not have different names.
1137
+ otherSide = uniqueName(`${alias}Inverse`, plan.used);
1138
+ context.warnings.push(`${label}: Sequelize 7 needs a name for the other side of a self-referencing many-to-many relation; "${otherSide}" was added.`);
1139
+ }
1140
+ // The two halves of a self-referencing relation would give the join model's associations the same names.
1141
+ const throughNames = isSelf && otherSide !== undefined
1142
+ ? `throughAssociations: { fromSource: ${quote(uniqueName(`${alias}Links`, plan.used))}, toSource: ${quote(`${alias}Source`)}, fromTarget: ${quote(uniqueName(`${otherSide}Links`, plan.used))}, toTarget: ${quote(`${alias}Target`)} }`
1143
+ : undefined;
1144
+ addAssociationV7(associations, plan.className, {
1145
+ decorator: 'BelongsToMany',
1146
+ alias,
1147
+ target: targetPlan.className,
1148
+ many: true,
1149
+ options: compact([
1150
+ `through: { model: ${table}, timestamps: false }`,
1151
+ `foreignKey: ${quote(ownerColumn)}`,
1152
+ `otherKey: ${quote(targetColumn)}`,
1153
+ otherSide === undefined ? undefined : `inverse: ${quote(otherSide)}`,
1154
+ throughNames,
1155
+ ]),
1156
+ });
1157
+ if (otherSide !== undefined) {
1158
+ addAssociationV7(associations, targetPlan.className, {
1159
+ alias: otherSide,
1160
+ target: plan.className,
1161
+ many: true,
1162
+ options: [],
1163
+ });
1164
+ }
1165
+ return;
1166
+ }
1094
1167
  addAssociation(associations, {
1095
1168
  owner: plan.className,
1096
1169
  method: 'belongsToMany',
@@ -1349,4 +1422,156 @@ function writeDefineIndex(context) {
1349
1422
  lines.push(`${INDENT}};`, `${INDENT}Object.values(models).forEach((model) => {`, `${INDENT}${INDENT}if (model.associate) {`, `${INDENT}${INDENT}${INDENT}model.associate(models);`, `${INDENT}${INDENT}}`, `${INDENT}});`, `${INDENT}return models;`, '}', '', 'module.exports = { initModels };', '');
1350
1423
  return lines.join('\n');
1351
1424
  }
1425
+ // ---------------------------------------------------------------------------
1426
+ // Output: Sequelize 7 style (decorators)
1427
+ // ---------------------------------------------------------------------------
1428
+ function addAssociationV7(associations, owner, association) {
1429
+ const list = associations.v7.get(owner) ?? [];
1430
+ list.push(association);
1431
+ associations.v7.set(owner, list);
1432
+ }
1433
+ /**
1434
+ * Declares a relation once. The model that holds the key gets `@BelongsTo`, whose `inverse` option defines the
1435
+ * other side as well; that side is only a typed property. A self reference is declared from the `@HasMany` /
1436
+ * `@HasOne` side, because Sequelize 7 cannot build the inverse of a self-referencing `@BelongsTo`.
1437
+ */
1438
+ function addRelationV7(associations, plan, targetPlan, relation, names) {
1439
+ const entries = compact([
1440
+ `onDelete: ${quote(ON_ACTIONS[relation.onDelete])}`,
1441
+ // ON UPDATE CASCADE is what Sequelize adds to every key; only another action is written.
1442
+ relation.onUpdate !== undefined && relation.onUpdate !== 'cascade'
1443
+ ? `onUpdate: ${quote(ON_ACTIONS[relation.onUpdate])}`
1444
+ : undefined,
1445
+ ]);
1446
+ const foreignKey = `foreignKey: { name: ${quote(names.key)}, ${entries.join(', ')} }`;
1447
+ const isOne = relation.kind === 'oneToOne';
1448
+ const { alias, inverse, targetKey } = names;
1449
+ if (inverse === undefined) {
1450
+ addAssociationV7(associations, plan.className, {
1451
+ decorator: 'BelongsTo',
1452
+ alias,
1453
+ target: targetPlan.className,
1454
+ many: false,
1455
+ options: compact([
1456
+ foreignKey,
1457
+ targetKey === undefined ? undefined : `targetKey: ${quote(targetKey)}`,
1458
+ ]),
1459
+ });
1460
+ }
1461
+ else if (plan.className !== targetPlan.className) {
1462
+ addAssociationV7(associations, plan.className, {
1463
+ decorator: 'BelongsTo',
1464
+ alias,
1465
+ target: targetPlan.className,
1466
+ many: false,
1467
+ options: compact([
1468
+ foreignKey,
1469
+ targetKey === undefined ? undefined : `targetKey: ${quote(targetKey)}`,
1470
+ `inverse: { type: ${quote(isOne ? 'hasOne' : 'hasMany')}, as: ${quote(inverse)} }`,
1471
+ ]),
1472
+ });
1473
+ addAssociationV7(associations, targetPlan.className, {
1474
+ alias: inverse,
1475
+ target: plan.className,
1476
+ many: !isOne,
1477
+ options: [],
1478
+ });
1479
+ }
1480
+ else {
1481
+ addAssociationV7(associations, plan.className, {
1482
+ decorator: isOne ? 'HasOne' : 'HasMany',
1483
+ alias: inverse,
1484
+ target: plan.className,
1485
+ many: !isOne,
1486
+ options: compact([
1487
+ foreignKey,
1488
+ targetKey === undefined ? undefined : `sourceKey: ${quote(targetKey)}`,
1489
+ `inverse: ${quote(alias)}`,
1490
+ ]),
1491
+ });
1492
+ addAssociationV7(associations, plan.className, {
1493
+ alias,
1494
+ target: plan.className,
1495
+ many: false,
1496
+ options: [],
1497
+ });
1498
+ }
1499
+ }
1500
+ /** What the Sequelize 7 text writer needs to know about one model. */
1501
+ function v7ModelOf(plan, rendered, associations) {
1502
+ const nullable = new Set();
1503
+ for (const field of plan.model.fields) {
1504
+ if (field.isNullable) {
1505
+ nullable.add(plan.attrs.get(field.name) ?? field.name);
1506
+ }
1507
+ }
1508
+ for (const relation of plan.model.relations) {
1509
+ const key = plan.keys.get(relation.name);
1510
+ if (relation.isNullable && key !== undefined) {
1511
+ nullable.add(key);
1512
+ }
1513
+ }
1514
+ const attributes = rendered.attributes.map((attribute) => ({
1515
+ name: attribute.name,
1516
+ type: attribute.type,
1517
+ entries: attribute.entries,
1518
+ ts: attribute.ts,
1519
+ nullable: nullable.has(attribute.name),
1520
+ optional: attribute.optional,
1521
+ isReadonly: attribute.isReadonly,
1522
+ ...(attribute.timestamp === undefined
1523
+ ? {}
1524
+ : { timestamp: attribute.timestamp }),
1525
+ }));
1526
+ // `@CreatedAt` / `@UpdatedAt` name the columns that Sequelize adds, so the options that rename them are redundant.
1527
+ const created = attributes.some((attribute) => attribute.timestamp === 'created');
1528
+ const updated = attributes.some((attribute) => attribute.timestamp === 'updated');
1529
+ return {
1530
+ className: plan.className,
1531
+ attributes,
1532
+ tableOptions: rendered.options.filter((entry) => !(created && entry.startsWith("createdAt: '")) &&
1533
+ !(updated && entry.startsWith("updatedAt: '"))),
1534
+ indexLines: rendered.indexLines,
1535
+ associations: associations.v7.get(plan.className) ?? [],
1536
+ noPrimaryKey: rendered.removeId,
1537
+ usesSql: rendered.usesLiteral,
1538
+ };
1539
+ }
1540
+ /** The Sequelize 7 dialect package and class for the provider, with the connection options of the example. */
1541
+ function v7DialectOf(context) {
1542
+ switch (context.dialect) {
1543
+ case 'mysql':
1544
+ return {
1545
+ package: '@sequelize/mysql',
1546
+ dialect: 'MySqlDialect',
1547
+ connection: ['url: process.env.DATABASE_URL'],
1548
+ };
1549
+ case 'sqlite':
1550
+ return {
1551
+ package: '@sequelize/sqlite3',
1552
+ dialect: 'SqliteDialect',
1553
+ connection: ["storage: 'database.sqlite'"],
1554
+ };
1555
+ case 'mssql':
1556
+ return {
1557
+ package: '@sequelize/mssql',
1558
+ dialect: 'MsSqlDialect',
1559
+ connection: ["server: 'localhost'", "database: 'app'"],
1560
+ };
1561
+ default:
1562
+ return {
1563
+ package: '@sequelize/postgres',
1564
+ dialect: 'PostgresDialect',
1565
+ connection: ['url: process.env.DATABASE_URL'],
1566
+ };
1567
+ }
1568
+ }
1569
+ /** Remarks for the comment of the `index` file. */
1570
+ function v7Notes(context) {
1571
+ return context.cockroach
1572
+ ? [
1573
+ 'CockroachDB: Sequelize 7 has no CockroachDB dialect; the PostgreSQL dialect above talks to it.',
1574
+ ]
1575
+ : [];
1576
+ }
1352
1577
  //# sourceMappingURL=sequelize.js.map