@strapi/database 5.0.0-beta.1 → 5.0.0-beta.3

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 (38) hide show
  1. package/dist/entity-manager/index.d.ts.map +1 -1
  2. package/dist/entity-manager/relations-orderer.d.ts.map +1 -1
  3. package/dist/index.d.ts +3 -6
  4. package/dist/index.d.ts.map +1 -1
  5. package/dist/index.js +829 -324
  6. package/dist/index.js.map +1 -1
  7. package/dist/index.mjs +827 -322
  8. package/dist/index.mjs.map +1 -1
  9. package/dist/lifecycles/types.d.ts +1 -0
  10. package/dist/lifecycles/types.d.ts.map +1 -1
  11. package/dist/metadata/index.d.ts +1 -1
  12. package/dist/metadata/index.d.ts.map +1 -1
  13. package/dist/metadata/metadata.d.ts +2 -1
  14. package/dist/metadata/metadata.d.ts.map +1 -1
  15. package/dist/metadata/relations.d.ts.map +1 -1
  16. package/dist/migrations/internal-migrations/5.0.0-01-convert-identifiers-long-than-max-length.d.ts +3 -0
  17. package/dist/migrations/internal-migrations/5.0.0-01-convert-identifiers-long-than-max-length.d.ts.map +1 -0
  18. package/dist/migrations/internal-migrations/5.0.0-02-document-id.d.ts +3 -0
  19. package/dist/migrations/internal-migrations/5.0.0-02-document-id.d.ts.map +1 -0
  20. package/dist/migrations/internal-migrations/index.d.ts.map +1 -1
  21. package/dist/query/helpers/join.d.ts.map +1 -1
  22. package/dist/query/helpers/populate/apply.d.ts.map +1 -1
  23. package/dist/query/helpers/where.d.ts +1 -0
  24. package/dist/query/helpers/where.d.ts.map +1 -1
  25. package/dist/schema/index.d.ts +0 -3
  26. package/dist/schema/index.d.ts.map +1 -1
  27. package/dist/types/index.d.ts +3 -0
  28. package/dist/types/index.d.ts.map +1 -1
  29. package/dist/utils/identifiers/hash.d.ts +31 -0
  30. package/dist/utils/identifiers/hash.d.ts.map +1 -0
  31. package/dist/utils/identifiers/index.d.ts +107 -47
  32. package/dist/utils/identifiers/index.d.ts.map +1 -1
  33. package/dist/utils/identifiers/types.d.ts +27 -0
  34. package/dist/utils/identifiers/types.d.ts.map +1 -0
  35. package/dist/validations/relations/bidirectional.d.ts.map +1 -1
  36. package/package.json +7 -6
  37. package/dist/utils/identifiers/shortener.d.ts +0 -73
  38. package/dist/utils/identifiers/shortener.d.ts.map +0 -1
package/dist/index.js CHANGED
@@ -7,13 +7,14 @@ const _ = require("lodash/fp");
7
7
  const crypto = require("crypto");
8
8
  const crypto$1 = require("node:crypto");
9
9
  const dateFns = require("date-fns");
10
- const utils$1 = require("@strapi/utils");
10
+ const utils = require("@strapi/utils");
11
11
  const KnexBuilder = require("knex/lib/query/querybuilder");
12
12
  const KnexRaw = require("knex/lib/raw");
13
13
  const stream = require("stream");
14
14
  const node_async_hooks = require("node:async_hooks");
15
15
  const _$1 = require("lodash");
16
16
  const umzug = require("umzug");
17
+ const cuid2 = require("@paralleldrive/cuid2");
17
18
  const assert = require("assert");
18
19
  const knex = require("knex");
19
20
  const _interopDefault = (e) => e && e.__esModule ? e : { default: e };
@@ -1007,7 +1008,7 @@ const getDialect = (db) => {
1007
1008
  const dialect = new constructor(db, dialectName);
1008
1009
  return dialect;
1009
1010
  };
