@strapi/data-transfer 5.51.2 → 5.52.1

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 (40) hide show
  1. package/dist/strapi/providers/local-destination/strategies/restore/index.js +1 -0
  2. package/dist/strapi/providers/local-destination/strategies/restore/index.js.map +1 -1
  3. package/dist/strapi/providers/local-destination/strategies/restore/index.mjs +1 -0
  4. package/dist/strapi/providers/local-destination/strategies/restore/index.mjs.map +1 -1
  5. package/dist/strapi/providers/local-destination/strategies/restore/links.d.ts +1 -0
  6. package/dist/strapi/providers/local-destination/strategies/restore/links.d.ts.map +1 -1
  7. package/dist/strapi/providers/local-destination/strategies/restore/links.js +57 -4
  8. package/dist/strapi/providers/local-destination/strategies/restore/links.js.map +1 -1
  9. package/dist/strapi/providers/local-destination/strategies/restore/links.mjs +57 -5
  10. package/dist/strapi/providers/local-destination/strategies/restore/links.mjs.map +1 -1
  11. package/dist/strapi/providers/local-source/index.d.ts.map +1 -1
  12. package/dist/strapi/providers/local-source/index.js +6 -1
  13. package/dist/strapi/providers/local-source/index.js.map +1 -1
  14. package/dist/strapi/providers/local-source/index.mjs +6 -1
  15. package/dist/strapi/providers/local-source/index.mjs.map +1 -1
  16. package/dist/strapi/providers/local-source/links.d.ts +4 -1
  17. package/dist/strapi/providers/local-source/links.d.ts.map +1 -1
  18. package/dist/strapi/providers/local-source/links.js +16 -2
  19. package/dist/strapi/providers/local-source/links.js.map +1 -1
  20. package/dist/strapi/providers/local-source/links.mjs +16 -3
  21. package/dist/strapi/providers/local-source/links.mjs.map +1 -1
  22. package/dist/strapi/queries/link.d.ts +4 -1
  23. package/dist/strapi/queries/link.d.ts.map +1 -1
  24. package/dist/strapi/queries/link.js +271 -64
  25. package/dist/strapi/queries/link.js.map +1 -1
  26. package/dist/strapi/queries/link.mjs +271 -64
  27. package/dist/strapi/queries/link.mjs.map +1 -1
  28. package/dist/utils/capped-warnings.d.ts +9 -0
  29. package/dist/utils/capped-warnings.d.ts.map +1 -0
  30. package/dist/utils/capped-warnings.js +28 -0
  31. package/dist/utils/capped-warnings.js.map +1 -0
  32. package/dist/utils/capped-warnings.mjs +25 -0
  33. package/dist/utils/capped-warnings.mjs.map +1 -0
  34. package/dist/utils/index.d.ts +1 -0
  35. package/dist/utils/index.d.ts.map +1 -1
  36. package/dist/utils/index.js +3 -0
  37. package/dist/utils/index.js.map +1 -1
  38. package/dist/utils/index.mjs +1 -0
  39. package/dist/utils/index.mjs.map +1 -1
  40. package/package.json +7 -7
@@ -1,7 +1,10 @@
1
1
  import { Readable } from 'stream';
2
2
  import type { Core } from '@strapi/types';
3
+ export declare const formatOrphanedLinksExportSummary: (count: number) => string;
3
4
  /**
4
5
  * Create a Readable which will stream all the links from a Strapi instance
5
6
  */
6
- export declare const createLinksStream: (strapi: Core.Strapi) => Readable;
7
+ export declare const createLinksStream: (strapi: Core.Strapi, options?: {
8
+ onWarning?: (message: string) => void;
9
+ }) => Readable;
7
10
  //# sourceMappingURL=links.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"links.d.ts","sourceRoot":"","sources":["../../../../src/strapi/providers/local-source/links.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,QAAQ,CAAC;AAClC,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,eAAe,CAAC;AAK1C;;GAEG;AACH,eAAO,MAAM,iBAAiB,GAAI,QAAQ,IAAI,CAAC,MAAM,KAAG,QAiBvD,CAAC"}
