@strapi/database 4.20.5 → 5.0.0-alpha.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 (61) hide show
  1. package/dist/connection.d.ts +2 -1
  2. package/dist/connection.d.ts.map +1 -1
  3. package/dist/dialects/dialect.d.ts +1 -2
  4. package/dist/dialects/dialect.d.ts.map +1 -1
  5. package/dist/dialects/index.d.ts.map +1 -1
  6. package/dist/dialects/mysql/database-inspector.d.ts +1 -1
  7. package/dist/dialects/mysql/database-inspector.d.ts.map +1 -1
  8. package/dist/dialects/mysql/index.d.ts +1 -2
  9. package/dist/dialects/mysql/index.d.ts.map +1 -1
  10. package/dist/dialects/postgresql/index.d.ts +1 -1
  11. package/dist/dialects/postgresql/index.d.ts.map +1 -1
  12. package/dist/dialects/sqlite/index.d.ts +1 -1
  13. package/dist/dialects/sqlite/index.d.ts.map +1 -1
  14. package/dist/entity-manager/entity-repository.d.ts.map +1 -1
  15. package/dist/entity-manager/index.d.ts.map +1 -1
  16. package/dist/entity-manager/regular-relations.d.ts +6 -31
  17. package/dist/entity-manager/regular-relations.d.ts.map +1 -1
  18. package/dist/entity-manager/types.d.ts +8 -26
  19. package/dist/entity-manager/types.d.ts.map +1 -1
  20. package/dist/index.d.ts +13 -5
  21. package/dist/index.d.ts.map +1 -1
  22. package/dist/index.js +518 -645
  23. package/dist/index.js.map +1 -1
  24. package/dist/index.mjs +514 -639
  25. package/dist/index.mjs.map +1 -1
  26. package/dist/metadata/index.d.ts +2 -2
  27. package/dist/metadata/index.d.ts.map +1 -1
  28. package/dist/metadata/metadata.d.ts +1 -4
  29. package/dist/metadata/metadata.d.ts.map +1 -1
  30. package/dist/metadata/relations.d.ts +0 -1
  31. package/dist/metadata/relations.d.ts.map +1 -1
  32. package/dist/migrations/common.d.ts +20 -0
  33. package/dist/migrations/common.d.ts.map +1 -0
  34. package/dist/migrations/index.d.ts +2 -9
  35. package/dist/migrations/index.d.ts.map +1 -1
  36. package/dist/migrations/internal-migrations/index.d.ts +12 -0
  37. package/dist/migrations/internal-migrations/index.d.ts.map +1 -0
  38. package/dist/migrations/internal.d.ts +4 -0
  39. package/dist/migrations/internal.d.ts.map +1 -0
  40. package/dist/migrations/storage.d.ts +1 -1
  41. package/dist/migrations/storage.d.ts.map +1 -1
  42. package/dist/migrations/users.d.ts +4 -0
  43. package/dist/migrations/users.d.ts.map +1 -0
  44. package/dist/schema/diff.d.ts.map +1 -1
  45. package/dist/schema/index.d.ts +2 -0
  46. package/dist/schema/index.d.ts.map +1 -1
  47. package/dist/schema/schema.d.ts.map +1 -1
  48. package/dist/types/index.d.ts +9 -5
  49. package/dist/types/index.d.ts.map +1 -1
  50. package/dist/utils/identifiers/index.d.ts +48 -0
  51. package/dist/utils/identifiers/index.d.ts.map +1 -0
  52. package/dist/utils/identifiers/shortener.d.ts +73 -0
  53. package/dist/utils/identifiers/shortener.d.ts.map +1 -0
  54. package/dist/utils/types.d.ts +0 -2
  55. package/dist/utils/types.d.ts.map +1 -1
  56. package/dist/validations/relations/bidirectional.d.ts.map +1 -1
  57. package/package.json +9 -9
  58. package/dist/entity-manager/relations/cloning/regular-relations.d.ts +0 -17
  59. package/dist/entity-manager/relations/cloning/regular-relations.d.ts.map +0 -1
  60. package/dist/utils/content-types.d.ts +0 -13
  61. package/dist/utils/content-types.d.ts.map +0 -1
package/dist/index.js CHANGED
@@ -1,23 +1,21 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
- const semver = require("semver");
4
3
  const path = require("path");
5
4
  const fse = require("fs-extra");
6
5
  const createDebug = require("debug");
7
6
  const _ = require("lodash/fp");
8
7
  const crypto = require("crypto");
9
- const utils = require("@strapi/utils");
8
+ const crypto$1 = require("node:crypto");
10
9
  const dateFns = require("date-fns");
10
+ const utils$1 = 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
- const path$1 = require("node:path");
17
16
  const umzug = require("umzug");
18
17
  const assert = require("assert");
19
18
  const knex = require("knex");
20
- const SqliteClient = require("knex/lib/dialects/sqlite3/index");
21
19
  const _interopDefault = (e) => e && e.__esModule ? e : { default: e };
