@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.mjs CHANGED
@@ -1,21 +1,19 @@
1
- import semver from "semver";
2
1
  import path from "path";
3
2
  import fse from "fs-extra";
4
3
  import createDebug from "debug";
5
- import _, { isNil, castArray, prop, omit, toString, toNumber, isString as isString$1, padCharsEnd, isArray, keys, isPlainObject, isFinite, groupBy, pipe, mapValues, map, isEmpty, maxBy, pick, flow, mergeWith, has, uniqBy, isNull, differenceWith, isEqual, compact, difference, isObject, isInteger, isNumber as isNumber$1, isUndefined, uniqWith } from "lodash/fp";
6
- import crypto, { randomBytes } from "crypto";
7
- import { isOperatorOfType, mapAsync } from "@strapi/utils";
4
+ import _, { isNil, castArray, prop, omit, isInteger, snakeCase, partition, sumBy, toString, toNumber, isString as isString$1, padCharsEnd, isArray, keys, isPlainObject, isFinite, groupBy, pipe, mapValues, map, isEmpty, maxBy, pick, has, uniqBy, isNull, differenceWith, isEqual, compact, difference, isObject, isNumber as isNumber$1, isUndefined, uniqWith } from "lodash/fp";
5
+ import crypto from "crypto";
6
+ import crypto$1 from "node:crypto";
8
7
  import * as dateFns from "date-fns";
8
+ import { isOperatorOfType } from "@strapi/utils";
9
9
  import KnexBuilder from "knex/lib/query/querybuilder";
10
10
  import KnexRaw from "knex/lib/raw";
11
11
  import { Readable } from "stream";
12
12
  import { AsyncLocalStorage } from "node:async_hooks";
13
13
  import _$1 from "lodash";
14
- import path$1 from "node:path";
15
14
  import { Umzug } from "umzug";
16
15
  import { strict } from "assert";
17
16
  import knex from "knex";