1010
- const debug$1 = createDebug__default.default("strapi::database");
1011
+ const debug$2 = createDebug__default.default("strapi::database");
1011
1012
  const createSchemaBuilder = (db) => {
1012
1013
  const helpers2 = createHelpers(db);
1013
1014
  return {
@@ -1033,12 +1034,12 @@ const createSchemaBuilder = (db) => {
1033
1034
  */
1034
1035
  async createTables(tables, trx) {
1035
1036
  for (const table of tables) {
1036
- debug$1(`Creating table: ${table.name}`);
1037
+ debug$2(`Creating table: ${table.name}`);
1037
1038
  const schemaBuilder = this.getSchemaBuilder(trx);
1038
1039
  await helpers2.createTable(schemaBuilder, table);
1039
1040
  }
1040
1041
  for (const table of tables) {
1041
- debug$1(`Creating table foreign keys: ${table.name}`);
1042
+ debug$2(`Creating table foreign keys: ${table.name}`);
1042
1043
  const schemaBuilder = this.getSchemaBuilder(trx);
1043
1044
  await helpers2.createTableForeignKeys(schemaBuilder, table);
1044
1045
  }
@@ -1069,18 +1070,18 @@ const createSchemaBuilder = (db) => {
1069
1070
  await this.createTables(schemaDiff.tables.added, trx);
1070
1071
  if (forceMigration) {
1071
1072
  for (const table of schemaDiff.tables.removed) {
1072
- debug$1(`Removing table foreign keys: ${table.name}`);
1073
+ debug$2(`Removing table foreign keys: ${table.name}`);
1073
1074
  const schemaBuilder = this.getSchemaBuilder(trx);
1074
1075
  await helpers2.dropTableForeignKeys(schemaBuilder, table);
1075
1076
  }
1076
1077
  for (const table of schemaDiff.tables.removed) {
1077
- debug$1(`Removing table: ${table.name}`);
1078
+ debug$2(`Removing table: ${table.name}`);
1078
1079
  const schemaBuilder = this.getSchemaBuilder(trx);
1079
1080
  await helpers2.dropTable(schemaBuilder, table);
1080
1081
  }
1081
1082
  }
1082
1083
  for (const table of schemaDiff.tables.updated) {
1083
- debug$1(`Updating table: ${table.name}`);
1084
+ debug$2(`Updating table: ${table.name}`);
1084
1085
  const schemaBuilder = this.getSchemaBuilder(trx);
1085
1086
  await helpers2.alterTable(schemaBuilder, table);
1086
1087
  }
@@ -1176,27 +1177,27 @@ const createHelpers = (db) => {
1176
1177
  const alterTable = async (schemaBuilder, table) => {
1177
1178
  await schemaBuilder.alterTable(table.name, (tableBuilder) => {
1178
1179
  for (const removedIndex of table.indexes.removed) {
1179
- debug$1(`Dropping index ${removedIndex.name}`);
1180
+ debug$2(`Dropping index ${removedIndex.name} on ${table.name}`);
1180
1181
  dropIndex(tableBuilder, removedIndex);
1181
1182
  }
1182
1183
  for (const updateddIndex of table.indexes.updated) {
1183
- debug$1(`Dropping updated index ${updateddIndex.name}`);
1184
+ debug$2(`Dropping updated index ${updateddIndex.name} on ${table.name}`);
1184
1185
  dropIndex(tableBuilder, updateddIndex.object);
1185
1186
  }
1186
1187
  for (const removedForeignKey of table.foreignKeys.removed) {
1187
- debug$1(`Dropping foreign key ${removedForeignKey.name}`);
1188
+ debug$2(`Dropping foreign key ${removedForeignKey.name} on ${table.name}`);
1188
1189
  dropForeignKey(tableBuilder, removedForeignKey);
1189
1190
  }
1190
1191
  for (const updatedForeignKey of table.foreignKeys.updated) {
1191
- debug$1(`Dropping updated foreign key ${updatedForeignKey.name}`);
1192
+ debug$2(`Dropping updated foreign key ${updatedForeignKey.name} on ${table.name}`);
1192
1193
  dropForeignKey(tableBuilder, updatedForeignKey.object);
1193
1194
  }
1194
1195
  for (const removedColumn of table.columns.removed) {
1195
- debug$1(`Dropping column ${removedColumn.name}`);
1196
+ debug$2(`Dropping column ${removedColumn.name} on ${table.name}`);
1196
1197
  dropColumn(tableBuilder, removedColumn);
1197
1198
  }
1198
1199
  for (const updatedColumn of table.columns.updated) {
1199
- debug$1(`Updating column ${updatedColumn.name}`);
1200
+ debug$2(`Updating column ${updatedColumn.name} on ${table.name}`);
1200
1201
  const { object } = updatedColumn;
1201
1202
  if (object.type === "increments") {
1202
1203
  createColumn2(tableBuilder, { ...object, type: "integer" }).alter();
@@ -1205,15 +1206,15 @@ const createHelpers = (db) => {
1205
1206
  }
1206
1207
  }
1207
1208
  for (const updatedForeignKey of table.foreignKeys.updated) {
1208
- debug$1(`Recreating updated foreign key ${updatedForeignKey.name}`);
1209
+ debug$2(`Recreating updated foreign key ${updatedForeignKey.name} on ${table.name}`);
1209
1210
  createForeignKey(tableBuilder, updatedForeignKey.object);
1210
1211
  }
1211
1212
  for (const updatedIndex of table.indexes.updated) {
1212
- debug$1(`Recreating updated index ${updatedIndex.name}`);
1213
+ debug$2(`Recreating updated index ${updatedIndex.name} on ${table.name}`);
1213
1214
  createIndex(tableBuilder, updatedIndex.object);
1214
1215
  }
1215
1216
  for (const addedColumn of table.columns.added) {
1216
- debug$1(`Creating column ${addedColumn.name}`);
1217
+ debug$2(`Creating column ${addedColumn.name} on ${table.name}`);
1217
1218
  if (addedColumn.type === "increments" && !db.dialect.canAddIncrements()) {
1218
1219
  tableBuilder.integer(addedColumn.name).unsigned();
1219
1220
  tableBuilder.primary([addedColumn.name]);
@@ -1222,11 +1223,11 @@ const createHelpers = (db) => {
1222
1223
  }
1223
1224
  }
1224
1225
  for (const addedForeignKey of table.foreignKeys.added) {
1225
- debug$1(`Creating foreign keys ${addedForeignKey.name}`);
1226
+ debug$2(`Creating foreign keys ${addedForeignKey.name} on ${table.name}`);
1226
1227
  createForeignKey(tableBuilder, addedForeignKey);
1227
1228
  }
1228
1229
  for (const addedIndex of table.indexes.added) {
1229
- debug$1(`Creating index ${addedIndex.name}`);
1230
+ debug$2(`Creating index ${addedIndex.name} on ${table.name}`);
1230
1231
  createIndex(tableBuilder, addedIndex);
1231
1232
  }
1232
1233
  });
@@ -1645,11 +1646,6 @@ const isScalar = (type) => SCALAR_TYPES.includes(type);
1645
1646
  const isRelation = (type) => type === "relation";
1646
1647
  const isScalarAttribute = (attribute) => isScalar(attribute.type);
1647
1648
  const isRelationalAttribute = (attribute) => isRelation(attribute.type);
1648
- const MAX_DB_IDENTIFIER_LENGTH = 0;
1649
- const HASH_LENGTH = 5;
1650
- const HASH_SEPARATOR = "";
1651
- const IDENTIFIER_SEPARATOR = "_";
1652
- const MIN_TOKEN_LENGTH = 3;
1653
1649
  function createHash(data, len) {
1654
1650
  if (!_.isInteger(len) || len <= 0) {
1655
1651
  throw new Error(`createHash length must be a positive integer, received ${len}`);
@@ -1657,201 +1653,334 @@ function createHash(data, len) {
1657
1653
  const hash = crypto__default$1.default.createHash("shake256", { outputLength: Math.ceil(len / 2) }).update(data);
1658
1654
  return hash.digest("hex").substring(0, len);
1659
1655
  }
1660
- function getShortenedName(name, len) {
1661
- if (!_.isInteger(len) || len <= 0) {
1662
- throw new Error(`tokenWithHash length must be a positive integer, received ${len}`);
1663
- }
1664
- if (name.length <= len) {
1665
- return name;
1666
- }
1667
- if (len < MIN_TOKEN_LENGTH + HASH_LENGTH) {
1668
- throw new Error(
1669
- `length for part of identifier too short, minimum is hash length (${HASH_LENGTH}) plus min token length (${MIN_TOKEN_LENGTH}), received ${len} for token ${name}`
1670
- );
1671
- }
1672
- const availableLength = len - HASH_LENGTH - HASH_SEPARATOR.length;
1673
- if (availableLength < MIN_TOKEN_LENGTH) {
1674
- throw new Error(
1675
- `length for part of identifier minimum is less than min token length (${MIN_TOKEN_LENGTH}), received ${len} for token ${name}`
1676
- );
1656
+ const IDENTIFIER_MAX_LENGTH = 55;
1657
+ class Identifiers {
1658
+ ID_COLUMN = "id";
1659
+ ORDER_COLUMN = "order";
1660
+ FIELD_COLUMN = "field";
1661
+ HASH_LENGTH = 5;
1662
+ HASH_SEPARATOR = "";
1663
+ // no separator is needed, we will just attach hash directly to shortened name
1664
+ IDENTIFIER_SEPARATOR = "_";
1665
+ MIN_TOKEN_LENGTH = 3;
1666
+ // the min characters required at the beginning of a name part
1667
+ // Fixed compression map for suffixes and prefixes
1668
+ #replacementMap = {
1669
+ links: "lnk",
1670
+ order_inv_fk: "oifk",
1671
+ order: "ord",
1672
+ morphs: "mph",
1673
+ index: "idx",
1674
+ inv_fk: "ifk",
1675
+ order_fk: "ofk",
1676
+ id_column_index: "idix",
1677
+ order_index: "oidx",
1678
+ unique: "uq",
1679
+ primary: "pk"
1680
+ };
1681
+ #options;
1682
+ constructor(options) {
1683
+ this.#options = options;
1677
1684
  }
1678
- return `${name.substring(0, availableLength)}${HASH_SEPARATOR}${createHash(name, HASH_LENGTH)}`;
1679
- }
1680
- function getNameFromTokens(nameTokens, maxLength = MAX_DB_IDENTIFIER_LENGTH) {
1681
- if (!_.isInteger(maxLength) || maxLength < 0) {
1682
- throw new Error("maxLength must be a positive integer or 0 (for unlimited length)");
1685
+ get replacementMap() {
1686
+ return this.#replacementMap;
1683
1687
  }
1684
- const fullLengthName = nameTokens.map((token) => _.snakeCase(token.name)).join(IDENTIFIER_SEPARATOR);
1685
- if (fullLengthName.length <= maxLength || maxLength === 0) {
1686
- return fullLengthName;
1688
+ get options() {
1689
+ return this.#options;
1687
1690
  }
1688
- const [compressible, incompressible] = _.partition(
1689
- (token) => token.compressible,
1690
- nameTokens
1691
- );
1692
- const totalIncompressibleLength = _.sumBy((token) => token.name.length)(incompressible);
1693
- const totalSeparatorsLength = nameTokens.length * IDENTIFIER_SEPARATOR.length - 1;
1694
- const available = maxLength - totalIncompressibleLength - totalSeparatorsLength;
1695
- const availablePerToken = Math.floor(available / compressible.length);
1696
- if (totalIncompressibleLength + totalSeparatorsLength > maxLength || availablePerToken < MIN_TOKEN_LENGTH) {
1697
- throw new Error("Maximum length is too small to accommodate all tokens");
1698
- }
1699
- let surplus = available % compressible.length;
1700
- const minHashedLength = HASH_LENGTH + HASH_SEPARATOR.length + MIN_TOKEN_LENGTH;
1701
- const totalLength = nameTokens.reduce((total, token) => {
1702
- if (token.compressible) {
1703
- if (token.name.length < availablePerToken) {
1704
- return total + token.name.length;
1705
- }
1706
- return total + minHashedLength;
1707
- }
1708
- return total + token.name.length;
1709
- }, nameTokens.length * IDENTIFIER_SEPARATOR.length - 1);
1710
- if (maxLength < totalLength) {
1711
- throw new Error("Maximum length is too small to accommodate all tokens");
1712
- }
1713
- let deficits = [];
1714
- compressible.forEach((token) => {
1715
- const actualLength = token.name.length;
1716
- if (actualLength < availablePerToken) {
1717
- surplus += availablePerToken - actualLength;
1718
- token.allocatedLength = actualLength;
1719
- } else {
1720
- token.allocatedLength = availablePerToken;
1721
- deficits.push(token);
1691
+ mapshortNames = (name) => {
1692
+ if (name in this.replacementMap) {
1693
+ return this.replacementMap[name];
1722
1694
  }
1723
- });
1724
- function filterAndIncreaseLength(token) {
1725
- if (token.allocatedLength < token.name.length && surplus > 0) {
1726
- token.allocatedLength += 1;
1727
- surplus -= 1;
1728
- return token.allocatedLength < token.name.length;
1695
+ return void 0;
1696
+ };
1697
+ // Generic name handler that must be used by all helper functions
1698
+ /**
1699
+ * TODO: we should be requiring snake_case inputs for all names here, but we
1700
+ * aren't and it will require some refactoring to make it work. Currently if
1701
+ * we get names 'myModel' and 'my_model' they would be converted to the same
1702
+ * final string my_model which generally works but is not entirely safe
1703
+ * */
1704
+ getName = (names, options) => {
1705
+ const tokens = ___default.default.castArray(names).map((name) => {
1706
+ return {
1707
+ name,
1708
+ compressible: true
1709
+ };
1710
+ });
1711
+ if (options?.suffix) {
1712
+ tokens.push({
1713
+ name: options.suffix,
1714
+ compressible: false,
1715
+ shortName: this.mapshortNames(options.suffix)
1716
+ });
1729
1717
  }
1730
- return false;
1731
- }
1732
- let previousSurplus = surplus + 1;
1733
- while (surplus > 0 && deficits.length > 0) {
1734
- deficits = deficits.filter((token) => filterAndIncreaseLength(token));
1735
- if (surplus === previousSurplus) {
1736
- break;
1718
+ if (options?.prefix) {
1719
+ tokens.unshift({
1720
+ name: options.prefix,
1721
+ compressible: false,
1722
+ shortName: this.mapshortNames(options.prefix)
1723
+ });
1737
1724
  }
1738
- previousSurplus = surplus;
1739
- }
1740
- const shortenedName = nameTokens.map((token) => {
1741
- if (token.compressible && "allocatedLength" in token && token.allocatedLength !== void 0) {
1742
- return getShortenedName(token.name, token.allocatedLength);
1725
+ return this.getNameFromTokens(tokens);
1726
+ };
1727
+ /**
1728
+ * TABLES
1729
+ */
1730
+ getTableName = (name, options) => {
1731
+ return this.getName(name, options);
1732
+ };
1733
+ getJoinTableName = (collectionName, attributeName, options) => {
1734
+ return this.getName([collectionName, attributeName], {
1735
+ suffix: "links",
1736
+ ...options
1737
+ });
1738
+ };
1739
+ getMorphTableName = (collectionName, attributeName, options) => {
1740
+ return this.getName([_.snakeCase(collectionName), _.snakeCase(attributeName)], {
1741
+ suffix: "morphs",
1742
+ ...options
1743
+ });
1744
+ };
1745
+ /**
1746
+ * COLUMNS
1747
+ */
1748
+ getColumnName = (attributeName, options) => {
1749
+ return this.getName(attributeName, options);
1750
+ };
1751
+ getJoinColumnAttributeIdName = (attributeName, options) => {
1752
+ return this.getName(attributeName, { suffix: "id", ...options });
1753
+ };
1754
+ getInverseJoinColumnAttributeIdName = (attributeName, options) => {
1755
+ return this.getName(_.snakeCase(attributeName), { suffix: "id", prefix: "inv", ...options });
1756
+ };
1757
+ getOrderColumnName = (singularName, options) => {
1758
+ return this.getName(singularName, { suffix: "order", ...options });
1759
+ };
1760
+ getInverseOrderColumnName = (singularName, options) => {
1761
+ return this.getName(singularName, {
1762
+ suffix: "order",
1763
+ prefix: "inv",
1764
+ ...options
1765
+ });
1766
+ };
1767
+ /**
1768
+ * Morph Join Tables
1769
+ */
1770
+ getMorphColumnJoinTableIdName = (singularName, options) => {
1771
+ return this.getName(_.snakeCase(singularName), { suffix: "id", ...options });
1772
+ };
1773
+ getMorphColumnAttributeIdName = (attributeName, options) => {
1774
+ return this.getName(_.snakeCase(attributeName), { suffix: "id", ...options });
1775
+ };
1776
+ getMorphColumnTypeName = (attributeName, options) => {
1777
+ return this.getName(_.snakeCase(attributeName), { suffix: "type", ...options });
1778
+ };
1779
+ /**
1780
+ * INDEXES
1781
+ * Note that these methods are generally used to reference full table names + attribute(s), which
1782
+ * may already be shortened strings rather than individual parts.
1783
+ * That is fine and expected to compress the previously incompressible parts of those strings,
1784
+ * because in these cases the relevant information is the table name and we can't really do
1785
+ * any better; shortening the individual parts again might make it even more confusing.
1786
+ *
1787
+ * So for example, the fk for the table `mytable_myattr4567d_localizations` will become
1788
+ * mytable_myattr4567d_loc63bf2_fk
1789
+ */
1790
+ // base index types
1791
+ getIndexName = (names, options) => {
1792
+ return this.getName(names, { suffix: "index", ...options });
1793
+ };
1794
+ getFkIndexName = (names, options) => {
1795
+ return this.getName(names, { suffix: "fk", ...options });
1796
+ };
1797
+ getUniqueIndexName = (names, options) => {
1798
+ return this.getName(names, { suffix: "unique", ...options });
1799
+ };
1800
+ getPrimaryIndexName = (names, options) => {
1801
+ return this.getName(names, { suffix: "primary", ...options });
1802
+ };
1803
+ // custom index types
1804
+ getInverseFkIndexName = (names, options) => {
1805
+ return this.getName(names, { suffix: "inv_fk", ...options });
1806
+ };
1807
+ getOrderFkIndexName = (names, options) => {
1808
+ return this.getName(names, { suffix: "order_fk", ...options });
1809
+ };
1810
+ getOrderInverseFkIndexName = (names, options) => {
1811
+ return this.getName(names, { suffix: "order_inv_fk", ...options });
1812
+ };
1813
+ getIdColumnIndexName = (names, options) => {
1814
+ return this.getName(names, { suffix: "id_column_index", ...options });
1815
+ };
1816
+ getOrderIndexName = (names, options) => {
1817
+ return this.getName(names, { suffix: "order_index", ...options });
1818
+ };
1819
+ /**
1820
+ * Generates a string with a max length, appending a hash at the end if necessary to keep it unique
1821
+ *
1822
+ * @example
1823
+ * // if we have strings such as "longstring1" and "longstring2" with a max length of 9,
1824
+ * // we don't want to end up with "longstrin" and "longstrin"
1825
+ * // we want something such as "longs0b23" and "longs953f"
1826
+ * const token1 = generateToken("longstring1", 9); // "longs0b23"
1827
+ * const token2 = generateToken("longstring2", 9); // "longs953f"
1828
+ *
1829
+ * @param name - The base name
1830
+ * @param len - The desired length of the token.
1831
+ * @returns The generated token with hash.
1832
+ * @throws Error if the length is not a positive integer, or if the length is too short for the token.
1833
+ * @internal
1834
+ */
1835
+ getShortenedName = (name, len) => {
1836
+ if (!_.isInteger(len) || len <= 0) {
1837
+ throw new Error(`tokenWithHash length must be a positive integer, received ${len}`);
1743
1838
  }
1744
- return token.name;
1745
- }).join(IDENTIFIER_SEPARATOR);
1746
- if (shortenedName.length > maxLength) {
1747
- throw new Error(
1748
- `name shortening failed to generate a name of the correct maxLength; name ${shortenedName}`
1839
+ if (name.length <= len) {
1840
+ return name;
1841
+ }
1842
+ if (len < this.MIN_TOKEN_LENGTH + this.HASH_LENGTH) {
1843
+ throw new Error(
1844
+ `length for part of identifier too short, minimum is hash length (${this.HASH_LENGTH}) plus min token length (${this.MIN_TOKEN_LENGTH}), received ${len} for token ${name}`
1845
+ );
1846
+ }
1847
+ const availableLength = len - this.HASH_LENGTH - this.HASH_SEPARATOR.length;
1848
+ if (availableLength < this.MIN_TOKEN_LENGTH) {
1849
+ throw new Error(
1850
+ `length for part of identifier minimum is less than min token length (${this.MIN_TOKEN_LENGTH}), received ${len} for token ${name}`
1851
+ );
1852
+ }
1853
+ return `${name.substring(0, availableLength)}${this.HASH_SEPARATOR}${createHash(
1854
+ name,
1855
+ this.HASH_LENGTH
1856
+ )}`;
1857
+ };
1858
+ /**
1859
+ * Constructs a name from an array of name tokens within a specified maximum length. It ensures the final name does not exceed
1860
+ * this limit by selectively compressing tokens marked as compressible. If the name exceeds the maximum length and cannot be
1861
+ * compressed sufficiently, an error is thrown. This function supports dynamic adjustment of token lengths to fit within the
1862
+ * maxLength constraint (that is, it will always make use of all available space), while also ensuring the preservation of
1863
+ * incompressible tokens.
1864
+ * @internal
1865
+ */
1866
+ getNameFromTokens = (nameTokens) => {
1867
+ const { maxLength } = this.options;
1868
+ if (!_.isInteger(maxLength) || maxLength < 0) {
1869
+ throw new Error("maxLength must be a positive integer or 0 (for unlimited length)");
1870
+ }
1871
+ const unshortenedName = nameTokens.map((token) => {
1872
+ return token.name;
1873
+ }).join(this.IDENTIFIER_SEPARATOR);
1874
+ if (maxLength === 0) {
1875
+ this.setUnshortenedName(unshortenedName, unshortenedName);
1876
+ return unshortenedName;
1877
+ }
1878
+ const fullLengthName = nameTokens.map((token) => {
1879
+ if (token.compressible) {
1880
+ return token.name;
1881
+ }
1882
+ return token.shortName ?? token.name;
1883
+ }).join(this.IDENTIFIER_SEPARATOR);
1884
+ if (fullLengthName.length <= maxLength) {
1885
+ this.setUnshortenedName(fullLengthName, unshortenedName);
1886
+ return fullLengthName;
1887
+ }
1888
+ const [compressible, incompressible] = _.partition(
1889
+ (token) => token.compressible,
1890
+ nameTokens
1749
1891
  );
1750
- }
1751
- return shortenedName;
1892
+ const totalIncompressibleLength = _.sumBy(
1893
+ (token) => token.compressible === false && token.shortName !== void 0 ? token.shortName.length : token.name.length
1894
+ )(incompressible);
1895
+ const totalSeparatorsLength = nameTokens.length * this.IDENTIFIER_SEPARATOR.length - 1;
1896
+ const available = maxLength - totalIncompressibleLength - totalSeparatorsLength;
1897
+ const availablePerToken = Math.floor(available / compressible.length);
1898
+ if (totalIncompressibleLength + totalSeparatorsLength > maxLength || availablePerToken < this.MIN_TOKEN_LENGTH) {
1899
+ throw new Error("Maximum length is too small to accommodate all tokens");
1900
+ }
1901
+ let surplus = available % compressible.length;
1902
+ const minHashedLength = this.HASH_LENGTH + this.HASH_SEPARATOR.length + this.MIN_TOKEN_LENGTH;
1903
+ const totalLength = nameTokens.reduce(
1904
+ (total, token) => {
1905
+ if (token.compressible) {
1906
+ if (token.name.length < availablePerToken) {
1907
+ return total + token.name.length;
1908
+ }
1909
+ return total + minHashedLength;
1910
+ }
1911
+ const tokenName = token.shortName ?? token.name;
1912
+ return total + tokenName.length;
1913
+ },
1914
+ nameTokens.length * this.IDENTIFIER_SEPARATOR.length - 1
1915
+ );
1916
+ if (maxLength < totalLength) {
1917
+ throw new Error("Maximum length is too small to accommodate all tokens");
1918
+ }
1919
+ let deficits = [];
1920
+ compressible.forEach((token) => {
1921
+ const actualLength = token.name.length;
1922
+ if (actualLength < availablePerToken) {
1923
+ surplus += availablePerToken - actualLength;
1924
+ token.allocatedLength = actualLength;
1925
+ } else {
1926
+ token.allocatedLength = availablePerToken;
1927
+ deficits.push(token);
1928
+ }
1929
+ });
1930
+ function filterAndIncreaseLength(token) {
1931
+ if (token.allocatedLength < token.name.length && surplus > 0) {
1932
+ token.allocatedLength += 1;
1933
+ surplus -= 1;
1934
+ return token.allocatedLength < token.name.length;
1935
+ }
1936
+ return false;
1937
+ }
1938
+ let previousSurplus = surplus + 1;
1939
+ while (surplus > 0 && deficits.length > 0) {
1940
+ deficits = deficits.filter((token) => filterAndIncreaseLength(token));
1941
+ if (surplus === previousSurplus) {
1942
+ break;
1943
+ }
1944
+ previousSurplus = surplus;
1945
+ }
1946
+ const shortenedName = nameTokens.map((token) => {
1947
+ if (token.compressible && "allocatedLength" in token && token.allocatedLength !== void 0) {
1948
+ return this.getShortenedName(token.name, token.allocatedLength);
1949
+ }
1950
+ if (token.compressible === false && token.shortName) {
1951
+ return token.shortName;
1952
+ }
1953
+ return token.name;
1954
+ }).join(this.IDENTIFIER_SEPARATOR);
1955
+ if (shortenedName.length > maxLength) {
1956
+ throw new Error(
1957
+ `name shortening failed to generate a name of the correct maxLength; name ${shortenedName}`
1958
+ );
1959
+ }
1960
+ this.setUnshortenedName(shortenedName, unshortenedName);
1961
+ return shortenedName;
1962
+ };
1963
+ // We need to be able to find the full-length name for any shortened name, primarily for migration purposes
1964
+ // Therefore we store every name that passes through so we can retrieve the original later
1965
+ nameMap = /* @__PURE__ */ new Map();
1966
+ getUnshortenedName = (shortName) => {
1967
+ return this.nameMap.get(this.serializeKey(shortName)) ?? shortName;
1968
+ };
1969
+ setUnshortenedName = (shortName, fullName) => {
1970
+ if (this.nameMap.get(this.serializeKey(shortName)) && shortName === fullName) {
1971
+ return;
1972
+ }
1973
+ this.nameMap.set(this.serializeKey(shortName), fullName);
1974
+ };
1975
+ serializeKey = (shortName) => {
1976
+ return `${shortName}.${this.options.maxLength}`;
1977
+ };
1752
1978
  }
1753
- const ID_COLUMN = "id";
1754
- const ORDER_COLUMN = "order";
1755
- const FIELD_COLUMN = "field";
1756
- const getName = (names, options) => {
1757
- const tokens = ___default.default.castArray(names).map((name) => {
1758
- return {
1759
- name,
1760
- compressible: true
1761
- };
1762
- });
1763
- if (options?.suffix) {
1764
- tokens.push({ name: options.suffix, compressible: false });
1765
- }
1766
- if (options?.prefix) {
1767
- tokens.unshift({ name: options.prefix, compressible: false });
1768
- }
1769
- const maxLength = options?.maxLength ?? MAX_DB_IDENTIFIER_LENGTH;
1770
- return getNameFromTokens(tokens, maxLength);
1771
- };
1772
- const getTableName = (name, options) => {
1773
- return getName(name, options);
1774
- };
1775
- const getJoinTableName = (collectionName, attributeName, options) => {
1776
- return getName([collectionName, attributeName], { suffix: "links", ...options });
1777
- };
1778
- const getMorphTableName = (collectionName, attributeName, options) => {
1779
- return getName([collectionName, attributeName], { suffix: "morphs", ...options });
1780
- };
1781
- const getColumnName = (attributeName, options) => {
1782
- return getName(attributeName, options);
1783
- };
1784
- const getJoinColumnAttributeIdName = (attributeName, options) => {
1785
- return getName(attributeName, { suffix: "id", ...options });
1786
- };
1787
- const getInverseJoinColumnAttributeIdName = (attributeName, options) => {
1788
- return getName(attributeName, { suffix: "id", prefix: "inv", ...options });
1789
- };
1790
- const getOrderColumnName = (singularName, options) => {
1791
- return getName(singularName, { suffix: "order", ...options });
1792
- };
1793
- const getInverseOrderColumnName = (singularName, options) => {
1794
- return getName(singularName, { suffix: "order", prefix: "inv", ...options });
1795
- };
1796
- const getMorphColumnJoinTableIdName = (singularName, options) => {
1797
- return getName(singularName, { suffix: "id", ...options });
1798
- };
1799
- const getMorphColumnAttributeIdName = (attributeName, options) => {
1800
- return getName(attributeName, { suffix: "id", ...options });
1801
- };
1802
- const getMorphColumnTypeName = (attributeName, options) => {
1803
- return getName(attributeName, { suffix: "type", ...options });
1804
- };
1805
- const getIndexName = (names, options) => {
1806
- return getName(names, { suffix: "index", ...options });
1807
- };
1808
- const getFkIndexName = (names, options) => {
1809
- return getName(names, { suffix: "fk", ...options });
1810
- };
1811
- const getInverseFkIndexName = (names, options) => {
1812
- return getName(names, { suffix: "inv_fk", ...options });
1813
- };
1814
- const getOrderFkIndexName = (names, options) => {
1815
- return getName(names, { suffix: "order_fk", ...options });
1816
- };
1817
- const getOrderInverseFkIndexName = (names, options) => {
1818
- return getName(names, { suffix: "order_inv_fk", ...options });
1819
- };
1820
- const getUniqueIndexName = (names, options) => {
1821
- return getName(names, { suffix: "unique", ...options });
1822
- };
1823
- const getPrimaryIndexName = (names) => {
1824
- return getName(names, { suffix: "primary" });
1825
- };
1826
- const identifiers = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
1827
- __proto__: null,
1828
- FIELD_COLUMN,
1829
- ID_COLUMN,
1830
- ORDER_COLUMN,
1831
- getColumnName,
1832
- getFkIndexName,
1833
- getIndexName,
1834
- getInverseFkIndexName,
1835
- getInverseJoinColumnAttributeIdName,
1836
- getInverseOrderColumnName,
1837
- getJoinColumnAttributeIdName,
1838
- getJoinTableName,
1839
- getMorphColumnAttributeIdName,
1840
- getMorphColumnJoinTableIdName,
1841
- getMorphColumnTypeName,
1842
- getMorphTableName,
1843
- getName,
1844
- getOrderColumnName,
1845
- getOrderFkIndexName,
1846
- getOrderInverseFkIndexName,
1847
- getPrimaryIndexName,
1848
- getTableName,
1849
- getUniqueIndexName
1850
- }, Symbol.toStringTag, { value: "Module" }));
1979
+ const identifiers = new Identifiers({ maxLength: IDENTIFIER_MAX_LENGTH });
1851
1980
  const createColumn = (name, attribute) => {
1852
1981
  const { type, args = [], ...opts } = getColumnType(attribute);
1853
1982
  return {
1854
- name,
1983
+ name: identifiers.getName(name),
1855
1984
  type,
1856
1985
  args,
1857
1986
  defaultTo: null,
@@ -1873,8 +2002,8 @@ const createTable = (meta) => {
1873
2002
  if (attribute.type === "relation") {
1874
2003
  if ("morphColumn" in attribute && attribute.morphColumn && attribute.owner) {
1875
2004
  const { idColumn, typeColumn } = attribute.morphColumn;
1876
- const idColumnName = getName(idColumn.name);
1877
- const typeColumnName = getName(typeColumn.name);
2005
+ const idColumnName = identifiers.getName(idColumn.name);
2006
+ const typeColumnName = identifiers.getName(typeColumn.name);
1878
2007
  table.columns.push(
1879
2008
  createColumn(idColumnName, {
1880
2009
  type: "integer",
@@ -1891,7 +2020,7 @@ const createTable = (meta) => {
1891
2020
  referencedTable,
1892
2021
  columnType = "integer"
1893
2022
  } = attribute.joinColumn;
1894
- const columnName = getName(columnNameFull);
2023
+ const columnName = identifiers.getName(columnNameFull);
1895
2024
  const column = createColumn(columnName, {
1896
2025
  // TODO: find the column type automatically, or allow passing all the column params
1897
2026
  type: columnType,
@@ -1900,7 +2029,7 @@ const createTable = (meta) => {
1900
2029
  }
1901
2030
  });
1902
2031
  table.columns.push(column);
1903
- const fkName = getFkIndexName([table.name, columnName]);
2032
+ const fkName = identifiers.getFkIndexName([table.name, columnName]);
1904
2033
  table.foreignKeys.push({
1905
2034
  name: fkName,
1906
2035
  columns: [column.name],
@@ -1915,19 +2044,19 @@ const createTable = (meta) => {
1915
2044
  });
1916
2045
  }
1917
2046
  } else if (isScalarAttribute(attribute)) {
1918
- const columnName = getName(attribute.columnName || key);
2047
+ const columnName = identifiers.getName(attribute.columnName || key);
1919
2048
  const column = createColumn(columnName, attribute);
1920
2049
  if (column.unique) {
1921
2050
  table.indexes.push({
1922
2051
  type: "unique",
1923
- name: getUniqueIndexName([table.name, column.name]),
2052
+ name: identifiers.getUniqueIndexName([table.name, column.name]),
1924
2053
  columns: [columnName]
1925
2054
  });
1926
2055
  }
1927
2056
  if (column.primary) {
1928
2057
  table.indexes.push({
1929
2058
  type: "primary",
1930
- name: getPrimaryIndexName([table.name, column.name]),
2059
+ name: identifiers.getPrimaryIndexName([table.name, column.name]),
1931
2060
  columns: [columnName]
1932
2061
  });
1933
2062
  }
@@ -2027,12 +2156,13 @@ const metadataToSchema = (metadata) => {
2027
2156
  });
2028
2157
  return schema;
2029
2158
  };
2030
- const debug = createDebug__default.default("strapi::database");
2159
+ const debug$1 = createDebug__default.default("strapi::database");
2031
2160
  const createSchemaProvider = (db) => {
2032
2161
  const state = {};
2033
2162
  return {
2034
2163
  get schema() {
2035
2164
  if (!state.schema) {
2165
+ debug$1("Converting metadata to database schema");
2036
2166
  state.schema = metadataToSchema(db.metadata);
2037
2167
  }
2038
2168
  return state.schema;
@@ -2044,7 +2174,7 @@ const createSchemaProvider = (db) => {
2044
2174
  * Drops the database schema
2045
2175
  */
2046
2176
  async drop() {
2047
- debug("Dropping database schema");
2177
+ debug$1("Dropping database schema");
2048
2178
  const DBSchema = await db.dialect.schemaInspector.getSchema();
2049
2179
  await this.builder.dropSchema(DBSchema);
2050
2180
  },
@@ -2052,19 +2182,19 @@ const createSchemaProvider = (db) => {
2052
2182
  * Creates the database schema
2053
2183
  */
2054
2184
  async create() {
2055
- debug("Created database schema");
2185
+ debug$1("Created database schema");
2056
2186
  await this.builder.createSchema(this.schema);
2057
2187
  },
2058
2188
  /**
2059
2189
  * Resets the database schema
2060
2190
  */
2061
2191
  async reset() {
2062
- debug("Resetting database schema");
2192
+ debug$1("Resetting database schema");
2063
2193
  await this.drop();
2064
2194
  await this.create();
2065
2195
  },
2066
2196
  async syncSchema() {
2067
- debug("Synchronizing database schema");
2197
+ debug$1("Synchronizing database schema");
2068
2198
  const DBSchema = await db.dialect.schemaInspector.getSchema();
2069
2199
  const { status, diff } = await this.schemaDiff.diff(DBSchema, this.schema);
2070
2200
  if (status === "CHANGED") {
@@ -2077,28 +2207,28 @@ const createSchemaProvider = (db) => {
2077
2207
  // TODO: Allow keeping extra indexes / extra tables / extra columns (globally or on a per table basis)
2078
2208
  async sync() {
2079
2209
  if (await db.migrations.shouldRun()) {
2080
- debug("Found migrations to run");
2210
+ debug$1("Found migrations to run");
2081
2211
  await db.migrations.up();
2082
2212
  return this.syncSchema();
2083
2213
  }
2084
2214
  const oldSchema = await this.schemaStorage.read();
2085
2215
  if (!oldSchema) {
2086
- debug("Schema not persisted yet");
2216
+ debug$1("Schema not persisted yet");
2087
2217
  return this.syncSchema();
2088
2218
  }
2089
2219
  const { hash: oldHash } = oldSchema;
2090
2220
  const hash = await this.schemaStorage.hashSchema(this.schema);
2091
2221
  if (oldHash !== hash) {
2092
- debug("Schema changed");
2222
+ debug$1("Schema changed");
2093
2223
  return this.syncSchema();
2094
2224
  }
2095
- debug("Schema unchanged");
2225
+ debug$1("Schema unchanged");
2096
2226
  }
2097
2227
  };
2098
2228
  };
2099
- const ID = ID_COLUMN;
2100
- const ORDER = ORDER_COLUMN;
2101
- const FIELD = FIELD_COLUMN;
2229
+ const ID = identifiers.ID_COLUMN;
2230
+ const ORDER = identifiers.ORDER_COLUMN;
2231
+ const FIELD = identifiers.FIELD_COLUMN;
2102
2232
  const hasInversedBy = (attr) => "inversedBy" in attr;
2103
2233
  const hasMappedBy = (attr) => "mappedBy" in attr;
2104
2234
  const isOneToAny = (attribute) => ["oneToOne", "oneToMany"].includes(attribute.relation);
@@ -2119,7 +2249,7 @@ const createOneToOne = (attributeName, attribute, meta, metadata) => {
2119
2249
  meta
2120
2250
  });
2121
2251
  } else {
2122
- createJoinColum(metadata, {
2252
+ createJoinColumn(metadata, {
2123
2253
  attribute,
2124
2254
  attributeName,
2125
2255
  meta
@@ -2149,7 +2279,7 @@ const createManyToOne = (attributeName, attribute, meta, metadata) => {
2149
2279
  meta
2150
2280
  });
2151
2281
  } else {
2152
- createJoinColum(metadata, {
2282
+ createJoinColumn(metadata, {
2153
2283
  attribute,
2154
2284
  attributeName,
2155
2285
  meta
@@ -2166,15 +2296,11 @@ const createManyToMany = (attributeName, attribute, meta, metadata) => {
2166
2296
  }
2167
2297
  };
2168
2298
  const createMorphToOne = (attributeName, attribute) => {
2169
- const idColumnName = getJoinColumnAttributeIdName("target");
2170
- const typeColumnName = getMorphColumnTypeName("target");
2171
- if ("morphColumn" in attribute && attribute.morphColumn) {
2172
- return;
2173
- }
2299
+ const idColumnName = identifiers.getJoinColumnAttributeIdName("target");
2300
+ const typeColumnName = identifiers.getMorphColumnTypeName("target");
2174
2301
  Object.assign(attribute, {
2175
2302
  owner: true,
2176
- morphColumn: {
2177
- // TODO: add referenced column
2303
+ morphColumn: attribute.morphColumn ?? {
2178
2304
  typeColumn: {
2179
2305
  name: typeColumnName
2180
2306
  },
@@ -2189,11 +2315,11 @@ const createMorphToMany = (attributeName, attribute, meta, metadata) => {
2189
2315
  if ("joinTable" in attribute && attribute.joinTable) {
2190
2316
  return;
2191
2317
  }
2192
- const joinTableName = getMorphTableName(meta.tableName, attributeName);
2193
- const joinColumnName = getMorphColumnJoinTableIdName(meta.singularName);
2194
- const idColumnName = getMorphColumnAttributeIdName(attributeName);
2195
- const typeColumnName = getMorphColumnTypeName(attributeName);
2196
- const fkIndexName = getFkIndexName(joinTableName);
2318
+ const joinTableName = identifiers.getMorphTableName(meta.tableName, attributeName);
2319
+ const joinColumnName = identifiers.getMorphColumnJoinTableIdName(_.snakeCase(meta.singularName));
2320
+ const idColumnName = identifiers.getMorphColumnAttributeIdName(attributeName);
2321
+ const typeColumnName = identifiers.getMorphColumnTypeName(attributeName);
2322
+ const fkIndexName = identifiers.getFkIndexName(joinTableName);
2197
2323
  metadata.add({
2198
2324
  singularName: joinTableName,
2199
2325
  uid: joinTableName,
@@ -2206,7 +2332,9 @@ const createMorphToMany = (attributeName, attribute, meta, metadata) => {
2206
2332
  type: "integer",
2207
2333
  column: {
2208
2334
  unsigned: true
2209
- }
2335
+ },
2336
+ // This must be set explicitly so that it is used instead of shortening the attribute name, which is already shortened
2337
+ columnName: joinColumnName
2210
2338
  },
2211
2339
  [idColumnName]: {
2212
2340
  type: "integer",
@@ -2233,11 +2361,11 @@ const createMorphToMany = (attributeName, attribute, meta, metadata) => {
2233
2361
  columns: [joinColumnName]
2234
2362
  },
2235
2363
  {
2236
- name: `${joinTableName}_order_index`,
2364
+ name: identifiers.getOrderIndexName(joinTableName),
2237
2365
  columns: [ORDER]
2238
2366
  },
2239
2367
  {
2240
- name: `${joinTableName}_id_column_index`,
2368
+ name: identifiers.getIdColumnIndexName(joinTableName),
2241
2369
  columns: [idColumnName]
2242
2370
  }
2243
2371
  ],
@@ -2246,7 +2374,7 @@ const createMorphToMany = (attributeName, attribute, meta, metadata) => {
2246
2374
  name: fkIndexName,
2247
2375
  columns: [joinColumnName],
2248
2376
  referencedColumns: [ID],
2249
- referencedTable: getTableName(meta.tableName),
2377
+ referencedTable: meta.tableName,
2250
2378
  onDelete: "CASCADE"
2251
2379
  }
2252
2380
  ],
@@ -2293,12 +2421,12 @@ const createMorphMany = (attributeName, attribute, meta, metadata) => {
2293
2421
  throw new Error(`Morph target attribute not found. Looking for ${attribute.morphBy}`);
2294
2422
  }
2295
2423
  };
2296
- const createJoinColum = (metadata, { attribute, attributeName }) => {
2424
+ const createJoinColumn = (metadata, { attribute, attributeName }) => {
2297
2425
  const targetMeta = metadata.get(attribute.target);
2298
2426
  if (!targetMeta) {
2299
2427
  throw new Error(`Unknown target ${attribute.target}`);
2300
2428
  }
2301
- const joinColumnName = ___default.default.snakeCase(`${attributeName}_id`);
2429
+ const joinColumnName = identifiers.getJoinColumnAttributeIdName(_.snakeCase(attributeName));
2302
2430
  const joinColumn = {
2303
2431
  name: joinColumnName,
2304
2432
  referencedColumn: ID,
@@ -2313,7 +2441,7 @@ const createJoinColum = (metadata, { attribute, attributeName }) => {
2313
2441
  Object.assign(inverseAttribute, {
2314
2442
  joinColumn: {
2315
2443
  name: joinColumn.referencedColumn,
2316
- referencedColumn: joinColumn.name
2444
+ referencedColumn: joinColumnName
2317
2445
  }
2318
2446
  });
2319
2447
  }
@@ -2326,21 +2454,26 @@ const createJoinTable = (metadata, { attributeName, attribute, meta }) => {
2326
2454
  if ("joinTable" in attribute && attribute.joinTable) {
2327
2455
  return;
2328
2456
  }
2329
- const joinTableName = getJoinTableName(meta.tableName, attributeName);
2330
- const joinColumnName = getJoinColumnAttributeIdName(meta.singularName);
2331
- let inverseJoinColumnName = getJoinColumnAttributeIdName(targetMeta.singularName);
2457
+ const joinTableName = identifiers.getJoinTableName(
2458
+ _.snakeCase(meta.tableName),
2459
+ _.snakeCase(attributeName)
2460
+ );
2461
+ const joinColumnName = identifiers.getJoinColumnAttributeIdName(_.snakeCase(meta.singularName));
2462
+ let inverseJoinColumnName = identifiers.getJoinColumnAttributeIdName(
2463
+ _.snakeCase(targetMeta.singularName)
2464
+ );
2332
2465
  if (joinColumnName === inverseJoinColumnName) {
2333
- inverseJoinColumnName = getInverseJoinColumnAttributeIdName(
2334
- targetMeta.singularName
2466
+ inverseJoinColumnName = identifiers.getInverseJoinColumnAttributeIdName(
2467
+ _.snakeCase(targetMeta.singularName)
2335
2468
  );
2336
2469
  }
2337
- const orderColumnName = getOrderColumnName(targetMeta.singularName);
2338
- let inverseOrderColumnName = getOrderColumnName(meta.singularName);
2470
+ const orderColumnName = identifiers.getOrderColumnName(_.snakeCase(targetMeta.singularName));
2471
+ let inverseOrderColumnName = identifiers.getOrderColumnName(_.snakeCase(meta.singularName));
2339
2472
  if (attribute.relation === "manyToMany" && orderColumnName === inverseOrderColumnName) {
2340
- inverseOrderColumnName = getInverseOrderColumnName(meta.singularName);
2473
+ inverseOrderColumnName = identifiers.getInverseOrderColumnName(_.snakeCase(meta.singularName));
2341
2474
  }
2342
- const fkIndexName = getFkIndexName(joinTableName);
2343
- const invFkIndexName = getInverseFkIndexName(joinTableName);
2475
+ const fkIndexName = identifiers.getFkIndexName(joinTableName);
2476
+ const invFkIndexName = identifiers.getInverseFkIndexName(joinTableName);
2344
2477
  const metadataSchema = {
2345
2478
  singularName: joinTableName,
2346
2479
  uid: joinTableName,
@@ -2353,13 +2486,17 @@ const createJoinTable = (metadata, { attributeName, attribute, meta }) => {
2353
2486
  type: "integer",
2354
2487
  column: {
2355
2488
  unsigned: true
2356
- }
2489
+ },
2490
+ // This must be set explicitly so that it is used instead of shortening the attribute name, which is already shortened
2491
+ columnName: joinColumnName
2357
2492
  },
2358
2493
  [inverseJoinColumnName]: {
2359
2494
  type: "integer",
2360
2495
  column: {
2361
2496
  unsigned: true
2362
- }
2497
+ },
2498
+ // This must be set explicitly so that it is used instead of shortening the attribute name, which is already shortened
2499
+ columnName: inverseJoinColumnName
2363
2500
  }
2364
2501
  // TODO: add extra pivot attributes -> user should use an intermediate entity
2365
2502
  },
@@ -2373,7 +2510,7 @@ const createJoinTable = (metadata, { attributeName, attribute, meta }) => {
2373
2510
  columns: [inverseJoinColumnName]
2374
2511
  },
2375
2512
  {
2376
- name: getUniqueIndexName(joinTableName),
2513
+ name: identifiers.getUniqueIndexName(joinTableName),
2377
2514
  columns: [joinColumnName, inverseJoinColumnName],
2378
2515
  type: "unique"
2379
2516
  }
@@ -2384,7 +2521,6 @@ const createJoinTable = (metadata, { attributeName, attribute, meta }) => {
2384
2521
  columns: [joinColumnName],
2385
2522
  referencedColumns: [ID],
2386
2523
  referencedTable: meta.tableName,
2387
- // TODO: does this need to be wrapped or do we trust meta.tableName to be the right shortened version?
2388
2524
  onDelete: "CASCADE"
2389
2525
  },
2390
2526
  {
@@ -2392,7 +2528,6 @@ const createJoinTable = (metadata, { attributeName, attribute, meta }) => {
2392
2528
  columns: [inverseJoinColumnName],
2393
2529
  referencedColumns: [ID],
2394
2530
  referencedTable: targetMeta.tableName,
2395
- // TODO: does this need to be wrapped or do we trust meta.tableName to be the right shortened version?
2396
2531
  onDelete: "CASCADE"
2397
2532
  }
2398
2533
  ],
@@ -2422,8 +2557,7 @@ const createJoinTable = (metadata, { attributeName, attribute, meta }) => {
2422
2557
  }
2423
2558
  };
2424
2559
  metadataSchema.indexes.push({
2425
- name: getOrderFkIndexName(joinTableName),
2426
- // TODO: should we send joinTableName as parts?
2560
+ name: identifiers.getOrderFkIndexName(joinTableName),
2427
2561
  columns: [orderColumnName]
2428
2562
  });
2429
2563
  joinTable.orderColumnName = orderColumnName;
@@ -2438,7 +2572,7 @@ const createJoinTable = (metadata, { attributeName, attribute, meta }) => {
2438
2572
  }
2439
2573
  };
2440
2574
  metadataSchema.indexes.push({
2441
- name: getOrderInverseFkIndexName(joinTableName),
2575
+ name: identifiers.getOrderInverseFkIndexName(joinTableName),
2442
2576
  columns: [inverseOrderColumnName]
2443
2577
  });
2444
2578
  joinTable.inverseOrderColumnName = inverseOrderColumnName;
@@ -2496,6 +2630,12 @@ const createRelation = (attributeName, attribute, meta, metadata) => {
2496
2630
  }
2497
2631
  };
2498
2632
  class Metadata extends Map {
2633
+ // TODO: we expose the global identifiers in this way so that in the future we can instantiate our own
2634
+ // However, it should NOT be done until all the methods used by metadata can be part of this metadata object
2635
+ // and access this one; currently they all access the global identifiers directly.
2636
+ get identifiers() {
2637
+ return identifiers;
2638
+ }
2499
2639
  get(key) {
2500
2640
  if (!super.has(key)) {
2501
2641
  throw new Error(`Metadata for "${key}" not found`);
@@ -2519,10 +2659,12 @@ class Metadata extends Map {
2519
2659
  seenTables.set(meta.tableName, true);
2520
2660
  }
2521
2661
  }
2522
- loadModels(models = []) {
2523
- for (const model of ___default.default.cloneDeep(models)) {
2662
+ loadModels(models) {
2663
+ for (const model of _.cloneDeep(models ?? [])) {
2664
+ const tableName = identifiers.getTableName(model.tableName);
2524
2665
  this.add({
2525
2666
  ...model,
2667
+ tableName,
2526
2668
  attributes: {
2527
2669
  ...model.attributes
2528
2670
  },
@@ -2563,10 +2705,13 @@ class Metadata extends Map {
2563
2705
  }
2564
2706
  }
2565
2707
  const createAttribute = (attributeName, attribute) => {
2566
- const columnName = getColumnName(attributeName);
2708
+ if ("columnName" in attribute && attribute.columnName) {
2709
+ return;
2710
+ }
2711
+ const columnName = identifiers.getColumnName(_.snakeCase(attributeName));
2567
2712
  Object.assign(attribute, { columnName });
2568
2713
  };
2569
- const createMetadata = (models = []) => {
2714
+ const createMetadata = (models) => {
2570
2715
  const metadata = new Metadata();
2571
2716
  if (models.length) {
2572
2717
  metadata.loadModels(models);
@@ -2917,11 +3062,54 @@ const createPivotJoin = (ctx, { alias, refAlias, joinTable, targetMeta }) => {
2917
3062
  return subAlias;
2918
3063
  };
2919
3064
  const createJoin = (ctx, { alias, refAlias, attributeName, attribute }) => {
2920
- const { db, qb } = ctx;
3065
+ const { db, qb, uid } = ctx;
2921
3066
  if (attribute.type !== "relation") {
2922
3067
  throw new Error(`Cannot join on non relational field ${attributeName}`);
2923
3068
  }
2924
3069
  const targetMeta = db.metadata.get(attribute.target);
3070
+ if (["morphOne", "morphMany"].includes(attribute.relation)) {
3071
+ const targetAttribute = targetMeta.attributes[attribute.morphBy];
3072
+ const { joinTable: joinTable2, morphColumn } = targetAttribute;
3073
+ if (morphColumn) {
3074
+ const subAlias = refAlias || qb.getAlias();
3075
+ qb.join({
3076
+ alias: subAlias,
3077
+ referencedTable: targetMeta.tableName,
3078
+ referencedColumn: morphColumn.idColumn.name,
3079
+ rootColumn: morphColumn.idColumn.referencedColumn,
3080
+ rootTable: alias,
3081
+ on: {
3082
+ [morphColumn.typeColumn.name]: uid,
3083
+ ...morphColumn.on
3084
+ }
3085
+ });
3086
+ return subAlias;
3087
+ }
3088
+ if (joinTable2) {
3089
+ const joinAlias = qb.getAlias();
3090
+ qb.join({
3091
+ alias: joinAlias,
3092
+ referencedTable: joinTable2.name,
3093
+ referencedColumn: joinTable2.morphColumn.idColumn.name,
3094
+ rootColumn: joinTable2.morphColumn.idColumn.referencedColumn,
3095
+ rootTable: alias,
3096
+ on: {
3097
+ [joinTable2.morphColumn.typeColumn.name]: uid,
3098
+ field: attributeName
3099
+ }
3100
+ });
3101
+ const subAlias = refAlias || qb.getAlias();
3102
+ qb.join({
3103
+ alias: subAlias,
3104
+ referencedTable: targetMeta.tableName,
3105
+ referencedColumn: joinTable2.joinColumn.referencedColumn,
3106
+ rootColumn: joinTable2.joinColumn.name,
3107
+ rootTable: joinAlias
3108
+ });
3109
+ return subAlias;
3110
+ }
3111
+ return alias;
3112
+ }
2925
3113
  const { joinColumn } = attribute;
2926
3114
  if (joinColumn) {
2927
3115
  const subAlias = refAlias || qb.getAlias();
@@ -3061,10 +3249,13 @@ const XtoOne = async (input, ctx) => {
3061
3249
  rootTable: qb2.alias,
3062
3250
  on: joinTable.on
3063
3251
  }).select([joinColAlias, qb2.raw("count(*) AS count")]).where({ [joinColAlias]: referencedValues }).groupBy(joinColAlias).execute({ mapResults: false });
3064
- const map2 = rows2.reduce((map3, row) => {
3065
- map3[row[joinColumnName]] = { count: Number(row.count) };
3066
- return map3;
3067
- }, {});
3252
+ const map2 = rows2.reduce(
3253
+ (map3, row) => {
3254
+ map3[row[joinColumnName]] = { count: Number(row.count) };
3255
+ return map3;
3256
+ },
3257
+ {}
3258
+ );
3068
3259
  results.forEach((result) => {
3069
3260
  result[attributeName] = map2[result[referencedColumnName]] || { count: 0 };
3070
3261
  });
@@ -3137,10 +3328,13 @@ const oneToMany = async (input, ctx) => {
3137
3328
  rootTable: qb2.alias,
3138
3329
  on: joinTable.on
3139
3330
  }).select([joinColAlias, qb2.raw("count(*) AS count")]).where({ [joinColAlias]: referencedValues }).groupBy(joinColAlias).execute({ mapResults: false });
3140
- const map2 = rows2.reduce((map3, row) => {
3141
- map3[row[joinColumnName]] = { count: Number(row.count) };
3142
- return map3;
3143
- }, {});
3331
+ const map2 = rows2.reduce(
3332
+ (map3, row) => {
3333
+ map3[row[joinColumnName]] = { count: Number(row.count) };
3334
+ return map3;
3335
+ },
3336
+ {}
3337
+ );
3144
3338
  results.forEach((result) => {
3145
3339
  result[attributeName] = map2[result[referencedColumnName]] || { count: 0 };
3146
3340
  });
@@ -3194,10 +3388,13 @@ const manyToMany = async (input, ctx) => {
3194
3388
  rootTable: populateQb.alias,
3195
3389
  on: joinTable.on
3196
3390
  }).select([joinColAlias, populateQb.raw("count(*) AS count")]).where({ [joinColAlias]: referencedValues }).groupBy(joinColAlias).execute({ mapResults: false });
3197
- const map2 = rows2.reduce((map3, row) => {
3198
- map3[row[joinColumnName]] = { count: Number(row.count) };
3199
- return map3;
3200
- }, {});
3391
+ const map2 = rows2.reduce(
3392
+ (map3, row) => {
3393
+ map3[row[joinColumnName]] = { count: Number(row.count) };
3394
+ return map3;
3395
+ },
3396
+ {}
3397
+ );
3201
3398
  results.forEach((result) => {
3202
3399
  result[attributeName] = map2[result[referencedColumnName]] || { count: 0 };
3203
3400
  });
@@ -3544,7 +3741,7 @@ const castValue = (value, attribute) => {
3544
3741
  };
3545
3742
  const processSingleAttributeWhere = (attribute, where, operator = "$eq") => {
3546
3743
  if (!isRecord$1(where)) {
3547
- if (utils$1.isOperatorOfType("cast", operator)) {
3744
+ if (utils.isOperatorOfType("cast", operator)) {
3548
3745
  return castValue(where, attribute);
3549
3746
  }
3550
3747
  return where;
@@ -3552,7 +3749,7 @@ const processSingleAttributeWhere = (attribute, where, operator = "$eq") => {
3552
3749
  const filters = {};
3553
3750
  for (const key of Object.keys(where)) {
3554
3751
  const value = where[key];
3555
- if (!utils$1.isOperatorOfType("where", key)) {
3752
+ if (!utils.isOperatorOfType("where", key)) {
3556
3753
  throw new Error(`Undefined attribute level operator ${key}`);
3557
3754
  }
3558
3755
  filters[key] = processAttributeWhere(attribute, value, key);
@@ -3571,6 +3768,28 @@ const processNested = (where, ctx) => {
3571
3768
  }
3572
3769
  return processWhere(where, ctx);
3573
3770
  };
3771
+ const processRelationWhere = (where, ctx) => {
3772
+ const { qb, alias } = ctx;
3773
+ const idAlias = qb.aliasColumn("id", alias);
3774
+ if (!isRecord$1(where)) {
3775
+ return { [idAlias]: where };
3776
+ }
3777
+ const keys = Object.keys(where);
3778
+ const operatorKeys = keys.filter((key) => utils.isOperator(key));
3779
+ if (operatorKeys.length > 0 && operatorKeys.length !== keys.length) {
3780
+ throw new Error(`Operator and non-operator keys cannot be mixed in a relation where clause`);
3781
+ }
3782
+ if (operatorKeys.length > 1) {
3783
+ throw new Error(
3784
+ `Only one operator key is allowed in a relation where clause, but found: ${operatorKeys}`
3785
+ );
3786
+ }
3787
+ if (operatorKeys.length === 1) {
3788
+ const operator = operatorKeys[0];
3789
+ return { [idAlias]: { [operator]: processNested(where[operator], ctx) } };
3790
+ }
3791
+ return processWhere(where, ctx);
3792
+ };
3574
3793
  function processWhere(where, ctx) {
3575
3794
  if (!_.isArray(where) && !isRecord$1(where)) {
3576
3795
  throw new Error("Where must be an array or an object");
@@ -3583,7 +3802,10 @@ function processWhere(where, ctx) {
3583
3802
  const filters = {};
3584
3803
  for (const key of Object.keys(where)) {
3585
3804
  const value = where[key];
3586
- if (utils$1.isOperatorOfType("group", key) && Array.isArray(value)) {
3805
+ if (utils.isOperatorOfType("group", key)) {
3806
+ if (!Array.isArray(value)) {
3807
+ throw new Error(`Operator ${key} must be an array`);
3808
+ }
3587
3809
  filters[key] = value.map((sub) => processNested(sub, ctx));
3588
3810
  continue;
3589
3811
  }
@@ -3591,7 +3813,7 @@ function processWhere(where, ctx) {
3591
3813
  filters[key] = processNested(value, ctx);
3592
3814
  continue;
3593
3815
  }
3594
- if (utils$1.isOperatorOfType("where", key)) {
3816
+ if (utils.isOperatorOfType("where", key)) {
3595
3817
  throw new Error(
3596
3818
  `Only $and, $or and $not can only be used as root level operators. Found ${key}.`
3597
3819
  );
@@ -3607,15 +3829,12 @@ function processWhere(where, ctx) {
3607
3829
  attributeName: key,
3608
3830
  attribute
3609
3831
  });
3610
- let nestedWhere = processNested(value, {
3832
+ const nestedWhere = processRelationWhere(value, {
3611
3833
  db,
3612
3834
  qb,
3613
3835
  alias: subAlias,
3614
3836
  uid: attribute.target
3615
3837
  });
3616
- if (!isRecord$1(nestedWhere) || utils$1.isOperatorOfType("where", _.keys(nestedWhere)[0])) {
3617
- nestedWhere = { [qb.aliasColumn("id", subAlias)]: nestedWhere };
3618
- }
3619
3838
  Object.assign(filters, nestedWhere);
3620
3839
  continue;
3621
3840
  }
@@ -3630,7 +3849,7 @@ function processWhere(where, ctx) {
3630
3849
  return filters;
3631
3850
  }
3632
3851
  const applyOperator = (qb, column, operator, value) => {
3633
- if (Array.isArray(value) && !utils$1.isOperatorOfType("array", operator)) {
3852
+ if (Array.isArray(value) && !utils.isOperatorOfType("array", operator)) {
3634
3853
  return qb.where((subQB) => {
3635
3854
  value.forEach(
3636
3855
  (subValue) => subQB.orWhere((innerQB) => {
@@ -3768,8 +3987,8 @@ const applyWhereToColumn = (qb, column, columnWhere) => {
3768
3987
  }
3769
3988
  return qb.where(column, columnWhere);
3770
3989
  }
3771
- const keys2 = Object.keys(columnWhere);
3772
- keys2.forEach((operator) => {
3990
+ const keys = Object.keys(columnWhere);
3991
+ keys.forEach((operator) => {
3773
3992
  const value = columnWhere[operator];
3774
3993
  applyOperator(qb, column, operator, value);
3775
3994
  });
@@ -4708,21 +4927,24 @@ const sortConnectArray = (connectArr, initialArr = [], strictSort = true) => {
4708
4927
  (acc, rel) => ({ ...acc, [rel.id]: true }),
4709
4928
  {}
4710
4929
  );
4711
- const mappedRelations = connectArr.reduce((mapper, relation) => {
4712
- const adjacentRelId = relation.position?.before || relation.position?.after;
4713
- if (!adjacentRelId || !relationInInitialArray[adjacentRelId] && !mapper[adjacentRelId]) {
4714
- needsSorting = true;
4715
- }
4716
- if (mapper[relation.id]) {
4717
- throw new InvalidRelationError(
4718
- `The relation with id ${relation.id} is already connected. You cannot connect the same relation twice.`
4719
- );
4720
- }
4721
- return {
4722
- [relation.id]: { ...relation, computed: false },
4723
- ...mapper
4724
- };
4725
- }, {});
4930
+ const mappedRelations = connectArr.reduce(
4931
+ (mapper, relation) => {
4932
+ const adjacentRelId = relation.position?.before || relation.position?.after;
4933
+ if (!adjacentRelId || !relationInInitialArray[adjacentRelId] && !mapper[adjacentRelId]) {
4934
+ needsSorting = true;
4935
+ }
4936
+ if (mapper[relation.id]) {
4937
+ throw new InvalidRelationError(
4938
+ `The relation with id ${relation.id} is already connected. You cannot connect the same relation twice.`
4939
+ );
4940
+ }
4941
+ return {
4942
+ [relation.id]: { ...relation, computed: false },
4943
+ ...mapper
4944
+ };
4945
+ },
4946
+ {}
4947
+ );
4726
4948
  if (!needsSorting)
4727
4949
  return connectArr;
4728
4950
  const computeRelation = (relation, relationsSeenInBranch) => {
@@ -4830,14 +5052,17 @@ const relationsOrderer = (initArr, idColumn, orderColumn, strict) => {
4830
5052
  * Get a map between the relation id and its order
4831
5053
  */
4832
5054
  getOrderMap() {
4833
- return ___default$1.default(computedRelations).groupBy("order").reduce((acc, relations) => {
4834
- if (relations[0]?.init)
5055
+ return ___default$1.default(computedRelations).groupBy("order").reduce(
5056
+ (acc, relations) => {
5057
+ if (relations[0]?.init)
5058
+ return acc;
5059
+ relations.forEach((relation, idx) => {
5060
+ acc[relation.id] = Math.floor(relation.order) + (idx + 1) / (relations.length + 1);
5061
+ });
4835
5062
  return acc;
4836
- relations.forEach((relation, idx) => {
4837
- acc[relation.id] = Math.floor(relation.order) + (idx + 1) / (relations.length + 1);
4838
- });
4839
- return acc;
4840
- }, {});
5063
+ },
5064
+ {}
5065
+ );
4841
5066
  }
4842
5067
  };
4843
5068
  };
@@ -5591,10 +5816,13 @@ const createEntityManager = (db) => {
5591
5816
  const entry = await this.findOne(uid, {
5592
5817
  select: ["id"],
5593
5818
  where: { id: entity.id },
5594
- populate: fieldsArr.reduce((acc, field) => {
5595
- acc[field] = populate || true;
5596
- return acc;
5597
- }, {})
5819
+ populate: fieldsArr.reduce(
5820
+ (acc, field) => {
5821
+ acc[field] = populate || true;
5822
+ return acc;
5823
+ },
5824
+ {}
5825
+ )
5598
5826
  });
5599
5827
  if (!entry) {
5600
5828
  return null;
@@ -5710,7 +5938,280 @@ const createUserMigrationProvider = (db) => {
5710
5938
  }
5711
5939
  };
5712
5940
  };
5713
- const internalMigrations = [];
5941
+ const QUERIES = {
5942
+ async postgres(knex2, params) {
5943
+ const res = await knex2.raw(
5944
+ `
5945
+ SELECT :tableName:.id as id, string_agg(DISTINCT :inverseJoinColumn:::character varying, ',') as other_ids
5946
+ FROM :tableName:
5947
+ LEFT JOIN :joinTableName: ON :tableName:.id = :joinTableName:.:joinColumn:
5948
+ WHERE document_id IS NULL
5949
+ GROUP BY :tableName:.id, :joinColumn:
5950
+ LIMIT 1;
5951
+ `,
5952
+ params
5953
+ );
5954
+ return res.rows;
5955
+ },
5956
+ async mysql(knex2, params) {
5957
+ const [res] = await knex2.raw(
5958
+ `
5959
+ SELECT :tableName:.id as id, group_concat(DISTINCT :inverseJoinColumn:) as other_ids
5960
+ FROM :tableName:
5961
+ LEFT JOIN :joinTableName: ON :tableName:.id = :joinTableName:.:joinColumn:
5962
+ WHERE document_id IS NULL
5963
+ GROUP BY :tableName:.id, :joinColumn:
5964
+ LIMIT 1;
5965
+ `,
5966
+ params
5967
+ );
5968
+ return res;
5969
+ },
5970
+ async sqlite(knex2, params) {
5971
+ return knex2.raw(
5972
+ `
5973
+ SELECT :tableName:.id as id, group_concat(DISTINCT :inverseJoinColumn:) as other_ids
5974
+ FROM :tableName:
5975
+ LEFT JOIN :joinTableName: ON :tableName:.id = :joinTableName:.:joinColumn:
5976
+ WHERE document_id IS NULL
5977
+ GROUP BY :joinColumn:
5978
+ LIMIT 1;
5979
+ `,
5980
+ params
5981
+ );
5982
+ }
5983
+ };
5984
+ const getNextIdsToCreateDocumentId = async (db, knex2, {
5985
+ joinColumn,
5986
+ inverseJoinColumn,
5987
+ tableName,
5988
+ joinTableName
5989
+ }) => {
5990
+ const res = await QUERIES[db.dialect.client](knex2, {
5991
+ joinColumn,
5992
+ inverseJoinColumn,
5993
+ tableName,
5994
+ joinTableName
5995
+ });
5996
+ if (res.length > 0) {
5997
+ const row = res[0];
5998
+ const otherIds = row.other_ids ? row.other_ids.split(",").map((v) => parseInt(v, 10)) : [];
5999
+ return [row.id, ...otherIds];
6000
+ }
6001
+ return [];
6002
+ };
6003
+ const migrateDocumentIdsWithLocalizations = async (db, knex2, meta) => {
6004
+ const singularName = meta.singularName.toLowerCase();
6005
+ const joinColumn = identifiers.getJoinColumnAttributeIdName(singularName);
6006
+ const inverseJoinColumn = identifiers.getInverseJoinColumnAttributeIdName(singularName);
6007
+ let ids;
6008
+ do {
6009
+ ids = await getNextIdsToCreateDocumentId(db, knex2, {
6010
+ joinColumn,
6011
+ inverseJoinColumn,
6012
+ tableName: meta.tableName,
6013
+ joinTableName: identifiers.getJoinTableName(meta.tableName, `localizations`)
6014
+ });
6015
+ if (ids.length > 0) {
6016
+ await knex2(meta.tableName).update({ document_id: cuid2.createId() }).whereIn("id", ids);
6017
+ }
6018
+ } while (ids.length > 0);
6019
+ };
6020
+ const migrationDocumentIds = async (db, knex2, meta) => {
6021
+ let run = true;
6022
+ do {
6023
+ const updatedRows = await knex2(meta.tableName).update({ document_id: cuid2.createId() }).whereIn("id", (builder) => {
6024
+ return builder.whereNull("document_id").select("id").limit(1);
6025
+ });
6026
+ if (updatedRows <= 0) {
6027
+ run = false;
6028
+ }
6029
+ } while (run);
6030
+ };
6031
+ const createDocumentIdColumn = async (knex2, tableName) => {
6032
+ await knex2.schema.alterTable(tableName, (table) => {
6033
+ table.string("document_id");
6034
+ });
6035
+ };
6036
+ const hasLocalizationsJoinTable = async (knex2, tableName) => {
6037
+ const joinTableName = identifiers.getJoinTableName(tableName, "localizations");
6038
+ return knex2.schema.hasTable(joinTableName);
6039
+ };
6040
+ const createdDocumentId = {
6041
+ name: "5.0.0-02-created-document-id",
6042
+ async up(knex2, db) {
6043
+ for (const meta of db.metadata.values()) {
6044
+ const hasTable = await knex2.schema.hasTable(meta.tableName);
6045
+ if (!hasTable) {
6046
+ continue;
6047
+ }
6048
+ if ("documentId" in meta.attributes) {
6049
+ const hasDocumentIdColumn = await knex2.schema.hasColumn(meta.tableName, "document_id");
6050
+ if (hasDocumentIdColumn) {
6051
+ continue;
6052
+ }
6053
+ await createDocumentIdColumn(knex2, meta.tableName);
6054
+ if (await hasLocalizationsJoinTable(knex2, meta.tableName)) {
6055
+ await migrateDocumentIdsWithLocalizations(db, knex2, meta);
6056
+ } else {
6057
+ await migrationDocumentIds(db, knex2, meta);
6058
+ }
6059
+ }
6060
+ }
6061
+ },
6062
+ async down() {
6063
+ throw new Error("not implemented");
6064
+ }
6065
+ };
6066
+ const debug = createDebug__default.default("strapi::database::migration");
6067
+ const renameIdentifiersLongerThanMaxLength = {
6068
+ name: "5.0.0-rename-identifiers-longer-than-max-length",
6069
+ async up(knex2, db) {
6070
+ const md = db.metadata;
6071
+ const diffs = findDiffs(md);
6072
+ for (const indexDiff of diffs.indexes) {
6073
+ await renameIndex(knex2, db, indexDiff);
6074
+ }
6075
+ for (const columnDiff of diffs.columns) {
6076
+ const { full, short } = columnDiff;
6077
+ const tableName = full.tableName;
6078
+ const hasTable = await knex2.schema.hasTable(tableName);
6079
+ if (hasTable) {
6080
+ const hasColumn = await knex2.schema.hasColumn(tableName, full.columnName);
6081
+ if (hasColumn) {
6082
+ await knex2.schema.alterTable(tableName, async (table) => {
6083
+ debug(`renaming column ${full.columnName} to ${short.columnName}`);
6084
+ table.renameColumn(full.columnName, short.columnName);
6085
+ });
6086
+ }
6087
+ }
6088
+ }
6089
+ for (const tableDiff of diffs.tables) {
6090
+ const hasTable = await knex2.schema.hasTable(tableDiff.full.tableName);
6091
+ if (hasTable) {
6092
+ debug(`renaming table ${tableDiff.full.tableName} to ${tableDiff.short.tableName}`);
6093
+ await knex2.schema.renameTable(tableDiff.full.tableName, tableDiff.short.tableName);
6094
+ }
6095
+ }
6096
+ },
6097
+ async down() {
6098
+ throw new Error("not implemented");
6099
+ }
6100
+ };
6101
+ const renameIndex = async (knex2, db, diff) => {
6102
+ const client = db.config.connection.client;
6103
+ const short = diff.short;
6104
+ const full = diff.full;
6105
+ if (full.indexName === short.indexName) {
6106
+ debug(`not renaming index ${full.indexName} because name hasn't changed`);
6107
+ return;
6108
+ }
6109
+ if (short.indexName.includes("_lnk_") || full.indexName.includes("_lnk_") || short.indexName.endsWith("fk") || full.indexName.endsWith("fk")) {
6110
+ return;
6111
+ }
6112
+ debug(`renaming index from ${full.indexName} to ${short.indexName}`);
6113
+ try {
6114
+ await knex2.transaction(async (trx) => {
6115
+ if (client === "mysql" || client === "mariadb") {
6116
+ await knex2.raw(
6117
+ `ALTER TABLE \`${full.tableName}\` RENAME INDEX \`${full.indexName}\` TO \`${short.indexName}\``
6118
+ ).transacting(trx);
6119
+ } else if (client === "pg" || client === "postgres") {
6120
+ await knex2.raw(`ALTER INDEX "${full.indexName}" RENAME TO "${short.indexName}"`).transacting(trx);
6121
+ } else if (client === "sqlite" || client === "better") {
6122
+ } else {
6123
+ debug("No db client name matches, not creating index");
6124
+ }
6125
+ });
6126
+ } catch (err) {
6127
+ debug(`error creating index: ${JSON.stringify(err)}`);
6128
+ }
6129
+ };
6130
+ const findDiffs = (shortMap) => {
6131
+ const diffs = {
6132
+ tables: [],
6133
+ columns: [],
6134
+ indexes: []
6135
+ };
6136
+ const shortArr = Array.from(shortMap.entries());
6137
+ shortArr.forEach(([, shortObj], index2) => {
6138
+ const fullTableName = identifiers.getUnshortenedName(shortObj.tableName);
6139
+ if (!fullTableName) {
6140
+ throw new Error(`Missing full table name for ${shortObj.tableName}`);
6141
+ }
6142
+ if (shortObj.tableName !== fullTableName) {
6143
+ diffs.tables.push({
6144
+ full: {
6145
+ index: index2,
6146
+ key: "tableName",
6147
+ tableName: fullTableName
6148
+ },
6149
+ short: {
6150
+ index: index2,
6151
+ key: "tableName",
6152
+ tableName: shortObj.tableName
6153
+ }
6154
+ });
6155
+ }
6156
+ for (const attrKey in shortObj.attributes) {
6157
+ if (shortObj.attributes[attrKey].type === "relation") {
6158
+ continue;
6159
+ }
6160
+ const attr = shortObj.attributes[attrKey];
6161
+ const shortColumnName = attr.columnName;
6162
+ const longColumnName = identifiers.getUnshortenedName(shortColumnName);
6163
+ if (!shortColumnName || !longColumnName) {
6164
+ throw new Error(`missing column name(s) for attribute ${JSON.stringify(attr, null, 2)}`);
6165
+ }
6166
+ if (shortColumnName && longColumnName && shortColumnName !== longColumnName) {
6167
+ diffs.columns.push({
6168
+ short: {
6169
+ index: index2,
6170
+ tableName: fullTableName,
6171
+ // NOTE: this means that we must rename columns before tables
6172
+ key: `attributes.${attrKey}`,
6173
+ columnName: shortColumnName
6174
+ },
6175
+ full: {
6176
+ index: index2,
6177
+ tableName: fullTableName,
6178
+ key: `attributes.${attrKey}`,
6179
+ columnName: longColumnName
6180
+ }
6181
+ });
6182
+ }
6183
+ }
6184
+ for (const attrKey in shortObj.indexes) {
6185
+ const shortIndexName = shortObj.indexes[attrKey].name;
6186
+ const longIndexName = identifiers.getUnshortenedName(shortIndexName);
6187
+ if (!longIndexName) {
6188
+ throw new Error(`Missing full index name for ${shortIndexName}`);
6189
+ }
6190
+ if (shortIndexName && longIndexName && shortIndexName !== longIndexName) {
6191
+ diffs.indexes.push({
6192
+ short: {
6193
+ index: index2,
6194
+ tableName: fullTableName,
6195
+ // NOTE: this means that we must rename columns before tables
6196
+ key: `indexes.${attrKey}`,
6197
+ indexName: shortIndexName
6198
+ },
6199
+ full: {
6200
+ index: index2,
6201
+ tableName: fullTableName,
6202
+ key: `indexes.${attrKey}`,
6203
+ indexName: longIndexName
6204
+ }
6205
+ });
6206
+ }
6207
+ }
6208
+ });
6209
+ return diffs;
6210
+ };
6211
+ const internalMigrations = [
6212
+ renameIdentifiersLongerThanMaxLength,
6213
+ createdDocumentId
6214
+ ];
5714
6215
  const createInternalMigrationProvider = (db) => {
5715
6216
  const context = { db };
5716
6217
  const umzugProvider = new umzug.Umzug({
@@ -5936,8 +6437,14 @@ const validateBidirectionalRelations = async (db) => {
5936
6437
  for (const { relation, invRelation } of invalidLinks) {
5937
6438
  const modelMetadata = db.metadata.get(invRelation.target);
5938
6439
  const invModelMetadata = db.metadata.get(relation.target);
5939
- const joinTableName = getJoinTableName(modelMetadata.tableName, invRelation.inversedBy);
5940
- const inverseJoinTableName = getJoinTableName(invModelMetadata.tableName, relation.inversedBy);
6440
+ const joinTableName = identifiers.getJoinTableName(
6441
+ _.snakeCase(modelMetadata.tableName),
6442
+ _.snakeCase(invRelation.inversedBy)
6443
+ );
6444
+ const inverseJoinTableName = identifiers.getJoinTableName(
6445
+ _.snakeCase(invModelMetadata.tableName),
6446
+ _.snakeCase(relation.inversedBy)
6447
+ );
5941
6448
  const joinTableEmpty = await isLinkTableEmpty(db, joinTableName);
5942
6449
  const inverseJoinTableEmpty = await isLinkTableEmpty(db, inverseJoinTableName);
5943
6450
  if (joinTableEmpty) {
@@ -5983,7 +6490,7 @@ class Database {
5983
6490
  };
5984
6491
  this.dialect = getDialect(this);
5985
6492
  this.dialect.configure();
5986
- this.metadata = createMetadata();
6493
+ this.metadata = createMetadata([]);
5987
6494
  this.connection = createConnection(this.config.connection, {
5988
6495
  pool: { afterCreate: afterCreate(this) }
5989
6496
  });
@@ -6060,9 +6567,7 @@ class Database {
6060
6567
  await this.connection.destroy();
6061
6568
  }
6062
6569
  }
6063
- const utils = { identifiers };
6064
6570
  exports.Database = Database;
6065
6571
  exports.errors = index;
6066
6572
  exports.isKnexQuery = isKnexQuery;
6067
- exports.utils = utils;
6068
6573
  //# sourceMappingURL=index.js.map