22
20
  function _interopNamespace(e) {
23
21
  if (e && e.__esModule)
@@ -37,19 +35,17 @@ function _interopNamespace(e) {
37
35
  n.default = e;
38
36
  return Object.freeze(n);
39
37
  }
40
- const semver__default = /* @__PURE__ */ _interopDefault(semver);
41
38
  const path__default = /* @__PURE__ */ _interopDefault(path);
42
39
  const fse__default = /* @__PURE__ */ _interopDefault(fse);
43
40
  const createDebug__default = /* @__PURE__ */ _interopDefault(createDebug);
44
41
  const ___default = /* @__PURE__ */ _interopDefault(_);
45
42
  const crypto__default = /* @__PURE__ */ _interopDefault(crypto);
43
+ const crypto__default$1 = /* @__PURE__ */ _interopDefault(crypto$1);
46
44
  const dateFns__namespace = /* @__PURE__ */ _interopNamespace(dateFns);
47
45
  const KnexBuilder__default = /* @__PURE__ */ _interopDefault(KnexBuilder);
48
46
  const KnexRaw__default = /* @__PURE__ */ _interopDefault(KnexRaw);
49
47
  const ___default$1 = /* @__PURE__ */ _interopDefault(_$1);
50
- const path__default$1 = /* @__PURE__ */ _interopDefault(path$1);
51
48
  const knex__default = /* @__PURE__ */ _interopDefault(knex);
52
- const SqliteClient__default = /* @__PURE__ */ _interopDefault(SqliteClient);
53
49
  class Dialect {
54
50
  db;
55
51
  schemaInspector = {};
@@ -60,7 +56,8 @@ class Dialect {
60
56
  }
61
57
  configure() {
62
58
  }
63
- initialize() {
59
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
60
+ async initialize(_nativeConnection) {
64
61
  }
65
62
  getSqlType(type) {
66
63
  return type;
@@ -77,9 +74,6 @@ class Dialect {
77
74
  supportsUnsigned() {
78
75
  return false;
79
76
  }
80
- supportsWindowFunctions() {
81
- return true;
82
- }
83
77
  supportsOperator() {
84
78
  return true;
85
79
  }
@@ -423,7 +417,7 @@ class PostgresDialect extends Dialect {
423
417
  useReturning() {
424
418
  return true;
425
419
  }
426
- async initialize() {
420
+ async initialize(nativeConnection) {
427
421
  this.db.connection.client.driver.types.setTypeParser(
428
422
  this.db.connection.client.driver.types.builtins.DATE,
429
423
  "text",
@@ -439,6 +433,10 @@ class PostgresDialect extends Dialect {
439
433
  "text",
440
434
  parseFloat
441
435
  );
436
+ const schemaName = this.db.getSchemaName();
437
+ if (schemaName) {
438
+ await this.db.connection.raw(`SET search_path TO "${schemaName}"`).connection(nativeConnection);
439
+ }
442
440
  }
443
441
  usesForeignKeys() {
444
442
  return true;
@@ -702,11 +700,11 @@ class MysqlDatabaseInspector {
702
700
  constructor(db) {
703
701
  this.db = db;
704
702
  }
705
- async getInformation() {
703
+ async getInformation(nativeConnection) {
706
704
  let database;
707
705
  let versionNumber;
708
706
  try {
709
- const [results] = await this.db.connection.raw(SQL_QUERIES$1.VERSION);
707
+ const [results] = await this.db.connection.raw(SQL_QUERIES$1.VERSION).connection(nativeConnection);
710
708
  const versionSplit = results[0].version.split("-");
711
709
  const databaseName = versionSplit[1];
712
710
  versionNumber = versionSplit[0];
@@ -753,12 +751,14 @@ class MysqlDialect extends Dialect {
753
751
  return next();
754
752
  };
755
753
  }
756
- async initialize() {
754
+ async initialize(nativeConnection) {
757
755
  try {
758
- await this.db.connection.raw(`set session sql_require_primary_key = 0;`);
756
+ await this.db.connection.raw(`set session sql_require_primary_key = 0;`).connection(nativeConnection);
759
757
  } catch (err) {
760
758
  }
761
- this.info = await this.databaseInspector.getInformation();
759
+ if (!this.info) {
760
+ this.info = await this.databaseInspector.getInformation(nativeConnection);
761
+ }
762
762
  }
763
763
  async startSchemaUpdate() {
764
764
  try {
@@ -773,14 +773,6 @@ class MysqlDialect extends Dialect {
773
773
  supportsUnsigned() {
774
774
  return true;
775
775
  }
776
- supportsWindowFunctions() {
777
- const isMysqlDB = !this.info?.database || this.info.database === MYSQL;
778
- const isBeforeV8 = !semver__default.default.valid(this.info?.version) || semver__default.default.lt(this.info?.version ?? "", "8.0.0");
779
- if (isMysqlDB && isBeforeV8) {
780
- return false;
781
- }
782
- return true;
783
- }
784
776
  usesForeignKeys() {
785
777
  return true;
786
778
  }
@@ -938,8 +930,8 @@ class SqliteDialect extends Dialect {
938
930
  useReturning() {
939
931
  return true;
940
932
  }
941
- async initialize() {
942
- await this.db.connection.raw("pragma foreign_keys = on");
933
+ async initialize(nativeConnection) {
934
+ await this.db.connection.raw("pragma foreign_keys = on").connection(nativeConnection);
943
935
  }
944
936
  canAlterConstraints() {
945
937
  return false;
@@ -1001,10 +993,8 @@ const getDialectName = (client) => {
1001
993
  case "postgres":
1002
994
  return "postgres";
1003
995
  case "mysql":
1004
- case "mysql2":
1005
996
  return "mysql";
1006
997
  case "sqlite":
1007
- case "sqlite-legacy":
1008
998
  return "sqlite";
1009
999
  default:
1010
1000
  throw new Error(`Unknown dialect ${client}`);
@@ -1268,7 +1258,11 @@ const createHelpers = (db) => {
1268
1258
  dropTableForeignKeys
1269
1259
  };
1270
1260
  };
1271
- const RESERVED_TABLE_NAMES = ["strapi_migrations", "strapi_database_schema"];
1261
+ const RESERVED_TABLE_NAMES = [
1262
+ "strapi_migrations",
1263
+ "strapi_migrations_internal",
1264
+ "strapi_database_schema"
1265
+ ];
1272
1266
  const statuses = {
1273
1267
  CHANGED: "CHANGED",
1274
1268
  UNCHANGED: "UNCHANGED"
@@ -1648,11 +1642,212 @@ const NUMBER_TYPES = ["biginteger", "integer", "decimal", "float"];
1648
1642
  const isString = (type) => STRING_TYPES.includes(type);
1649
1643
  const isNumber = (type) => NUMBER_TYPES.includes(type);
1650
1644
  const isScalar = (type) => SCALAR_TYPES.includes(type);
1651
- const isComponent = (type) => type === "component";
1652
- const isDynamicZone = (type) => type === "dynamiczone";
1653
1645
  const isRelation = (type) => type === "relation";
1654
1646
  const isScalarAttribute = (attribute) => isScalar(attribute.type);
1655
1647
  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
+ function createHash(data, len) {
1654
+ if (!_.isInteger(len) || len <= 0) {
1655
+ throw new Error(`createHash length must be a positive integer, received ${len}`);
1656
+ }
1657
+ const hash = crypto__default$1.default.createHash("shake256", { outputLength: Math.ceil(len / 2) }).update(data);
1658
+ return hash.digest("hex").substring(0, len);
1659
+ }
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
+ );
1677
+ }
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)");
1683
+ }
1684
+ const fullLengthName = nameTokens.map((token) => _.snakeCase(token.name)).join(IDENTIFIER_SEPARATOR);
1685
+ if (fullLengthName.length <= maxLength || maxLength === 0) {
1686
+ return fullLengthName;
1687
+ }
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);
1722
+ }
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;
1729
+ }
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;
1737
+ }
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);
1743
+ }
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}`
1749
+ );
1750
+ }
1751
+ return shortenedName;
1752
+ }
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" }));
1656
1851
  const createColumn = (name, attribute) => {
1657
1852
  const { type, args = [], ...opts } = getColumnType(attribute);
1658
1853
  return {
@@ -1678,51 +1873,62 @@ const createTable = (meta) => {
1678
1873
  if (attribute.type === "relation") {
1679
1874
  if ("morphColumn" in attribute && attribute.morphColumn && attribute.owner) {
1680
1875
  const { idColumn, typeColumn } = attribute.morphColumn;
1876
+ const idColumnName = getName(idColumn.name);
1877
+ const typeColumnName = getName(typeColumn.name);
1681
1878
  table.columns.push(
1682
- createColumn(idColumn.name, {
1879
+ createColumn(idColumnName, {
1683
1880
  type: "integer",
1684
1881
  column: {
1685
1882
  unsigned: true
1686
1883
  }
1687
1884
  })
1688
1885
  );
1689
- table.columns.push(createColumn(typeColumn.name, { type: "string" }));
1886
+ table.columns.push(createColumn(typeColumnName, { type: "string" }));
1690
1887
  } else if ("joinColumn" in attribute && attribute.joinColumn && attribute.owner && attribute.joinColumn.referencedTable) {
1691
- const { name: columnName, referencedColumn, referencedTable } = attribute.joinColumn;
1888
+ const {
1889
+ name: columnNameFull,
1890
+ referencedColumn,
1891
+ referencedTable,
1892
+ columnType = "integer"
1893
+ } = attribute.joinColumn;
1894
+ const columnName = getName(columnNameFull);
1692
1895
  const column = createColumn(columnName, {
1693
- type: "integer",
1896
+ // TODO: find the column type automatically, or allow passing all the column params
1897
+ type: columnType,
1694
1898
  column: {
1695
1899
  unsigned: true
1696
1900
  }
1697
1901
  });
1698
1902
  table.columns.push(column);
1903
+ const fkName = getFkIndexName([table.name, columnName]);
1699
1904
  table.foreignKeys.push({
1700
- name: `${table.name}_${columnName}_fk`,
1701
- columns: [columnName],
1905
+ name: fkName,
1906
+ columns: [column.name],
1702
1907
  referencedTable,
1703
1908
  referencedColumns: [referencedColumn],
1704
1909
  // NOTE: could allow configuration
1705
1910
  onDelete: "SET NULL"
1706
1911
  });
1707
1912
  table.indexes.push({
1708
- name: `${table.name}_${columnName}_fk`,
1709
- columns: [columnName]
1913
+ name: fkName,
1914
+ columns: [column.name]
1710
1915
  });
1711
1916
  }
1712
1917
  } else if (isScalarAttribute(attribute)) {
1713
- const column = createColumn(attribute.columnName || key, attribute);
1918
+ const columnName = getName(attribute.columnName || key);
1919
+ const column = createColumn(columnName, attribute);
1714
1920
  if (column.unique) {
1715
1921
  table.indexes.push({
1716
1922
  type: "unique",
1717
- name: `${table.name}_${column.name}_unique`,
1718
- columns: [column.name]
1923
+ name: getUniqueIndexName([table.name, column.name]),
1924
+ columns: [columnName]
1719
1925
  });
1720
1926
  }
1721
1927
  if (column.primary) {
1722
1928
  table.indexes.push({
1723
1929
  type: "primary",
1724
- name: `${table.name}_${column.name}_primary`,
1725
- columns: [column.name]
1930
+ name: getPrimaryIndexName([table.name, column.name]),
1931
+ columns: [columnName]
1726
1932
  });
1727
1933
  }
1728
1934
  table.columns.push(column);
@@ -1750,8 +1956,7 @@ const getColumnType = (attribute) => {
1750
1956
  }
1751
1957
  case "uid": {
1752
1958
  return {
1753
- type: "string",
1754
- unique: true
1959
+ type: "string"
1755
1960
  };
1756
1961
  }
1757
1962
  case "richtext":
@@ -1824,8 +2029,14 @@ const metadataToSchema = (metadata) => {
1824
2029
  };
1825
2030
  const debug = createDebug__default.default("strapi::database");
1826
2031
  const createSchemaProvider = (db) => {
1827
- const schema = metadataToSchema(db.metadata);
2032
+ const state = {};
1828
2033
  return {
2034
+ get schema() {
2035
+ if (!state.schema) {
2036
+ state.schema = metadataToSchema(db.metadata);
2037
+ }
2038
+ return state.schema;
2039
+ },
1829
2040
  builder: createSchemaBuilder(db),
1830
2041
  schemaDiff: createSchemaDiff(db),
1831
2042
  schemaStorage: createSchemaStorage(db),
@@ -1842,7 +2053,7 @@ const createSchemaProvider = (db) => {
1842
2053
  */
1843
2054
  async create() {
1844
2055
  debug("Created database schema");
1845
- await this.builder.createSchema(schema);
2056
+ await this.builder.createSchema(this.schema);
1846
2057
  },
1847
2058
  /**
1848
2059
  * Resets the database schema
@@ -1855,11 +2066,11 @@ const createSchemaProvider = (db) => {
1855
2066
  async syncSchema() {
1856
2067
  debug("Synchronizing database schema");
1857
2068
  const DBSchema = await db.dialect.schemaInspector.getSchema();
1858
- const { status, diff } = await this.schemaDiff.diff(DBSchema, schema);
2069
+ const { status, diff } = await this.schemaDiff.diff(DBSchema, this.schema);
1859
2070
  if (status === "CHANGED") {
1860
2071
  await this.builder.updateSchema(diff);
1861
2072
  }
1862
- await this.schemaStorage.add(schema);
2073
+ await this.schemaStorage.add(this.schema);
1863
2074
  },
1864
2075
  // TODO: support options to migrate softly or forcefully
1865
2076
  // TODO: support option to disable auto migration & run a CLI command instead to avoid doing it at startup
@@ -1876,7 +2087,7 @@ const createSchemaProvider = (db) => {
1876
2087
  return this.syncSchema();
1877
2088
  }
1878
2089
  const { hash: oldHash } = oldSchema;
1879
- const hash = await this.schemaStorage.hashSchema(schema);
2090
+ const hash = await this.schemaStorage.hashSchema(this.schema);
1880
2091
  if (oldHash !== hash) {
1881
2092
  debug("Schema changed");
1882
2093
  return this.syncSchema();
@@ -1885,9 +2096,11 @@ const createSchemaProvider = (db) => {
1885
2096
  }
1886
2097
  };
1887
2098
  };
2099
+ const ID = ID_COLUMN;
2100
+ const ORDER = ORDER_COLUMN;
2101
+ const FIELD = FIELD_COLUMN;
1888
2102
  const hasInversedBy = (attr) => "inversedBy" in attr;
1889
2103
  const hasMappedBy = (attr) => "mappedBy" in attr;
1890
- const isPolymorphic = (attribute) => ["morphOne", "morphMany", "morphToOne", "morphToMany"].includes(attribute.relation);
1891
2104
  const isOneToAny = (attribute) => ["oneToOne", "oneToMany"].includes(attribute.relation);
1892
2105
  const isManyToAny = (attribute) => ["manyToMany", "manyToOne"].includes(attribute.relation);
1893
2106
  const isAnyToOne = (attribute) => ["oneToOne", "manyToOne"].includes(attribute.relation);
@@ -1895,7 +2108,6 @@ const isAnyToMany = (attribute) => ["oneToMany", "manyToMany"].includes(attribut
1895
2108
  const isBidirectional = (attribute) => hasInversedBy(attribute) || hasMappedBy(attribute);
1896
2109
  const isOwner = (attribute) => !isBidirectional(attribute) || hasInversedBy(attribute);
1897
2110
  const shouldUseJoinTable = (attribute) => !("useJoinTable" in attribute) || attribute.useJoinTable !== false;
1898
- const getJoinTableName = (tableName, attributeName) => ___default.default.snakeCase(`${tableName}_${attributeName}_links`);
1899
2111
  const hasOrderColumn = (attribute) => isAnyToMany(attribute);
1900
2112
  const hasInverseOrderColumn = (attribute) => isBidirectional(attribute) && isManyToAny(attribute);
1901
2113
  const createOneToOne = (attributeName, attribute, meta, metadata) => {
@@ -1954,8 +2166,11 @@ const createManyToMany = (attributeName, attribute, meta, metadata) => {
1954
2166
  }
1955
2167
  };
1956
2168
  const createMorphToOne = (attributeName, attribute) => {
1957
- const idColumnName = "target_id";
1958
- const typeColumnName = "target_type";
2169
+ const idColumnName = getJoinColumnAttributeIdName("target");
2170
+ const typeColumnName = getMorphColumnTypeName("target");
2171
+ if ("morphColumn" in attribute && attribute.morphColumn) {
2172
+ return;
2173
+ }
1959
2174
  Object.assign(attribute, {
1960
2175
  owner: true,
1961
2176
  morphColumn: {
@@ -1965,23 +2180,26 @@ const createMorphToOne = (attributeName, attribute) => {
1965
2180
  },
1966
2181
  idColumn: {
1967
2182
  name: idColumnName,
1968
- referencedColumn: "id"
2183
+ referencedColumn: ID
1969
2184
  }
1970
2185
  }
1971
2186
  });
1972
2187
  };
1973
2188
  const createMorphToMany = (attributeName, attribute, meta, metadata) => {
1974
- const joinTableName = ___default.default.snakeCase(`${meta.tableName}_${attributeName}_morphs`);
1975
- const joinColumnName = ___default.default.snakeCase(`${meta.singularName}_id`);
1976
- const morphColumnName = ___default.default.snakeCase(`${attributeName}`);
1977
- const idColumnName = `${morphColumnName}_id`;
1978
- const typeColumnName = `${morphColumnName}_type`;
2189
+ if ("joinTable" in attribute && attribute.joinTable) {
2190
+ return;
2191
+ }
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);
1979
2197
  metadata.add({
1980
2198
  singularName: joinTableName,
1981
2199
  uid: joinTableName,
1982
2200
  tableName: joinTableName,
1983
2201
  attributes: {
1984
- id: {
2202
+ [ID]: {
1985
2203
  type: "increments"
1986
2204
  },
1987
2205
  [joinColumnName]: {
@@ -1999,10 +2217,10 @@ const createMorphToMany = (attributeName, attribute, meta, metadata) => {
1999
2217
  [typeColumnName]: {
2000
2218
  type: "string"
2001
2219
  },
2002
- field: {
2220
+ [FIELD]: {
2003
2221
  type: "string"
2004
2222
  },
2005
- order: {
2223
+ [ORDER]: {
2006
2224
  type: "float",
2007
2225
  column: {
2008
2226
  unsigned: true
@@ -2011,12 +2229,12 @@ const createMorphToMany = (attributeName, attribute, meta, metadata) => {
2011
2229
  },
2012
2230
  indexes: [
2013
2231
  {
2014
- name: `${joinTableName}_fk`,
2232
+ name: fkIndexName,
2015
2233
  columns: [joinColumnName]
2016
2234
  },
2017
2235
  {
2018
2236
  name: `${joinTableName}_order_index`,
2019
- columns: ["order"]
2237
+ columns: [ORDER]
2020
2238
  },
2021
2239
  {
2022
2240
  name: `${joinTableName}_id_column_index`,
@@ -2025,10 +2243,10 @@ const createMorphToMany = (attributeName, attribute, meta, metadata) => {
2025
2243
  ],
2026
2244
  foreignKeys: [
2027
2245
  {
2028
- name: `${joinTableName}_fk`,
2246
+ name: fkIndexName,
2029
2247
  columns: [joinColumnName],
2030
- referencedColumns: ["id"],
2031
- referencedTable: meta.tableName,
2248
+ referencedColumns: [ID],
2249
+ referencedTable: getTableName(meta.tableName),
2032
2250
  onDelete: "CASCADE"
2033
2251
  }
2034
2252
  ],
@@ -2039,7 +2257,7 @@ const createMorphToMany = (attributeName, attribute, meta, metadata) => {
2039
2257
  name: joinTableName,
2040
2258
  joinColumn: {
2041
2259
  name: joinColumnName,
2042
- referencedColumn: "id"
2260
+ referencedColumn: ID
2043
2261
  },
2044
2262
  morphColumn: {
2045
2263
  typeColumn: {
@@ -2047,7 +2265,7 @@ const createMorphToMany = (attributeName, attribute, meta, metadata) => {
2047
2265
  },
2048
2266
  idColumn: {
2049
2267
  name: idColumnName,
2050
- referencedColumn: "id"
2268
+ referencedColumn: ID
2051
2269
  }
2052
2270
  },
2053
2271
  orderBy: {
@@ -2083,9 +2301,12 @@ const createJoinColum = (metadata, { attribute, attributeName }) => {
2083
2301
  const joinColumnName = ___default.default.snakeCase(`${attributeName}_id`);
2084
2302
  const joinColumn = {
2085
2303
  name: joinColumnName,
2086
- referencedColumn: "id",
2304
+ referencedColumn: ID,
2087
2305
  referencedTable: targetMeta.tableName
2088
2306
  };
2307
+ if ("joinColumn" in attribute) {
2308
+ Object.assign(joinColumn, attribute.joinColumn);
2309
+ }
2089
2310
  Object.assign(attribute, { owner: true, joinColumn });
2090
2311
  if (isBidirectional(attribute)) {
2091
2312
  const inverseAttribute = targetMeta.attributes[attribute.inversedBy];
@@ -2102,23 +2323,30 @@ const createJoinTable = (metadata, { attributeName, attribute, meta }) => {
2102
2323
  if (!targetMeta) {
2103
2324
  throw new Error(`Unknown target ${attribute.target}`);
2104
2325
  }
2326
+ if ("joinTable" in attribute && attribute.joinTable) {
2327
+ return;
2328
+ }
2105
2329
  const joinTableName = getJoinTableName(meta.tableName, attributeName);
2106
- const joinColumnName = ___default.default.snakeCase(`${meta.singularName}_id`);
2107
- let inverseJoinColumnName = ___default.default.snakeCase(`${targetMeta.singularName}_id`);
2330
+ const joinColumnName = getJoinColumnAttributeIdName(meta.singularName);
2331
+ let inverseJoinColumnName = getJoinColumnAttributeIdName(targetMeta.singularName);
2108
2332
  if (joinColumnName === inverseJoinColumnName) {
2109
- inverseJoinColumnName = `inv_${inverseJoinColumnName}`;
2333
+ inverseJoinColumnName = getInverseJoinColumnAttributeIdName(
2334
+ targetMeta.singularName
2335
+ );
2110
2336
  }
2111
- const orderColumnName = ___default.default.snakeCase(`${targetMeta.singularName}_order`);
2112
- let inverseOrderColumnName = ___default.default.snakeCase(`${meta.singularName}_order`);
2337
+ const orderColumnName = getOrderColumnName(targetMeta.singularName);
2338
+ let inverseOrderColumnName = getOrderColumnName(meta.singularName);
2113
2339
  if (attribute.relation === "manyToMany" && orderColumnName === inverseOrderColumnName) {
2114
- inverseOrderColumnName = `inv_${inverseOrderColumnName}`;
2340
+ inverseOrderColumnName = getInverseOrderColumnName(meta.singularName);
2115
2341
  }
2342
+ const fkIndexName = getFkIndexName(joinTableName);
2343
+ const invFkIndexName = getInverseFkIndexName(joinTableName);
2116
2344
  const metadataSchema = {
2117
2345
  singularName: joinTableName,
2118
2346
  uid: joinTableName,
2119
2347
  tableName: joinTableName,
2120
2348
  attributes: {
2121
- id: {
2349
+ [ID]: {
2122
2350
  type: "increments"
2123
2351
  },
2124
2352
  [joinColumnName]: {
@@ -2137,32 +2365,34 @@ const createJoinTable = (metadata, { attributeName, attribute, meta }) => {
2137
2365
  },
2138
2366
  indexes: [
2139
2367
  {
2140
- name: `${joinTableName}_fk`,
2368
+ name: fkIndexName,
2141
2369
  columns: [joinColumnName]
2142
2370
  },
2143
2371
  {
2144
- name: `${joinTableName}_inv_fk`,
2372
+ name: invFkIndexName,
2145
2373
  columns: [inverseJoinColumnName]
2146
2374
  },
2147
2375
  {
2148
- name: `${joinTableName}_unique`,
2376
+ name: getUniqueIndexName(joinTableName),
2149
2377
  columns: [joinColumnName, inverseJoinColumnName],
2150
2378
  type: "unique"
2151
2379
  }
2152
2380
  ],
2153
2381
  foreignKeys: [
2154
2382
  {
2155
- name: `${joinTableName}_fk`,
2383
+ name: fkIndexName,
2156
2384
  columns: [joinColumnName],
2157
- referencedColumns: ["id"],
2385
+ referencedColumns: [ID],
2158
2386
  referencedTable: meta.tableName,
2387
+ // TODO: does this need to be wrapped or do we trust meta.tableName to be the right shortened version?
2159
2388
  onDelete: "CASCADE"
2160
2389
  },
2161
2390
  {
2162
- name: `${joinTableName}_inv_fk`,
2391
+ name: invFkIndexName,
2163
2392
  columns: [inverseJoinColumnName],
2164
- referencedColumns: ["id"],
2393
+ referencedColumns: [ID],
2165
2394
  referencedTable: targetMeta.tableName,
2395
+ // TODO: does this need to be wrapped or do we trust meta.tableName to be the right shortened version?
2166
2396
  onDelete: "CASCADE"
2167
2397
  }
2168
2398
  ],
@@ -2173,11 +2403,13 @@ const createJoinTable = (metadata, { attributeName, attribute, meta }) => {
2173
2403
  name: joinTableName,
2174
2404
  joinColumn: {
2175
2405
  name: joinColumnName,
2176
- referencedColumn: "id"
2406
+ referencedColumn: ID,
2407
+ referencedTable: meta.tableName
2177
2408
  },
2178
2409
  inverseJoinColumn: {
2179
2410
  name: inverseJoinColumnName,
2180
- referencedColumn: "id"
2411
+ referencedColumn: ID,
2412
+ referencedTable: targetMeta.tableName
2181
2413
  },
2182
2414
  pivotColumns: [joinColumnName, inverseJoinColumnName]
2183
2415
  };
@@ -2190,7 +2422,8 @@ const createJoinTable = (metadata, { attributeName, attribute, meta }) => {
2190
2422
  }
2191
2423
  };
2192
2424
  metadataSchema.indexes.push({
2193
- name: `${joinTableName}_order_fk`,
2425
+ name: getOrderFkIndexName(joinTableName),
2426
+ // TODO: should we send joinTableName as parts?
2194
2427
  columns: [orderColumnName]
2195
2428
  });
2196
2429
  joinTable.orderColumnName = orderColumnName;
@@ -2205,7 +2438,7 @@ const createJoinTable = (metadata, { attributeName, attribute, meta }) => {
2205
2438
  }
2206
2439
  };
2207
2440
  metadataSchema.indexes.push({
2208
- name: `${joinTableName}_order_inv_fk`,
2441
+ name: getOrderInverseFkIndexName(joinTableName),
2209
2442
  columns: [inverseOrderColumnName]
2210
2443
  });
2211
2444
  joinTable.inverseOrderColumnName = inverseOrderColumnName;
@@ -2286,207 +2519,60 @@ class Metadata extends Map {
2286
2519
  seenTables.set(meta.tableName, true);
2287
2520
  }
2288
2521
  }
2289
- }
2290
- const createMetadata = (models = []) => {
2291
- const metadata = new Metadata();
2292
- for (const model of ___default.default.cloneDeep(models)) {
2293
- if ("id" in model.attributes) {
2294
- throw new Error('The attribute "id" is reserved and cannot be used in a model');
2295
- }
2296
- metadata.add({
2297
- ...model,
2298
- attributes: {
2299
- id: {
2300
- type: "increments"
2522
+ loadModels(models = []) {
2523
+ for (const model of ___default.default.cloneDeep(models)) {
2524
+ this.add({
2525
+ ...model,
2526
+ attributes: {
2527
+ ...model.attributes
2301
2528
  },
2302
- ...model.attributes
2303
- },
2304
- lifecycles: model.lifecycles ?? {},
2305
- indexes: model.indexes || [],
2306
- foreignKeys: model.foreignKeys || [],
2307
- columnToAttribute: {}
2308
- });
2309
- }
2310
- for (const meta of metadata.values()) {
2311
- if (hasComponentsOrDz(meta)) {
2312
- const compoLinkModelMeta = createCompoLinkModelMeta(meta);
2313
- meta.componentLink = compoLinkModelMeta;
2314
- metadata.add(compoLinkModelMeta);
2529
+ lifecycles: model.lifecycles ?? {},
2530
+ indexes: model.indexes ?? [],
2531
+ foreignKeys: model.foreignKeys ?? [],
2532
+ columnToAttribute: {}
2533
+ });
2315
2534
  }
2316
- for (const [attributeName, attribute] of Object.entries(meta.attributes)) {
2317
- try {
2318
- if (isComponent(attribute.type) && hasComponentsOrDz(meta)) {
2319
- createComponent(attributeName, attribute, meta);
2320
- continue;
2321
- }
2322
- if (isDynamicZone(attribute.type) && hasComponentsOrDz(meta)) {
2323
- createDynamicZone(attributeName, attribute, meta);
2324
- continue;
2325
- }
2326
- if (isRelationalAttribute(attribute)) {
2327
- createRelation(attributeName, attribute, meta, metadata);
2328
- continue;
2329
- }
2330
- createAttribute(attributeName, attribute);
2331
- } catch (error) {
2332
- console.log(error);
2333
- if (error instanceof Error) {
2334
- throw new Error(
2335
- `Error on attribute ${attributeName} in model ${meta.singularName}(${meta.uid}): ${error.message}`
2336
- );
2535
+ for (const meta of this.values()) {
2536
+ for (const [attributeName, attribute] of Object.entries(meta.attributes)) {
2537
+ try {
2538
+ if (isRelationalAttribute(attribute)) {
2539
+ createRelation(attributeName, attribute, meta, this);
2540
+ continue;
2541
+ }
2542
+ createAttribute(attributeName, attribute);
2543
+ } catch (error) {
2544
+ if (error instanceof Error) {
2545
+ throw new Error(
2546
+ `Error on attribute ${attributeName} in model ${meta.singularName}(${meta.uid}): ${error.message}`
2547
+ );
2548
+ }
2337
2549
  }
2338
2550
  }
2339
2551
  }
2340
- }
2341
- for (const meta of metadata.values()) {
2342
- const columnToAttribute = Object.keys(meta.attributes).reduce((acc, key) => {
2343
- const attribute = meta.attributes[key];
2344
- if ("columnName" in attribute) {
2345
- return Object.assign(acc, { [attribute.columnName || key]: key });
2346
- }
2347
- return Object.assign(acc, { [key]: key });
2348
- }, {});
2349
- meta.columnToAttribute = columnToAttribute;
2350
- }
2351
- metadata.validate();
2352
- return metadata;
2353
- };
2354
- const hasComponentsOrDz = (model) => {
2355
- return Object.values(model.attributes).some(
2356
- ({ type }) => isComponent(type) || isDynamicZone(type)
2357
- );
2358
- };
2359
- const createCompoLinkModelMeta = (baseModelMeta) => {
2360
- const name = `${baseModelMeta.tableName}_components`;
2361
- return {
2362
- // TODO: make sure there can't be any conflicts with a prefix
2363
- singularName: name,
2364
- uid: name,
2365
- tableName: name,
2366
- attributes: {
2367
- id: {
2368
- type: "increments"
2369
- },
2370
- entity_id: {
2371
- type: "integer",
2372
- column: {
2373
- unsigned: true
2374
- }
2375
- },
2376
- component_id: {
2377
- type: "integer",
2378
- column: {
2379
- unsigned: true
2380
- }
2381
- },
2382
- component_type: {
2383
- type: "string"
2384
- },
2385
- field: {
2386
- type: "string"
2387
- },
2388
- order: {
2389
- type: "float",
2390
- column: {
2391
- unsigned: true,
2392
- defaultTo: null
2552
+ for (const meta of this.values()) {
2553
+ const columnToAttribute = Object.keys(meta.attributes).reduce((acc, key) => {
2554
+ const attribute = meta.attributes[key];
2555
+ if ("columnName" in attribute) {
2556
+ return Object.assign(acc, { [attribute.columnName || key]: key });
2393
2557
  }
2394
- }
2395
- },
2396
- indexes: [
2397
- {
2398
- name: `${baseModelMeta.tableName}_field_index`,
2399
- columns: ["field"]
2400
- },
2401
- {
2402
- name: `${baseModelMeta.tableName}_component_type_index`,
2403
- columns: ["component_type"]
2404
- },
2405
- {
2406
- name: `${baseModelMeta.tableName}_entity_fk`,
2407
- columns: ["entity_id"]
2408
- },
2409
- {
2410
- name: `${baseModelMeta.tableName}_unique`,
2411
- columns: ["entity_id", "component_id", "field", "component_type"],
2412
- type: "unique"
2413
- }
2414
- ],
2415
- foreignKeys: [
2416
- {
2417
- name: `${baseModelMeta.tableName}_entity_fk`,
2418
- columns: ["entity_id"],
2419
- referencedColumns: ["id"],
2420
- referencedTable: baseModelMeta.tableName,
2421
- onDelete: "CASCADE"
2422
- }
2423
- ],
2424
- lifecycles: {},
2425
- columnToAttribute: {}
2426
- };
2427
- };
2428
- const createDynamicZone = (attributeName, attribute, meta) => {
2429
- Object.assign(attribute, {
2430
- type: "relation",
2431
- relation: "morphToMany",
2432
- // TODO: handle restrictions at some point
2433
- // target: attribute.components,
2434
- joinTable: {
2435
- name: meta.componentLink.tableName,
2436
- joinColumn: {
2437
- name: "entity_id",
2438
- referencedColumn: "id"
2439
- },
2440
- morphColumn: {
2441
- idColumn: {
2442
- name: "component_id",
2443
- referencedColumn: "id"
2444
- },
2445
- typeColumn: {
2446
- name: "component_type"
2447
- },
2448
- typeField: "__component"
2449
- },
2450
- on: {
2451
- field: attributeName
2452
- },
2453
- orderBy: {
2454
- order: "asc"
2455
- },
2456
- pivotColumns: ["entity_id", "component_id", "field", "component_type"]
2457
- }
2458
- });
2459
- };
2460
- const createComponent = (attributeName, attribute, meta) => {
2461
- Object.assign(attribute, {
2462
- type: "relation",
2463
- relation: "repeatable" in attribute && attribute.repeatable === true ? "oneToMany" : "oneToOne",
2464
- target: "component" in attribute && attribute.component,
2465
- joinTable: {
2466
- name: meta.componentLink.tableName,
2467
- joinColumn: {
2468
- name: "entity_id",
2469
- referencedColumn: "id"
2470
- },
2471
- inverseJoinColumn: {
2472
- name: "component_id",
2473
- referencedColumn: "id"
2474
- },
2475
- on: {
2476
- field: attributeName
2477
- },
2478
- orderColumnName: "order",
2479
- orderBy: {
2480
- order: "asc"
2481
- },
2482
- pivotColumns: ["entity_id", "component_id", "field", "component_type"]
2558
+ return Object.assign(acc, { [key]: key });
2559
+ }, {});
2560
+ meta.columnToAttribute = columnToAttribute;
2483
2561
  }
2484
- });
2485
- };
2562
+ this.validate();
2563
+ }
2564
+ }
2486
2565
  const createAttribute = (attributeName, attribute) => {
2487
- const columnName = ___default.default.snakeCase(attributeName);
2566
+ const columnName = getColumnName(attributeName);
2488
2567
  Object.assign(attribute, { columnName });
2489
2568
  };
2569
+ const createMetadata = (models = []) => {
2570
+ const metadata = new Metadata();
2571
+ if (models.length) {
2572
+ metadata.loadModels(models);
2573
+ }
2574
+ return metadata;
2575
+ };
2490
2576
  class Field {
2491
2577
  config;
2492
2578
  constructor(config) {
@@ -3458,7 +3544,7 @@ const castValue = (value, attribute) => {
3458
3544
  };
3459
3545
  const processSingleAttributeWhere = (attribute, where, operator = "$eq") => {
3460
3546
  if (!isRecord$1(where)) {
3461
- if (utils.isOperatorOfType("cast", operator)) {
3547
+ if (utils$1.isOperatorOfType("cast", operator)) {
3462
3548
  return castValue(where, attribute);
3463
3549
  }
3464
3550
  return where;
@@ -3466,7 +3552,7 @@ const processSingleAttributeWhere = (attribute, where, operator = "$eq") => {
3466
3552
  const filters = {};
3467
3553
  for (const key of Object.keys(where)) {
3468
3554
  const value = where[key];
3469
- if (!utils.isOperatorOfType("where", key)) {
3555
+ if (!utils$1.isOperatorOfType("where", key)) {
3470
3556
  throw new Error(`Undefined attribute level operator ${key}`);
3471
3557
  }
3472
3558
  filters[key] = processAttributeWhere(attribute, value, key);
@@ -3497,7 +3583,7 @@ function processWhere(where, ctx) {
3497
3583
  const filters = {};
3498
3584
  for (const key of Object.keys(where)) {
3499
3585
  const value = where[key];
3500
- if (utils.isOperatorOfType("group", key) && Array.isArray(value)) {
3586
+ if (utils$1.isOperatorOfType("group", key) && Array.isArray(value)) {
3501
3587
  filters[key] = value.map((sub) => processNested(sub, ctx));
3502
3588
  continue;
3503
3589
  }
@@ -3505,7 +3591,7 @@ function processWhere(where, ctx) {
3505
3591
  filters[key] = processNested(value, ctx);
3506
3592
  continue;
3507
3593
  }
3508
- if (utils.isOperatorOfType("where", key)) {
3594
+ if (utils$1.isOperatorOfType("where", key)) {
3509
3595
  throw new Error(
3510
3596
  `Only $and, $or and $not can only be used as root level operators. Found ${key}.`
3511
3597
  );
@@ -3527,7 +3613,7 @@ function processWhere(where, ctx) {
3527
3613
  alias: subAlias,
3528
3614
  uid: attribute.target
3529
3615
  });
3530
- if (!isRecord$1(nestedWhere) || utils.isOperatorOfType("where", _.keys(nestedWhere)[0])) {
3616
+ if (!isRecord$1(nestedWhere) || utils$1.isOperatorOfType("where", _.keys(nestedWhere)[0])) {
3531
3617
  nestedWhere = { [qb.aliasColumn("id", subAlias)]: nestedWhere };
3532
3618
  }
3533
3619
  Object.assign(filters, nestedWhere);
@@ -3544,7 +3630,7 @@ function processWhere(where, ctx) {
3544
3630
  return filters;
3545
3631
  }
3546
3632
  const applyOperator = (qb, column, operator, value) => {
3547
- if (Array.isArray(value) && !utils.isOperatorOfType("array", operator)) {
3633
+ if (Array.isArray(value) && !utils$1.isOperatorOfType("array", operator)) {
3548
3634
  return qb.where((subQB) => {
3549
3635
  value.forEach(
3550
3636
  (subValue) => subQB.orWhere((innerQB) => {
@@ -4343,9 +4429,6 @@ const createRepository = (uid, db) => {
4343
4429
  updateMany(params) {
4344
4430
  return db.entityManager.updateMany(uid, params);
4345
4431
  },
4346
- clone(id, params) {
4347
- return db.entityManager.clone(uid, id, params);
4348
- },
4349
4432
  delete(params) {
4350
4433
  return db.entityManager.delete(uid, params);
4351
4434
  },
@@ -4371,9 +4454,6 @@ const createRepository = (uid, db) => {
4371
4454
  deleteRelations(id) {
4372
4455
  return db.entityManager.deleteRelations(uid, id);
4373
4456
  },
4374
- cloneRelations(targetId, sourceId, params) {
4375
- return db.entityManager.cloneRelations(uid, targetId, sourceId, params);
4376
- },
4377
4457
  populate(entity, populate) {
4378
4458
  return db.entityManager.populate(uid, entity, populate);
4379
4459
  },
@@ -4449,6 +4529,19 @@ const deleteRelatedMorphOneRelationsAfterMorphToManyUpdate = async (rows, {
4449
4529
  await createQueryBuilder(joinTable.name, db).delete().where({ $or: orWhere }).transacting(trx).execute();
4450
4530
  }
4451
4531
  };
4532
+ const getDocumentSiblingIdsQuery = (con, tableName, id) => {
4533
+ const models = Array.from(strapi.db.metadata.values());
4534
+ const isContentType = models.find((model) => {
4535
+ return model.tableName === tableName && model.attributes.documentId;
4536
+ });
4537
+ if (!isContentType) {
4538
+ return [id];
4539
+ }
4540
+ return con.from(tableName).select("id").where(
4541
+ "document_id",
4542
+ con.from(tableName).select("document_id").where("id", id)
4543
+ );
4544
+ };
4452
4545
  const deletePreviousOneToAnyRelations = async ({
4453
4546
  id,
4454
4547
  attribute,
@@ -4463,10 +4556,8 @@ const deletePreviousOneToAnyRelations = async ({
4463
4556
  }
4464
4557
  const { joinTable } = attribute;
4465
4558
  const { joinColumn, inverseJoinColumn } = joinTable;
4466
- await createQueryBuilder(joinTable.name, db).delete().where({
4467
- [inverseJoinColumn.name]: relIdsToadd,
4468
- [joinColumn.name]: { $ne: id }
4469
- }).where(joinTable.on || {}).transacting(trx).execute();
4559
+ const con = db.getConnection();
4560
+ await con.delete().from(joinTable.name).whereNotIn(joinColumn.name, getDocumentSiblingIdsQuery(con, joinColumn.referencedTable, id)).whereIn(inverseJoinColumn.name, relIdsToadd).where(joinTable.on || {}).transacting(trx);
4470
4561
  await cleanOrderColumns({ attribute, db, inverseRelIds: relIdsToadd, transaction: trx });
4471
4562
  };
4472
4563
  const deletePreviousAnyToOneRelations = async ({
@@ -4478,14 +4569,15 @@ const deletePreviousAnyToOneRelations = async ({
4478
4569
  }) => {
4479
4570
  const { joinTable } = attribute;
4480
4571
  const { joinColumn, inverseJoinColumn } = joinTable;
4572
+ const con = db.getConnection();
4481
4573
  if (!isAnyToOne(attribute)) {
4482
4574
  throw new Error("deletePreviousAnyToOneRelations can only be called for anyToOne relations");
4483
4575
  }
4484
4576
  if (isManyToAny(attribute)) {
4485
- const relsToDelete = await createQueryBuilder(joinTable.name, db).select(inverseJoinColumn.name).where({
4486
- [joinColumn.name]: id,
4487
- [inverseJoinColumn.name]: { $ne: relIdToadd }
4488
- }).where(joinTable.on || {}).transacting(trx).execute();
4577
+ const relsToDelete = await con.select(inverseJoinColumn.name).from(joinTable.name).where(joinColumn.name, id).whereNotIn(
4578
+ inverseJoinColumn.name,
4579
+ getDocumentSiblingIdsQuery(con, inverseJoinColumn.referencedTable, relIdToadd)
4580
+ ).where(joinTable.on || {}).transacting(trx);
4489
4581
  const relIdsToDelete = _.map(inverseJoinColumn.name, relsToDelete);
4490
4582
  await createQueryBuilder(joinTable.name, db).delete().where({
4491
4583
  [joinColumn.name]: id,
@@ -4493,10 +4585,10 @@ const deletePreviousAnyToOneRelations = async ({
4493
4585
  }).where(joinTable.on || {}).transacting(trx).execute();
4494
4586
  await cleanOrderColumns({ attribute, db, inverseRelIds: relIdsToDelete, transaction: trx });
4495
4587
  } else {
4496
- await createQueryBuilder(joinTable.name, db).delete().where({
4497
- [joinColumn.name]: id,
4498
- [inverseJoinColumn.name]: { $ne: relIdToadd }
4499
- }).where(joinTable.on || {}).transacting(trx).execute();
4588
+ await con.delete().from(joinTable.name).where(joinColumn.name, id).whereNotIn(
4589
+ inverseJoinColumn.name,
4590
+ getDocumentSiblingIdsQuery(con, inverseJoinColumn.referencedTable, relIdToadd)
4591
+ ).where(joinTable.on || {}).transacting(trx);
4500
4592
  }
4501
4593
  };
4502
4594
  const deleteRelations = async ({
@@ -4548,10 +4640,6 @@ const cleanOrderColumns = async ({
4548
4640
  if (!(hasOrderColumn(attribute) && id) && !(hasInverseOrderColumn(attribute) && !_.isEmpty(inverseRelIds))) {
4549
4641
  return;
4550
4642
  }
4551
- if (!strapi.db.dialect.supportsWindowFunctions()) {
4552
- await cleanOrderColumnsForOldDatabases({ id, attribute, db, inverseRelIds, transaction: trx });
4553
- return;
4554
- }
4555
4643
  const { joinTable } = attribute;
4556
4644
  const { joinColumn, inverseJoinColumn, orderColumnName, inverseOrderColumnName } = joinTable;
4557
4645
  const updateOrderColumn = async () => {
@@ -4613,94 +4701,6 @@ const cleanOrderColumns = async ({
4613
4701
  };
4614
4702
  return Promise.all([updateOrderColumn(), updateInverseOrderColumn()]);
4615
4703
  };
4616
- const cleanOrderColumnsForOldDatabases = async ({
4617
- id,
4618
- attribute,
4619
- db,
4620
- inverseRelIds,
4621
- transaction: trx
4622
- }) => {
4623
- const { joinTable } = attribute;
4624
- const { joinColumn, inverseJoinColumn, orderColumnName, inverseOrderColumnName } = joinTable;
4625
- const randomSuffix = `${(/* @__PURE__ */ new Date()).valueOf()}_${crypto.randomBytes(16).toString("hex")}`;
4626
- if (hasOrderColumn(attribute) && id) {
4627
- const orderVar = `order_${randomSuffix}`;
4628
- await db.connection.raw(`SET @${orderVar} = 0;`).transacting(trx);
4629
- await db.connection.raw(
4630
- `UPDATE :joinTableName: as a, (
4631
- SELECT id, (@${orderVar}:=@${orderVar} + 1) AS src_order
4632
- FROM :joinTableName:
4633
- WHERE :joinColumnName: = :id
4634
- ORDER BY :orderColumnName:
4635
- ) AS b
4636
- SET :orderColumnName: = b.src_order
4637
- WHERE a.id = b.id
4638
- AND a.:joinColumnName: = :id`,
4639
- {
4640
- joinTableName: joinTable.name,
4641
- orderColumnName,
4642
- joinColumnName: joinColumn.name,
4643
- id
4644
- }
4645
- ).transacting(trx);
4646
- }
4647
- if (hasInverseOrderColumn(attribute) && !_.isEmpty(inverseRelIds)) {
4648
- const orderVar = `order_${randomSuffix}`;
4649
- const columnVar = `col_${randomSuffix}`;
4650
- await db.connection.raw(`SET @${orderVar} = 0;`).transacting(trx);
4651
- await db.connection.raw(
4652
- `UPDATE ?? as a, (
4653
- SELECT
4654
- id,
4655
- @${orderVar}:=CASE WHEN @${columnVar} = ?? THEN @${orderVar} + 1 ELSE 1 END AS inv_order,
4656
- @${columnVar}:=?? ??
4657
- FROM ?? a
4658
- WHERE ?? IN(${inverseRelIds.map(() => "?").join(", ")})
4659
- ORDER BY ??, ??
4660
- ) AS b
4661
- SET ?? = b.inv_order
4662
- WHERE a.id = b.id
4663
- AND a.?? IN(${inverseRelIds.map(() => "?").join(", ")})`,
4664
- [
4665
- joinTable.name,
4666
- inverseJoinColumn.name,
4667
- inverseJoinColumn.name,
4668
- inverseJoinColumn.name,
4669
- joinTable.name,
4670
- inverseJoinColumn.name,
4671
- ...inverseRelIds,
4672
- inverseJoinColumn.name,
4673
- joinColumn.name,
4674
- inverseOrderColumnName,
4675
- inverseJoinColumn.name,
4676
- ...inverseRelIds
4677
- ]
4678
- ).transacting(trx);
4679
- }
4680
- };
4681
- const cleanInverseOrderColumn = async ({
4682
- id,
4683
- attribute,
4684
- trx
4685
- }) => {
4686
- const con = strapi.db.connection;
4687
- const { joinTable } = attribute;
4688
- const { joinColumn, inverseJoinColumn, inverseOrderColumnName } = joinTable;
4689
- switch (strapi.db.dialect.client) {
4690
- case "mysql": {
4691
- const subQuery = con(joinTable.name).select(inverseJoinColumn.name).max(inverseOrderColumnName, { as: "max_inv_order" }).groupBy(inverseJoinColumn.name).as("t2");
4692
- await con(`${joinTable.name} as t1`).join(subQuery, `t1.${inverseJoinColumn.name}`, "=", `t2.${inverseJoinColumn.name}`).where(joinColumn.name, id).update({
4693
- [inverseOrderColumnName]: con.raw("t2.max_inv_order + 1")
4694
- }).transacting(trx);
4695
- break;
4696
- }
4697
- default: {
4698
- const selectMaxInverseOrder = con.raw(`max(${inverseOrderColumnName}) + 1`);
4699
- const subQuery = con(`${joinTable.name} as t2`).select(selectMaxInverseOrder).whereRaw(`t2.${inverseJoinColumn.name} = t1.${inverseJoinColumn.name}`);
4700
- await con(`${joinTable.name} as t1`).where(`t1.${joinColumn.name}`, id).update({ [inverseOrderColumnName]: subQuery }).transacting(trx);
4701
- }
4702
- }
4703
- };
4704
4704
  const sortConnectArray = (connectArr, initialArr = [], strictSort = true) => {
4705
4705
  const sortedConnect = [];
4706
4706
  let needsSorting = false;
@@ -4841,55 +4841,6 @@ const relationsOrderer = (initArr, idColumn, orderColumn, strict) => {
4841
4841
  }
4842
4842
  };
4843
4843
  };
4844
- const replaceRegularRelations = async ({
4845
- targetId,
4846
- sourceId,
4847
- attribute,
4848
- omitIds,
4849
- transaction: trx
4850
- }) => {
4851
- const { joinTable } = attribute;
4852
- const { joinColumn, inverseJoinColumn } = joinTable;
4853
- await strapi.db.entityManager.createQueryBuilder(joinTable.name).update({ [joinColumn.name]: targetId }).where({ [joinColumn.name]: sourceId }).where({ $not: { [inverseJoinColumn.name]: omitIds } }).onConflict([joinColumn.name, inverseJoinColumn.name]).ignore().transacting(trx).execute();
4854
- };
4855
- const cloneRegularRelations = async ({
4856
- targetId,
4857
- sourceId,
4858
- attribute,
4859
- transaction: trx
4860
- }) => {
4861
- const { joinTable } = attribute;
4862
- const { joinColumn, inverseJoinColumn, orderColumnName, inverseOrderColumnName } = joinTable;
4863
- const connection = strapi.db.getConnection();
4864
- const columns = [joinColumn.name, inverseJoinColumn.name];
4865
- if (orderColumnName) {
4866
- columns.push(orderColumnName);
4867
- }
4868
- if (inverseOrderColumnName) {
4869
- columns.push(inverseOrderColumnName);
4870
- }
4871
- if (joinTable.on) {
4872
- columns.push(...Object.keys(joinTable.on));
4873
- }
4874
- const selectStatement = connection.select(
4875
- // Override joinColumn with the new id
4876
- { [joinColumn.name]: targetId },
4877
- ...columns.slice(1)
4878
- ).where(joinColumn.name, sourceId).from(joinTable.name).toSQL();
4879
- await strapi.db.entityManager.createQueryBuilder(joinTable.name).insert(
4880
- strapi.db.connection.raw(
4881
- `(${columns.join(",")}) ${selectStatement.sql}`,
4882
- selectStatement.bindings
4883
- )
4884
- ).onConflict([joinColumn.name, inverseJoinColumn.name]).ignore().transacting(trx).execute();
4885
- if (inverseOrderColumnName) {
4886
- await cleanInverseOrderColumn({
4887
- id: targetId,
4888
- attribute,
4889
- trx
4890
- });
4891
- }
4892
- };
4893
4844
  const isRecord = (value) => _.isObject(value) && !_.isNil(value);
4894
4845
  const toId = (value) => {
4895
4846
  if (isRecord(value) && "id" in value && isValidId(value.id)) {
@@ -5039,7 +4990,8 @@ const createEntityManager = (db) => {
5039
4990
  const result = await this.findOne(uid, {
5040
4991
  where: { id },
5041
4992
  select: params.select,
5042
- populate: params.populate
4993
+ populate: params.populate,
4994
+ filters: params.filters
5043
4995
  });
5044
4996
  await db.lifecycles.run("afterCreate", uid, { params, result }, states);
5045
4997
  return result;
@@ -5097,7 +5049,8 @@ const createEntityManager = (db) => {
5097
5049
  const result = await this.findOne(uid, {
5098
5050
  where: { id },
5099
5051
  select: params.select,
5100
- populate: params.populate
5052
+ populate: params.populate,
5053
+ filters: params.filters
5101
5054
  });
5102
5055
  await db.lifecycles.run("afterUpdate", uid, { params, result }, states);
5103
5056
  return result;
@@ -5116,50 +5069,6 @@ const createEntityManager = (db) => {
5116
5069
  await db.lifecycles.run("afterUpdateMany", uid, { params, result }, states);
5117
5070
  return result;
5118
5071
  },
5119
- async clone(uid, cloneId, params = {}) {
5120
- const states = await db.lifecycles.run("beforeCreate", uid, { params });
5121
- const metadata = db.metadata.get(uid);
5122
- const { data } = params;
5123
- if (!_.isNil(data) && !_.isPlainObject(data)) {
5124
- throw new Error("Create expects a data object");
5125
- }
5126
- const entity = await this.findOne(uid, { where: { id: cloneId } });
5127
- const dataToInsert = _.flow(
5128
- // Omit unwanted properties
5129
- _.omit(["id", "created_at", "updated_at"]),
5130
- // Merge with provided data, set attribute to null if data attribute is null
5131
- _.mergeWith(
5132
- data || {},
5133
- (original, override) => override === null ? override : original
5134
- ),
5135
- // Process data with metadata
5136
- (entity2) => processData(metadata, entity2, { withDefaults: true })
5137
- )(entity);
5138
- const res = await this.createQueryBuilder(uid).insert(dataToInsert).execute();
5139
- const id = isRecord(res[0]) ? res[0].id : res[0];
5140
- const trx = await strapi.db.transaction();
5141
- try {
5142
- const cloneAttrs = Object.entries(metadata.attributes).reduce((acc, [attrName, attr]) => {
5143
- if (isRelationalAttribute(attr) && "joinTable" in attr && attr.joinTable && !("component" in attr)) {
5144
- acc.push(attrName);
5145
- }
5146
- return acc;
5147
- }, []);
5148
- await this.cloneRelations(uid, id, cloneId, data, { cloneAttrs, transaction: trx.get() });
5149
- await trx.commit();
5150
- } catch (e) {
5151
- await trx.rollback();
5152
- await this.createQueryBuilder(uid).where({ id }).delete().execute();
5153
- throw e;
5154
- }
5155
- const result = await this.findOne(uid, {
5156
- where: { id },
5157
- select: params.select,
5158
- populate: params.populate
5159
- });
5160
- await db.lifecycles.run("afterCreate", uid, { params, result }, states);
5161
- return result;
5162
- },
5163
5072
  async delete(uid, params = {}) {
5164
5073
  const states = await db.lifecycles.run("beforeDelete", uid, { params });
5165
5074
  const { where, select, populate } = params;
@@ -5445,9 +5354,8 @@ const createEntityManager = (db) => {
5445
5354
  const isPartialUpdate = !_.has("set", cleanRelationData);
5446
5355
  let relIdsToaddOrMove;
5447
5356
  if (isPartialUpdate) {
5448
- if (isAnyToOne(attribute)) {
5449
- cleanRelationData.connect = cleanRelationData.connect?.slice(-1);
5450
- }
5357
+ if (isAnyToOne(attribute))
5358
+ ;
5451
5359
  relIdsToaddOrMove = toIds(cleanRelationData.connect);
5452
5360
  const relIdsToDelete = toIds(
5453
5361
  _.differenceWith(
@@ -5661,58 +5569,6 @@ const createEntityManager = (db) => {
5661
5569
  }
5662
5570
  }
5663
5571
  },
5664
- // TODO: Clone polymorphic relations
5665
- /**
5666
- *
5667
- * @param {string} uid - uid of the entity to clone
5668
- * @param {number} targetId - id of the entity to clone into
5669
- * @param {number} sourceId - id of the entity to clone from
5670
- * @param {object} opt
5671
- * @param {object} opt.cloneAttrs - key value pair of attributes to clone
5672
- * @param {object} opt.transaction - transaction to use
5673
- * @example cloneRelations('user', 3, 1, { cloneAttrs: ["comments"]})
5674
- * @example cloneRelations('post', 5, 2, { cloneAttrs: ["comments", "likes"] })
5675
- */
5676
- async cloneRelations(uid, targetId, sourceId, data, options) {
5677
- const { attributes } = db.metadata.get(uid);
5678
- const { cloneAttrs = [], transaction } = options ?? {};
5679
- if (!attributes) {
5680
- return;
5681
- }
5682
- await utils.mapAsync(cloneAttrs, async (attrName) => {
5683
- const attribute = attributes[attrName];
5684
- if (attribute.type !== "relation") {
5685
- throw new DatabaseError(
5686
- `Attribute ${attrName} is not a relation attribute. Cloning relations is only supported for relation attributes.`
5687
- );
5688
- }
5689
- if (isPolymorphic(attribute)) {
5690
- return;
5691
- }
5692
- if ("joinColumn" in attribute) {
5693
- return;
5694
- }
5695
- if (!attribute.joinTable) {
5696
- return;
5697
- }
5698
- let omitIds = [];
5699
- if (_.has(attrName, data)) {
5700
- const cleanRelationData = toAssocs(data[attrName]);
5701
- if (cleanRelationData.set) {
5702
- return;
5703
- }
5704
- if (cleanRelationData.disconnect) {
5705
- omitIds = toIds(cleanRelationData.disconnect);
5706
- }
5707
- }
5708
- if (isOneToAny(attribute) && isBidirectional(attribute)) {
5709
- await replaceRegularRelations({ targetId, sourceId, attribute, omitIds, transaction });
5710
- } else {
5711
- await cloneRegularRelations({ targetId, sourceId, attribute, transaction });
5712
- }
5713
- });
5714
- await this.updateRelations(uid, targetId, data, { transaction });
5715
- },
5716
5572
  // TODO: add lifecycle events
5717
5573
  async populate(uid, entity, populate) {
5718
5574
  const entry = await this.findOne(uid, {
@@ -5774,7 +5630,7 @@ const createEntityManager = (db) => {
5774
5630
  };
5775
5631
  };
5776
5632
  const createStorage = (opts) => {
5777
- const { db, tableName = "strapi_migrations" } = opts;
5633
+ const { db, tableName } = opts;
5778
5634
  const hasMigrationTable = () => db.getSchemaConnection().hasTable(tableName);
5779
5635
  const createMigrationTable = () => {
5780
5636
  return db.getSchemaConnection().createTable(tableName, (table) => {
@@ -5804,7 +5660,7 @@ const createStorage = (opts) => {
5804
5660
  };
5805
5661
  };
5806
5662
  const wrapTransaction = (db) => (fn) => () => {
5807
- return db.connection.transaction((trx) => Promise.resolve(fn(trx)));
5663
+ return db.connection.transaction((trx) => Promise.resolve(fn(trx, db)));
5808
5664
  };
5809
5665
  const migrationResolver = ({ name, path: path2, context }) => {
5810
5666
  const { db } = context;
@@ -5828,31 +5684,82 @@ const migrationResolver = ({ name, path: path2, context }) => {
5828
5684
  down: wrapTransaction(db)(migration.down)
5829
5685
  };
5830
5686
  };
5831
- const createUmzugProvider = (db) => {
5832
- const migrationDir = path__default$1.default.join(strapi.dirs.app.root, "database/migrations");
5833
- fse__default.default.ensureDirSync(migrationDir);
5834
- return new umzug.Umzug({
5687
+ const createUserMigrationProvider = (db) => {
5688
+ const dir = db.config.settings.migrations.dir;
5689
+ fse__default.default.ensureDirSync(dir);
5690
+ const context = { db };
5691
+ const umzugProvider = new umzug.Umzug({
5835
5692
  storage: createStorage({ db, tableName: "strapi_migrations" }),
5836
5693
  logger: console,
5837
- context: { db },
5694
+ context,
5838
5695
  migrations: {
5839
- glob: ["*.{js,sql}", { cwd: migrationDir }],
5696
+ glob: ["*.{js,sql}", { cwd: dir }],
5840
5697
  resolve: migrationResolver
5841
5698
  }
5842
5699
  });
5700
+ return {
5701
+ async shouldRun() {
5702
+ const pendingMigrations = await umzugProvider.pending();
5703
+ return pendingMigrations.length > 0 && db.config?.settings?.runMigrations === true;
5704
+ },
5705
+ async up() {
5706
+ await umzugProvider.up();
5707
+ },
5708
+ async down() {
5709
+ await umzugProvider.down();
5710
+ }
5711
+ };
5712
+ };
5713
+ const internalMigrations = [];
5714
+ const createInternalMigrationProvider = (db) => {
5715
+ const context = { db };
5716
+ const umzugProvider = new umzug.Umzug({
5717
+ storage: createStorage({ db, tableName: "strapi_migrations_internal" }),
5718
+ logger: console,
5719
+ context,
5720
+ migrations: internalMigrations.map((migration) => {
5721
+ return {
5722
+ name: migration.name,
5723
+ up: wrapTransaction(context.db)(migration.up),
5724
+ down: wrapTransaction(context.db)(migration.down)
5725
+ };
5726
+ })
5727
+ });
5728
+ return {
5729
+ async shouldRun() {
5730
+ const pendingMigrations = await umzugProvider.pending();
5731
+ return pendingMigrations.length > 0;
5732
+ },
5733
+ async up() {
5734
+ await umzugProvider.up();
5735
+ },
5736
+ async down() {
5737
+ await umzugProvider.down();
5738
+ }
5739
+ };
5843
5740
  };
5844
5741
  const createMigrationsProvider = (db) => {
5845
- const migrations = createUmzugProvider(db);
5742
+ const providers = [createUserMigrationProvider(db), createInternalMigrationProvider(db)];
5846
5743
  return {
5847
5744
  async shouldRun() {
5848
- const pending = await migrations.pending();
5849
- return pending.length > 0 && db.config?.settings?.runMigrations === true;
5745
+ const shouldRunResponses = await Promise.all(
5746
+ providers.map((provider) => provider.shouldRun())
5747
+ );
5748
+ return shouldRunResponses.some((shouldRun) => shouldRun);
5850
5749
  },
5851
5750
  async up() {
5852
- await migrations.up();
5751
+ for (const provider of providers) {
5752
+ if (await provider.shouldRun()) {
5753
+ await provider.up();
5754
+ }
5755
+ }
5853
5756
  },
5854
5757
  async down() {
5855
- await migrations.down();
5758
+ for (const provider of providers) {
5759
+ if (await provider.shouldRun()) {
5760
+ await provider.down();
5761
+ }
5762
+ }
5856
5763
  }
5857
5764
  };
5858
5765
  };
@@ -5965,82 +5872,41 @@ const createLifecyclesProvider = (db) => {
5965
5872
  }
5966
5873
  };
5967
5874
  };
5968
- class LegacySqliteClient extends SqliteClient__default.default {
5969
- _driver() {
5970
- return require("sqlite3");
5971
- }
5972
- }
5973
5875
  const clientMap = {
5974
- "better-sqlite3": "better-sqlite3",
5975
- "@vscode/sqlite3": "sqlite",
5976
- sqlite3: LegacySqliteClient
5876
+ sqlite: "better-sqlite3",
5877
+ mysql: "mysql2",
5878
+ postgres: "pg"
5977
5879
  };
5978
- const trySqlitePackage = (packageName) => {
5979
- try {
5980
- require.resolve(packageName);
5981
- return packageName;
5982
- } catch (error) {
5983
- if (error instanceof Error && "code" in error && error.code === "MODULE_NOT_FOUND") {
5984
- return false;
5985
- }
5986
- throw error;
5987
- }
5988
- };
5989
- const getSqlitePackageName = () => {
5990
- if (typeof process.env.SQLITE_PKG !== "undefined") {
5991
- return process.env.SQLITE_PKG;
5992
- }
5993
- const matchingPackage = trySqlitePackage("better-sqlite3") || trySqlitePackage("@vscode/sqlite3") || trySqlitePackage("sqlite3");
5994
- if (!matchingPackage) {
5995
- throw new Error("No sqlite package found");
5996
- }
5997
- return matchingPackage;
5998
- };
5999
- const createConnection = (config) => {
6000
- const knexConfig = { ...config };
6001
- if (knexConfig.client === "sqlite") {
6002
- const sqlitePackageName = getSqlitePackageName();
6003
- knexConfig.client = clientMap[sqlitePackageName];
5880
+ function isClientValid(config) {
5881
+ return Object.keys(clientMap).includes(config.client);
5882
+ }
5883
+ const createConnection = (userConfig, strapiConfig) => {
5884
+ if (!isClientValid(userConfig)) {
5885
+ throw new Error(`Unsupported database client ${userConfig.client}`);
5886
+ }
5887
+ const knexConfig = { ...userConfig, client: clientMap[userConfig.client] };
5888
+ if (strapiConfig?.pool?.afterCreate) {
5889
+ knexConfig.pool = knexConfig.pool || {};
5890
+ const userAfterCreate = knexConfig.pool?.afterCreate;
5891
+ const strapiAfterCreate = strapiConfig.pool.afterCreate;
5892
+ knexConfig.pool.afterCreate = (conn, done) => {
5893
+ strapiAfterCreate(conn, (err, nativeConn) => {
5894
+ if (err) {
5895
+ return done(err, nativeConn);
5896
+ }
5897
+ if (userAfterCreate) {
5898
+ return userAfterCreate(nativeConn, done);
5899
+ }
5900
+ return done(null, nativeConn);
5901
+ });
5902
+ };
6004
5903
  }
6005
5904
  return knex__default.default(knexConfig);
6006
5905
  };
6007
- const transformAttribute = (attribute) => {
6008
- switch (attribute.type) {
6009
- case "media": {
6010
- return {
6011
- type: "relation",
6012
- relation: attribute.multiple === true ? "morphMany" : "morphOne",
6013
- target: "plugin::upload.file",
6014
- morphBy: "related"
6015
- };
6016
- }
6017
- default: {
6018
- return attribute;
6019
- }
6020
- }
6021
- };
6022
- const transformContentTypes = (contentTypes) => {
6023
- return contentTypes.map((contentType) => {
6024
- const model = {
6025
- ...contentType,
6026
- // reuse new model def
6027
- singularName: contentType.modelName,
6028
- tableName: contentType.collectionName,
6029
- attributes: {
6030
- ...Object.keys(contentType.attributes || {}).reduce((attrs, attrName) => {
6031
- return Object.assign(attrs, {
6032
- [attrName]: transformAttribute(contentType.attributes[attrName])
6033
- });
6034
- }, {})
6035
- }
6036
- };
6037
- return model;
6038
- });
6039
- };
6040
5906
  const getLinksWithoutMappedBy = (db) => {
6041
5907
  const relationsToUpdate = {};
6042
- db.metadata.forEach((contentType) => {
6043
- const attributes = contentType.attributes;
5908
+ db.metadata.forEach((modelMetadata) => {
5909
+ const attributes = modelMetadata.attributes;
6044
5910
  Object.values(attributes).forEach((attribute) => {
6045
5911
  if (attribute.type !== "relation") {
6046
5912
  return;
@@ -6068,19 +5934,19 @@ const isLinkTableEmpty = async (db, linkTableName) => {
6068
5934
  const validateBidirectionalRelations = async (db) => {
6069
5935
  const invalidLinks = getLinksWithoutMappedBy(db);
6070
5936
  for (const { relation, invRelation } of invalidLinks) {
6071
- const contentType = db.metadata.get(invRelation.target);
6072
- const invContentType = db.metadata.get(relation.target);
6073
- const joinTableName = getJoinTableName(contentType.tableName, invRelation.inversedBy);
6074
- const inverseJoinTableName = getJoinTableName(invContentType.tableName, relation.inversedBy);
5937
+ const modelMetadata = db.metadata.get(invRelation.target);
5938
+ const invModelMetadata = db.metadata.get(relation.target);
5939
+ const joinTableName = getJoinTableName(modelMetadata.tableName, invRelation.inversedBy);
5940
+ const inverseJoinTableName = getJoinTableName(invModelMetadata.tableName, relation.inversedBy);
6075
5941
  const joinTableEmpty = await isLinkTableEmpty(db, joinTableName);
6076
5942
  const inverseJoinTableEmpty = await isLinkTableEmpty(db, inverseJoinTableName);
6077
5943
  if (joinTableEmpty) {
6078
5944
  process.emitWarning(
6079
- `Error on attribute "${invRelation.inversedBy}" in model "${contentType.singularName}" (${contentType.uid}). Please modify your ${contentType.singularName} schema by renaming the key "inversedBy" to "mappedBy". Ex: { "inversedBy": "${relation.inversedBy}" } -> { "mappedBy": "${relation.inversedBy}" }`
5945
+ `Error on attribute "${invRelation.inversedBy}" in model "${modelMetadata.singularName}" (${modelMetadata.uid}). Please modify your ${modelMetadata.singularName} schema by renaming the key "inversedBy" to "mappedBy". Ex: { "inversedBy": "${relation.inversedBy}" } -> { "mappedBy": "${relation.inversedBy}" }`
6080
5946
  );
6081
5947
  } else if (inverseJoinTableEmpty) {
6082
5948
  process.emitWarning(
6083
- `Error on attribute "${relation.inversedBy}" in model "${invContentType.singularName}" (${invContentType.uid}). Please modify your ${invContentType.singularName} schema by renaming the key "inversedBy" to "mappedBy". Ex: { "inversedBy": "${invRelation.inversedBy}" } -> { "mappedBy": "${invRelation.inversedBy}" }`
5949
+ `Error on attribute "${relation.inversedBy}" in model "${invModelMetadata.singularName}" (${invModelMetadata.uid}). Please modify your ${invModelMetadata.singularName} schema by renaming the key "inversedBy" to "mappedBy". Ex: { "inversedBy": "${invRelation.inversedBy}" } -> { "mappedBy": "${invRelation.inversedBy}" }`
6084
5950
  );
6085
5951
  } else
6086
5952
  ;
@@ -6092,6 +5958,11 @@ const validateRelations = async (db) => {
6092
5958
  async function validateDatabase(db) {
6093
5959
  await validateRelations(db);
6094
5960
  }
5961
+ const afterCreate = (db) => (nativeConnection, done) => {
5962
+ db.dialect.initialize(nativeConnection).then(() => {
5963
+ return done(null, nativeConnection);
5964
+ });
5965
+ };
6095
5966
  class Database {
6096
5967
  connection;
6097
5968
  dialect;
@@ -6101,14 +5972,7 @@ class Database {
6101
5972
  migrations;
6102
5973
  lifecycles;
6103
5974
  entityManager;
6104
- static transformContentTypes = transformContentTypes;
6105
- static async init(config) {
6106
- const db = new Database(config);
6107
- await validateDatabase(db);
6108
- return db;
6109
- }
6110
5975
  constructor(config) {
6111
- this.metadata = createMetadata(config.models);
6112
5976
  this.config = {
6113
5977
  ...config,
6114
5978
  settings: {
@@ -6119,13 +5983,20 @@ class Database {
6119
5983
  };
6120
5984
  this.dialect = getDialect(this);
6121
5985
  this.dialect.configure();
6122
- this.connection = createConnection(this.config.connection);
6123
- this.dialect.initialize();
5986
+ this.metadata = createMetadata();
5987
+ this.connection = createConnection(this.config.connection, {
5988
+ pool: { afterCreate: afterCreate(this) }
5989
+ });
6124
5990
  this.schema = createSchemaProvider(this);
6125
5991
  this.migrations = createMigrationsProvider(this);
6126
5992
  this.lifecycles = createLifecyclesProvider(this);
6127
5993
  this.entityManager = createEntityManager(this);
6128
5994
  }
5995
+ async init({ models }) {
5996
+ this.metadata.loadModels(models);
5997
+ await validateDatabase(this);
5998
+ return this;
5999
+ }
6129
6000
  query(uid) {
6130
6001
  if (!this.metadata.has(uid)) {
6131
6002
  throw new Error(`Model ${uid} not found`);
@@ -6189,7 +6060,9 @@ class Database {
6189
6060
  await this.connection.destroy();
6190
6061
  }
6191
6062
  }
6063
+ const utils = { identifiers };
6192
6064
  exports.Database = Database;
6193
6065
  exports.errors = index;
6194
6066
  exports.isKnexQuery = isKnexQuery;
6067
+ exports.utils = utils;
6195
6068
  //# sourceMappingURL=index.js.map