1
+ {"version":3,"file":"links.d.ts","sourceRoot":"","sources":["../../../../src/strapi/providers/local-source/links.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,QAAQ,CAAC;AAClC,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,eAAe,CAAC;AAS1C,eAAO,MAAM,gCAAgC,GAAI,OAAO,MAAM,WACkE,CAAC;AAEjI;;GAEG;AACH,eAAO,MAAM,iBAAiB,GAC5B,QAAQ,IAAI,CAAC,MAAM,EACnB,UAAS;IAAE,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAA;CAAO,aA6BxD,CAAC"}
@@ -1,26 +1,40 @@
1
1
  'use strict';
2
2
 
3
3
  var stream = require('stream');
4
+ var cappedWarnings = require('../../../utils/capped-warnings.js');
4
5
  var link = require('../../queries/link.js');
5
6
 
7
+ const formatOrphanedExportLinkWarning = (link)=>`Omitting link ${link.left.type}:${link.left.ref} -> ${link.right.type}:${link.right.ref} from export because a referenced entity no longer exists in the database.`;
8
+ const formatOrphanedLinksExportSummary = (count)=>`Links export omitted ${count} relation(s) pointing at missing entities. Verify relations after import if this is unexpected.`;
6
9
  /**
7
10
  * Create a Readable which will stream all the links from a Strapi instance
8
- */ const createLinksStream = (strapi)=>{
11
+ */ const createLinksStream = (strapi, options = {})=>{
9
12
  const uids = [
10
13
  ...Object.keys(strapi.contentTypes),
11
14
  ...Object.keys(strapi.components)
12
15
  ];
16
+ let orphanedLinkCount = 0;
17
+ const warnings = cappedWarnings.createCappedWarningReporter(options.onWarning);
18
+ const query = link.createLinkQuery(strapi, undefined, {
19
+ onOrphanedLink (link) {
20
+ orphanedLinkCount += 1;
21
+ warnings.warn(formatOrphanedExportLinkWarning(link));
22
+ }
23
+ });
13
24
  // Async generator stream that returns every link from a Strapi instance
14
25
  return stream.Readable.from(async function* linkGenerator() {
15
- const query = link.createLinkQuery(strapi);
16
26
  for (const uid of uids){
17
27
  const generator = query().generateAll(uid);
18
28
  for await (const link of generator){
19
29
  yield link;
20
30
  }
21
31
  }
32
+ if (orphanedLinkCount > 0) {
33
+ options.onWarning?.(formatOrphanedLinksExportSummary(orphanedLinkCount));
34
+ }
22
35
  }());
23
36
  };
24
37
 
25
38
  exports.createLinksStream = createLinksStream;
39
+ exports.formatOrphanedLinksExportSummary = formatOrphanedLinksExportSummary;
26
40
  //# sourceMappingURL=links.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"links.js","sources":["../../../../src/strapi/providers/local-source/links.ts"],"sourcesContent":["import { Readable } from 'stream';\nimport type { Core } from '@strapi/types';\n\nimport type { ILink } from '../../../types';\nimport { createLinkQuery } from '../../queries/link';\n\n/**\n * Create a Readable which will stream all the links from a Strapi instance\n */\nexport const createLinksStream = (strapi: Core.Strapi): Readable => {\n const uids = [...Object.keys(strapi.contentTypes), ...Object.keys(strapi.components)] as string[];\n\n // Async generator stream that returns every link from a Strapi instance\n return Readable.from(\n (async function* linkGenerator(): AsyncGenerator<ILink> {\n const query = createLinkQuery(strapi);\n\n for (const uid of uids) {\n const generator = query().generateAll(uid);\n\n for await (const link of generator) {\n yield link;\n }\n }\n })()\n );\n};\n"],"names":["createLinksStream","strapi","uids","Object","keys","contentTypes","components","Readable","from","linkGenerator","query","createLinkQuery","uid","generator","generateAll","link"],"mappings":";;;;;AAMA;;IAGO,MAAMA,iBAAAA,GAAoB,CAACC,MAAAA,GAAAA;AAChC,IAAA,MAAMC,IAAAA,GAAO;WAAIC,MAAAA,CAAOC,IAAI,CAACH,MAAAA,CAAOI,YAAY,CAAA;WAAMF,MAAAA,CAAOC,IAAI,CAACH,MAAAA,CAAOK,UAAU;AAAE,KAAA;;AAGrF,IAAA,OAAOC,eAAAA,CAASC,IAAI,CACjB,gBAAgBC,aAAAA,GAAAA;AACf,QAAA,MAAMC,QAAQC,oBAAAA,CAAgBV,MAAAA,CAAAA;QAE9B,KAAK,MAAMW,OAAOV,IAAAA,CAAM;YACtB,MAAMW,SAAAA,GAAYH,KAAAA,EAAAA,CAAQI,WAAW,CAACF,GAAAA,CAAAA;YAEtC,WAAW,MAAMG,QAAQF,SAAAA,CAAW;gBAClC,MAAME,IAAAA;AACR,YAAA;AACF,QAAA;AACF,IAAA,CAAA,EAAA,CAAA;AAEJ;;;;"}
1
+ {"version":3,"file":"links.js","sources":["../../../../src/strapi/providers/local-source/links.ts"],"sourcesContent":["import { Readable } from 'stream';\nimport type { Core } from '@strapi/types';\n\nimport type { ILink } from '../../../types';\nimport { createCappedWarningReporter } from '../../../utils/capped-warnings';\nimport { createLinkQuery } from '../../queries/link';\n\nconst formatOrphanedExportLinkWarning = (link: ILink) =>\n `Omitting link ${link.left.type}:${link.left.ref} -> ${link.right.type}:${link.right.ref} from export because a referenced entity no longer exists in the database.`;\n\nexport const formatOrphanedLinksExportSummary = (count: number) =>\n `Links export omitted ${count} relation(s) pointing at missing entities. Verify relations after import if this is unexpected.`;\n\n/**\n * Create a Readable which will stream all the links from a Strapi instance\n */\nexport const createLinksStream = (\n strapi: Core.Strapi,\n options: { onWarning?: (message: string) => void } = {}\n) => {\n const uids = [...Object.keys(strapi.contentTypes), ...Object.keys(strapi.components)] as string[];\n let orphanedLinkCount = 0;\n const warnings = createCappedWarningReporter(options.onWarning);\n\n const query = createLinkQuery(strapi, undefined, {\n onOrphanedLink(link) {\n orphanedLinkCount += 1;\n warnings.warn(formatOrphanedExportLinkWarning(link));\n },\n });\n\n // Async generator stream that returns every link from a Strapi instance\n return Readable.from(\n (async function* linkGenerator(): AsyncGenerator<ILink> {\n for (const uid of uids) {\n const generator = query().generateAll(uid);\n\n for await (const link of generator) {\n yield link;\n }\n }\n\n if (orphanedLinkCount > 0) {\n options.onWarning?.(formatOrphanedLinksExportSummary(orphanedLinkCount));\n }\n })()\n );\n};\n"],"names":["formatOrphanedExportLinkWarning","link","left","type","ref","right","formatOrphanedLinksExportSummary","count","createLinksStream","strapi","options","uids","Object","keys","contentTypes","components","orphanedLinkCount","warnings","createCappedWarningReporter","onWarning","query","createLinkQuery","undefined","onOrphanedLink","warn","Readable","from","linkGenerator","uid","generator","generateAll"],"mappings":";;;;;;AAOA,MAAMA,+BAAAA,GAAkC,CAACC,IAAAA,GACvC,CAAC,cAAc,EAAEA,IAAAA,CAAKC,IAAI,CAACC,IAAI,CAAC,CAAC,EAAEF,KAAKC,IAAI,CAACE,GAAG,CAAC,IAAI,EAAEH,IAAAA,CAAKI,KAAK,CAACF,IAAI,CAAC,CAAC,EAAEF,KAAKI,KAAK,CAACD,GAAG,CAAC,0EAA0E,CAAC;AAE/J,MAAME,gCAAAA,GAAmC,CAACC,KAAAA,GAC/C,CAAC,qBAAqB,EAAEA,KAAAA,CAAM,+FAA+F;AAE/H;;AAEC,IACM,MAAMC,iBAAAA,GAAoB,CAC/BC,MAAAA,EACAC,OAAAA,GAAqD,EAAE,GAAA;AAEvD,IAAA,MAAMC,IAAAA,GAAO;WAAIC,MAAAA,CAAOC,IAAI,CAACJ,MAAAA,CAAOK,YAAY,CAAA;WAAMF,MAAAA,CAAOC,IAAI,CAACJ,MAAAA,CAAOM,UAAU;AAAE,KAAA;AACrF,IAAA,IAAIC,iBAAAA,GAAoB,CAAA;IACxB,MAAMC,QAAAA,GAAWC,0CAAAA,CAA4BR,OAAAA,CAAQS,SAAS,CAAA;IAE9D,MAAMC,KAAAA,GAAQC,oBAAAA,CAAgBZ,MAAAA,EAAQa,SAAAA,EAAW;AAC/CC,QAAAA,cAAAA,CAAAA,CAAetB,IAAI,EAAA;YACjBe,iBAAAA,IAAqB,CAAA;YACrBC,QAAAA,CAASO,IAAI,CAACxB,+BAAAA,CAAgCC,IAAAA,CAAAA,CAAAA;AAChD,QAAA;AACF,KAAA,CAAA;;AAGA,IAAA,OAAOwB,eAAAA,CAASC,IAAI,CACjB,gBAAgBC,aAAAA,GAAAA;QACf,KAAK,MAAMC,OAAOjB,IAAAA,CAAM;YACtB,MAAMkB,SAAAA,GAAYT,KAAAA,EAAAA,CAAQU,WAAW,CAACF,GAAAA,CAAAA;YAEtC,WAAW,MAAM3B,QAAQ4B,SAAAA,CAAW;gBAClC,MAAM5B,IAAAA;AACR,YAAA;AACF,QAAA;AAEA,QAAA,IAAIe,oBAAoB,CAAA,EAAG;YACzBN,OAAAA,CAAQS,SAAS,GAAGb,gCAAAA,CAAiCU,iBAAAA,CAAAA,CAAAA;AACvD,QAAA;AACF,IAAA,CAAA,EAAA,CAAA;AAEJ;;;;;"}
@@ -1,24 +1,37 @@
1
1
  import { Readable } from 'stream';
2
+ import { createCappedWarningReporter } from '../../../utils/capped-warnings.mjs';
2
3
  import { createLinkQuery } from '../../queries/link.mjs';
3
4
 
5
+ const formatOrphanedExportLinkWarning = (link)=>`Omitting link ${link.left.type}:${link.left.ref} -> ${link.right.type}:${link.right.ref} from export because a referenced entity no longer exists in the database.`;
6
+ const formatOrphanedLinksExportSummary = (count)=>`Links export omitted ${count} relation(s) pointing at missing entities. Verify relations after import if this is unexpected.`;
4
7
  /**
5
8
  * Create a Readable which will stream all the links from a Strapi instance
6
- */ const createLinksStream = (strapi)=>{
9
+ */ const createLinksStream = (strapi, options = {})=>{
7
10
  const uids = [
8
11
  ...Object.keys(strapi.contentTypes),
9
12
  ...Object.keys(strapi.components)
10
13
  ];
14
+ let orphanedLinkCount = 0;
15
+ const warnings = createCappedWarningReporter(options.onWarning);
16
+ const query = createLinkQuery(strapi, undefined, {
17
+ onOrphanedLink (link) {
18
+ orphanedLinkCount += 1;
19
+ warnings.warn(formatOrphanedExportLinkWarning(link));
20
+ }
21
+ });
11
22
  // Async generator stream that returns every link from a Strapi instance
12
23
  return Readable.from(async function* linkGenerator() {
13
- const query = createLinkQuery(strapi);
14
24
  for (const uid of uids){
15
25
  const generator = query().generateAll(uid);
16
26
  for await (const link of generator){
17
27
  yield link;
18
28
  }
19
29
  }
30
+ if (orphanedLinkCount > 0) {
31
+ options.onWarning?.(formatOrphanedLinksExportSummary(orphanedLinkCount));
32
+ }
20
33
  }());
21
34
  };
22
35
 
23
- export { createLinksStream };
36
+ export { createLinksStream, formatOrphanedLinksExportSummary };
24
37
  //# sourceMappingURL=links.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"links.mjs","sources":["../../../../src/strapi/providers/local-source/links.ts"],"sourcesContent":["import { Readable } from 'stream';\nimport type { Core } from '@strapi/types';\n\nimport type { ILink } from '../../../types';\nimport { createLinkQuery } from '../../queries/link';\n\n/**\n * Create a Readable which will stream all the links from a Strapi instance\n */\nexport const createLinksStream = (strapi: Core.Strapi): Readable => {\n const uids = [...Object.keys(strapi.contentTypes), ...Object.keys(strapi.components)] as string[];\n\n // Async generator stream that returns every link from a Strapi instance\n return Readable.from(\n (async function* linkGenerator(): AsyncGenerator<ILink> {\n const query = createLinkQuery(strapi);\n\n for (const uid of uids) {\n const generator = query().generateAll(uid);\n\n for await (const link of generator) {\n yield link;\n }\n }\n })()\n );\n};\n"],"names":["createLinksStream","strapi","uids","Object","keys","contentTypes","components","Readable","from","linkGenerator","query","createLinkQuery","uid","generator","generateAll","link"],"mappings":";;;AAMA;;IAGO,MAAMA,iBAAAA,GAAoB,CAACC,MAAAA,GAAAA;AAChC,IAAA,MAAMC,IAAAA,GAAO;WAAIC,MAAAA,CAAOC,IAAI,CAACH,MAAAA,CAAOI,YAAY,CAAA;WAAMF,MAAAA,CAAOC,IAAI,CAACH,MAAAA,CAAOK,UAAU;AAAE,KAAA;;AAGrF,IAAA,OAAOC,QAAAA,CAASC,IAAI,CACjB,gBAAgBC,aAAAA,GAAAA;AACf,QAAA,MAAMC,QAAQC,eAAAA,CAAgBV,MAAAA,CAAAA;QAE9B,KAAK,MAAMW,OAAOV,IAAAA,CAAM;YACtB,MAAMW,SAAAA,GAAYH,KAAAA,EAAAA,CAAQI,WAAW,CAACF,GAAAA,CAAAA;YAEtC,WAAW,MAAMG,QAAQF,SAAAA,CAAW;gBAClC,MAAME,IAAAA;AACR,YAAA;AACF,QAAA;AACF,IAAA,CAAA,EAAA,CAAA;AAEJ;;;;"}
1
+ {"version":3,"file":"links.mjs","sources":["../../../../src/strapi/providers/local-source/links.ts"],"sourcesContent":["import { Readable } from 'stream';\nimport type { Core } from '@strapi/types';\n\nimport type { ILink } from '../../../types';\nimport { createCappedWarningReporter } from '../../../utils/capped-warnings';\nimport { createLinkQuery } from '../../queries/link';\n\nconst formatOrphanedExportLinkWarning = (link: ILink) =>\n `Omitting link ${link.left.type}:${link.left.ref} -> ${link.right.type}:${link.right.ref} from export because a referenced entity no longer exists in the database.`;\n\nexport const formatOrphanedLinksExportSummary = (count: number) =>\n `Links export omitted ${count} relation(s) pointing at missing entities. Verify relations after import if this is unexpected.`;\n\n/**\n * Create a Readable which will stream all the links from a Strapi instance\n */\nexport const createLinksStream = (\n strapi: Core.Strapi,\n options: { onWarning?: (message: string) => void } = {}\n) => {\n const uids = [...Object.keys(strapi.contentTypes), ...Object.keys(strapi.components)] as string[];\n let orphanedLinkCount = 0;\n const warnings = createCappedWarningReporter(options.onWarning);\n\n const query = createLinkQuery(strapi, undefined, {\n onOrphanedLink(link) {\n orphanedLinkCount += 1;\n warnings.warn(formatOrphanedExportLinkWarning(link));\n },\n });\n\n // Async generator stream that returns every link from a Strapi instance\n return Readable.from(\n (async function* linkGenerator(): AsyncGenerator<ILink> {\n for (const uid of uids) {\n const generator = query().generateAll(uid);\n\n for await (const link of generator) {\n yield link;\n }\n }\n\n if (orphanedLinkCount > 0) {\n options.onWarning?.(formatOrphanedLinksExportSummary(orphanedLinkCount));\n }\n })()\n );\n};\n"],"names":["formatOrphanedExportLinkWarning","link","left","type","ref","right","formatOrphanedLinksExportSummary","count","createLinksStream","strapi","options","uids","Object","keys","contentTypes","components","orphanedLinkCount","warnings","createCappedWarningReporter","onWarning","query","createLinkQuery","undefined","onOrphanedLink","warn","Readable","from","linkGenerator","uid","generator","generateAll"],"mappings":";;;;AAOA,MAAMA,+BAAAA,GAAkC,CAACC,IAAAA,GACvC,CAAC,cAAc,EAAEA,IAAAA,CAAKC,IAAI,CAACC,IAAI,CAAC,CAAC,EAAEF,KAAKC,IAAI,CAACE,GAAG,CAAC,IAAI,EAAEH,IAAAA,CAAKI,KAAK,CAACF,IAAI,CAAC,CAAC,EAAEF,KAAKI,KAAK,CAACD,GAAG,CAAC,0EAA0E,CAAC;AAE/J,MAAME,gCAAAA,GAAmC,CAACC,KAAAA,GAC/C,CAAC,qBAAqB,EAAEA,KAAAA,CAAM,+FAA+F;AAE/H;;AAEC,IACM,MAAMC,iBAAAA,GAAoB,CAC/BC,MAAAA,EACAC,OAAAA,GAAqD,EAAE,GAAA;AAEvD,IAAA,MAAMC,IAAAA,GAAO;WAAIC,MAAAA,CAAOC,IAAI,CAACJ,MAAAA,CAAOK,YAAY,CAAA;WAAMF,MAAAA,CAAOC,IAAI,CAACJ,MAAAA,CAAOM,UAAU;AAAE,KAAA;AACrF,IAAA,IAAIC,iBAAAA,GAAoB,CAAA;IACxB,MAAMC,QAAAA,GAAWC,2BAAAA,CAA4BR,OAAAA,CAAQS,SAAS,CAAA;IAE9D,MAAMC,KAAAA,GAAQC,eAAAA,CAAgBZ,MAAAA,EAAQa,SAAAA,EAAW;AAC/CC,QAAAA,cAAAA,CAAAA,CAAetB,IAAI,EAAA;YACjBe,iBAAAA,IAAqB,CAAA;YACrBC,QAAAA,CAASO,IAAI,CAACxB,+BAAAA,CAAgCC,IAAAA,CAAAA,CAAAA;AAChD,QAAA;AACF,KAAA,CAAA;;AAGA,IAAA,OAAOwB,QAAAA,CAASC,IAAI,CACjB,gBAAgBC,aAAAA,GAAAA;QACf,KAAK,MAAMC,OAAOjB,IAAAA,CAAM;YACtB,MAAMkB,SAAAA,GAAYT,KAAAA,EAAAA,CAAQU,WAAW,CAACF,GAAAA,CAAAA;YAEtC,WAAW,MAAM3B,QAAQ4B,SAAAA,CAAW;gBAClC,MAAM5B,IAAAA;AACR,YAAA;AACF,QAAA;AAEA,QAAA,IAAIe,oBAAoB,CAAA,EAAG;YACzBN,OAAAA,CAAQS,SAAS,GAAGb,gCAAAA,CAAiCU,iBAAAA,CAAAA,CAAAA;AACvD,QAAA;AACF,IAAA,CAAA,EAAA,CAAA;AAEJ;;;;"}
@@ -1,7 +1,10 @@
1
1
  import type { Knex } from 'knex';
2
2
  import type { Core } from '@strapi/types';
3
3
  import { ILink } from '../../types';
4
- export declare const createLinkQuery: (strapi: Core.Strapi, trx?: Knex.Transaction) => () => {
4
+ export interface LinkQueryOptions {
5
+ onOrphanedLink?: (link: ILink) => void;
6
+ }
7
+ export declare const createLinkQuery: (strapi: Core.Strapi, trx?: Knex.Transaction, options?: LinkQueryOptions) => () => {
5
8
  generateAll: (uid: string) => AsyncGenerator<ILink>;
6
9
  generateAllForAttribute: (uid: string, fieldName: string) => AsyncGenerator<ILink>;
7
10
  insert: (link: ILink) => Promise<void>;
@@ -1 +1 @@
1
- {"version":3,"file":"link.d.ts","sourceRoot":"","sources":["../../../src/strapi/queries/link.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAEjC,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,eAAe,CAAC;AAE1C,OAAO,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AAIpC,eAAO,MAAM,eAAe,GAAI,QAAQ,IAAI,CAAC,MAAM,EAAE,MAAM,IAAI,CAAC,WAAW;uBAmMtC,MAAM,KAAG,cAAc,CAAC,KAAK,CAAC;mCAzLlB,MAAM,aAAa,MAAM,KAAG,cAAc,CAAC,KAAK,CAAC;mBAyMlE,KAAK;CA+GpC,CAAC;AAEF,eAAO,MAAM,+BAA+B,GAAI,YAAY,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,wBAY9E,CAAC"}
1
+ {"version":3,"file":"link.d.ts","sourceRoot":"","sources":["../../../src/strapi/queries/link.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAEjC,OAAO,KAAK,EAAE,IAAI,EAAO,MAAM,eAAe,CAAC;AAE/C,OAAO,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AAkHpC,MAAM,WAAW,gBAAgB;IAC/B,cAAc,CAAC,EAAE,CAAC,IAAI,EAAE,KAAK,KAAK,IAAI,CAAC;CACxC;AAOD,eAAO,MAAM,eAAe,GAC1B,QAAQ,IAAI,CAAC,MAAM,EACnB,MAAM,IAAI,CAAC,WAAW,EACtB,UAAU,gBAAgB;uBAgVS,MAAM,KAAG,cAAc,CAAC,KAAK,CAAC;mCAnUlB,MAAM,aAAa,MAAM,KAAG,cAAc,CAAC,KAAK,CAAC;mBAmVlE,KAAK;CA+GpC,CAAC;AAEF,eAAO,MAAM,+BAA+B,GAAI,YAAY,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,wBAY9E,CAAC"}
@@ -3,7 +3,105 @@
3
3
  var fp = require('lodash/fp');
4
4
 
5
5
  // TODO: Remove any types when we'll have types for DB metadata
6
- const createLinkQuery = (strapi, trx)=>{
6
+ const TARGET_EXISTS_ALIAS = '__target_exists';
7
+ const LEFT_EXISTS_ALIAS = '__left_exists';
8
+ const RIGHT_EXISTS_ALIAS = '__right_exists';
9
+ const EXISTENCE_CHECK_CHUNK_SIZE = 1000;
10
+ const getMetadataTableName = (strapi, uid)=>{
11
+ const metadata = strapi.db.metadata.get(uid);
12
+ if (!metadata) {
13
+ throw new Error(`No metadata found for ${uid}`);
14
+ }
15
+ return metadata.tableName;
16
+ };
17
+ const refKey = (ref)=>`${typeof ref}:${ref}`;
18
+ /**
19
+ * Batch-load which refs exist for a content type. Handles numeric `id` refs and
20
+ * string `documentId` refs separately, chunked to keep `IN (...)` lists bounded.
21
+ */ const loadExistingRefs = async (strapi, uid, refs)=>{
22
+ const existing = new Set();
23
+ // Stale morph type UIDs (removed components/CTs) have no metadata — treat as orphaned.
24
+ if (!strapi.db.metadata.has(uid)) {
25
+ return existing;
26
+ }
27
+ const uniqueRefs = [
28
+ ...new Set(refs)
29
+ ];
30
+ const numericIds = uniqueRefs.filter((ref)=>typeof ref === 'number');
31
+ const documentIds = uniqueRefs.filter((ref)=>typeof ref === 'string');
32
+ for(let i = 0; i < numericIds.length; i += EXISTENCE_CHECK_CHUNK_SIZE){
33
+ const chunk = numericIds.slice(i, i + EXISTENCE_CHECK_CHUNK_SIZE);
34
+ const rows = await strapi.db.query(uid).findMany({
35
+ select: [
36
+ 'id'
37
+ ],
38
+ where: {
39
+ id: {
40
+ $in: chunk
41
+ }
42
+ }
43
+ });
44
+ for (const row of rows){
45
+ existing.add(refKey(row.id));
46
+ }
47
+ }
48
+ for(let i = 0; i < documentIds.length; i += EXISTENCE_CHECK_CHUNK_SIZE){
49
+ const chunk = documentIds.slice(i, i + EXISTENCE_CHECK_CHUNK_SIZE);
50
+ const rows = await strapi.db.query(uid).findMany({
51
+ select: [
52
+ 'id',
53
+ 'documentId'
54
+ ],
55
+ where: {
56
+ documentId: {
57
+ $in: chunk
58
+ }
59
+ }
60
+ });
61
+ for (const row of rows){
62
+ if (row.documentId != null) {
63
+ existing.add(refKey(row.documentId));
64
+ }
65
+ }
66
+ }
67
+ return existing;
68
+ };
69
+ /**
70
+ * Filter morph links in batches. The morph owner (`left`) is not checked:
71
+ * morphColumn rows come from the owner table itself, and morph join-table
72
+ * owners are FK-backed. Only dynamic morph targets (`right`) need existence
73
+ * checks, and those have no DB-level FK.
74
+ */ const filterMorphLinksByTargetExistence = async function* filterMorphLinksByTargetExistence(strapi, links, onOrphanedLink) {
75
+ const refsByType = new Map();
76
+ for (const link of links){
77
+ const { type, ref } = link.right;
78
+ if (ref == null || type == null) {
79
+ onOrphanedLink?.(link);
80
+ } else {
81
+ const bucket = refsByType.get(type) ?? [];
82
+ bucket.push(ref);
83
+ refsByType.set(type, bucket);
84
+ }
85
+ }
86
+ const existingByType = new Map();
87
+ for (const [type, refs] of refsByType){
88
+ existingByType.set(type, await loadExistingRefs(strapi, type, refs));
89
+ }
90
+ for (const link of links){
91
+ const { type, ref } = link.right;
92
+ if (ref == null || type == null) ; else if (existingByType.get(type)?.has(refKey(ref))) {
93
+ yield link;
94
+ } else {
95
+ onOrphanedLink?.(link);
96
+ }
97
+ }
98
+ };
99
+ const aliasColumn = (tableAlias, column)=>`${tableAlias}.${column}`;
100
+ const selectAliasedColumns = (tableAlias, columns)=>columns.map((column)=>({
101
+ [column]: aliasColumn(tableAlias, column)
102
+ }));
103
+ const createLinkQuery = (strapi, trx, options)=>{
104
+ const { onOrphanedLink } = options ?? {};
7
105
  const query = ()=>{
8
106
  const { connection } = strapi.db;
9
107
  // TODO: Export utils from database and use the addSchema that is already written
@@ -27,7 +125,34 @@ const createLinkQuery = (strapi, trx)=>{
27
125
  // TODO: handle manyToOne joinColumn
28
126
  if (attribute.joinColumn) {
29
127
  const joinColumnName = attribute.joinColumn.name;
30
- const qb = connection.queryBuilder().select('id', joinColumnName).from(addSchema(metadata.tableName));
128
+ const referencedColumn = attribute.joinColumn.referencedColumn ?? 'id';
129
+ const ownerAlias = 'owner';
130
+ const targetAlias = 'target';
131
+ const ownerTable = addSchema(metadata.tableName);
132
+ const targetTable = addSchema(getMetadataTableName(strapi, target));
133
+ // Use EXISTS (not JOIN): joining on non-unique referencedColumn (e.g. i18n
134
+ // document_id) multiplies owner rows by matching target locales.
135
+ const targetExistsSubquery = ()=>connection.queryBuilder().select(connection.raw('1')).from({
136
+ [targetAlias]: targetTable
137
+ }).whereRaw('??.?? = ??.??', [
138
+ targetAlias,
139
+ referencedColumn,
140
+ ownerAlias,
141
+ joinColumnName
142
+ ]);
143
+ const qb = connection.queryBuilder().select(`${ownerAlias}.id`, `${ownerAlias}.${joinColumnName} as ${joinColumnName}`, ...onOrphanedLink ? // becomes `exists ((select ...))`, which SQLite rejects in a SELECT
144
+ // list. Wrap the EXISTS expression so the compiled SQL is valid.
145
+ [
146
+ connection.raw('(exists ?) as ??', [
147
+ targetExistsSubquery(),
148
+ TARGET_EXISTS_ALIAS
149
+ ])
150
+ ] : []).from({
151
+ [ownerAlias]: ownerTable
152
+ }).whereNotNull(aliasColumn(ownerAlias, joinColumnName));
153
+ if (!onOrphanedLink) {
154
+ qb.whereExists(targetExistsSubquery());
155
+ }
31
156
  if (trx) {
32
157
  qb.transacting(trx);
33
158
  }
@@ -35,27 +160,30 @@ const createLinkQuery = (strapi, trx)=>{
35
160
  const entries = await qb;
36
161
  for (const entry of entries){
37
162
  const ref = entry[joinColumnName];
38
- if (ref !== null) {
39
- yield {
40
- kind,
41
- relation,
42
- left: {
43
- type: uid,
44
- ref: entry.id,
45
- field: fieldName
46
- },
47
- right: {
48
- type: target,
49
- ref
50
- }
51
- };
163
+ const link = {
164
+ kind,
165
+ relation,
166
+ left: {
167
+ type: uid,
168
+ ref: entry.id,
169
+ field: fieldName
170
+ },
171
+ right: {
172
+ type: target,
173
+ ref
174
+ }
175
+ };
176
+ // EXISTS yields 0/1 (or boolean); treat falsy as orphaned.
177
+ if (onOrphanedLink && !entry[TARGET_EXISTS_ALIAS]) {
178
+ onOrphanedLink(link);
179
+ } else {
180
+ yield link;
52
181
  }
53
182
  }
54
183
  }
55
184
  // The relation uses a join table
56
185
  if (attribute.joinTable) {
57
186
  const { name, joinColumn, inverseJoinColumn, orderColumnName, morphColumn, inverseOrderColumnName } = attribute.joinTable;
58
- const qb = connection.queryBuilder().from(addSchema(name));
59
187
  const columns = {
60
188
  left: {
61
189
  ref: null
@@ -80,56 +208,137 @@ const createLinkQuery = (strapi, trx)=>{
80
208
  if (inverseOrderColumnName) {
81
209
  columns.right.order = inverseOrderColumnName;
82
210
  }
211
+ const joinAlias = 'join';
212
+ const leftAlias = 'left';
213
+ const rightAlias = 'right';
214
+ const joinTable = addSchema(name);
215
+ const leftTable = addSchema(metadata.tableName);
216
+ const rightTable = addSchema(getMetadataTableName(strapi, attribute.target));
217
+ const leftReferencedColumn = aliasColumn(leftAlias, joinColumn.referencedColumn ?? 'id');
218
+ const rightReferencedColumn = aliasColumn(rightAlias, inverseJoinColumn.referencedColumn ?? 'id');
219
+ const validColumns = [
220
+ columns.left.ref,
221
+ columns.left.order,
222
+ columns.right.ref,
223
+ columns.right.order
224
+ ].filter((column)=>!fp.isNil(column));
225
+ const buildJoinTableLink = (entry)=>{
226
+ const linkLeft = {
227
+ type: uid,
228
+ field: fieldName
229
+ };
230
+ const linkRight = {
231
+ type: attribute.target,
232
+ field: attribute.inversedBy
233
+ };
234
+ if (columns.left.ref) {
235
+ linkLeft.ref = entry[columns.left.ref];
236
+ }
237
+ if (columns.right.ref) {
238
+ linkRight.ref = entry[columns.right.ref];
239
+ }
240
+ if (columns.left.order) {
241
+ linkLeft.pos = entry[columns.left.order];
242
+ }
243
+ if (columns.right.order) {
244
+ linkRight.pos = entry[columns.right.order];
245
+ }
246
+ return {
247
+ kind,
248
+ relation,
249
+ left: fp.clone(linkLeft),
250
+ right: fp.clone(linkRight)
251
+ };
252
+ };
253
+ const selectColumns = [
254
+ ...selectAliasedColumns(joinAlias, validColumns),
255
+ ...onOrphanedLink ? [
256
+ {
257
+ [LEFT_EXISTS_ALIAS]: leftReferencedColumn
258
+ },
259
+ {
260
+ [RIGHT_EXISTS_ALIAS]: rightReferencedColumn
261
+ }
262
+ ] : []
263
+ ];
264
+ const joinQb = connection.queryBuilder().select(selectColumns).from({
265
+ [joinAlias]: joinTable
266
+ });
267
+ if (onOrphanedLink) {
268
+ joinQb.leftJoin({
269
+ [leftAlias]: leftTable
270
+ }, aliasColumn(joinAlias, joinColumn.name), leftReferencedColumn).leftJoin({
271
+ [rightAlias]: rightTable
272
+ }, aliasColumn(joinAlias, inverseJoinColumn.name), rightReferencedColumn);
273
+ } else {
274
+ joinQb.innerJoin({
275
+ [leftAlias]: leftTable
276
+ }, aliasColumn(joinAlias, joinColumn.name), leftReferencedColumn).innerJoin({
277
+ [rightAlias]: rightTable
278
+ }, aliasColumn(joinAlias, inverseJoinColumn.name), rightReferencedColumn);
279
+ }
280
+ if (trx) {
281
+ joinQb.transacting(trx);
282
+ }
283
+ const entries = await joinQb;
284
+ for (const entry of entries){
285
+ const link = buildJoinTableLink(entry);
286
+ if (onOrphanedLink && (entry[LEFT_EXISTS_ALIAS] == null || entry[RIGHT_EXISTS_ALIAS] == null)) {
287
+ onOrphanedLink(link);
288
+ } else {
289
+ yield link;
290
+ }
291
+ }
83
292
  }
84
293
  if (kind === 'relation.morph') {
294
+ const qb = connection.queryBuilder().from(addSchema(name));
85
295
  columns.left.ref = joinColumn.name;
86
296
  columns.right.ref = morphColumn.idColumn.name;
87
297
  columns.right.type = morphColumn.typeColumn.name;
88
298
  columns.right.field = 'field';
89
299
  columns.right.order = 'order';
90
- }
91
- const validColumns = [
92
- // Left
93
- columns.left.ref,
94
- columns.left.order,
95
- // Right
96
- columns.right.ref,
97
- columns.right.type,
98
- columns.right.field,
99
- columns.right.order
100
- ].filter((column)=>!fp.isNil(column));
101
- qb.select(validColumns);
102
- if (trx) {
103
- qb.transacting(trx);
104
- }
105
- // TODO: stream the query to improve performances
106
- const entries = await qb;
107
- for (const entry of entries){
108
- if (columns.left.ref) {
109
- left.ref = entry[columns.left.ref];
110
- }
111
- if (columns.right.ref) {
112
- right.ref = entry[columns.right.ref];
300
+ const validColumns = [
301
+ columns.left.ref,
302
+ columns.left.order,
303
+ columns.right.ref,
304
+ columns.right.type,
305
+ columns.right.field,
306
+ columns.right.order
307
+ ].filter((column)=>!fp.isNil(column));
308
+ qb.select(validColumns);
309
+ if (trx) {
310
+ qb.transacting(trx);
113
311
  }
114
- if (columns.left.order) {
115
- left.pos = entry[columns.left.order];
116
- }
117
- if (columns.right.order) {
118
- right.pos = entry[columns.right.order];
119
- }
120
- if (columns.right.type) {
121
- right.type = entry[columns.right.type];
122
- }
123
- if (columns.right.field) {
124
- right.field = entry[columns.right.field];
312
+ // TODO: stream the query to improve performances
313
+ const entries = await qb;
314
+ const morphLinks = [];
315
+ for (const entry of entries){
316
+ if (columns.left.ref) {
317
+ left.ref = entry[columns.left.ref];
318
+ }
319
+ if (columns.right.ref) {
320
+ right.ref = entry[columns.right.ref];
321
+ }
322
+ if (columns.left.order) {
323
+ left.pos = entry[columns.left.order];
324
+ }
325
+ if (columns.right.order) {
326
+ right.pos = entry[columns.right.order];
327
+ }
328
+ if (columns.right.type) {
329
+ right.type = entry[columns.right.type];
330
+ }
331
+ if (columns.right.field) {
332
+ right.field = entry[columns.right.field];
333
+ }
334
+ morphLinks.push({
335
+ kind,
336
+ relation,
337
+ left: fp.clone(left),
338
+ right: fp.clone(right)
339
+ });
125
340
  }
126
- const link = {
127
- kind,
128
- relation,
129
- left: fp.clone(left),
130
- right: fp.clone(right)
131
- };
132
- yield link;
341
+ yield* filterMorphLinksByTargetExistence(strapi, morphLinks, onOrphanedLink);
133
342
  }
134
343
  }
135
344
  if (attribute.morphColumn) {
@@ -139,9 +348,7 @@ const createLinkQuery = (strapi, trx)=>{
139
348
  qb.transacting(trx);
140
349
  }
141
350
  const entries = await qb;
142
- for (const entry of entries){
143
- const ref = entry[idColumn.name];
144
- yield {
351
+ const morphLinks = entries.map((entry)=>({
145
352
  kind,
146
353
  relation,
147
354
  left: {
@@ -151,10 +358,10 @@ const createLinkQuery = (strapi, trx)=>{
151
358
  },
152
359
  right: {
153
360
  type: entry[typeColumn.name],
154
- ref
361
+ ref: entry[idColumn.name]
155
362
  }
156
- };
157
- }
363
+ }));
364
+ yield* filterMorphLinksByTargetExistence(strapi, morphLinks, onOrphanedLink);
158
365
  }
159
366
  }
160
367
  async function* generateAll(uid) {