18
- import SqliteClient from "knex/lib/dialects/sqlite3/index";
19
17
  class Dialect {
20
18
  db;
21
19
  schemaInspector = {};
@@ -26,7 +24,8 @@ class Dialect {
26
24
  }
27
25
  configure() {
28
26
  }
29
- initialize() {
27
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
28
+ async initialize(_nativeConnection) {
30
29
  }
31
30
  getSqlType(type) {
32
31
  return type;
@@ -43,9 +42,6 @@ class Dialect {
43
42
  supportsUnsigned() {
44
43
  return false;
45
44
  }
46
- supportsWindowFunctions() {
47
- return true;
48
- }
49
45
  supportsOperator() {
50
46
  return true;
51
47
  }
@@ -389,7 +385,7 @@ class PostgresDialect extends Dialect {
389
385
  useReturning() {
390
386
  return true;
391
387
  }
392
- async initialize() {
388
+ async initialize(nativeConnection) {
393
389
  this.db.connection.client.driver.types.setTypeParser(
394
390
  this.db.connection.client.driver.types.builtins.DATE,
395
391
  "text",
@@ -405,6 +401,10 @@ class PostgresDialect extends Dialect {
405
401
  "text",
406
402
  parseFloat
407
403
  );
404
+ const schemaName = this.db.getSchemaName();
405
+ if (schemaName) {
406
+ await this.db.connection.raw(`SET search_path TO "${schemaName}"`).connection(nativeConnection);
407
+ }
408
408
  }
409
409
  usesForeignKeys() {
410
410
  return true;
@@ -668,11 +668,11 @@ class MysqlDatabaseInspector {
668
668
  constructor(db) {
669
669
  this.db = db;
670
670
  }
671
- async getInformation() {
671
+ async getInformation(nativeConnection) {
672
672
  let database;
673
673
  let versionNumber;
674
674
  try {
675
- const [results] = await this.db.connection.raw(SQL_QUERIES$1.VERSION);
675
+ const [results] = await this.db.connection.raw(SQL_QUERIES$1.VERSION).connection(nativeConnection);
676
676
  const versionSplit = results[0].version.split("-");
677
677
  const databaseName = versionSplit[1];
678
678
  versionNumber = versionSplit[0];
@@ -719,12 +719,14 @@ class MysqlDialect extends Dialect {
719
719
  return next();
720
720
  };
721
721
  }
722
- async initialize() {
722
+ async initialize(nativeConnection) {
723
723
  try {
724
- await this.db.connection.raw(`set session sql_require_primary_key = 0;`);
724
+ await this.db.connection.raw(`set session sql_require_primary_key = 0;`).connection(nativeConnection);
725
725
  } catch (err) {
726
726
  }
727
- this.info = await this.databaseInspector.getInformation();
727
+ if (!this.info) {
728
+ this.info = await this.databaseInspector.getInformation(nativeConnection);
729
+ }
728
730
  }
729
731
  async startSchemaUpdate() {
730
732
  try {
@@ -739,14 +741,6 @@ class MysqlDialect extends Dialect {
739
741
  supportsUnsigned() {
740
742
  return true;
741
743
  }
742
- supportsWindowFunctions() {
743
- const isMysqlDB = !this.info?.database || this.info.database === MYSQL;
744
- const isBeforeV8 = !semver.valid(this.info?.version) || semver.lt(this.info?.version ?? "", "8.0.0");
745
- if (isMysqlDB && isBeforeV8) {
746
- return false;
747
- }
748
- return true;
749
- }
750
744
  usesForeignKeys() {
751
745
  return true;
752
746
  }
@@ -904,8 +898,8 @@ class SqliteDialect extends Dialect {
904
898
  useReturning() {
905
899
  return true;
906
900
  }
907
- async initialize() {
908
- await this.db.connection.raw("pragma foreign_keys = on");
901
+ async initialize(nativeConnection) {
902
+ await this.db.connection.raw("pragma foreign_keys = on").connection(nativeConnection);
909
903
  }
910
904
  canAlterConstraints() {
911
905
  return false;
@@ -967,10 +961,8 @@ const getDialectName = (client) => {
967
961
  case "postgres":
968
962
  return "postgres";
969
963
  case "mysql":
970
- case "mysql2":
971
964
  return "mysql";
972
965
  case "sqlite":
973
- case "sqlite-legacy":
974
966
  return "sqlite";
975
967
  default:
976
968
  throw new Error(`Unknown dialect ${client}`);
@@ -1234,7 +1226,11 @@ const createHelpers = (db) => {
1234
1226
  dropTableForeignKeys
1235
1227
  };
1236
1228
  };
1237
- const RESERVED_TABLE_NAMES = ["strapi_migrations", "strapi_database_schema"];
1229
+ const RESERVED_TABLE_NAMES = [
1230
+ "strapi_migrations",
1231
+ "strapi_migrations_internal",
1232
+ "strapi_database_schema"
1233
+ ];
1238
1234
  const statuses = {
1239
1235
  CHANGED: "CHANGED",
1240
1236
  UNCHANGED: "UNCHANGED"
@@ -1614,11 +1610,212 @@ const NUMBER_TYPES = ["biginteger", "integer", "decimal", "float"];
1614
1610
  const isString = (type) => STRING_TYPES.includes(type);
1615
1611
  const isNumber = (type) => NUMBER_TYPES.includes(type);
1616
1612
  const isScalar = (type) => SCALAR_TYPES.includes(type);
1617
- const isComponent = (type) => type === "component";
1618
- const isDynamicZone = (type) => type === "dynamiczone";
1619
1613
  const isRelation = (type) => type === "relation";
1620
1614
  const isScalarAttribute = (attribute) => isScalar(attribute.type);
1621
1615
  const isRelationalAttribute = (attribute) => isRelation(attribute.type);
1616
+ const MAX_DB_IDENTIFIER_LENGTH = 0;
1617
+ const HASH_LENGTH = 5;
1618
+ const HASH_SEPARATOR = "";
1619
+ const IDENTIFIER_SEPARATOR = "_";
1620
+ const MIN_TOKEN_LENGTH = 3;
1621
+ function createHash(data, len) {
1622
+ if (!isInteger(len) || len <= 0) {
1623
+ throw new Error(`createHash length must be a positive integer, received ${len}`);
1624
+ }
1625
+ const hash = crypto$1.createHash("shake256", { outputLength: Math.ceil(len / 2) }).update(data);
1626
+ return hash.digest("hex").substring(0, len);
1627
+ }
1628
+ function getShortenedName(name, len) {
1629
+ if (!isInteger(len) || len <= 0) {
1630
+ throw new Error(`tokenWithHash length must be a positive integer, received ${len}`);
1631
+ }
1632
+ if (name.length <= len) {
1633
+ return name;
1634
+ }
1635
+ if (len < MIN_TOKEN_LENGTH + HASH_LENGTH) {
1636
+ throw new Error(
1637
+ `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}`
1638
+ );
1639
+ }
1640
+ const availableLength = len - HASH_LENGTH - HASH_SEPARATOR.length;
1641
+ if (availableLength < MIN_TOKEN_LENGTH) {
1642
+ throw new Error(
1643
+ `length for part of identifier minimum is less than min token length (${MIN_TOKEN_LENGTH}), received ${len} for token ${name}`
1644
+ );
1645
+ }
1646
+ return `${name.substring(0, availableLength)}${HASH_SEPARATOR}${createHash(name, HASH_LENGTH)}`;
1647
+ }
1648
+ function getNameFromTokens(nameTokens, maxLength = MAX_DB_IDENTIFIER_LENGTH) {
1649
+ if (!isInteger(maxLength) || maxLength < 0) {
1650
+ throw new Error("maxLength must be a positive integer or 0 (for unlimited length)");
1651
+ }
1652
+ const fullLengthName = nameTokens.map((token) => snakeCase(token.name)).join(IDENTIFIER_SEPARATOR);
1653
+ if (fullLengthName.length <= maxLength || maxLength === 0) {
1654
+ return fullLengthName;
1655
+ }
1656
+ const [compressible, incompressible] = partition(
1657
+ (token) => token.compressible,
1658
+ nameTokens
1659
+ );
1660
+ const totalIncompressibleLength = sumBy((token) => token.name.length)(incompressible);
1661
+ const totalSeparatorsLength = nameTokens.length * IDENTIFIER_SEPARATOR.length - 1;
1662
+ const available = maxLength - totalIncompressibleLength - totalSeparatorsLength;
1663
+ const availablePerToken = Math.floor(available / compressible.length);
1664
+ if (totalIncompressibleLength + totalSeparatorsLength > maxLength || availablePerToken < MIN_TOKEN_LENGTH) {
1665
+ throw new Error("Maximum length is too small to accommodate all tokens");
1666
+ }
1667
+ let surplus = available % compressible.length;
1668
+ const minHashedLength = HASH_LENGTH + HASH_SEPARATOR.length + MIN_TOKEN_LENGTH;
1669
+ const totalLength = nameTokens.reduce((total, token) => {
1670
+ if (token.compressible) {
1671
+ if (token.name.length < availablePerToken) {
1672
+ return total + token.name.length;
1673
+ }
1674
+ return total + minHashedLength;
1675
+ }
1676
+ return total + token.name.length;
1677
+ }, nameTokens.length * IDENTIFIER_SEPARATOR.length - 1);
1678
+ if (maxLength < totalLength) {
1679
+ throw new Error("Maximum length is too small to accommodate all tokens");
1680
+ }
1681
+ let deficits = [];
1682
+ compressible.forEach((token) => {
1683
+ const actualLength = token.name.length;
1684
+ if (actualLength < availablePerToken) {
1685
+ surplus += availablePerToken - actualLength;
1686
+ token.allocatedLength = actualLength;
1687
+ } else {
1688
+ token.allocatedLength = availablePerToken;
1689
+ deficits.push(token);
1690
+ }
1691
+ });
1692
+ function filterAndIncreaseLength(token) {
1693
+ if (token.allocatedLength < token.name.length && surplus > 0) {
1694
+ token.allocatedLength += 1;
1695
+ surplus -= 1;
1696
+ return token.allocatedLength < token.name.length;
1697
+ }
1698
+ return false;
1699
+ }
1700
+ let previousSurplus = surplus + 1;
1701
+ while (surplus > 0 && deficits.length > 0) {
1702
+ deficits = deficits.filter((token) => filterAndIncreaseLength(token));
1703
+ if (surplus === previousSurplus) {
1704
+ break;
1705
+ }
1706
+ previousSurplus = surplus;
1707
+ }
1708
+ const shortenedName = nameTokens.map((token) => {
1709
+ if (token.compressible && "allocatedLength" in token && token.allocatedLength !== void 0) {
1710
+ return getShortenedName(token.name, token.allocatedLength);
1711
+ }
1712
+ return token.name;
1713
+ }).join(IDENTIFIER_SEPARATOR);
1714
+ if (shortenedName.length > maxLength) {
1715
+ throw new Error(
1716
+ `name shortening failed to generate a name of the correct maxLength; name ${shortenedName}`
1717
+ );
1718
+ }
1719
+ return shortenedName;
1720
+ }
1721
+ const ID_COLUMN = "id";
1722
+ const ORDER_COLUMN = "order";
1723
+ const FIELD_COLUMN = "field";
1724
+ const getName = (names, options) => {
1725
+ const tokens = _.castArray(names).map((name) => {
1726
+ return {
1727
+ name,
1728
+ compressible: true
1729
+ };
1730
+ });
1731
+ if (options?.suffix) {
1732
+ tokens.push({ name: options.suffix, compressible: false });
1733
+ }
1734
+ if (options?.prefix) {
1735
+ tokens.unshift({ name: options.prefix, compressible: false });
1736
+ }
1737
+ const maxLength = options?.maxLength ?? MAX_DB_IDENTIFIER_LENGTH;
1738
+ return getNameFromTokens(tokens, maxLength);
1739
+ };
1740
+ const getTableName = (name, options) => {
1741
+ return getName(name, options);
1742
+ };
1743
+ const getJoinTableName = (collectionName, attributeName, options) => {
1744
+ return getName([collectionName, attributeName], { suffix: "links", ...options });
1745
+ };
1746
+ const getMorphTableName = (collectionName, attributeName, options) => {
1747
+ return getName([collectionName, attributeName], { suffix: "morphs", ...options });
1748
+ };
1749
+ const getColumnName = (attributeName, options) => {
1750
+ return getName(attributeName, options);
1751
+ };
1752
+ const getJoinColumnAttributeIdName = (attributeName, options) => {
1753
+ return getName(attributeName, { suffix: "id", ...options });
1754
+ };
1755
+ const getInverseJoinColumnAttributeIdName = (attributeName, options) => {
1756
+ return getName(attributeName, { suffix: "id", prefix: "inv", ...options });
1757
+ };
1758
+ const getOrderColumnName = (singularName, options) => {
1759
+ return getName(singularName, { suffix: "order", ...options });
1760
+ };
1761
+ const getInverseOrderColumnName = (singularName, options) => {
1762
+ return getName(singularName, { suffix: "order", prefix: "inv", ...options });
1763
+ };
1764
+ const getMorphColumnJoinTableIdName = (singularName, options) => {
1765
+ return getName(singularName, { suffix: "id", ...options });
1766
+ };
1767
+ const getMorphColumnAttributeIdName = (attributeName, options) => {
1768
+ return getName(attributeName, { suffix: "id", ...options });
1769
+ };
1770
+ const getMorphColumnTypeName = (attributeName, options) => {
1771
+ return getName(attributeName, { suffix: "type", ...options });
1772
+ };
1773
+ const getIndexName = (names, options) => {
1774
+ return getName(names, { suffix: "index", ...options });
1775
+ };
1776
+ const getFkIndexName = (names, options) => {
1777
+ return getName(names, { suffix: "fk", ...options });
1778
+ };
1779
+ const getInverseFkIndexName = (names, options) => {
1780
+ return getName(names, { suffix: "inv_fk", ...options });
1781
+ };
1782
+ const getOrderFkIndexName = (names, options) => {
1783
+ return getName(names, { suffix: "order_fk", ...options });
1784
+ };
1785
+ const getOrderInverseFkIndexName = (names, options) => {
1786
+ return getName(names, { suffix: "order_inv_fk", ...options });
1787
+ };
1788
+ const getUniqueIndexName = (names, options) => {
1789
+ return getName(names, { suffix: "unique", ...options });
1790
+ };
1791
+ const getPrimaryIndexName = (names) => {
1792
+ return getName(names, { suffix: "primary" });
1793
+ };
1794
+ const identifiers = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
1795
+ __proto__: null,
1796
+ FIELD_COLUMN,
1797
+ ID_COLUMN,
1798
+ ORDER_COLUMN,
1799
+ getColumnName,
1800
+ getFkIndexName,
1801
+ getIndexName,
1802
+ getInverseFkIndexName,
1803
+ getInverseJoinColumnAttributeIdName,
1804
+ getInverseOrderColumnName,
1805
+ getJoinColumnAttributeIdName,
1806
+ getJoinTableName,
1807
+ getMorphColumnAttributeIdName,
1808
+ getMorphColumnJoinTableIdName,
1809
+ getMorphColumnTypeName,
1810
+ getMorphTableName,
1811
+ getName,
1812
+ getOrderColumnName,
1813
+ getOrderFkIndexName,
1814
+ getOrderInverseFkIndexName,
1815
+ getPrimaryIndexName,
1816
+ getTableName,
1817
+ getUniqueIndexName
1818
+ }, Symbol.toStringTag, { value: "Module" }));
1622
1819
  const createColumn = (name, attribute) => {
1623
1820
  const { type, args = [], ...opts } = getColumnType(attribute);
1624
1821
  return {
@@ -1644,51 +1841,62 @@ const createTable = (meta) => {
1644
1841
  if (attribute.type === "relation") {
1645
1842
  if ("morphColumn" in attribute && attribute.morphColumn && attribute.owner) {
1646
1843
  const { idColumn, typeColumn } = attribute.morphColumn;
1844
+ const idColumnName = getName(idColumn.name);
1845
+ const typeColumnName = getName(typeColumn.name);
1647
1846
  table.columns.push(
1648
- createColumn(idColumn.name, {
1847
+ createColumn(idColumnName, {
1649
1848
  type: "integer",
1650
1849
  column: {
1651
1850
  unsigned: true
1652
1851
  }
1653
1852
  })
1654
1853
  );
1655
- table.columns.push(createColumn(typeColumn.name, { type: "string" }));
1854
+ table.columns.push(createColumn(typeColumnName, { type: "string" }));
1656
1855
  } else if ("joinColumn" in attribute && attribute.joinColumn && attribute.owner && attribute.joinColumn.referencedTable) {
1657
- const { name: columnName, referencedColumn, referencedTable } = attribute.joinColumn;
1856
+ const {
1857
+ name: columnNameFull,
1858
+ referencedColumn,
1859
+ referencedTable,
1860
+ columnType = "integer"
1861
+ } = attribute.joinColumn;
1862
+ const columnName = getName(columnNameFull);
1658
1863
  const column = createColumn(columnName, {
1659
- type: "integer",
1864
+ // TODO: find the column type automatically, or allow passing all the column params
1865
+ type: columnType,
1660
1866
  column: {
1661
1867
  unsigned: true
1662
1868
  }
1663
1869
  });
1664
1870
  table.columns.push(column);
1871
+ const fkName = getFkIndexName([table.name, columnName]);
1665
1872
  table.foreignKeys.push({
1666
- name: `${table.name}_${columnName}_fk`,
1667
- columns: [columnName],
1873
+ name: fkName,
1874
+ columns: [column.name],
1668
1875
  referencedTable,
1669
1876
  referencedColumns: [referencedColumn],
1670
1877
  // NOTE: could allow configuration
1671
1878
  onDelete: "SET NULL"
1672
1879
  });
1673
1880
  table.indexes.push({
1674
- name: `${table.name}_${columnName}_fk`,
1675
- columns: [columnName]
1881
+ name: fkName,
1882
+ columns: [column.name]
1676
1883
  });
1677
1884
  }
1678
1885
  } else if (isScalarAttribute(attribute)) {
1679
- const column = createColumn(attribute.columnName || key, attribute);
1886
+ const columnName = getName(attribute.columnName || key);
1887
+ const column = createColumn(columnName, attribute);
1680
1888
  if (column.unique) {
1681
1889
  table.indexes.push({
1682
1890
  type: "unique",
1683
- name: `${table.name}_${column.name}_unique`,
1684
- columns: [column.name]
1891
+ name: getUniqueIndexName([table.name, column.name]),
1892
+ columns: [columnName]
1685
1893
  });
1686
1894
  }
1687
1895
  if (column.primary) {
1688
1896
  table.indexes.push({
1689
1897
  type: "primary",
1690
- name: `${table.name}_${column.name}_primary`,
1691
- columns: [column.name]
1898
+ name: getPrimaryIndexName([table.name, column.name]),
1899
+ columns: [columnName]
1692
1900
  });
1693
1901
  }
1694
1902
  table.columns.push(column);
@@ -1716,8 +1924,7 @@ const getColumnType = (attribute) => {
1716
1924
  }
1717
1925
  case "uid": {
1718
1926
  return {
1719
- type: "string",
1720
- unique: true
1927
+ type: "string"
1721
1928
  };
1722
1929
  }
1723
1930
  case "richtext":
@@ -1790,8 +1997,14 @@ const metadataToSchema = (metadata) => {
1790
1997
  };
1791
1998
  const debug = createDebug("strapi::database");
1792
1999
  const createSchemaProvider = (db) => {
1793
- const schema = metadataToSchema(db.metadata);
2000
+ const state = {};
1794
2001
  return {
2002
+ get schema() {
2003
+ if (!state.schema) {
2004
+ state.schema = metadataToSchema(db.metadata);
2005
+ }
2006
+ return state.schema;
2007
+ },
1795
2008
  builder: createSchemaBuilder(db),
1796
2009
  schemaDiff: createSchemaDiff(db),
1797
2010
  schemaStorage: createSchemaStorage(db),
@@ -1808,7 +2021,7 @@ const createSchemaProvider = (db) => {
1808
2021
  */
1809
2022
  async create() {
1810
2023
  debug("Created database schema");
1811
- await this.builder.createSchema(schema);
2024
+ await this.builder.createSchema(this.schema);
1812
2025
  },
1813
2026
  /**
1814
2027
  * Resets the database schema
@@ -1821,11 +2034,11 @@ const createSchemaProvider = (db) => {
1821
2034
  async syncSchema() {
1822
2035
  debug("Synchronizing database schema");
1823
2036
  const DBSchema = await db.dialect.schemaInspector.getSchema();
1824
- const { status, diff } = await this.schemaDiff.diff(DBSchema, schema);
2037
+ const { status, diff } = await this.schemaDiff.diff(DBSchema, this.schema);
1825
2038
  if (status === "CHANGED") {
1826
2039
  await this.builder.updateSchema(diff);
1827
2040
  }
1828
- await this.schemaStorage.add(schema);
2041
+ await this.schemaStorage.add(this.schema);
1829
2042
  },
1830
2043
  // TODO: support options to migrate softly or forcefully
1831
2044
  // TODO: support option to disable auto migration & run a CLI command instead to avoid doing it at startup
@@ -1842,7 +2055,7 @@ const createSchemaProvider = (db) => {
1842
2055
  return this.syncSchema();
1843
2056
  }
1844
2057
  const { hash: oldHash } = oldSchema;
1845
- const hash = await this.schemaStorage.hashSchema(schema);
2058
+ const hash = await this.schemaStorage.hashSchema(this.schema);
1846
2059
  if (oldHash !== hash) {
1847
2060
  debug("Schema changed");
1848
2061
  return this.syncSchema();
@@ -1851,9 +2064,11 @@ const createSchemaProvider = (db) => {
1851
2064
  }
1852
2065
  };
1853
2066
  };
2067
+ const ID = ID_COLUMN;
2068
+ const ORDER = ORDER_COLUMN;
2069
+ const FIELD = FIELD_COLUMN;
1854
2070
  const hasInversedBy = (attr) => "inversedBy" in attr;
1855
2071
  const hasMappedBy = (attr) => "mappedBy" in attr;
1856
- const isPolymorphic = (attribute) => ["morphOne", "morphMany", "morphToOne", "morphToMany"].includes(attribute.relation);
1857
2072
  const isOneToAny = (attribute) => ["oneToOne", "oneToMany"].includes(attribute.relation);
1858
2073
  const isManyToAny = (attribute) => ["manyToMany", "manyToOne"].includes(attribute.relation);
1859
2074
  const isAnyToOne = (attribute) => ["oneToOne", "manyToOne"].includes(attribute.relation);
@@ -1861,7 +2076,6 @@ const isAnyToMany = (attribute) => ["oneToMany", "manyToMany"].includes(attribut
1861
2076
  const isBidirectional = (attribute) => hasInversedBy(attribute) || hasMappedBy(attribute);
1862
2077
  const isOwner = (attribute) => !isBidirectional(attribute) || hasInversedBy(attribute);
1863
2078
  const shouldUseJoinTable = (attribute) => !("useJoinTable" in attribute) || attribute.useJoinTable !== false;
1864
- const getJoinTableName = (tableName, attributeName) => _.snakeCase(`${tableName}_${attributeName}_links`);
1865
2079
  const hasOrderColumn = (attribute) => isAnyToMany(attribute);
1866
2080
  const hasInverseOrderColumn = (attribute) => isBidirectional(attribute) && isManyToAny(attribute);
1867
2081
  const createOneToOne = (attributeName, attribute, meta, metadata) => {
@@ -1920,8 +2134,11 @@ const createManyToMany = (attributeName, attribute, meta, metadata) => {
1920
2134
  }
1921
2135
  };
1922
2136
  const createMorphToOne = (attributeName, attribute) => {
1923
- const idColumnName = "target_id";
1924
- const typeColumnName = "target_type";
2137
+ const idColumnName = getJoinColumnAttributeIdName("target");
2138
+ const typeColumnName = getMorphColumnTypeName("target");
2139
+ if ("morphColumn" in attribute && attribute.morphColumn) {
2140
+ return;
2141
+ }
1925
2142
  Object.assign(attribute, {
1926
2143
  owner: true,
1927
2144
  morphColumn: {
@@ -1931,23 +2148,26 @@ const createMorphToOne = (attributeName, attribute) => {
1931
2148
  },
1932
2149
  idColumn: {
1933
2150
  name: idColumnName,
1934
- referencedColumn: "id"
2151
+ referencedColumn: ID
1935
2152
  }
1936
2153
  }
1937
2154
  });
1938
2155
  };
1939
2156
  const createMorphToMany = (attributeName, attribute, meta, metadata) => {
1940
- const joinTableName = _.snakeCase(`${meta.tableName}_${attributeName}_morphs`);
1941
- const joinColumnName = _.snakeCase(`${meta.singularName}_id`);
1942
- const morphColumnName = _.snakeCase(`${attributeName}`);
1943
- const idColumnName = `${morphColumnName}_id`;
1944
- const typeColumnName = `${morphColumnName}_type`;
2157
+ if ("joinTable" in attribute && attribute.joinTable) {
2158
+ return;
2159
+ }
2160
+ const joinTableName = getMorphTableName(meta.tableName, attributeName);
2161
+ const joinColumnName = getMorphColumnJoinTableIdName(meta.singularName);
2162
+ const idColumnName = getMorphColumnAttributeIdName(attributeName);
2163
+ const typeColumnName = getMorphColumnTypeName(attributeName);
2164
+ const fkIndexName = getFkIndexName(joinTableName);
1945
2165
  metadata.add({
1946
2166
  singularName: joinTableName,
1947
2167
  uid: joinTableName,
1948
2168
  tableName: joinTableName,
1949
2169
  attributes: {
1950
- id: {
2170
+ [ID]: {
1951
2171
  type: "increments"
1952
2172
  },
1953
2173
  [joinColumnName]: {
@@ -1965,10 +2185,10 @@ const createMorphToMany = (attributeName, attribute, meta, metadata) => {
1965
2185
  [typeColumnName]: {
1966
2186
  type: "string"
1967
2187
  },
1968
- field: {
2188
+ [FIELD]: {
1969
2189
  type: "string"
1970
2190
  },
1971
- order: {
2191
+ [ORDER]: {
1972
2192
  type: "float",
1973
2193
  column: {
1974
2194
  unsigned: true
@@ -1977,12 +2197,12 @@ const createMorphToMany = (attributeName, attribute, meta, metadata) => {
1977
2197
  },
1978
2198
  indexes: [
1979
2199
  {
1980
- name: `${joinTableName}_fk`,
2200
+ name: fkIndexName,
1981
2201
  columns: [joinColumnName]
1982
2202
  },
1983
2203
  {
1984
2204
  name: `${joinTableName}_order_index`,
1985
- columns: ["order"]
2205
+ columns: [ORDER]
1986
2206
  },
1987
2207
  {
1988
2208
  name: `${joinTableName}_id_column_index`,
@@ -1991,10 +2211,10 @@ const createMorphToMany = (attributeName, attribute, meta, metadata) => {
1991
2211
  ],
1992
2212
  foreignKeys: [
1993
2213
  {
1994
- name: `${joinTableName}_fk`,
2214
+ name: fkIndexName,
1995
2215
  columns: [joinColumnName],
1996
- referencedColumns: ["id"],
1997
- referencedTable: meta.tableName,
2216
+ referencedColumns: [ID],
2217
+ referencedTable: getTableName(meta.tableName),
1998
2218
  onDelete: "CASCADE"
1999
2219
  }
2000
2220
  ],
@@ -2005,7 +2225,7 @@ const createMorphToMany = (attributeName, attribute, meta, metadata) => {
2005
2225
  name: joinTableName,
2006
2226
  joinColumn: {
2007
2227
  name: joinColumnName,
2008
- referencedColumn: "id"
2228
+ referencedColumn: ID
2009
2229
  },
2010
2230
  morphColumn: {
2011
2231
  typeColumn: {
@@ -2013,7 +2233,7 @@ const createMorphToMany = (attributeName, attribute, meta, metadata) => {
2013
2233
  },
2014
2234
  idColumn: {
2015
2235
  name: idColumnName,
2016
- referencedColumn: "id"
2236
+ referencedColumn: ID
2017
2237
  }
2018
2238
  },
2019
2239
  orderBy: {
@@ -2049,9 +2269,12 @@ const createJoinColum = (metadata, { attribute, attributeName }) => {
2049
2269
  const joinColumnName = _.snakeCase(`${attributeName}_id`);
2050
2270
  const joinColumn = {
2051
2271
  name: joinColumnName,
2052
- referencedColumn: "id",
2272
+ referencedColumn: ID,
2053
2273
  referencedTable: targetMeta.tableName
2054
2274
  };
2275
+ if ("joinColumn" in attribute) {
2276
+ Object.assign(joinColumn, attribute.joinColumn);
2277
+ }
2055
2278
  Object.assign(attribute, { owner: true, joinColumn });
2056
2279
  if (isBidirectional(attribute)) {
2057
2280
  const inverseAttribute = targetMeta.attributes[attribute.inversedBy];
@@ -2068,23 +2291,30 @@ const createJoinTable = (metadata, { attributeName, attribute, meta }) => {
2068
2291
  if (!targetMeta) {
2069
2292
  throw new Error(`Unknown target ${attribute.target}`);
2070
2293
  }
2294
+ if ("joinTable" in attribute && attribute.joinTable) {
2295
+ return;
2296
+ }
2071
2297
  const joinTableName = getJoinTableName(meta.tableName, attributeName);
2072
- const joinColumnName = _.snakeCase(`${meta.singularName}_id`);
2073
- let inverseJoinColumnName = _.snakeCase(`${targetMeta.singularName}_id`);
2298
+ const joinColumnName = getJoinColumnAttributeIdName(meta.singularName);
2299
+ let inverseJoinColumnName = getJoinColumnAttributeIdName(targetMeta.singularName);
2074
2300
  if (joinColumnName === inverseJoinColumnName) {
2075
- inverseJoinColumnName = `inv_${inverseJoinColumnName}`;
2301
+ inverseJoinColumnName = getInverseJoinColumnAttributeIdName(
2302
+ targetMeta.singularName
2303
+ );
2076
2304
  }
2077
- const orderColumnName = _.snakeCase(`${targetMeta.singularName}_order`);
2078
- let inverseOrderColumnName = _.snakeCase(`${meta.singularName}_order`);
2305
+ const orderColumnName = getOrderColumnName(targetMeta.singularName);
2306
+ let inverseOrderColumnName = getOrderColumnName(meta.singularName);
2079
2307
  if (attribute.relation === "manyToMany" && orderColumnName === inverseOrderColumnName) {
2080
- inverseOrderColumnName = `inv_${inverseOrderColumnName}`;
2308
+ inverseOrderColumnName = getInverseOrderColumnName(meta.singularName);
2081
2309
  }
2310
+ const fkIndexName = getFkIndexName(joinTableName);
2311
+ const invFkIndexName = getInverseFkIndexName(joinTableName);
2082
2312
  const metadataSchema = {
2083
2313
  singularName: joinTableName,
2084
2314
  uid: joinTableName,
2085
2315
  tableName: joinTableName,
2086
2316
  attributes: {
2087
- id: {
2317
+ [ID]: {
2088
2318
  type: "increments"
2089
2319
  },
2090
2320
  [joinColumnName]: {
@@ -2103,32 +2333,34 @@ const createJoinTable = (metadata, { attributeName, attribute, meta }) => {
2103
2333
  },
2104
2334
  indexes: [
2105
2335
  {
2106
- name: `${joinTableName}_fk`,
2336
+ name: fkIndexName,
2107
2337
  columns: [joinColumnName]
2108
2338
  },
2109
2339
  {
2110
- name: `${joinTableName}_inv_fk`,
2340
+ name: invFkIndexName,
2111
2341
  columns: [inverseJoinColumnName]
2112
2342
  },
2113
2343
  {
2114
- name: `${joinTableName}_unique`,
2344
+ name: getUniqueIndexName(joinTableName),
2115
2345
  columns: [joinColumnName, inverseJoinColumnName],
2116
2346
  type: "unique"
2117
2347
  }
2118
2348
  ],
2119
2349
  foreignKeys: [
2120
2350
  {
2121
- name: `${joinTableName}_fk`,
2351
+ name: fkIndexName,
2122
2352
  columns: [joinColumnName],
2123
- referencedColumns: ["id"],
2353
+ referencedColumns: [ID],
2124
2354
  referencedTable: meta.tableName,
2355
+ // TODO: does this need to be wrapped or do we trust meta.tableName to be the right shortened version?
2125
2356
  onDelete: "CASCADE"
2126
2357
  },
2127
2358
  {
2128
- name: `${joinTableName}_inv_fk`,
2359
+ name: invFkIndexName,
2129
2360
  columns: [inverseJoinColumnName],
2130
- referencedColumns: ["id"],
2361
+ referencedColumns: [ID],
2131
2362
  referencedTable: targetMeta.tableName,
2363
+ // TODO: does this need to be wrapped or do we trust meta.tableName to be the right shortened version?
2132
2364
  onDelete: "CASCADE"
2133
2365
  }
2134
2366
  ],
@@ -2139,11 +2371,13 @@ const createJoinTable = (metadata, { attributeName, attribute, meta }) => {
2139
2371
  name: joinTableName,
2140
2372
  joinColumn: {
2141
2373
  name: joinColumnName,
2142
- referencedColumn: "id"
2374
+ referencedColumn: ID,
2375
+ referencedTable: meta.tableName
2143
2376
  },
2144
2377
  inverseJoinColumn: {
2145
2378
  name: inverseJoinColumnName,
2146
- referencedColumn: "id"
2379
+ referencedColumn: ID,
2380
+ referencedTable: targetMeta.tableName
2147
2381
  },
2148
2382
  pivotColumns: [joinColumnName, inverseJoinColumnName]
2149
2383
  };
@@ -2156,7 +2390,8 @@ const createJoinTable = (metadata, { attributeName, attribute, meta }) => {
2156
2390
  }
2157
2391
  };
2158
2392
  metadataSchema.indexes.push({
2159
- name: `${joinTableName}_order_fk`,
2393
+ name: getOrderFkIndexName(joinTableName),
2394
+ // TODO: should we send joinTableName as parts?
2160
2395
  columns: [orderColumnName]
2161
2396
  });
2162
2397
  joinTable.orderColumnName = orderColumnName;
@@ -2171,7 +2406,7 @@ const createJoinTable = (metadata, { attributeName, attribute, meta }) => {
2171
2406
  }
2172
2407
  };
2173
2408
  metadataSchema.indexes.push({
2174
- name: `${joinTableName}_order_inv_fk`,
2409
+ name: getOrderInverseFkIndexName(joinTableName),
2175
2410
  columns: [inverseOrderColumnName]
2176
2411
  });
2177
2412
  joinTable.inverseOrderColumnName = inverseOrderColumnName;
@@ -2252,207 +2487,60 @@ class Metadata extends Map {
2252
2487
  seenTables.set(meta.tableName, true);
2253
2488
  }
2254
2489
  }
2255
- }
2256
- const createMetadata = (models = []) => {
2257
- const metadata = new Metadata();
2258
- for (const model of _.cloneDeep(models)) {
2259
- if ("id" in model.attributes) {
2260
- throw new Error('The attribute "id" is reserved and cannot be used in a model');
2261
- }
2262
- metadata.add({
2263
- ...model,
2264
- attributes: {
2265
- id: {
2266
- type: "increments"
2490
+ loadModels(models = []) {
2491
+ for (const model of _.cloneDeep(models)) {
2492
+ this.add({
2493
+ ...model,
2494
+ attributes: {
2495
+ ...model.attributes
2267
2496
  },
2268
- ...model.attributes
2269
- },
2270
- lifecycles: model.lifecycles ?? {},
2271
- indexes: model.indexes || [],
2272
- foreignKeys: model.foreignKeys || [],
2273
- columnToAttribute: {}
2274
- });
2275
- }
2276
- for (const meta of metadata.values()) {
2277
- if (hasComponentsOrDz(meta)) {
2278
- const compoLinkModelMeta = createCompoLinkModelMeta(meta);
2279
- meta.componentLink = compoLinkModelMeta;
2280
- metadata.add(compoLinkModelMeta);
2497
+ lifecycles: model.lifecycles ?? {},
2498
+ indexes: model.indexes ?? [],
2499
+ foreignKeys: model.foreignKeys ?? [],
2500
+ columnToAttribute: {}
2501
+ });
2281
2502
  }
2282
- for (const [attributeName, attribute] of Object.entries(meta.attributes)) {
2283
- try {
2284
- if (isComponent(attribute.type) && hasComponentsOrDz(meta)) {
2285
- createComponent(attributeName, attribute, meta);
2286
- continue;
2287
- }
2288
- if (isDynamicZone(attribute.type) && hasComponentsOrDz(meta)) {
2289
- createDynamicZone(attributeName, attribute, meta);
2290
- continue;
2291
- }
2292
- if (isRelationalAttribute(attribute)) {
2293
- createRelation(attributeName, attribute, meta, metadata);
2294
- continue;
2295
- }
2296
- createAttribute(attributeName, attribute);
2297
- } catch (error) {
2298
- console.log(error);
2299
- if (error instanceof Error) {
2300
- throw new Error(
2301
- `Error on attribute ${attributeName} in model ${meta.singularName}(${meta.uid}): ${error.message}`
2302
- );
2503
+ for (const meta of this.values()) {
2504
+ for (const [attributeName, attribute] of Object.entries(meta.attributes)) {
2505
+ try {
2506
+ if (isRelationalAttribute(attribute)) {
2507
+ createRelation(attributeName, attribute, meta, this);
2508
+ continue;
2509
+ }
2510
+ createAttribute(attributeName, attribute);
2511
+ } catch (error) {
2512
+ if (error instanceof Error) {
2513
+ throw new Error(
2514
+ `Error on attribute ${attributeName} in model ${meta.singularName}(${meta.uid}): ${error.message}`
2515
+ );
2516
+ }
2303
2517
  }
2304
2518
  }
2305
2519
  }
2306
- }
2307
- for (const meta of metadata.values()) {
2308
- const columnToAttribute = Object.keys(meta.attributes).reduce((acc, key) => {
2309
- const attribute = meta.attributes[key];
2310
- if ("columnName" in attribute) {
2311
- return Object.assign(acc, { [attribute.columnName || key]: key });
2312
- }
2313
- return Object.assign(acc, { [key]: key });
2314
- }, {});
2315
- meta.columnToAttribute = columnToAttribute;
2316
- }
2317
- metadata.validate();
2318
- return metadata;
2319
- };
2320
- const hasComponentsOrDz = (model) => {
2321
- return Object.values(model.attributes).some(
2322
- ({ type }) => isComponent(type) || isDynamicZone(type)
2323
- );
2324
- };
2325
- const createCompoLinkModelMeta = (baseModelMeta) => {
2326
- const name = `${baseModelMeta.tableName}_components`;
2327
- return {
2328
- // TODO: make sure there can't be any conflicts with a prefix
2329
- singularName: name,
2330
- uid: name,
2331
- tableName: name,
2332
- attributes: {
2333
- id: {
2334
- type: "increments"
2335
- },
2336
- entity_id: {
2337
- type: "integer",
2338
- column: {
2339
- unsigned: true
2340
- }
2341
- },
2342
- component_id: {
2343
- type: "integer",
2344
- column: {
2345
- unsigned: true
2346
- }
2347
- },
2348
- component_type: {
2349
- type: "string"
2350
- },
2351
- field: {
2352
- type: "string"
2353
- },
2354
- order: {
2355
- type: "float",
2356
- column: {
2357
- unsigned: true,
2358
- defaultTo: null
2520
+ for (const meta of this.values()) {
2521
+ const columnToAttribute = Object.keys(meta.attributes).reduce((acc, key) => {
2522
+ const attribute = meta.attributes[key];
2523
+ if ("columnName" in attribute) {
2524
+ return Object.assign(acc, { [attribute.columnName || key]: key });
2359
2525
  }
2360
- }
2361
- },
2362
- indexes: [
2363
- {
2364
- name: `${baseModelMeta.tableName}_field_index`,
2365
- columns: ["field"]
2366
- },
2367
- {
2368
- name: `${baseModelMeta.tableName}_component_type_index`,
2369
- columns: ["component_type"]
2370
- },
2371
- {
2372
- name: `${baseModelMeta.tableName}_entity_fk`,
2373
- columns: ["entity_id"]
2374
- },
2375
- {
2376
- name: `${baseModelMeta.tableName}_unique`,
2377
- columns: ["entity_id", "component_id", "field", "component_type"],
2378
- type: "unique"
2379
- }
2380
- ],
2381
- foreignKeys: [
2382
- {
2383
- name: `${baseModelMeta.tableName}_entity_fk`,
2384
- columns: ["entity_id"],
2385
- referencedColumns: ["id"],
2386
- referencedTable: baseModelMeta.tableName,
2387
- onDelete: "CASCADE"
2388
- }
2389
- ],
2390
- lifecycles: {},
2391
- columnToAttribute: {}
2392
- };
2393
- };
2394
- const createDynamicZone = (attributeName, attribute, meta) => {
2395
- Object.assign(attribute, {
2396
- type: "relation",
2397
- relation: "morphToMany",
2398
- // TODO: handle restrictions at some point
2399
- // target: attribute.components,
2400
- joinTable: {
2401
- name: meta.componentLink.tableName,
2402
- joinColumn: {
2403
- name: "entity_id",
2404
- referencedColumn: "id"
2405
- },
2406
- morphColumn: {
2407
- idColumn: {
2408
- name: "component_id",
2409
- referencedColumn: "id"
2410
- },
2411
- typeColumn: {
2412
- name: "component_type"
2413
- },
2414
- typeField: "__component"
2415
- },
2416
- on: {
2417
- field: attributeName
2418
- },
2419
- orderBy: {
2420
- order: "asc"
2421
- },
2422
- pivotColumns: ["entity_id", "component_id", "field", "component_type"]
2423
- }
2424
- });
2425
- };
2426
- const createComponent = (attributeName, attribute, meta) => {
2427
- Object.assign(attribute, {
2428
- type: "relation",
2429
- relation: "repeatable" in attribute && attribute.repeatable === true ? "oneToMany" : "oneToOne",
2430
- target: "component" in attribute && attribute.component,
2431
- joinTable: {
2432
- name: meta.componentLink.tableName,
2433
- joinColumn: {
2434
- name: "entity_id",
2435
- referencedColumn: "id"
2436
- },
2437
- inverseJoinColumn: {
2438
- name: "component_id",
2439
- referencedColumn: "id"
2440
- },
2441
- on: {
2442
- field: attributeName
2443
- },
2444
- orderColumnName: "order",
2445
- orderBy: {
2446
- order: "asc"
2447
- },
2448
- pivotColumns: ["entity_id", "component_id", "field", "component_type"]
2526
+ return Object.assign(acc, { [key]: key });
2527
+ }, {});
2528
+ meta.columnToAttribute = columnToAttribute;
2449
2529
  }
2450
- });
2451
- };
2530
+ this.validate();
2531
+ }
2532
+ }
2452
2533
  const createAttribute = (attributeName, attribute) => {
2453
- const columnName = _.snakeCase(attributeName);
2534
+ const columnName = getColumnName(attributeName);
2454
2535
  Object.assign(attribute, { columnName });
2455
2536
  };
2537
+ const createMetadata = (models = []) => {
2538
+ const metadata = new Metadata();
2539
+ if (models.length) {
2540
+ metadata.loadModels(models);
2541
+ }
2542
+ return metadata;
2543
+ };
2456
2544
  class Field {
2457
2545
  config;
2458
2546
  constructor(config) {
@@ -4309,9 +4397,6 @@ const createRepository = (uid, db) => {
4309
4397
  updateMany(params) {
4310
4398
  return db.entityManager.updateMany(uid, params);
4311
4399
  },
4312
- clone(id, params) {
4313
- return db.entityManager.clone(uid, id, params);
4314
- },
4315
4400
  delete(params) {
4316
4401
  return db.entityManager.delete(uid, params);
4317
4402
  },
@@ -4337,9 +4422,6 @@ const createRepository = (uid, db) => {
4337
4422
  deleteRelations(id) {
4338
4423
  return db.entityManager.deleteRelations(uid, id);
4339
4424
  },
4340
- cloneRelations(targetId, sourceId, params) {
4341
- return db.entityManager.cloneRelations(uid, targetId, sourceId, params);
4342
- },
4343
4425
  populate(entity, populate) {
4344
4426
  return db.entityManager.populate(uid, entity, populate);
4345
4427
  },
@@ -4415,6 +4497,19 @@ const deleteRelatedMorphOneRelationsAfterMorphToManyUpdate = async (rows, {
4415
4497
  await createQueryBuilder(joinTable.name, db).delete().where({ $or: orWhere }).transacting(trx).execute();
4416
4498
  }
4417
4499
  };
4500
+ const getDocumentSiblingIdsQuery = (con, tableName, id) => {
4501
+ const models = Array.from(strapi.db.metadata.values());
4502
+ const isContentType = models.find((model) => {
4503
+ return model.tableName === tableName && model.attributes.documentId;
4504
+ });
4505
+ if (!isContentType) {
4506
+ return [id];
4507
+ }
4508
+ return con.from(tableName).select("id").where(
4509
+ "document_id",
4510
+ con.from(tableName).select("document_id").where("id", id)
4511
+ );
4512
+ };
4418
4513
  const deletePreviousOneToAnyRelations = async ({
4419
4514
  id,
4420
4515
  attribute,
@@ -4429,10 +4524,8 @@ const deletePreviousOneToAnyRelations = async ({
4429
4524
  }
4430
4525
  const { joinTable } = attribute;
4431
4526
  const { joinColumn, inverseJoinColumn } = joinTable;
4432
- await createQueryBuilder(joinTable.name, db).delete().where({
4433
- [inverseJoinColumn.name]: relIdsToadd,
4434
- [joinColumn.name]: { $ne: id }
4435
- }).where(joinTable.on || {}).transacting(trx).execute();
4527
+ const con = db.getConnection();
4528
+ await con.delete().from(joinTable.name).whereNotIn(joinColumn.name, getDocumentSiblingIdsQuery(con, joinColumn.referencedTable, id)).whereIn(inverseJoinColumn.name, relIdsToadd).where(joinTable.on || {}).transacting(trx);
4436
4529
  await cleanOrderColumns({ attribute, db, inverseRelIds: relIdsToadd, transaction: trx });
4437
4530
  };
4438
4531
  const deletePreviousAnyToOneRelations = async ({
@@ -4444,14 +4537,15 @@ const deletePreviousAnyToOneRelations = async ({
4444
4537
  }) => {
4445
4538
  const { joinTable } = attribute;
4446
4539
  const { joinColumn, inverseJoinColumn } = joinTable;
4540
+ const con = db.getConnection();
4447
4541
  if (!isAnyToOne(attribute)) {
4448
4542
  throw new Error("deletePreviousAnyToOneRelations can only be called for anyToOne relations");
4449
4543
  }
4450
4544
  if (isManyToAny(attribute)) {
4451
- const relsToDelete = await createQueryBuilder(joinTable.name, db).select(inverseJoinColumn.name).where({
4452
- [joinColumn.name]: id,
4453
- [inverseJoinColumn.name]: { $ne: relIdToadd }
4454
- }).where(joinTable.on || {}).transacting(trx).execute();
4545
+ const relsToDelete = await con.select(inverseJoinColumn.name).from(joinTable.name).where(joinColumn.name, id).whereNotIn(
4546
+ inverseJoinColumn.name,
4547
+ getDocumentSiblingIdsQuery(con, inverseJoinColumn.referencedTable, relIdToadd)
4548
+ ).where(joinTable.on || {}).transacting(trx);
4455
4549
  const relIdsToDelete = map(inverseJoinColumn.name, relsToDelete);
4456
4550
  await createQueryBuilder(joinTable.name, db).delete().where({
4457
4551
  [joinColumn.name]: id,
@@ -4459,10 +4553,10 @@ const deletePreviousAnyToOneRelations = async ({
4459
4553
  }).where(joinTable.on || {}).transacting(trx).execute();
4460
4554
  await cleanOrderColumns({ attribute, db, inverseRelIds: relIdsToDelete, transaction: trx });
4461
4555
  } else {
4462
- await createQueryBuilder(joinTable.name, db).delete().where({
4463
- [joinColumn.name]: id,
4464
- [inverseJoinColumn.name]: { $ne: relIdToadd }
4465
- }).where(joinTable.on || {}).transacting(trx).execute();
4556
+ await con.delete().from(joinTable.name).where(joinColumn.name, id).whereNotIn(
4557
+ inverseJoinColumn.name,
4558
+ getDocumentSiblingIdsQuery(con, inverseJoinColumn.referencedTable, relIdToadd)
4559
+ ).where(joinTable.on || {}).transacting(trx);
4466
4560
  }
4467
4561
  };
4468
4562
  const deleteRelations = async ({
@@ -4514,10 +4608,6 @@ const cleanOrderColumns = async ({
4514
4608
  if (!(hasOrderColumn(attribute) && id) && !(hasInverseOrderColumn(attribute) && !isEmpty(inverseRelIds))) {
4515
4609
  return;
4516
4610
  }
4517
- if (!strapi.db.dialect.supportsWindowFunctions()) {
4518
- await cleanOrderColumnsForOldDatabases({ id, attribute, db, inverseRelIds, transaction: trx });
4519
- return;
4520
- }
4521
4611
  const { joinTable } = attribute;
4522
4612
  const { joinColumn, inverseJoinColumn, orderColumnName, inverseOrderColumnName } = joinTable;
4523
4613
  const updateOrderColumn = async () => {
@@ -4579,94 +4669,6 @@ const cleanOrderColumns = async ({
4579
4669
  };
4580
4670
  return Promise.all([updateOrderColumn(), updateInverseOrderColumn()]);
4581
4671
  };
4582
- const cleanOrderColumnsForOldDatabases = async ({
4583
- id,
4584
- attribute,
4585
- db,
4586
- inverseRelIds,
4587
- transaction: trx
4588
- }) => {
4589
- const { joinTable } = attribute;
4590
- const { joinColumn, inverseJoinColumn, orderColumnName, inverseOrderColumnName } = joinTable;
4591
- const randomSuffix = `${(/* @__PURE__ */ new Date()).valueOf()}_${randomBytes(16).toString("hex")}`;
4592
- if (hasOrderColumn(attribute) && id) {
4593
- const orderVar = `order_${randomSuffix}`;
4594
- await db.connection.raw(`SET @${orderVar} = 0;`).transacting(trx);
4595
- await db.connection.raw(
4596
- `UPDATE :joinTableName: as a, (
4597
- SELECT id, (@${orderVar}:=@${orderVar} + 1) AS src_order
4598
- FROM :joinTableName:
4599
- WHERE :joinColumnName: = :id
4600
- ORDER BY :orderColumnName:
4601
- ) AS b
4602
- SET :orderColumnName: = b.src_order
4603
- WHERE a.id = b.id
4604
- AND a.:joinColumnName: = :id`,
4605
- {
4606
- joinTableName: joinTable.name,
4607
- orderColumnName,
4608
- joinColumnName: joinColumn.name,
4609
- id
4610
- }
4611
- ).transacting(trx);
4612
- }
4613
- if (hasInverseOrderColumn(attribute) && !isEmpty(inverseRelIds)) {
4614
- const orderVar = `order_${randomSuffix}`;
4615
- const columnVar = `col_${randomSuffix}`;
4616
- await db.connection.raw(`SET @${orderVar} = 0;`).transacting(trx);
4617
- await db.connection.raw(
4618
- `UPDATE ?? as a, (
4619
- SELECT
4620
- id,
4621
- @${orderVar}:=CASE WHEN @${columnVar} = ?? THEN @${orderVar} + 1 ELSE 1 END AS inv_order,
4622
- @${columnVar}:=?? ??
4623
- FROM ?? a
4624
- WHERE ?? IN(${inverseRelIds.map(() => "?").join(", ")})
4625
- ORDER BY ??, ??
4626
- ) AS b
4627
- SET ?? = b.inv_order
4628
- WHERE a.id = b.id
4629
- AND a.?? IN(${inverseRelIds.map(() => "?").join(", ")})`,
4630
- [
4631
- joinTable.name,
4632
- inverseJoinColumn.name,
4633
- inverseJoinColumn.name,
4634
- inverseJoinColumn.name,
4635
- joinTable.name,
4636
- inverseJoinColumn.name,
4637
- ...inverseRelIds,
4638
- inverseJoinColumn.name,
4639
- joinColumn.name,
4640
- inverseOrderColumnName,
4641
- inverseJoinColumn.name,
4642
- ...inverseRelIds
4643
- ]
4644
- ).transacting(trx);
4645
- }
4646
- };
4647
- const cleanInverseOrderColumn = async ({
4648
- id,
4649
- attribute,
4650
- trx
4651
- }) => {
4652
- const con = strapi.db.connection;
4653
- const { joinTable } = attribute;
4654
- const { joinColumn, inverseJoinColumn, inverseOrderColumnName } = joinTable;
4655
- switch (strapi.db.dialect.client) {
4656
- case "mysql": {
4657
- const subQuery = con(joinTable.name).select(inverseJoinColumn.name).max(inverseOrderColumnName, { as: "max_inv_order" }).groupBy(inverseJoinColumn.name).as("t2");
4658
- await con(`${joinTable.name} as t1`).join(subQuery, `t1.${inverseJoinColumn.name}`, "=", `t2.${inverseJoinColumn.name}`).where(joinColumn.name, id).update({
4659
- [inverseOrderColumnName]: con.raw("t2.max_inv_order + 1")
4660
- }).transacting(trx);
4661
- break;
4662
- }
4663
- default: {
4664
- const selectMaxInverseOrder = con.raw(`max(${inverseOrderColumnName}) + 1`);
4665
- const subQuery = con(`${joinTable.name} as t2`).select(selectMaxInverseOrder).whereRaw(`t2.${inverseJoinColumn.name} = t1.${inverseJoinColumn.name}`);
4666
- await con(`${joinTable.name} as t1`).where(`t1.${joinColumn.name}`, id).update({ [inverseOrderColumnName]: subQuery }).transacting(trx);
4667
- }
4668
- }
4669
- };
4670
4672
  const sortConnectArray = (connectArr, initialArr = [], strictSort = true) => {
4671
4673
  const sortedConnect = [];
4672
4674
  let needsSorting = false;
@@ -4807,55 +4809,6 @@ const relationsOrderer = (initArr, idColumn, orderColumn, strict2) => {
4807
4809
  }
4808
4810
  };
4809
4811
  };
4810
- const replaceRegularRelations = async ({
4811
- targetId,
4812
- sourceId,
4813
- attribute,
4814
- omitIds,
4815
- transaction: trx
4816
- }) => {
4817
- const { joinTable } = attribute;
4818
- const { joinColumn, inverseJoinColumn } = joinTable;
4819
- 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();
4820
- };
4821
- const cloneRegularRelations = async ({
4822
- targetId,
4823
- sourceId,
4824
- attribute,
4825
- transaction: trx
4826
- }) => {
4827
- const { joinTable } = attribute;
4828
- const { joinColumn, inverseJoinColumn, orderColumnName, inverseOrderColumnName } = joinTable;
4829
- const connection = strapi.db.getConnection();
4830
- const columns = [joinColumn.name, inverseJoinColumn.name];
4831
- if (orderColumnName) {
4832
- columns.push(orderColumnName);
4833
- }
4834
- if (inverseOrderColumnName) {
4835
- columns.push(inverseOrderColumnName);
4836
- }
4837
- if (joinTable.on) {
4838
- columns.push(...Object.keys(joinTable.on));
4839
- }
4840
- const selectStatement = connection.select(
4841
- // Override joinColumn with the new id
4842
- { [joinColumn.name]: targetId },
4843
- ...columns.slice(1)
4844
- ).where(joinColumn.name, sourceId).from(joinTable.name).toSQL();
4845
- await strapi.db.entityManager.createQueryBuilder(joinTable.name).insert(
4846
- strapi.db.connection.raw(
4847
- `(${columns.join(",")}) ${selectStatement.sql}`,
4848
- selectStatement.bindings
4849
- )
4850
- ).onConflict([joinColumn.name, inverseJoinColumn.name]).ignore().transacting(trx).execute();
4851
- if (inverseOrderColumnName) {
4852
- await cleanInverseOrderColumn({
4853
- id: targetId,
4854
- attribute,
4855
- trx
4856
- });
4857
- }
4858
- };
4859
4812
  const isRecord = (value) => isObject(value) && !isNil(value);
4860
4813
  const toId = (value) => {
4861
4814
  if (isRecord(value) && "id" in value && isValidId(value.id)) {
@@ -5005,7 +4958,8 @@ const createEntityManager = (db) => {
5005
4958
  const result = await this.findOne(uid, {
5006
4959
  where: { id },
5007
4960
  select: params.select,
5008
- populate: params.populate
4961
+ populate: params.populate,
4962
+ filters: params.filters
5009
4963
  });
5010
4964
  await db.lifecycles.run("afterCreate", uid, { params, result }, states);
5011
4965
  return result;
@@ -5063,7 +5017,8 @@ const createEntityManager = (db) => {
5063
5017
  const result = await this.findOne(uid, {
5064
5018
  where: { id },
5065
5019
  select: params.select,
5066
- populate: params.populate
5020
+ populate: params.populate,
5021
+ filters: params.filters
5067
5022
  });
5068
5023
  await db.lifecycles.run("afterUpdate", uid, { params, result }, states);
5069
5024
  return result;
@@ -5082,50 +5037,6 @@ const createEntityManager = (db) => {
5082
5037
  await db.lifecycles.run("afterUpdateMany", uid, { params, result }, states);
5083
5038
  return result;
5084
5039
  },
5085
- async clone(uid, cloneId, params = {}) {
5086
- const states = await db.lifecycles.run("beforeCreate", uid, { params });
5087
- const metadata = db.metadata.get(uid);
5088
- const { data } = params;
5089
- if (!isNil(data) && !isPlainObject(data)) {
5090
- throw new Error("Create expects a data object");
5091
- }
5092
- const entity = await this.findOne(uid, { where: { id: cloneId } });
5093
- const dataToInsert = flow(
5094
- // Omit unwanted properties
5095
- omit(["id", "created_at", "updated_at"]),
5096
- // Merge with provided data, set attribute to null if data attribute is null
5097
- mergeWith(
5098
- data || {},
5099
- (original, override) => override === null ? override : original
5100
- ),
5101
- // Process data with metadata
5102
- (entity2) => processData(metadata, entity2, { withDefaults: true })
5103
- )(entity);
5104
- const res = await this.createQueryBuilder(uid).insert(dataToInsert).execute();
5105
- const id = isRecord(res[0]) ? res[0].id : res[0];
5106
- const trx = await strapi.db.transaction();
5107
- try {
5108
- const cloneAttrs = Object.entries(metadata.attributes).reduce((acc, [attrName, attr]) => {
5109
- if (isRelationalAttribute(attr) && "joinTable" in attr && attr.joinTable && !("component" in attr)) {
5110
- acc.push(attrName);
5111
- }
5112
- return acc;
5113
- }, []);
5114
- await this.cloneRelations(uid, id, cloneId, data, { cloneAttrs, transaction: trx.get() });
5115
- await trx.commit();
5116
- } catch (e) {
5117
- await trx.rollback();
5118
- await this.createQueryBuilder(uid).where({ id }).delete().execute();
5119
- throw e;
5120
- }
5121
- const result = await this.findOne(uid, {
5122
- where: { id },
5123
- select: params.select,
5124
- populate: params.populate
5125
- });
5126
- await db.lifecycles.run("afterCreate", uid, { params, result }, states);
5127
- return result;
5128
- },
5129
5040
  async delete(uid, params = {}) {
5130
5041
  const states = await db.lifecycles.run("beforeDelete", uid, { params });
5131
5042
  const { where, select, populate } = params;
@@ -5411,9 +5322,8 @@ const createEntityManager = (db) => {
5411
5322
  const isPartialUpdate = !has("set", cleanRelationData);
5412
5323
  let relIdsToaddOrMove;
5413
5324
  if (isPartialUpdate) {
5414
- if (isAnyToOne(attribute)) {
5415
- cleanRelationData.connect = cleanRelationData.connect?.slice(-1);
5416
- }
5325
+ if (isAnyToOne(attribute))
5326
+ ;
5417
5327
  relIdsToaddOrMove = toIds(cleanRelationData.connect);
5418
5328
  const relIdsToDelete = toIds(
5419
5329
  differenceWith(
@@ -5627,58 +5537,6 @@ const createEntityManager = (db) => {
5627
5537
  }
5628
5538
  }
5629
5539
  },
5630
- // TODO: Clone polymorphic relations
5631
- /**
5632
- *
5633
- * @param {string} uid - uid of the entity to clone
5634
- * @param {number} targetId - id of the entity to clone into
5635
- * @param {number} sourceId - id of the entity to clone from
5636
- * @param {object} opt
5637
- * @param {object} opt.cloneAttrs - key value pair of attributes to clone
5638
- * @param {object} opt.transaction - transaction to use
5639
- * @example cloneRelations('user', 3, 1, { cloneAttrs: ["comments"]})
5640
- * @example cloneRelations('post', 5, 2, { cloneAttrs: ["comments", "likes"] })
5641
- */
5642
- async cloneRelations(uid, targetId, sourceId, data, options) {
5643
- const { attributes } = db.metadata.get(uid);
5644
- const { cloneAttrs = [], transaction } = options ?? {};
5645
- if (!attributes) {
5646
- return;
5647
- }
5648
- await mapAsync(cloneAttrs, async (attrName) => {
5649
- const attribute = attributes[attrName];
5650
- if (attribute.type !== "relation") {
5651
- throw new DatabaseError(
5652
- `Attribute ${attrName} is not a relation attribute. Cloning relations is only supported for relation attributes.`
5653
- );
5654
- }
5655
- if (isPolymorphic(attribute)) {
5656
- return;
5657
- }
5658
- if ("joinColumn" in attribute) {
5659
- return;
5660
- }
5661
- if (!attribute.joinTable) {
5662
- return;
5663
- }
5664
- let omitIds = [];
5665
- if (has(attrName, data)) {
5666
- const cleanRelationData = toAssocs(data[attrName]);
5667
- if (cleanRelationData.set) {
5668
- return;
5669
- }
5670
- if (cleanRelationData.disconnect) {
5671
- omitIds = toIds(cleanRelationData.disconnect);
5672
- }
5673
- }
5674
- if (isOneToAny(attribute) && isBidirectional(attribute)) {
5675
- await replaceRegularRelations({ targetId, sourceId, attribute, omitIds, transaction });
5676
- } else {
5677
- await cloneRegularRelations({ targetId, sourceId, attribute, transaction });
5678
- }
5679
- });
5680
- await this.updateRelations(uid, targetId, data, { transaction });
5681
- },
5682
5540
  // TODO: add lifecycle events
5683
5541
  async populate(uid, entity, populate) {
5684
5542
  const entry = await this.findOne(uid, {
@@ -5740,7 +5598,7 @@ const createEntityManager = (db) => {
5740
5598
  };
5741
5599
  };
5742
5600
  const createStorage = (opts) => {
5743
- const { db, tableName = "strapi_migrations" } = opts;
5601
+ const { db, tableName } = opts;
5744
5602
  const hasMigrationTable = () => db.getSchemaConnection().hasTable(tableName);
5745
5603
  const createMigrationTable = () => {
5746
5604
  return db.getSchemaConnection().createTable(tableName, (table) => {
@@ -5770,7 +5628,7 @@ const createStorage = (opts) => {
5770
5628
  };
5771
5629
  };
5772
5630
  const wrapTransaction = (db) => (fn) => () => {
5773
- return db.connection.transaction((trx) => Promise.resolve(fn(trx)));
5631
+ return db.connection.transaction((trx) => Promise.resolve(fn(trx, db)));
5774
5632
  };
5775
5633
  const migrationResolver = ({ name, path: path2, context }) => {
5776
5634
  const { db } = context;
@@ -5794,31 +5652,82 @@ const migrationResolver = ({ name, path: path2, context }) => {
5794
5652
  down: wrapTransaction(db)(migration.down)
5795
5653
  };
5796
5654
  };
5797
- const createUmzugProvider = (db) => {
5798
- const migrationDir = path$1.join(strapi.dirs.app.root, "database/migrations");
5799
- fse.ensureDirSync(migrationDir);
5800
- return new Umzug({
5655
+ const createUserMigrationProvider = (db) => {
5656
+ const dir = db.config.settings.migrations.dir;
5657
+ fse.ensureDirSync(dir);
5658
+ const context = { db };
5659
+ const umzugProvider = new Umzug({
5801
5660
  storage: createStorage({ db, tableName: "strapi_migrations" }),
5802
5661
  logger: console,
5803
- context: { db },
5662
+ context,
5804
5663
  migrations: {
5805
- glob: ["*.{js,sql}", { cwd: migrationDir }],
5664
+ glob: ["*.{js,sql}", { cwd: dir }],
5806
5665
  resolve: migrationResolver
5807
5666
  }
5808
5667
  });
5668
+ return {
5669
+ async shouldRun() {
5670
+ const pendingMigrations = await umzugProvider.pending();
5671
+ return pendingMigrations.length > 0 && db.config?.settings?.runMigrations === true;
5672
+ },
5673
+ async up() {
5674
+ await umzugProvider.up();
5675
+ },
5676
+ async down() {
5677
+ await umzugProvider.down();
5678
+ }
5679
+ };
5680
+ };
5681
+ const internalMigrations = [];
5682
+ const createInternalMigrationProvider = (db) => {
5683
+ const context = { db };
5684
+ const umzugProvider = new Umzug({
5685
+ storage: createStorage({ db, tableName: "strapi_migrations_internal" }),
5686
+ logger: console,
5687
+ context,
5688
+ migrations: internalMigrations.map((migration) => {
5689
+ return {
5690
+ name: migration.name,
5691
+ up: wrapTransaction(context.db)(migration.up),
5692
+ down: wrapTransaction(context.db)(migration.down)
5693
+ };
5694
+ })
5695
+ });
5696
+ return {
5697
+ async shouldRun() {
5698
+ const pendingMigrations = await umzugProvider.pending();
5699
+ return pendingMigrations.length > 0;
5700
+ },
5701
+ async up() {
5702
+ await umzugProvider.up();
5703
+ },
5704
+ async down() {
5705
+ await umzugProvider.down();
5706
+ }
5707
+ };
5809
5708
  };
5810
5709
  const createMigrationsProvider = (db) => {
5811
- const migrations = createUmzugProvider(db);
5710
+ const providers = [createUserMigrationProvider(db), createInternalMigrationProvider(db)];
5812
5711
  return {
5813
5712
  async shouldRun() {
5814
- const pending = await migrations.pending();
5815
- return pending.length > 0 && db.config?.settings?.runMigrations === true;
5713
+ const shouldRunResponses = await Promise.all(
5714
+ providers.map((provider) => provider.shouldRun())
5715
+ );
5716
+ return shouldRunResponses.some((shouldRun) => shouldRun);
5816
5717
  },
5817
5718
  async up() {
5818
- await migrations.up();
5719
+ for (const provider of providers) {
5720
+ if (await provider.shouldRun()) {
5721
+ await provider.up();
5722
+ }
5723
+ }
5819
5724
  },
5820
5725
  async down() {
5821
- await migrations.down();
5726
+ for (const provider of providers) {
5727
+ if (await provider.shouldRun()) {
5728
+ await provider.down();
5729
+ }
5730
+ }
5822
5731
  }
5823
5732
  };
5824
5733
  };
@@ -5931,82 +5840,41 @@ const createLifecyclesProvider = (db) => {
5931
5840
  }
5932
5841
  };
5933
5842
  };
5934
- class LegacySqliteClient extends SqliteClient {
5935
- _driver() {
5936
- return require("sqlite3");
5937
- }
5938
- }
5939
5843
  const clientMap = {
5940
- "better-sqlite3": "better-sqlite3",
5941
- "@vscode/sqlite3": "sqlite",
5942
- sqlite3: LegacySqliteClient
5844
+ sqlite: "better-sqlite3",
5845
+ mysql: "mysql2",
5846
+ postgres: "pg"
5943
5847
  };
5944
- const trySqlitePackage = (packageName) => {
5945
- try {
5946
- require.resolve(packageName);
5947
- return packageName;
5948
- } catch (error) {
5949
- if (error instanceof Error && "code" in error && error.code === "MODULE_NOT_FOUND") {
5950
- return false;
5951
- }
5952
- throw error;
5953
- }
5954
- };
5955
- const getSqlitePackageName = () => {
5956
- if (typeof process.env.SQLITE_PKG !== "undefined") {
5957
- return process.env.SQLITE_PKG;
5958
- }
5959
- const matchingPackage = trySqlitePackage("better-sqlite3") || trySqlitePackage("@vscode/sqlite3") || trySqlitePackage("sqlite3");
5960
- if (!matchingPackage) {
5961
- throw new Error("No sqlite package found");
5962
- }
5963
- return matchingPackage;
5964
- };
5965
- const createConnection = (config) => {
5966
- const knexConfig = { ...config };
5967
- if (knexConfig.client === "sqlite") {
5968
- const sqlitePackageName = getSqlitePackageName();
5969
- knexConfig.client = clientMap[sqlitePackageName];
5848
+ function isClientValid(config) {
5849
+ return Object.keys(clientMap).includes(config.client);
5850
+ }
5851
+ const createConnection = (userConfig, strapiConfig) => {
5852
+ if (!isClientValid(userConfig)) {
5853
+ throw new Error(`Unsupported database client ${userConfig.client}`);
5854
+ }
5855
+ const knexConfig = { ...userConfig, client: clientMap[userConfig.client] };
5856
+ if (strapiConfig?.pool?.afterCreate) {
5857
+ knexConfig.pool = knexConfig.pool || {};
5858
+ const userAfterCreate = knexConfig.pool?.afterCreate;
5859
+ const strapiAfterCreate = strapiConfig.pool.afterCreate;
5860
+ knexConfig.pool.afterCreate = (conn, done) => {
5861
+ strapiAfterCreate(conn, (err, nativeConn) => {
5862
+ if (err) {
5863
+ return done(err, nativeConn);
5864
+ }
5865
+ if (userAfterCreate) {
5866
+ return userAfterCreate(nativeConn, done);
5867
+ }
5868
+ return done(null, nativeConn);
5869
+ });
5870
+ };
5970
5871
  }
5971
5872
  return knex(knexConfig);
5972
5873
  };
5973
- const transformAttribute = (attribute) => {
5974
- switch (attribute.type) {
5975
- case "media": {
5976
- return {
5977
- type: "relation",
5978
- relation: attribute.multiple === true ? "morphMany" : "morphOne",
5979
- target: "plugin::upload.file",
5980
- morphBy: "related"
5981
- };
5982
- }
5983
- default: {
5984
- return attribute;
5985
- }
5986
- }
5987
- };
5988
- const transformContentTypes = (contentTypes) => {
5989
- return contentTypes.map((contentType) => {
5990
- const model = {
5991
- ...contentType,
5992
- // reuse new model def
5993
- singularName: contentType.modelName,
5994
- tableName: contentType.collectionName,
5995
- attributes: {
5996
- ...Object.keys(contentType.attributes || {}).reduce((attrs, attrName) => {
5997
- return Object.assign(attrs, {
5998
- [attrName]: transformAttribute(contentType.attributes[attrName])
5999
- });
6000
- }, {})
6001
- }
6002
- };
6003
- return model;
6004
- });
6005
- };
6006
5874
  const getLinksWithoutMappedBy = (db) => {
6007
5875
  const relationsToUpdate = {};
6008
- db.metadata.forEach((contentType) => {
6009
- const attributes = contentType.attributes;
5876
+ db.metadata.forEach((modelMetadata) => {
5877
+ const attributes = modelMetadata.attributes;
6010
5878
  Object.values(attributes).forEach((attribute) => {
6011
5879
  if (attribute.type !== "relation") {
6012
5880
  return;
@@ -6034,19 +5902,19 @@ const isLinkTableEmpty = async (db, linkTableName) => {
6034
5902
  const validateBidirectionalRelations = async (db) => {
6035
5903
  const invalidLinks = getLinksWithoutMappedBy(db);
6036
5904
  for (const { relation, invRelation } of invalidLinks) {
6037
- const contentType = db.metadata.get(invRelation.target);
6038
- const invContentType = db.metadata.get(relation.target);
6039
- const joinTableName = getJoinTableName(contentType.tableName, invRelation.inversedBy);
6040
- const inverseJoinTableName = getJoinTableName(invContentType.tableName, relation.inversedBy);
5905
+ const modelMetadata = db.metadata.get(invRelation.target);
5906
+ const invModelMetadata = db.metadata.get(relation.target);
5907
+ const joinTableName = getJoinTableName(modelMetadata.tableName, invRelation.inversedBy);
5908
+ const inverseJoinTableName = getJoinTableName(invModelMetadata.tableName, relation.inversedBy);
6041
5909
  const joinTableEmpty = await isLinkTableEmpty(db, joinTableName);
6042
5910
  const inverseJoinTableEmpty = await isLinkTableEmpty(db, inverseJoinTableName);
6043
5911
  if (joinTableEmpty) {
6044
5912
  process.emitWarning(
6045
- `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}" }`
5913
+ `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}" }`
6046
5914
  );
6047
5915
  } else if (inverseJoinTableEmpty) {
6048
5916
  process.emitWarning(
6049
- `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}" }`
5917
+ `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}" }`
6050
5918
  );
6051
5919
  } else
6052
5920
  ;
@@ -6058,6 +5926,11 @@ const validateRelations = async (db) => {
6058
5926
  async function validateDatabase(db) {
6059
5927
  await validateRelations(db);
6060
5928
  }
5929
+ const afterCreate = (db) => (nativeConnection, done) => {
5930
+ db.dialect.initialize(nativeConnection).then(() => {
5931
+ return done(null, nativeConnection);
5932
+ });
5933
+ };
6061
5934
  class Database {
6062
5935
  connection;
6063
5936
  dialect;
@@ -6067,14 +5940,7 @@ class Database {
6067
5940
  migrations;
6068
5941
  lifecycles;
6069
5942
  entityManager;
6070
- static transformContentTypes = transformContentTypes;
6071
- static async init(config) {
6072
- const db = new Database(config);
6073
- await validateDatabase(db);
6074
- return db;
6075
- }
6076
5943
  constructor(config) {
6077
- this.metadata = createMetadata(config.models);
6078
5944
  this.config = {
6079
5945
  ...config,
6080
5946
  settings: {
@@ -6085,13 +5951,20 @@ class Database {
6085
5951
  };
6086
5952
  this.dialect = getDialect(this);
6087
5953
  this.dialect.configure();
6088
- this.connection = createConnection(this.config.connection);
6089
- this.dialect.initialize();
5954
+ this.metadata = createMetadata();
5955
+ this.connection = createConnection(this.config.connection, {
5956
+ pool: { afterCreate: afterCreate(this) }
5957
+ });
6090
5958
  this.schema = createSchemaProvider(this);
6091
5959
  this.migrations = createMigrationsProvider(this);
6092
5960
  this.lifecycles = createLifecyclesProvider(this);
6093
5961
  this.entityManager = createEntityManager(this);
6094
5962
  }
5963
+ async init({ models }) {
5964
+ this.metadata.loadModels(models);
5965
+ await validateDatabase(this);
5966
+ return this;
5967
+ }
6095
5968
  query(uid) {
6096
5969
  if (!this.metadata.has(uid)) {
6097
5970
  throw new Error(`Model ${uid} not found`);
@@ -6155,9 +6028,11 @@ class Database {
6155
6028
  await this.connection.destroy();
6156
6029
  }
6157
6030
  }
6031
+ const utils = { identifiers };
6158
6032
  export {
6159
6033
  Database,
6160
6034
  index as errors,
6161
- isKnexQuery
6035
+ isKnexQuery,
6036
+ utils
6162
6037
  };
6163
6038
  //# sourceMappingURL=index.mjs.map