payload 3.80.0-internal.cee0ccf → 3.80.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 (135) hide show
  1. package/dist/auth/endpoints/forgotPassword.d.ts.map +1 -1
  2. package/dist/auth/endpoints/forgotPassword.js +0 -2
  3. package/dist/auth/endpoints/forgotPassword.js.map +1 -1
  4. package/dist/auth/extractJWT.d.ts.map +1 -1
  5. package/dist/auth/extractJWT.js +18 -4
  6. package/dist/auth/extractJWT.js.map +1 -1
  7. package/dist/auth/operations/forgotPassword.d.ts.map +1 -1
  8. package/dist/auth/operations/forgotPassword.js +5 -4
  9. package/dist/auth/operations/forgotPassword.js.map +1 -1
  10. package/dist/auth/operations/me.js +5 -5
  11. package/dist/auth/operations/me.js.map +1 -1
  12. package/dist/auth/sendVerificationEmail.d.ts.map +1 -1
  13. package/dist/auth/sendVerificationEmail.js +5 -4
  14. package/dist/auth/sendVerificationEmail.js.map +1 -1
  15. package/dist/collections/operations/update.js +1 -1
  16. package/dist/collections/operations/update.js.map +1 -1
  17. package/dist/collections/operations/utilities/update.d.ts.map +1 -1
  18. package/dist/collections/operations/utilities/update.js +2 -1
  19. package/dist/collections/operations/utilities/update.js.map +1 -1
  20. package/dist/config/orderable/index.d.ts +2 -2
  21. package/dist/config/orderable/index.d.ts.map +1 -1
  22. package/dist/config/orderable/index.js +60 -34
  23. package/dist/config/orderable/index.js.map +1 -1
  24. package/dist/config/orderable/utils/buildJoinScopeWhere.d.ts +10 -0
  25. package/dist/config/orderable/utils/buildJoinScopeWhere.d.ts.map +1 -0
  26. package/dist/config/orderable/utils/buildJoinScopeWhere.js +43 -0
  27. package/dist/config/orderable/utils/buildJoinScopeWhere.js.map +1 -0
  28. package/dist/config/orderable/utils/getJoinScopeContext.d.ts +16 -0
  29. package/dist/config/orderable/utils/getJoinScopeContext.d.ts.map +1 -0
  30. package/dist/config/orderable/utils/getJoinScopeContext.js +42 -0
  31. package/dist/config/orderable/utils/getJoinScopeContext.js.map +1 -0
  32. package/dist/config/orderable/utils/getJoinScopeWhereFromDocData.d.ts +12 -0
  33. package/dist/config/orderable/utils/getJoinScopeWhereFromDocData.d.ts.map +1 -0
  34. package/dist/config/orderable/utils/getJoinScopeWhereFromDocData.js +18 -0
  35. package/dist/config/orderable/utils/getJoinScopeWhereFromDocData.js.map +1 -0
  36. package/dist/config/orderable/utils/getValueAtPath.d.ts +5 -0
  37. package/dist/config/orderable/utils/getValueAtPath.d.ts.map +1 -0
  38. package/dist/config/orderable/utils/getValueAtPath.js +18 -0
  39. package/dist/config/orderable/utils/getValueAtPath.js.map +1 -0
  40. package/dist/config/orderable/utils/resolvePendingTargetKey.d.ts +13 -0
  41. package/dist/config/orderable/utils/resolvePendingTargetKey.d.ts.map +1 -0
  42. package/dist/config/orderable/utils/resolvePendingTargetKey.js +24 -0
  43. package/dist/config/orderable/utils/resolvePendingTargetKey.js.map +1 -0
  44. package/dist/config/types.d.ts +1 -1
  45. package/dist/config/types.js.map +1 -1
  46. package/dist/database/getLocalizedPaths.d.ts.map +1 -1
  47. package/dist/database/getLocalizedPaths.js +2 -1
  48. package/dist/database/getLocalizedPaths.js.map +1 -1
  49. package/dist/database/queryValidation/validateQueryPaths.js +1 -1
  50. package/dist/database/queryValidation/validateQueryPaths.js.map +1 -1
  51. package/dist/database/queryValidation/validateSearchParams.d.ts.map +1 -1
  52. package/dist/database/queryValidation/validateSearchParams.js +2 -1
  53. package/dist/database/queryValidation/validateSearchParams.js.map +1 -1
  54. package/dist/database/sanitizeJoinQuery.d.ts.map +1 -1
  55. package/dist/database/sanitizeJoinQuery.js +6 -0
  56. package/dist/database/sanitizeJoinQuery.js.map +1 -1
  57. package/dist/database/types.d.ts +0 -1
  58. package/dist/database/types.d.ts.map +1 -1
  59. package/dist/database/types.js.map +1 -1
  60. package/dist/exports/shared.d.ts +1 -0
  61. package/dist/exports/shared.d.ts.map +1 -1
  62. package/dist/exports/shared.js +1 -0
  63. package/dist/exports/shared.js.map +1 -1
  64. package/dist/fields/baseFields/slug/index.d.ts +7 -0
  65. package/dist/fields/baseFields/slug/index.d.ts.map +1 -1
  66. package/dist/fields/baseFields/slug/index.js +2 -2
  67. package/dist/fields/baseFields/slug/index.js.map +1 -1
  68. package/dist/fields/validations.js +1 -1
  69. package/dist/fields/validations.js.map +1 -1
  70. package/dist/fields/validations.spec.js +25 -0
  71. package/dist/fields/validations.spec.js.map +1 -1
  72. package/dist/globals/operations/update.d.ts.map +1 -1
  73. package/dist/globals/operations/update.js +2 -1
  74. package/dist/globals/operations/update.js.map +1 -1
  75. package/dist/index.bundled.d.ts +16 -13
  76. package/dist/index.d.ts +1 -7
  77. package/dist/index.d.ts.map +1 -1
  78. package/dist/index.js +1 -10
  79. package/dist/index.js.map +1 -1
  80. package/dist/queues/localAPI.d.ts +0 -1
  81. package/dist/queues/localAPI.d.ts.map +1 -1
  82. package/dist/queues/localAPI.js +0 -4
  83. package/dist/queues/localAPI.js.map +1 -1
  84. package/dist/queues/operations/runJobs/index.d.ts +0 -1
  85. package/dist/queues/operations/runJobs/index.d.ts.map +1 -1
  86. package/dist/queues/operations/runJobs/index.js +1 -96
  87. package/dist/queues/operations/runJobs/index.js.map +1 -1
  88. package/dist/queues/utilities/updateJob.d.ts +1 -2
  89. package/dist/queues/utilities/updateJob.d.ts.map +1 -1
  90. package/dist/queues/utilities/updateJob.js +31 -92
  91. package/dist/queues/utilities/updateJob.js.map +1 -1
  92. package/dist/types/constants.d.ts +5 -0
  93. package/dist/types/constants.d.ts.map +1 -1
  94. package/dist/types/constants.js +4 -0
  95. package/dist/types/constants.js.map +1 -1
  96. package/dist/uploads/endpoints/getFile.d.ts.map +1 -1
  97. package/dist/uploads/endpoints/getFile.js +7 -1
  98. package/dist/uploads/endpoints/getFile.js.map +1 -1
  99. package/dist/uploads/endpoints/getFileFromURL.d.ts.map +1 -1
  100. package/dist/uploads/endpoints/getFileFromURL.js +67 -28
  101. package/dist/uploads/endpoints/getFileFromURL.js.map +1 -1
  102. package/dist/uploads/getExternalFile.d.ts.map +1 -1
  103. package/dist/uploads/getExternalFile.js +3 -0
  104. package/dist/uploads/getExternalFile.js.map +1 -1
  105. package/dist/uploads/safeFetch.d.ts +1 -1
  106. package/dist/uploads/safeFetch.d.ts.map +1 -1
  107. package/dist/uploads/safeFetch.js.map +1 -1
  108. package/dist/utilities/addDataAndFileToRequest.d.ts.map +1 -1
  109. package/dist/utilities/addDataAndFileToRequest.js +7 -1
  110. package/dist/utilities/addDataAndFileToRequest.js.map +1 -1
  111. package/dist/utilities/configToJSONSchema.d.ts +7 -3
  112. package/dist/utilities/configToJSONSchema.d.ts.map +1 -1
  113. package/dist/utilities/configToJSONSchema.js +23 -33
  114. package/dist/utilities/configToJSONSchema.js.map +1 -1
  115. package/dist/utilities/configToJSONSchema.spec.js +75 -1
  116. package/dist/utilities/configToJSONSchema.spec.js.map +1 -1
  117. package/dist/utilities/getRequestOrigin.d.ts +10 -0
  118. package/dist/utilities/getRequestOrigin.d.ts.map +1 -0
  119. package/dist/utilities/getRequestOrigin.js +50 -0
  120. package/dist/utilities/getRequestOrigin.js.map +1 -0
  121. package/dist/utilities/getRequestOrigin.spec.js +151 -0
  122. package/dist/utilities/getRequestOrigin.spec.js.map +1 -0
  123. package/dist/utilities/sanitizeUrl.d.ts +7 -0
  124. package/dist/utilities/sanitizeUrl.d.ts.map +1 -0
  125. package/dist/utilities/sanitizeUrl.js +28 -0
  126. package/dist/utilities/sanitizeUrl.js.map +1 -0
  127. package/dist/versions/saveVersion.d.ts +1 -0
  128. package/dist/versions/saveVersion.d.ts.map +1 -1
  129. package/dist/versions/saveVersion.js +16 -66
  130. package/dist/versions/saveVersion.js.map +1 -1
  131. package/dist/versions/updateLatestVersion.d.ts +24 -0
  132. package/dist/versions/updateLatestVersion.d.ts.map +1 -0
  133. package/dist/versions/updateLatestVersion.js +64 -0
  134. package/dist/versions/updateLatestVersion.js.map +1 -0
  135. package/package.json +4 -4
@@ -471,7 +471,9 @@ describe('configToJSONSchema', ()=>{
471
471
  ],
472
472
  items: {
473
473
  oneOf: [
474
- expectedBlockSchema
474
+ {
475
+ $ref: '#/definitions/SharedBlock'
476
+ }
475
477
  ]
476
478
  }
477
479
  }
@@ -512,6 +514,78 @@ describe('configToJSONSchema', ()=>{
512
514
  // @ts-expect-error
513
515
  expect(schema.definitions.test.properties.title.required).toStrictEqual(false);
514
516
  });
517
+ it('should propagate forceInlineBlocks to nested fields (array, group, tab)', async ()=>{
518
+ const namedBlock = {
519
+ slug: 'myBlock',
520
+ interfaceName: 'MyBlock',
521
+ fields: [
522
+ {
523
+ name: 'text',
524
+ type: 'text'
525
+ }
526
+ ]
527
+ };
528
+ // @ts-expect-error
529
+ const config = {
530
+ collections: [
531
+ {
532
+ slug: 'test',
533
+ fields: [
534
+ {
535
+ name: 'arr',
536
+ type: 'array',
537
+ fields: [
538
+ {
539
+ name: 'blocks',
540
+ type: 'blocks',
541
+ blocks: [
542
+ namedBlock
543
+ ]
544
+ }
545
+ ]
546
+ },
547
+ {
548
+ name: 'grp',
549
+ type: 'group',
550
+ fields: [
551
+ {
552
+ name: 'blocks',
553
+ type: 'blocks',
554
+ blocks: [
555
+ namedBlock
556
+ ]
557
+ }
558
+ ]
559
+ }
560
+ ],
561
+ timestamps: false
562
+ }
563
+ ]
564
+ };
565
+ const sanitizedConfig = await sanitizeConfig(config);
566
+ // Without forceInlineBlocks: blocks field uses $ref
567
+ const schemaDefault = configToJSONSchema(sanitizedConfig, 'text');
568
+ const arrItemsDefault = schemaDefault.definitions.test.properties.arr.items;
569
+ const arrBlocksDefault = arrItemsDefault.properties.blocks.items.oneOf[0];
570
+ expect(arrBlocksDefault).toStrictEqual({
571
+ $ref: '#/definitions/MyBlock'
572
+ });
573
+ // With forceInlineBlocks: blocks field is inlined, no $ref
574
+ const schemaInline = configToJSONSchema(sanitizedConfig, 'text', undefined, {
575
+ forceInlineBlocks: true
576
+ });
577
+ const arrItemsInline = schemaInline.definitions.test.properties.arr.items;
578
+ const arrBlocksInline = arrItemsInline.properties.blocks.items.oneOf[0];
579
+ expect(arrBlocksInline).not.toHaveProperty('$ref');
580
+ expect(arrBlocksInline.properties?.blockType).toStrictEqual({
581
+ const: 'myBlock'
582
+ });
583
+ const grpBlocksInline = schemaInline.definitions.test.properties.grp.properties.blocks.items.oneOf[0];
584
+ expect(grpBlocksInline).not.toHaveProperty('$ref');
585
+ expect(grpBlocksInline.properties?.blockType).toStrictEqual({
586
+ const: 'myBlock'
587
+ });
588
+ });
515
589
  });
516
590
 
517
591
  //# sourceMappingURL=configToJSONSchema.spec.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/utilities/configToJSONSchema.spec.ts"],"sourcesContent":["import type { JSONSchema4 } from 'json-schema'\nimport { describe, it, expect } from 'vitest'\n\nimport type { Config } from '../config/types.js'\n\nimport { sanitizeConfig } from '../config/sanitize.js'\nimport { configToJSONSchema } from './configToJSONSchema.js'\nimport type { Block, BlocksField, RichTextField } from '../fields/config/types.js'\n\ndescribe('configToJSONSchema', () => {\n it('should handle optional arrays with required fields', async () => {\n // @ts-expect-error\n const config: Config = {\n collections: [\n {\n slug: 'test',\n fields: [\n {\n name: 'someRequiredField',\n type: 'array',\n fields: [\n {\n name: 'someRequiredField',\n type: 'text',\n required: true,\n },\n ],\n },\n ],\n timestamps: false,\n },\n ],\n }\n\n const sanitizedConfig = await sanitizeConfig(config)\n const schema = configToJSONSchema(sanitizedConfig, 'text')\n\n expect(schema?.definitions?.test).toStrictEqual({\n type: 'object',\n additionalProperties: false,\n properties: {\n id: {\n type: 'string',\n },\n someRequiredField: {\n type: ['array', 'null'],\n items: {\n type: 'object',\n additionalProperties: false,\n properties: {\n id: {\n type: ['string', 'null'],\n },\n someRequiredField: {\n type: 'string',\n },\n },\n required: ['someRequiredField'],\n },\n },\n },\n required: ['id'],\n title: 'Test',\n })\n })\n\n it('should handle block fields with no blocks', async () => {\n // @ts-expect-error\n const config: Config = {\n collections: [\n {\n slug: 'test',\n fields: [\n {\n name: 'blockField',\n type: 'blocks',\n blocks: [],\n },\n {\n name: 'blockFieldRequired',\n type: 'blocks',\n blocks: [],\n required: true,\n },\n {\n name: 'blockFieldWithFields',\n type: 'blocks',\n blocks: [\n {\n slug: 'test',\n fields: [\n {\n name: 'field',\n type: 'text',\n },\n ],\n },\n ],\n },\n {\n name: 'blockFieldWithFieldsRequired',\n type: 'blocks',\n blocks: [\n {\n slug: 'test',\n fields: [\n {\n name: 'field',\n type: 'text',\n required: true,\n },\n ],\n },\n ],\n },\n ],\n timestamps: false,\n },\n ],\n }\n\n const sanitizedConfig = await sanitizeConfig(config)\n const schema = configToJSONSchema(sanitizedConfig, 'text')\n\n expect(schema?.definitions?.test).toStrictEqual({\n type: 'object',\n additionalProperties: false,\n properties: {\n id: {\n type: 'string',\n },\n blockField: {\n type: ['array', 'null'],\n items: {},\n },\n blockFieldRequired: {\n type: 'array',\n items: {},\n },\n blockFieldWithFields: {\n type: ['array', 'null'],\n items: {\n oneOf: [\n {\n type: 'object',\n additionalProperties: false,\n properties: {\n id: {\n type: ['string', 'null'],\n },\n blockName: {\n type: ['string', 'null'],\n },\n blockType: {\n const: 'test',\n },\n field: {\n type: ['string', 'null'],\n },\n },\n required: ['blockType'],\n },\n ],\n },\n },\n blockFieldWithFieldsRequired: {\n type: ['array', 'null'],\n items: {\n oneOf: [\n {\n type: 'object',\n additionalProperties: false,\n properties: {\n id: {\n type: ['string', 'null'],\n },\n blockName: {\n type: ['string', 'null'],\n },\n blockType: {\n const: 'test',\n },\n field: {\n type: 'string',\n },\n },\n required: ['blockType', 'field'],\n },\n ],\n },\n },\n },\n required: ['id', 'blockFieldRequired'],\n title: 'Test',\n })\n })\n\n it('should handle tabs and named tabs with required fields', async () => {\n // @ts-expect-error\n const config: Config = {\n collections: [\n {\n slug: 'test',\n fields: [\n {\n type: 'tabs',\n tabs: [\n {\n fields: [\n {\n name: 'fieldInUnnamedTab',\n type: 'text',\n },\n ],\n label: 'unnamedTab',\n },\n {\n name: 'namedTab',\n fields: [\n {\n name: 'fieldInNamedTab',\n type: 'text',\n },\n ],\n label: 'namedTab',\n },\n {\n name: 'namedTabWithRequired',\n fields: [\n {\n name: 'fieldInNamedTab',\n type: 'text',\n required: true,\n },\n ],\n label: 'namedTabWithRequired',\n },\n ],\n },\n ],\n timestamps: false,\n },\n ],\n }\n\n const sanitizedConfig = await sanitizeConfig(config)\n const schema = configToJSONSchema(sanitizedConfig, 'text')\n\n expect(schema?.definitions?.test).toStrictEqual({\n type: 'object',\n additionalProperties: false,\n properties: {\n id: {\n type: 'string',\n },\n fieldInUnnamedTab: {\n type: ['string', 'null'],\n },\n namedTab: {\n type: 'object',\n additionalProperties: false,\n properties: {\n fieldInNamedTab: {\n type: ['string', 'null'],\n },\n },\n required: [],\n },\n namedTabWithRequired: {\n type: 'object',\n additionalProperties: false,\n properties: {\n fieldInNamedTab: {\n type: 'string',\n },\n },\n required: ['fieldInNamedTab'],\n },\n },\n required: ['id', 'namedTabWithRequired'],\n title: 'Test',\n })\n })\n\n it('should handle custom typescript schema and JSON field schema', async () => {\n const customSchema: JSONSchema4 = {\n type: 'object',\n properties: {\n id: {\n type: 'number',\n },\n required: ['id'],\n },\n }\n\n const config: Partial<Config> = {\n collections: [\n {\n slug: 'test',\n fields: [\n {\n name: 'withCustom',\n type: 'text',\n typescriptSchema: [() => customSchema],\n },\n {\n name: 'jsonWithSchema',\n type: 'json',\n jsonSchema: {\n fileMatch: ['a://b/foo.json'],\n schema: customSchema,\n uri: 'a://b/foo.json',\n },\n },\n ],\n timestamps: false,\n },\n ],\n }\n\n const sanitizedConfig = await sanitizeConfig(config as Config)\n const schema = configToJSONSchema(sanitizedConfig, 'text')\n\n expect(schema?.definitions?.test).toStrictEqual({\n type: 'object',\n additionalProperties: false,\n properties: {\n id: {\n type: 'string',\n },\n jsonWithSchema: customSchema,\n withCustom: customSchema,\n },\n required: ['id'],\n title: 'Test',\n })\n })\n\n it('should handle same block object being referenced in both collection and config.blocks', async () => {\n const sharedBlock: Block = {\n slug: 'sharedBlock',\n interfaceName: 'SharedBlock',\n fields: [\n {\n name: 'richText',\n type: 'richText',\n editor: () => {\n // stub rich text editor\n return {\n CellComponent: '',\n FieldComponent: '',\n validate: () => true,\n }\n },\n },\n ],\n }\n\n // @ts-expect-error\n const config: Config = {\n blocks: [sharedBlock],\n collections: [\n {\n slug: 'test',\n fields: [\n {\n name: 'someBlockField',\n type: 'blocks',\n blocks: [sharedBlock],\n },\n ],\n timestamps: false,\n },\n ],\n }\n\n // Ensure both rich text editor are sanitized\n const sanitizedConfig = await sanitizeConfig(config)\n expect(typeof (sanitizedConfig?.blocks?.[0]?.fields?.[0] as RichTextField)?.editor).toBe(\n 'object',\n )\n expect(\n typeof (\n (sanitizedConfig.collections[0].fields[0] as BlocksField)?.blocks?.[0]\n ?.fields?.[0] as RichTextField\n )?.editor,\n ).toBe('object')\n\n const schema = configToJSONSchema(sanitizedConfig, 'text')\n\n const expectedBlockSchema = {\n type: 'object',\n additionalProperties: false,\n properties: {\n id: { type: ['string', 'null'] },\n blockName: { type: ['string', 'null'] },\n blockType: { const: 'sharedBlock' },\n richText: { type: ['array', 'null'], items: { type: 'object' } },\n },\n required: ['blockType'],\n }\n\n expect(schema?.definitions?.test).toStrictEqual({\n type: 'object',\n additionalProperties: false,\n title: 'Test',\n properties: {\n id: {\n type: 'string',\n },\n someBlockField: {\n type: ['array', 'null'],\n items: {\n oneOf: [expectedBlockSchema],\n },\n },\n },\n required: ['id'],\n })\n\n // The definition should still be registered for TypeScript type generation\n expect(schema?.definitions?.SharedBlock).toStrictEqual(expectedBlockSchema)\n })\n\n it('should allow overriding required to false', async () => {\n // @ts-expect-error\n const config: Config = {\n collections: [\n {\n slug: 'test',\n fields: [\n {\n name: 'title',\n type: 'text',\n required: true,\n defaultValue: 'test',\n typescriptSchema: [\n () => ({\n type: 'string',\n required: false,\n }),\n ],\n },\n ],\n timestamps: false,\n },\n ],\n }\n\n const sanitizedConfig = await sanitizeConfig(config)\n const schema = configToJSONSchema(sanitizedConfig, 'text')\n\n // @ts-expect-error\n expect(schema.definitions.test.properties.title.required).toStrictEqual(false)\n })\n})\n"],"names":["describe","it","expect","sanitizeConfig","configToJSONSchema","config","collections","slug","fields","name","type","required","timestamps","sanitizedConfig","schema","definitions","test","toStrictEqual","additionalProperties","properties","id","someRequiredField","items","title","blocks","blockField","blockFieldRequired","blockFieldWithFields","oneOf","blockName","blockType","const","field","blockFieldWithFieldsRequired","tabs","label","fieldInUnnamedTab","namedTab","fieldInNamedTab","namedTabWithRequired","customSchema","typescriptSchema","jsonSchema","fileMatch","uri","jsonWithSchema","withCustom","sharedBlock","interfaceName","editor","CellComponent","FieldComponent","validate","toBe","expectedBlockSchema","richText","someBlockField","SharedBlock","defaultValue"],"mappings":"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"}
1
+ {"version":3,"sources":["../../src/utilities/configToJSONSchema.spec.ts"],"sourcesContent":["import type { JSONSchema4 } from 'json-schema'\nimport { describe, it, expect } from 'vitest'\n\nimport type { Config } from '../config/types.js'\n\nimport { sanitizeConfig } from '../config/sanitize.js'\nimport { configToJSONSchema } from './configToJSONSchema.js'\nimport type { Block, BlocksField, RichTextField } from '../fields/config/types.js'\n\ndescribe('configToJSONSchema', () => {\n it('should handle optional arrays with required fields', async () => {\n // @ts-expect-error\n const config: Config = {\n collections: [\n {\n slug: 'test',\n fields: [\n {\n name: 'someRequiredField',\n type: 'array',\n fields: [\n {\n name: 'someRequiredField',\n type: 'text',\n required: true,\n },\n ],\n },\n ],\n timestamps: false,\n },\n ],\n }\n\n const sanitizedConfig = await sanitizeConfig(config)\n const schema = configToJSONSchema(sanitizedConfig, 'text')\n\n expect(schema?.definitions?.test).toStrictEqual({\n type: 'object',\n additionalProperties: false,\n properties: {\n id: {\n type: 'string',\n },\n someRequiredField: {\n type: ['array', 'null'],\n items: {\n type: 'object',\n additionalProperties: false,\n properties: {\n id: {\n type: ['string', 'null'],\n },\n someRequiredField: {\n type: 'string',\n },\n },\n required: ['someRequiredField'],\n },\n },\n },\n required: ['id'],\n title: 'Test',\n })\n })\n\n it('should handle block fields with no blocks', async () => {\n // @ts-expect-error\n const config: Config = {\n collections: [\n {\n slug: 'test',\n fields: [\n {\n name: 'blockField',\n type: 'blocks',\n blocks: [],\n },\n {\n name: 'blockFieldRequired',\n type: 'blocks',\n blocks: [],\n required: true,\n },\n {\n name: 'blockFieldWithFields',\n type: 'blocks',\n blocks: [\n {\n slug: 'test',\n fields: [\n {\n name: 'field',\n type: 'text',\n },\n ],\n },\n ],\n },\n {\n name: 'blockFieldWithFieldsRequired',\n type: 'blocks',\n blocks: [\n {\n slug: 'test',\n fields: [\n {\n name: 'field',\n type: 'text',\n required: true,\n },\n ],\n },\n ],\n },\n ],\n timestamps: false,\n },\n ],\n }\n\n const sanitizedConfig = await sanitizeConfig(config)\n const schema = configToJSONSchema(sanitizedConfig, 'text')\n\n expect(schema?.definitions?.test).toStrictEqual({\n type: 'object',\n additionalProperties: false,\n properties: {\n id: {\n type: 'string',\n },\n blockField: {\n type: ['array', 'null'],\n items: {},\n },\n blockFieldRequired: {\n type: 'array',\n items: {},\n },\n blockFieldWithFields: {\n type: ['array', 'null'],\n items: {\n oneOf: [\n {\n type: 'object',\n additionalProperties: false,\n properties: {\n id: {\n type: ['string', 'null'],\n },\n blockName: {\n type: ['string', 'null'],\n },\n blockType: {\n const: 'test',\n },\n field: {\n type: ['string', 'null'],\n },\n },\n required: ['blockType'],\n },\n ],\n },\n },\n blockFieldWithFieldsRequired: {\n type: ['array', 'null'],\n items: {\n oneOf: [\n {\n type: 'object',\n additionalProperties: false,\n properties: {\n id: {\n type: ['string', 'null'],\n },\n blockName: {\n type: ['string', 'null'],\n },\n blockType: {\n const: 'test',\n },\n field: {\n type: 'string',\n },\n },\n required: ['blockType', 'field'],\n },\n ],\n },\n },\n },\n required: ['id', 'blockFieldRequired'],\n title: 'Test',\n })\n })\n\n it('should handle tabs and named tabs with required fields', async () => {\n // @ts-expect-error\n const config: Config = {\n collections: [\n {\n slug: 'test',\n fields: [\n {\n type: 'tabs',\n tabs: [\n {\n fields: [\n {\n name: 'fieldInUnnamedTab',\n type: 'text',\n },\n ],\n label: 'unnamedTab',\n },\n {\n name: 'namedTab',\n fields: [\n {\n name: 'fieldInNamedTab',\n type: 'text',\n },\n ],\n label: 'namedTab',\n },\n {\n name: 'namedTabWithRequired',\n fields: [\n {\n name: 'fieldInNamedTab',\n type: 'text',\n required: true,\n },\n ],\n label: 'namedTabWithRequired',\n },\n ],\n },\n ],\n timestamps: false,\n },\n ],\n }\n\n const sanitizedConfig = await sanitizeConfig(config)\n const schema = configToJSONSchema(sanitizedConfig, 'text')\n\n expect(schema?.definitions?.test).toStrictEqual({\n type: 'object',\n additionalProperties: false,\n properties: {\n id: {\n type: 'string',\n },\n fieldInUnnamedTab: {\n type: ['string', 'null'],\n },\n namedTab: {\n type: 'object',\n additionalProperties: false,\n properties: {\n fieldInNamedTab: {\n type: ['string', 'null'],\n },\n },\n required: [],\n },\n namedTabWithRequired: {\n type: 'object',\n additionalProperties: false,\n properties: {\n fieldInNamedTab: {\n type: 'string',\n },\n },\n required: ['fieldInNamedTab'],\n },\n },\n required: ['id', 'namedTabWithRequired'],\n title: 'Test',\n })\n })\n\n it('should handle custom typescript schema and JSON field schema', async () => {\n const customSchema: JSONSchema4 = {\n type: 'object',\n properties: {\n id: {\n type: 'number',\n },\n required: ['id'],\n },\n }\n\n const config: Partial<Config> = {\n collections: [\n {\n slug: 'test',\n fields: [\n {\n name: 'withCustom',\n type: 'text',\n typescriptSchema: [() => customSchema],\n },\n {\n name: 'jsonWithSchema',\n type: 'json',\n jsonSchema: {\n fileMatch: ['a://b/foo.json'],\n schema: customSchema,\n uri: 'a://b/foo.json',\n },\n },\n ],\n timestamps: false,\n },\n ],\n }\n\n const sanitizedConfig = await sanitizeConfig(config as Config)\n const schema = configToJSONSchema(sanitizedConfig, 'text')\n\n expect(schema?.definitions?.test).toStrictEqual({\n type: 'object',\n additionalProperties: false,\n properties: {\n id: {\n type: 'string',\n },\n jsonWithSchema: customSchema,\n withCustom: customSchema,\n },\n required: ['id'],\n title: 'Test',\n })\n })\n\n it('should handle same block object being referenced in both collection and config.blocks', async () => {\n const sharedBlock: Block = {\n slug: 'sharedBlock',\n interfaceName: 'SharedBlock',\n fields: [\n {\n name: 'richText',\n type: 'richText',\n editor: () => {\n // stub rich text editor\n return {\n CellComponent: '',\n FieldComponent: '',\n validate: () => true,\n }\n },\n },\n ],\n }\n\n // @ts-expect-error\n const config: Config = {\n blocks: [sharedBlock],\n collections: [\n {\n slug: 'test',\n fields: [\n {\n name: 'someBlockField',\n type: 'blocks',\n blocks: [sharedBlock],\n },\n ],\n timestamps: false,\n },\n ],\n }\n\n // Ensure both rich text editor are sanitized\n const sanitizedConfig = await sanitizeConfig(config)\n expect(typeof (sanitizedConfig?.blocks?.[0]?.fields?.[0] as RichTextField)?.editor).toBe(\n 'object',\n )\n expect(\n typeof (\n (sanitizedConfig.collections[0].fields[0] as BlocksField)?.blocks?.[0]\n ?.fields?.[0] as RichTextField\n )?.editor,\n ).toBe('object')\n\n const schema = configToJSONSchema(sanitizedConfig, 'text')\n\n const expectedBlockSchema = {\n type: 'object',\n additionalProperties: false,\n properties: {\n id: { type: ['string', 'null'] },\n blockName: { type: ['string', 'null'] },\n blockType: { const: 'sharedBlock' },\n richText: { type: ['array', 'null'], items: { type: 'object' } },\n },\n required: ['blockType'],\n }\n\n expect(schema?.definitions?.test).toStrictEqual({\n type: 'object',\n additionalProperties: false,\n title: 'Test',\n properties: {\n id: {\n type: 'string',\n },\n someBlockField: {\n type: ['array', 'null'],\n items: {\n oneOf: [\n {\n $ref: '#/definitions/SharedBlock',\n },\n ],\n },\n },\n },\n required: ['id'],\n })\n\n // The definition should still be registered for TypeScript type generation\n expect(schema?.definitions?.SharedBlock).toStrictEqual(expectedBlockSchema)\n })\n\n it('should allow overriding required to false', async () => {\n // @ts-expect-error\n const config: Config = {\n collections: [\n {\n slug: 'test',\n fields: [\n {\n name: 'title',\n type: 'text',\n required: true,\n defaultValue: 'test',\n typescriptSchema: [\n () => ({\n type: 'string',\n required: false,\n }),\n ],\n },\n ],\n timestamps: false,\n },\n ],\n }\n\n const sanitizedConfig = await sanitizeConfig(config)\n const schema = configToJSONSchema(sanitizedConfig, 'text')\n\n // @ts-expect-error\n expect(schema.definitions.test.properties.title.required).toStrictEqual(false)\n })\n\n it('should propagate forceInlineBlocks to nested fields (array, group, tab)', async () => {\n const namedBlock: Block = {\n slug: 'myBlock',\n interfaceName: 'MyBlock',\n fields: [{ name: 'text', type: 'text' }],\n }\n\n // @ts-expect-error\n const config: Config = {\n collections: [\n {\n slug: 'test',\n fields: [\n {\n name: 'arr',\n type: 'array',\n fields: [{ name: 'blocks', type: 'blocks', blocks: [namedBlock] }],\n },\n {\n name: 'grp',\n type: 'group',\n fields: [{ name: 'blocks', type: 'blocks', blocks: [namedBlock] }],\n },\n ],\n timestamps: false,\n },\n ],\n }\n\n const sanitizedConfig = await sanitizeConfig(config)\n\n // Without forceInlineBlocks: blocks field uses $ref\n const schemaDefault = configToJSONSchema(sanitizedConfig, 'text')\n const arrItemsDefault = schemaDefault.definitions!.test.properties!.arr.items as JSONSchema4\n const arrBlocksDefault = (arrItemsDefault.properties!.blocks.items as JSONSchema4).oneOf![0]\n expect(arrBlocksDefault).toStrictEqual({ $ref: '#/definitions/MyBlock' })\n\n // With forceInlineBlocks: blocks field is inlined, no $ref\n const schemaInline = configToJSONSchema(sanitizedConfig, 'text', undefined, {\n forceInlineBlocks: true,\n })\n const arrItemsInline = schemaInline.definitions!.test.properties!.arr.items as JSONSchema4\n const arrBlocksInline = (arrItemsInline.properties!.blocks.items as JSONSchema4).oneOf![0]\n expect(arrBlocksInline).not.toHaveProperty('$ref')\n expect(arrBlocksInline.properties?.blockType).toStrictEqual({ const: 'myBlock' })\n\n const grpBlocksInline = (\n schemaInline.definitions!.test.properties!.grp.properties!.blocks.items as JSONSchema4\n ).oneOf![0]\n expect(grpBlocksInline).not.toHaveProperty('$ref')\n expect(grpBlocksInline.properties?.blockType).toStrictEqual({ const: 'myBlock' })\n })\n})\n"],"names":["describe","it","expect","sanitizeConfig","configToJSONSchema","config","collections","slug","fields","name","type","required","timestamps","sanitizedConfig","schema","definitions","test","toStrictEqual","additionalProperties","properties","id","someRequiredField","items","title","blocks","blockField","blockFieldRequired","blockFieldWithFields","oneOf","blockName","blockType","const","field","blockFieldWithFieldsRequired","tabs","label","fieldInUnnamedTab","namedTab","fieldInNamedTab","namedTabWithRequired","customSchema","typescriptSchema","jsonSchema","fileMatch","uri","jsonWithSchema","withCustom","sharedBlock","interfaceName","editor","CellComponent","FieldComponent","validate","toBe","expectedBlockSchema","richText","someBlockField","$ref","SharedBlock","defaultValue","namedBlock","schemaDefault","arrItemsDefault","arr","arrBlocksDefault","schemaInline","undefined","forceInlineBlocks","arrItemsInline","arrBlocksInline","not","toHaveProperty","grpBlocksInline","grp"],"mappings":"AACA,SAASA,QAAQ,EAAEC,EAAE,EAAEC,MAAM,QAAQ,SAAQ;AAI7C,SAASC,cAAc,QAAQ,wBAAuB;AACtD,SAASC,kBAAkB,QAAQ,0BAAyB;AAG5DJ,SAAS,sBAAsB;IAC7BC,GAAG,sDAAsD;QACvD,mBAAmB;QACnB,MAAMI,SAAiB;YACrBC,aAAa;gBACX;oBACEC,MAAM;oBACNC,QAAQ;wBACN;4BACEC,MAAM;4BACNC,MAAM;4BACNF,QAAQ;gCACN;oCACEC,MAAM;oCACNC,MAAM;oCACNC,UAAU;gCACZ;6BACD;wBACH;qBACD;oBACDC,YAAY;gBACd;aACD;QACH;QAEA,MAAMC,kBAAkB,MAAMV,eAAeE;QAC7C,MAAMS,SAASV,mBAAmBS,iBAAiB;QAEnDX,OAAOY,QAAQC,aAAaC,MAAMC,aAAa,CAAC;YAC9CP,MAAM;YACNQ,sBAAsB;YACtBC,YAAY;gBACVC,IAAI;oBACFV,MAAM;gBACR;gBACAW,mBAAmB;oBACjBX,MAAM;wBAAC;wBAAS;qBAAO;oBACvBY,OAAO;wBACLZ,MAAM;wBACNQ,sBAAsB;wBACtBC,YAAY;4BACVC,IAAI;gCACFV,MAAM;oCAAC;oCAAU;iCAAO;4BAC1B;4BACAW,mBAAmB;gCACjBX,MAAM;4BACR;wBACF;wBACAC,UAAU;4BAAC;yBAAoB;oBACjC;gBACF;YACF;YACAA,UAAU;gBAAC;aAAK;YAChBY,OAAO;QACT;IACF;IAEAtB,GAAG,6CAA6C;QAC9C,mBAAmB;QACnB,MAAMI,SAAiB;YACrBC,aAAa;gBACX;oBACEC,MAAM;oBACNC,QAAQ;wBACN;4BACEC,MAAM;4BACNC,MAAM;4BACNc,QAAQ,EAAE;wBACZ;wBACA;4BACEf,MAAM;4BACNC,MAAM;4BACNc,QAAQ,EAAE;4BACVb,UAAU;wBACZ;wBACA;4BACEF,MAAM;4BACNC,MAAM;4BACNc,QAAQ;gCACN;oCACEjB,MAAM;oCACNC,QAAQ;wCACN;4CACEC,MAAM;4CACNC,MAAM;wCACR;qCACD;gCACH;6BACD;wBACH;wBACA;4BACED,MAAM;4BACNC,MAAM;4BACNc,QAAQ;gCACN;oCACEjB,MAAM;oCACNC,QAAQ;wCACN;4CACEC,MAAM;4CACNC,MAAM;4CACNC,UAAU;wCACZ;qCACD;gCACH;6BACD;wBACH;qBACD;oBACDC,YAAY;gBACd;aACD;QACH;QAEA,MAAMC,kBAAkB,MAAMV,eAAeE;QAC7C,MAAMS,SAASV,mBAAmBS,iBAAiB;QAEnDX,OAAOY,QAAQC,aAAaC,MAAMC,aAAa,CAAC;YAC9CP,MAAM;YACNQ,sBAAsB;YACtBC,YAAY;gBACVC,IAAI;oBACFV,MAAM;gBACR;gBACAe,YAAY;oBACVf,MAAM;wBAAC;wBAAS;qBAAO;oBACvBY,OAAO,CAAC;gBACV;gBACAI,oBAAoB;oBAClBhB,MAAM;oBACNY,OAAO,CAAC;gBACV;gBACAK,sBAAsB;oBACpBjB,MAAM;wBAAC;wBAAS;qBAAO;oBACvBY,OAAO;wBACLM,OAAO;4BACL;gCACElB,MAAM;gCACNQ,sBAAsB;gCACtBC,YAAY;oCACVC,IAAI;wCACFV,MAAM;4CAAC;4CAAU;yCAAO;oCAC1B;oCACAmB,WAAW;wCACTnB,MAAM;4CAAC;4CAAU;yCAAO;oCAC1B;oCACAoB,WAAW;wCACTC,OAAO;oCACT;oCACAC,OAAO;wCACLtB,MAAM;4CAAC;4CAAU;yCAAO;oCAC1B;gCACF;gCACAC,UAAU;oCAAC;iCAAY;4BACzB;yBACD;oBACH;gBACF;gBACAsB,8BAA8B;oBAC5BvB,MAAM;wBAAC;wBAAS;qBAAO;oBACvBY,OAAO;wBACLM,OAAO;4BACL;gCACElB,MAAM;gCACNQ,sBAAsB;gCACtBC,YAAY;oCACVC,IAAI;wCACFV,MAAM;4CAAC;4CAAU;yCAAO;oCAC1B;oCACAmB,WAAW;wCACTnB,MAAM;4CAAC;4CAAU;yCAAO;oCAC1B;oCACAoB,WAAW;wCACTC,OAAO;oCACT;oCACAC,OAAO;wCACLtB,MAAM;oCACR;gCACF;gCACAC,UAAU;oCAAC;oCAAa;iCAAQ;4BAClC;yBACD;oBACH;gBACF;YACF;YACAA,UAAU;gBAAC;gBAAM;aAAqB;YACtCY,OAAO;QACT;IACF;IAEAtB,GAAG,0DAA0D;QAC3D,mBAAmB;QACnB,MAAMI,SAAiB;YACrBC,aAAa;gBACX;oBACEC,MAAM;oBACNC,QAAQ;wBACN;4BACEE,MAAM;4BACNwB,MAAM;gCACJ;oCACE1B,QAAQ;wCACN;4CACEC,MAAM;4CACNC,MAAM;wCACR;qCACD;oCACDyB,OAAO;gCACT;gCACA;oCACE1B,MAAM;oCACND,QAAQ;wCACN;4CACEC,MAAM;4CACNC,MAAM;wCACR;qCACD;oCACDyB,OAAO;gCACT;gCACA;oCACE1B,MAAM;oCACND,QAAQ;wCACN;4CACEC,MAAM;4CACNC,MAAM;4CACNC,UAAU;wCACZ;qCACD;oCACDwB,OAAO;gCACT;6BACD;wBACH;qBACD;oBACDvB,YAAY;gBACd;aACD;QACH;QAEA,MAAMC,kBAAkB,MAAMV,eAAeE;QAC7C,MAAMS,SAASV,mBAAmBS,iBAAiB;QAEnDX,OAAOY,QAAQC,aAAaC,MAAMC,aAAa,CAAC;YAC9CP,MAAM;YACNQ,sBAAsB;YACtBC,YAAY;gBACVC,IAAI;oBACFV,MAAM;gBACR;gBACA0B,mBAAmB;oBACjB1B,MAAM;wBAAC;wBAAU;qBAAO;gBAC1B;gBACA2B,UAAU;oBACR3B,MAAM;oBACNQ,sBAAsB;oBACtBC,YAAY;wBACVmB,iBAAiB;4BACf5B,MAAM;gCAAC;gCAAU;6BAAO;wBAC1B;oBACF;oBACAC,UAAU,EAAE;gBACd;gBACA4B,sBAAsB;oBACpB7B,MAAM;oBACNQ,sBAAsB;oBACtBC,YAAY;wBACVmB,iBAAiB;4BACf5B,MAAM;wBACR;oBACF;oBACAC,UAAU;wBAAC;qBAAkB;gBAC/B;YACF;YACAA,UAAU;gBAAC;gBAAM;aAAuB;YACxCY,OAAO;QACT;IACF;IAEAtB,GAAG,gEAAgE;QACjE,MAAMuC,eAA4B;YAChC9B,MAAM;YACNS,YAAY;gBACVC,IAAI;oBACFV,MAAM;gBACR;gBACAC,UAAU;oBAAC;iBAAK;YAClB;QACF;QAEA,MAAMN,SAA0B;YAC9BC,aAAa;gBACX;oBACEC,MAAM;oBACNC,QAAQ;wBACN;4BACEC,MAAM;4BACNC,MAAM;4BACN+B,kBAAkB;gCAAC,IAAMD;6BAAa;wBACxC;wBACA;4BACE/B,MAAM;4BACNC,MAAM;4BACNgC,YAAY;gCACVC,WAAW;oCAAC;iCAAiB;gCAC7B7B,QAAQ0B;gCACRI,KAAK;4BACP;wBACF;qBACD;oBACDhC,YAAY;gBACd;aACD;QACH;QAEA,MAAMC,kBAAkB,MAAMV,eAAeE;QAC7C,MAAMS,SAASV,mBAAmBS,iBAAiB;QAEnDX,OAAOY,QAAQC,aAAaC,MAAMC,aAAa,CAAC;YAC9CP,MAAM;YACNQ,sBAAsB;YACtBC,YAAY;gBACVC,IAAI;oBACFV,MAAM;gBACR;gBACAmC,gBAAgBL;gBAChBM,YAAYN;YACd;YACA7B,UAAU;gBAAC;aAAK;YAChBY,OAAO;QACT;IACF;IAEAtB,GAAG,yFAAyF;QAC1F,MAAM8C,cAAqB;YACzBxC,MAAM;YACNyC,eAAe;YACfxC,QAAQ;gBACN;oBACEC,MAAM;oBACNC,MAAM;oBACNuC,QAAQ;wBACN,wBAAwB;wBACxB,OAAO;4BACLC,eAAe;4BACfC,gBAAgB;4BAChBC,UAAU,IAAM;wBAClB;oBACF;gBACF;aACD;QACH;QAEA,mBAAmB;QACnB,MAAM/C,SAAiB;YACrBmB,QAAQ;gBAACuB;aAAY;YACrBzC,aAAa;gBACX;oBACEC,MAAM;oBACNC,QAAQ;wBACN;4BACEC,MAAM;4BACNC,MAAM;4BACNc,QAAQ;gCAACuB;6BAAY;wBACvB;qBACD;oBACDnC,YAAY;gBACd;aACD;QACH;QAEA,6CAA6C;QAC7C,MAAMC,kBAAkB,MAAMV,eAAeE;QAC7CH,OAAO,OAAQW,iBAAiBW,QAAQ,CAAC,EAAE,EAAEhB,QAAQ,CAAC,EAAE,EAAoByC,QAAQI,IAAI,CACtF;QAEFnD,OACE,OACGW,gBAAgBP,WAAW,CAAC,EAAE,CAACE,MAAM,CAAC,EAAE,EAAkBgB,QAAQ,CAAC,EAAE,EAClEhB,QAAQ,CAAC,EAAE,EACdyC,QACHI,IAAI,CAAC;QAEP,MAAMvC,SAASV,mBAAmBS,iBAAiB;QAEnD,MAAMyC,sBAAsB;YAC1B5C,MAAM;YACNQ,sBAAsB;YACtBC,YAAY;gBACVC,IAAI;oBAAEV,MAAM;wBAAC;wBAAU;qBAAO;gBAAC;gBAC/BmB,WAAW;oBAAEnB,MAAM;wBAAC;wBAAU;qBAAO;gBAAC;gBACtCoB,WAAW;oBAAEC,OAAO;gBAAc;gBAClCwB,UAAU;oBAAE7C,MAAM;wBAAC;wBAAS;qBAAO;oBAAEY,OAAO;wBAAEZ,MAAM;oBAAS;gBAAE;YACjE;YACAC,UAAU;gBAAC;aAAY;QACzB;QAEAT,OAAOY,QAAQC,aAAaC,MAAMC,aAAa,CAAC;YAC9CP,MAAM;YACNQ,sBAAsB;YACtBK,OAAO;YACPJ,YAAY;gBACVC,IAAI;oBACFV,MAAM;gBACR;gBACA8C,gBAAgB;oBACd9C,MAAM;wBAAC;wBAAS;qBAAO;oBACvBY,OAAO;wBACLM,OAAO;4BACL;gCACE6B,MAAM;4BACR;yBACD;oBACH;gBACF;YACF;YACA9C,UAAU;gBAAC;aAAK;QAClB;QAEA,2EAA2E;QAC3ET,OAAOY,QAAQC,aAAa2C,aAAazC,aAAa,CAACqC;IACzD;IAEArD,GAAG,6CAA6C;QAC9C,mBAAmB;QACnB,MAAMI,SAAiB;YACrBC,aAAa;gBACX;oBACEC,MAAM;oBACNC,QAAQ;wBACN;4BACEC,MAAM;4BACNC,MAAM;4BACNC,UAAU;4BACVgD,cAAc;4BACdlB,kBAAkB;gCAChB,IAAO,CAAA;wCACL/B,MAAM;wCACNC,UAAU;oCACZ,CAAA;6BACD;wBACH;qBACD;oBACDC,YAAY;gBACd;aACD;QACH;QAEA,MAAMC,kBAAkB,MAAMV,eAAeE;QAC7C,MAAMS,SAASV,mBAAmBS,iBAAiB;QAEnD,mBAAmB;QACnBX,OAAOY,OAAOC,WAAW,CAACC,IAAI,CAACG,UAAU,CAACI,KAAK,CAACZ,QAAQ,EAAEM,aAAa,CAAC;IAC1E;IAEAhB,GAAG,2EAA2E;QAC5E,MAAM2D,aAAoB;YACxBrD,MAAM;YACNyC,eAAe;YACfxC,QAAQ;gBAAC;oBAAEC,MAAM;oBAAQC,MAAM;gBAAO;aAAE;QAC1C;QAEA,mBAAmB;QACnB,MAAML,SAAiB;YACrBC,aAAa;gBACX;oBACEC,MAAM;oBACNC,QAAQ;wBACN;4BACEC,MAAM;4BACNC,MAAM;4BACNF,QAAQ;gCAAC;oCAAEC,MAAM;oCAAUC,MAAM;oCAAUc,QAAQ;wCAACoC;qCAAW;gCAAC;6BAAE;wBACpE;wBACA;4BACEnD,MAAM;4BACNC,MAAM;4BACNF,QAAQ;gCAAC;oCAAEC,MAAM;oCAAUC,MAAM;oCAAUc,QAAQ;wCAACoC;qCAAW;gCAAC;6BAAE;wBACpE;qBACD;oBACDhD,YAAY;gBACd;aACD;QACH;QAEA,MAAMC,kBAAkB,MAAMV,eAAeE;QAE7C,oDAAoD;QACpD,MAAMwD,gBAAgBzD,mBAAmBS,iBAAiB;QAC1D,MAAMiD,kBAAkBD,cAAc9C,WAAW,CAAEC,IAAI,CAACG,UAAU,CAAE4C,GAAG,CAACzC,KAAK;QAC7E,MAAM0C,mBAAmB,AAACF,gBAAgB3C,UAAU,CAAEK,MAAM,CAACF,KAAK,CAAiBM,KAAK,AAAC,CAAC,EAAE;QAC5F1B,OAAO8D,kBAAkB/C,aAAa,CAAC;YAAEwC,MAAM;QAAwB;QAEvE,2DAA2D;QAC3D,MAAMQ,eAAe7D,mBAAmBS,iBAAiB,QAAQqD,WAAW;YAC1EC,mBAAmB;QACrB;QACA,MAAMC,iBAAiBH,aAAalD,WAAW,CAAEC,IAAI,CAACG,UAAU,CAAE4C,GAAG,CAACzC,KAAK;QAC3E,MAAM+C,kBAAkB,AAACD,eAAejD,UAAU,CAAEK,MAAM,CAACF,KAAK,CAAiBM,KAAK,AAAC,CAAC,EAAE;QAC1F1B,OAAOmE,iBAAiBC,GAAG,CAACC,cAAc,CAAC;QAC3CrE,OAAOmE,gBAAgBlD,UAAU,EAAEW,WAAWb,aAAa,CAAC;YAAEc,OAAO;QAAU;QAE/E,MAAMyC,kBAAkB,AACtBP,aAAalD,WAAW,CAAEC,IAAI,CAACG,UAAU,CAAEsD,GAAG,CAACtD,UAAU,CAAEK,MAAM,CAACF,KAAK,CACvEM,KAAK,AAAC,CAAC,EAAE;QACX1B,OAAOsE,iBAAiBF,GAAG,CAACC,cAAc,CAAC;QAC3CrE,OAAOsE,gBAAgBrD,UAAU,EAAEW,WAAWb,aAAa,CAAC;YAAEc,OAAO;QAAU;IACjF;AACF"}
@@ -0,0 +1,10 @@
1
+ import type { SanitizedConfig } from '../config/types.js';
2
+ import type { PayloadRequest } from '../types/index.js';
3
+ /**
4
+ * Returns a trusted request origin
5
+ */
6
+ export declare const getRequestOrigin: ({ config, req, }: {
7
+ config: Pick<SanitizedConfig, "cors" | "csrf" | "serverURL">;
8
+ req: Pick<PayloadRequest, "headers" | "payload" | "url">;
9
+ }) => string;
10
+ //# sourceMappingURL=getRequestOrigin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"getRequestOrigin.d.ts","sourceRoot":"","sources":["../../src/utilities/getRequestOrigin.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAc,eAAe,EAAE,MAAM,oBAAoB,CAAA;AACrE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAA;AA8BvD;;GAEG;AACH,eAAO,MAAM,gBAAgB,qBAG1B;IACD,MAAM,EAAE,IAAI,CAAC,eAAe,EAAE,MAAM,GAAG,MAAM,GAAG,WAAW,CAAC,CAAA;IAC5D,GAAG,EAAE,IAAI,CAAC,cAAc,EAAE,SAAS,GAAG,SAAS,GAAG,KAAK,CAAC,CAAA;CACzD,KAAG,MA4BH,CAAA"}
@@ -0,0 +1,50 @@
1
+ const getTrustedOrigins = (config)=>{
2
+ const origins = new Set();
3
+ const { cors, csrf } = config;
4
+ if (cors === '*') {
5
+ return null;
6
+ }
7
+ if (Array.isArray(cors)) {
8
+ cors.forEach((o)=>origins.add(o));
9
+ } else if (cors && typeof cors === 'object') {
10
+ const corsOrigins = cors.origins;
11
+ if (corsOrigins === '*') {
12
+ return null;
13
+ }
14
+ if (Array.isArray(corsOrigins)) {
15
+ corsOrigins.forEach((o)=>origins.add(o));
16
+ }
17
+ }
18
+ if (Array.isArray(csrf)) {
19
+ csrf.forEach((o)=>origins.add(o));
20
+ }
21
+ return [
22
+ ...origins
23
+ ];
24
+ };
25
+ /**
26
+ * Returns a trusted request origin
27
+ */ export const getRequestOrigin = ({ config, req })=>{
28
+ if (config.serverURL !== null && config.serverURL !== '') {
29
+ return config.serverURL;
30
+ }
31
+ let origin = '';
32
+ try {
33
+ const protocol = new URL(req.url).protocol;
34
+ const host = req.headers?.get('host');
35
+ if (host) {
36
+ origin = `${protocol}//${host}`;
37
+ }
38
+ } catch {
39
+ // req.url is malformed; origin stays empty
40
+ }
41
+ const trustedOrigins = getTrustedOrigins(config);
42
+ if (trustedOrigins !== null && origin && trustedOrigins.includes(origin)) {
43
+ // Host header value is explicitly listed in the CORS/CSRF allowlist — safe to use.
44
+ return origin;
45
+ }
46
+ req.payload.logger.warn(`Request origin "${origin}" is not in the CORS/CSRF allowlist. Falling back to empty string as request origin. It is recommended to explicitly set the serverURL in the config to avoid this warning and ensure correct request origin is used.`);
47
+ return '';
48
+ };
49
+
50
+ //# sourceMappingURL=getRequestOrigin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/utilities/getRequestOrigin.ts"],"sourcesContent":["import type { CORSConfig, SanitizedConfig } from '../config/types.js'\nimport type { PayloadRequest } from '../types/index.js'\n\nconst getTrustedOrigins = (config: Pick<SanitizedConfig, 'cors' | 'csrf'>): null | string[] => {\n const origins = new Set<string>()\n\n const { cors, csrf } = config\n\n if (cors === '*') {\n return null\n }\n\n if (Array.isArray(cors)) {\n cors.forEach((o) => origins.add(o))\n } else if (cors && typeof cors === 'object') {\n const corsOrigins = (cors as CORSConfig).origins\n if (corsOrigins === '*') {\n return null\n }\n if (Array.isArray(corsOrigins)) {\n corsOrigins.forEach((o) => origins.add(o))\n }\n }\n\n if (Array.isArray(csrf)) {\n csrf.forEach((o) => origins.add(o))\n }\n\n return [...origins]\n}\n\n/**\n * Returns a trusted request origin\n */\nexport const getRequestOrigin = ({\n config,\n req,\n}: {\n config: Pick<SanitizedConfig, 'cors' | 'csrf' | 'serverURL'>\n req: Pick<PayloadRequest, 'headers' | 'payload' | 'url'>\n}): string => {\n if (config.serverURL !== null && config.serverURL !== '') {\n return config.serverURL\n }\n\n let origin = ''\n try {\n const protocol = new URL(req.url!).protocol\n const host = req.headers?.get('host')\n if (host) {\n origin = `${protocol}//${host}`\n }\n } catch {\n // req.url is malformed; origin stays empty\n }\n\n const trustedOrigins = getTrustedOrigins(config)\n\n if (trustedOrigins !== null && origin && trustedOrigins.includes(origin)) {\n // Host header value is explicitly listed in the CORS/CSRF allowlist — safe to use.\n return origin\n }\n\n req.payload.logger.warn(\n `Request origin \"${origin}\" is not in the CORS/CSRF allowlist. Falling back to empty string as request origin. It is recommended to explicitly set the serverURL in the config to avoid this warning and ensure correct request origin is used.`,\n )\n\n return ''\n}\n"],"names":["getTrustedOrigins","config","origins","Set","cors","csrf","Array","isArray","forEach","o","add","corsOrigins","getRequestOrigin","req","serverURL","origin","protocol","URL","url","host","headers","get","trustedOrigins","includes","payload","logger","warn"],"mappings":"AAGA,MAAMA,oBAAoB,CAACC;IACzB,MAAMC,UAAU,IAAIC;IAEpB,MAAM,EAAEC,IAAI,EAAEC,IAAI,EAAE,GAAGJ;IAEvB,IAAIG,SAAS,KAAK;QAChB,OAAO;IACT;IAEA,IAAIE,MAAMC,OAAO,CAACH,OAAO;QACvBA,KAAKI,OAAO,CAAC,CAACC,IAAMP,QAAQQ,GAAG,CAACD;IAClC,OAAO,IAAIL,QAAQ,OAAOA,SAAS,UAAU;QAC3C,MAAMO,cAAc,AAACP,KAAoBF,OAAO;QAChD,IAAIS,gBAAgB,KAAK;YACvB,OAAO;QACT;QACA,IAAIL,MAAMC,OAAO,CAACI,cAAc;YAC9BA,YAAYH,OAAO,CAAC,CAACC,IAAMP,QAAQQ,GAAG,CAACD;QACzC;IACF;IAEA,IAAIH,MAAMC,OAAO,CAACF,OAAO;QACvBA,KAAKG,OAAO,CAAC,CAACC,IAAMP,QAAQQ,GAAG,CAACD;IAClC;IAEA,OAAO;WAAIP;KAAQ;AACrB;AAEA;;CAEC,GACD,OAAO,MAAMU,mBAAmB,CAAC,EAC/BX,MAAM,EACNY,GAAG,EAIJ;IACC,IAAIZ,OAAOa,SAAS,KAAK,QAAQb,OAAOa,SAAS,KAAK,IAAI;QACxD,OAAOb,OAAOa,SAAS;IACzB;IAEA,IAAIC,SAAS;IACb,IAAI;QACF,MAAMC,WAAW,IAAIC,IAAIJ,IAAIK,GAAG,EAAGF,QAAQ;QAC3C,MAAMG,OAAON,IAAIO,OAAO,EAAEC,IAAI;QAC9B,IAAIF,MAAM;YACRJ,SAAS,GAAGC,SAAS,EAAE,EAAEG,MAAM;QACjC;IACF,EAAE,OAAM;IACN,2CAA2C;IAC7C;IAEA,MAAMG,iBAAiBtB,kBAAkBC;IAEzC,IAAIqB,mBAAmB,QAAQP,UAAUO,eAAeC,QAAQ,CAACR,SAAS;QACxE,mFAAmF;QACnF,OAAOA;IACT;IAEAF,IAAIW,OAAO,CAACC,MAAM,CAACC,IAAI,CACrB,CAAC,gBAAgB,EAAEX,OAAO,qNAAqN,CAAC;IAGlP,OAAO;AACT,EAAC"}
@@ -0,0 +1,151 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { getRequestOrigin } from './getRequestOrigin';
3
+ const makeReq = (url, hostOverride)=>{
4
+ let host = hostOverride;
5
+ if (host === undefined) {
6
+ try {
7
+ host = new URL(url).host;
8
+ } catch {
9
+ host = undefined;
10
+ }
11
+ }
12
+ return {
13
+ url,
14
+ headers: new Headers(host !== undefined ? {
15
+ host
16
+ } : {}),
17
+ payload: {
18
+ logger: {
19
+ warn: ()=>{}
20
+ }
21
+ }
22
+ };
23
+ };
24
+ describe('getRequestOrigin', ()=>{
25
+ describe('when config.serverURL is set', ()=>{
26
+ it('should return serverURL regardless of Host header', ()=>{
27
+ const req = makeReq('https://ignored.com/api/forgot-password', 'attacker.com');
28
+ const result = getRequestOrigin({
29
+ config: {
30
+ serverURL: 'https://myapp.com',
31
+ cors: '*',
32
+ csrf: []
33
+ },
34
+ req
35
+ });
36
+ expect(result).toBe('https://myapp.com');
37
+ });
38
+ });
39
+ describe('when config.serverURL is not set', ()=>{
40
+ describe('CORS allowlist validation', ()=>{
41
+ it('should return origin when Host header matches a CORS string array entry', ()=>{
42
+ const req = makeReq('https://myapp.com/api/forgot-password');
43
+ const result = getRequestOrigin({
44
+ config: {
45
+ serverURL: '',
46
+ cors: [
47
+ 'https://myapp.com',
48
+ 'https://other.com'
49
+ ],
50
+ csrf: []
51
+ },
52
+ req
53
+ });
54
+ expect(result).toBe('https://myapp.com');
55
+ });
56
+ it('should return origin when Host header matches a CORSConfig origins entry', ()=>{
57
+ const req = makeReq('https://myapp.com/api/verify');
58
+ const result = getRequestOrigin({
59
+ config: {
60
+ serverURL: '',
61
+ cors: {
62
+ headers: [],
63
+ origins: [
64
+ 'https://myapp.com'
65
+ ]
66
+ },
67
+ csrf: []
68
+ },
69
+ req
70
+ });
71
+ expect(result).toBe('https://myapp.com');
72
+ });
73
+ it('should return empty string when Host header is forged and not in CORS allowlist', ()=>{
74
+ const req = makeReq('https://myapp.com/api/forgot-password', 'attacker.com');
75
+ const result = getRequestOrigin({
76
+ config: {
77
+ serverURL: '',
78
+ cors: [
79
+ 'https://myapp.com'
80
+ ],
81
+ csrf: []
82
+ },
83
+ req
84
+ });
85
+ expect(result).toBe('');
86
+ });
87
+ });
88
+ describe('CSRF allowlist validation', ()=>{
89
+ it('should return origin when Host header matches a CSRF entry', ()=>{
90
+ const req = makeReq('https://myapp.com/api/verify');
91
+ const result = getRequestOrigin({
92
+ config: {
93
+ serverURL: '',
94
+ cors: [],
95
+ csrf: [
96
+ 'https://myapp.com'
97
+ ]
98
+ },
99
+ req
100
+ });
101
+ expect(result).toBe('https://myapp.com');
102
+ });
103
+ it('should return origin when Host header is in CSRF but not in CORS', ()=>{
104
+ const req = makeReq('https://myapp.com/api/verify');
105
+ const result = getRequestOrigin({
106
+ config: {
107
+ serverURL: '',
108
+ cors: [
109
+ 'https://other.com'
110
+ ],
111
+ csrf: [
112
+ 'https://myapp.com'
113
+ ]
114
+ },
115
+ req
116
+ });
117
+ expect(result).toBe('https://myapp.com');
118
+ });
119
+ });
120
+ describe('malformed or missing Host header', ()=>{
121
+ it('should return empty string when req.url is not a valid URL', ()=>{
122
+ const req = makeReq('not-a-url');
123
+ const result = getRequestOrigin({
124
+ config: {
125
+ serverURL: '',
126
+ cors: [],
127
+ csrf: []
128
+ },
129
+ req
130
+ });
131
+ expect(result).toBe('');
132
+ });
133
+ it('should return empty string when Host header is absent', ()=>{
134
+ const req = makeReq('https://myapp.com/api/forgot-password', '');
135
+ const result = getRequestOrigin({
136
+ config: {
137
+ serverURL: '',
138
+ cors: [
139
+ 'https://myapp.com'
140
+ ],
141
+ csrf: []
142
+ },
143
+ req
144
+ });
145
+ expect(result).toBe('');
146
+ });
147
+ });
148
+ });
149
+ });
150
+
151
+ //# sourceMappingURL=getRequestOrigin.spec.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/utilities/getRequestOrigin.spec.ts"],"sourcesContent":["import type { PayloadRequest } from '../types/index.js'\n\nimport { describe, expect, it } from 'vitest'\n\nimport { getRequestOrigin } from './getRequestOrigin'\n\ntype MinimalReq = Pick<PayloadRequest, 'headers' | 'payload' | 'url'>\n\nconst makeReq = (url: string, hostOverride?: string): MinimalReq => {\n let host = hostOverride\n if (host === undefined) {\n try {\n host = new URL(url).host\n } catch {\n host = undefined\n }\n }\n return {\n url,\n headers: new Headers(host !== undefined ? { host } : {}),\n payload: {\n logger: {\n warn: () => {},\n },\n },\n } as unknown as MinimalReq\n}\n\ndescribe('getRequestOrigin', () => {\n describe('when config.serverURL is set', () => {\n it('should return serverURL regardless of Host header', () => {\n const req = makeReq('https://ignored.com/api/forgot-password', 'attacker.com')\n const result = getRequestOrigin({\n config: { serverURL: 'https://myapp.com', cors: '*', csrf: [] },\n req,\n })\n expect(result).toBe('https://myapp.com')\n })\n })\n\n describe('when config.serverURL is not set', () => {\n describe('CORS allowlist validation', () => {\n it('should return origin when Host header matches a CORS string array entry', () => {\n const req = makeReq('https://myapp.com/api/forgot-password')\n const result = getRequestOrigin({\n config: { serverURL: '', cors: ['https://myapp.com', 'https://other.com'], csrf: [] },\n req,\n })\n expect(result).toBe('https://myapp.com')\n })\n\n it('should return origin when Host header matches a CORSConfig origins entry', () => {\n const req = makeReq('https://myapp.com/api/verify')\n const result = getRequestOrigin({\n config: {\n serverURL: '',\n cors: { headers: [], origins: ['https://myapp.com'] },\n csrf: [],\n },\n req,\n })\n expect(result).toBe('https://myapp.com')\n })\n\n it('should return empty string when Host header is forged and not in CORS allowlist', () => {\n const req = makeReq('https://myapp.com/api/forgot-password', 'attacker.com')\n const result = getRequestOrigin({\n config: { serverURL: '', cors: ['https://myapp.com'], csrf: [] },\n req,\n })\n expect(result).toBe('')\n })\n })\n\n describe('CSRF allowlist validation', () => {\n it('should return origin when Host header matches a CSRF entry', () => {\n const req = makeReq('https://myapp.com/api/verify')\n const result = getRequestOrigin({\n config: { serverURL: '', cors: [], csrf: ['https://myapp.com'] },\n req,\n })\n expect(result).toBe('https://myapp.com')\n })\n\n it('should return origin when Host header is in CSRF but not in CORS', () => {\n const req = makeReq('https://myapp.com/api/verify')\n const result = getRequestOrigin({\n config: {\n serverURL: '',\n cors: ['https://other.com'],\n csrf: ['https://myapp.com'],\n },\n req,\n })\n expect(result).toBe('https://myapp.com')\n })\n })\n\n describe('malformed or missing Host header', () => {\n it('should return empty string when req.url is not a valid URL', () => {\n const req = makeReq('not-a-url')\n const result = getRequestOrigin({\n config: { serverURL: '', cors: [], csrf: [] },\n req,\n })\n expect(result).toBe('')\n })\n\n it('should return empty string when Host header is absent', () => {\n const req = makeReq('https://myapp.com/api/forgot-password', '')\n const result = getRequestOrigin({\n config: { serverURL: '', cors: ['https://myapp.com'], csrf: [] },\n req,\n })\n expect(result).toBe('')\n })\n })\n })\n})\n"],"names":["describe","expect","it","getRequestOrigin","makeReq","url","hostOverride","host","undefined","URL","headers","Headers","payload","logger","warn","req","result","config","serverURL","cors","csrf","toBe","origins"],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Sanitizes a URL to ensure only allowed protocols are used.
3
+ * Allows: http, https, mailto, tel, relative paths, and fragment (#) URLs.
4
+ * Returns '#' for any URL with a disallowed protocol (e.g. javascript:, data:).
5
+ */
6
+ export declare function sanitizeUrl(url: string): string;
7
+ //# sourceMappingURL=sanitizeUrl.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sanitizeUrl.d.ts","sourceRoot":"","sources":["../../src/utilities/sanitizeUrl.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAgC/C"}
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Sanitizes a URL to ensure only allowed protocols are used.
3
+ * Allows: http, https, mailto, tel, relative paths, and fragment (#) URLs.
4
+ * Returns '#' for any URL with a disallowed protocol (e.g. javascript:, data:).
5
+ */ export function sanitizeUrl(url) {
6
+ if (!url) {
7
+ return '';
8
+ }
9
+ const trimmed = url.trim();
10
+ // eslint-disable-next-line no-control-regex
11
+ const cleaned = trimmed.replace(/[\x00-\x1f\x7f]/g, '');
12
+ if (cleaned.startsWith('#')) {
13
+ return cleaned;
14
+ }
15
+ if (cleaned.startsWith('/') || cleaned.startsWith('./') || cleaned.startsWith('../')) {
16
+ return cleaned;
17
+ }
18
+ const protocolMatch = cleaned.match(/^([a-z][a-z0-9+\-.]*):(?=.)/i);
19
+ if (protocolMatch) {
20
+ const protocol = protocolMatch[1].toLowerCase();
21
+ if (protocol !== 'http' && protocol !== 'https' && protocol !== 'mailto' && protocol !== 'tel') {
22
+ return '#';
23
+ }
24
+ }
25
+ return cleaned;
26
+ }
27
+
28
+ //# sourceMappingURL=sanitizeUrl.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/utilities/sanitizeUrl.ts"],"sourcesContent":["/**\n * Sanitizes a URL to ensure only allowed protocols are used.\n * Allows: http, https, mailto, tel, relative paths, and fragment (#) URLs.\n * Returns '#' for any URL with a disallowed protocol (e.g. javascript:, data:).\n */\nexport function sanitizeUrl(url: string): string {\n if (!url) {\n return ''\n }\n\n const trimmed = url.trim()\n\n // eslint-disable-next-line no-control-regex\n const cleaned = trimmed.replace(/[\\x00-\\x1f\\x7f]/g, '')\n\n if (cleaned.startsWith('#')) {\n return cleaned\n }\n\n if (cleaned.startsWith('/') || cleaned.startsWith('./') || cleaned.startsWith('../')) {\n return cleaned\n }\n\n const protocolMatch = cleaned.match(/^([a-z][a-z0-9+\\-.]*):(?=.)/i)\n if (protocolMatch) {\n const protocol = protocolMatch[1]!.toLowerCase()\n if (\n protocol !== 'http' &&\n protocol !== 'https' &&\n protocol !== 'mailto' &&\n protocol !== 'tel'\n ) {\n return '#'\n }\n }\n\n return cleaned\n}\n"],"names":["sanitizeUrl","url","trimmed","trim","cleaned","replace","startsWith","protocolMatch","match","protocol","toLowerCase"],"mappings":"AAAA;;;;CAIC,GACD,OAAO,SAASA,YAAYC,GAAW;IACrC,IAAI,CAACA,KAAK;QACR,OAAO;IACT;IAEA,MAAMC,UAAUD,IAAIE,IAAI;IAExB,4CAA4C;IAC5C,MAAMC,UAAUF,QAAQG,OAAO,CAAC,oBAAoB;IAEpD,IAAID,QAAQE,UAAU,CAAC,MAAM;QAC3B,OAAOF;IACT;IAEA,IAAIA,QAAQE,UAAU,CAAC,QAAQF,QAAQE,UAAU,CAAC,SAASF,QAAQE,UAAU,CAAC,QAAQ;QACpF,OAAOF;IACT;IAEA,MAAMG,gBAAgBH,QAAQI,KAAK,CAAC;IACpC,IAAID,eAAe;QACjB,MAAME,WAAWF,aAAa,CAAC,EAAE,CAAEG,WAAW;QAC9C,IACED,aAAa,UACbA,aAAa,WACbA,aAAa,YACbA,aAAa,OACb;YACA,OAAO;QACT;IACF;IAEA,OAAOL;AACT"}
@@ -16,6 +16,7 @@ type Args<T extends JsonObject = JsonObject> = {
16
16
  returning?: boolean;
17
17
  select?: SelectType;
18
18
  snapshot?: any;
19
+ unpublish?: boolean;
19
20
  };
20
21
  export declare function saveVersion<TData extends JsonObject = JsonObject>(args: {
21
22
  returning: false;
@@ -1 +1 @@
1
- {"version":3,"file":"saveVersion.d.ts","sourceRoot":"","sources":["../../src/versions/saveVersion.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,gCAAgC,CAAA;AAC/E,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAA;AACvE,OAAO,KAAK,EAA8C,OAAO,EAAE,MAAM,aAAa,CAAA;AACtF,OAAO,KAAK,EAAE,UAAU,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAA;AAS/E,KAAK,IAAI,CAAC,CAAC,SAAS,UAAU,GAAG,UAAU,IAAI;IAC7C,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,UAAU,CAAC,EAAE,yBAAyB,CAAA;IACtC,cAAc,EAAE,CAAC,CAAA;IACjB,KAAK,CAAC,EAAE,OAAO,CAAA;IACf,MAAM,CAAC,EAAE,qBAAqB,CAAA;IAC9B,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;IACpB,SAAS,CAAC,EAAE,QAAQ,GAAG,gBAAgB,GAAG,QAAQ,CAAA;IAClD,OAAO,EAAE,OAAO,CAAA;IAChB,qBAAqB,CAAC,EAAE,MAAM,CAAA;IAC9B,GAAG,CAAC,EAAE,cAAc,CAAA;IACpB,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,MAAM,CAAC,EAAE,UAAU,CAAA;IACnB,QAAQ,CAAC,EAAE,GAAG,CAAA;CACf,CAAA;AAED,wBAAsB,WAAW,CAAC,KAAK,SAAS,UAAU,GAAG,UAAU,EACrE,IAAI,EAAE;IAAE,SAAS,EAAE,KAAK,CAAA;CAAE,GAAG,IAAI,CAAC,KAAK,CAAC,GACvC,OAAO,CAAC,IAAI,CAAC,CAAA;AAChB,wBAAsB,WAAW,CAAC,KAAK,SAAS,UAAU,GAAG,UAAU,EACrE,IAAI,EAAE;IAAE,SAAS,EAAE,IAAI,CAAA;CAAE,GAAG,IAAI,CAAC,KAAK,CAAC,GACtC,OAAO,CAAC,UAAU,CAAC,CAAA;AACtB,wBAAsB,WAAW,CAAC,KAAK,SAAS,UAAU,GAAG,UAAU,EACrE,IAAI,EAAE,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,WAAW,CAAC,GACnC,OAAO,CAAC,UAAU,CAAC,CAAA"}
1
+ {"version":3,"file":"saveVersion.d.ts","sourceRoot":"","sources":["../../src/versions/saveVersion.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,gCAAgC,CAAA;AAC/E,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAA;AACvE,OAAO,KAAK,EAA8C,OAAO,EAAE,MAAM,aAAa,CAAA;AACtF,OAAO,KAAK,EAAE,UAAU,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAA;AAU/E,KAAK,IAAI,CAAC,CAAC,SAAS,UAAU,GAAG,UAAU,IAAI;IAC7C,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,UAAU,CAAC,EAAE,yBAAyB,CAAA;IACtC,cAAc,EAAE,CAAC,CAAA;IACjB,KAAK,CAAC,EAAE,OAAO,CAAA;IACf,MAAM,CAAC,EAAE,qBAAqB,CAAA;IAC9B,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;IACpB,SAAS,CAAC,EAAE,QAAQ,GAAG,gBAAgB,GAAG,QAAQ,CAAA;IAClD,OAAO,EAAE,OAAO,CAAA;IAChB,qBAAqB,CAAC,EAAE,MAAM,CAAA;IAC9B,GAAG,CAAC,EAAE,cAAc,CAAA;IACpB,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,MAAM,CAAC,EAAE,UAAU,CAAA;IACnB,QAAQ,CAAC,EAAE,GAAG,CAAA;IACd,SAAS,CAAC,EAAE,OAAO,CAAA;CACpB,CAAA;AAED,wBAAsB,WAAW,CAAC,KAAK,SAAS,UAAU,GAAG,UAAU,EACrE,IAAI,EAAE;IAAE,SAAS,EAAE,KAAK,CAAA;CAAE,GAAG,IAAI,CAAC,KAAK,CAAC,GACvC,OAAO,CAAC,IAAI,CAAC,CAAA;AAChB,wBAAsB,WAAW,CAAC,KAAK,SAAS,UAAU,GAAG,UAAU,EACrE,IAAI,EAAE;IAAE,SAAS,EAAE,IAAI,CAAA;CAAE,GAAG,IAAI,CAAC,KAAK,CAAC,GACtC,OAAO,CAAC,UAAU,CAAC,CAAA;AACtB,wBAAsB,WAAW,CAAC,KAAK,SAAS,UAAU,GAAG,UAAU,EACrE,IAAI,EAAE,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,WAAW,CAAC,GACnC,OAAO,CAAC,UAAU,CAAC,CAAA"}
@@ -4,9 +4,10 @@ import { sanitizeInternalFields } from '../utilities/sanitizeInternalFields.js';
4
4
  import { getQueryDraftsSelect } from './drafts/getQueryDraftsSelect.js';
5
5
  import { enforceMaxVersions } from './enforceMaxVersions.js';
6
6
  import { saveSnapshot } from './saveSnapshot.js';
7
- export async function saveVersion({ id, autosave, collection, docWithLocales, draft, global, operation, payload, publishSpecificLocale, req, returning, select, snapshot }) {
7
+ import { updateLatestVersion } from './updateLatestVersion.js';
8
+ export async function saveVersion({ id, autosave, collection, docWithLocales, draft, global, operation, payload, publishSpecificLocale, req, returning, select, snapshot, unpublish }) {
8
9
  let result;
9
- let createNewVersion = true;
10
+ let createdNewVersion = false;
10
11
  const now = new Date().toISOString();
11
12
  const versionData = deepCopyObjectSimple(docWithLocales);
12
13
  if ((collection?.timestamps || global) && draft) {
@@ -16,71 +17,20 @@ export async function saveVersion({ id, autosave, collection, docWithLocales, dr
16
17
  delete versionData._id;
17
18
  }
18
19
  try {
19
- if (autosave) {
20
- let docs;
21
- const findVersionArgs = {
22
- limit: 1,
23
- pagination: false,
20
+ if (unpublish || autosave) {
21
+ result = await updateLatestVersion({
22
+ id,
23
+ collection,
24
+ global,
25
+ now,
26
+ payload,
24
27
  req,
25
- sort: '-updatedAt'
26
- };
27
- if (collection) {
28
- ;
29
- ({ docs } = await payload.db.findVersions({
30
- ...findVersionArgs,
31
- collection: collection.slug,
32
- limit: 1,
33
- pagination: false,
34
- req,
35
- where: {
36
- parent: {
37
- equals: id
38
- }
39
- }
40
- }));
41
- } else {
42
- ;
43
- ({ docs } = await payload.db.findGlobalVersions({
44
- ...findVersionArgs,
45
- global: global.slug,
46
- limit: 1,
47
- pagination: false,
48
- req
49
- }));
50
- }
51
- const [latestVersion] = docs;
52
- // overwrite the latest version if it's set to autosave
53
- if (latestVersion && 'autosave' in latestVersion && latestVersion.autosave === true) {
54
- createNewVersion = false;
55
- const updateVersionArgs = {
56
- id: latestVersion.id,
57
- req,
58
- versionData: {
59
- createdAt: new Date(latestVersion.createdAt).toISOString(),
60
- latest: true,
61
- parent: id,
62
- updatedAt: now,
63
- version: {
64
- ...versionData
65
- }
66
- }
67
- };
68
- if (collection) {
69
- result = await payload.db.updateVersion({
70
- ...updateVersionArgs,
71
- collection: collection.slug,
72
- req
73
- });
74
- } else {
75
- result = await payload.db.updateGlobalVersion({
76
- ...updateVersionArgs,
77
- global: global.slug,
78
- req
79
- });
80
- }
81
- }
28
+ shouldUpdate: autosave ? (v)=>'autosave' in v && v.autosave === true : undefined,
29
+ versionData
30
+ });
82
31
  }
83
- if (createNewVersion) {
32
+ if (!result) {
33
+ createdNewVersion = true;
84
34
  const createVersionArgs = {
85
35
  autosave: Boolean(autosave),
86
36
  collectionSlug: undefined,
@@ -133,7 +83,7 @@ export async function saveVersion({ id, autosave, collection, docWithLocales, dr
133
83
  return undefined;
134
84
  }
135
85
  const max = getVersionsMax(collection || global);
136
- if (createNewVersion && max > 0) {
86
+ if (createdNewVersion && max > 0) {
137
87
  await enforceMaxVersions({
138
88
  id,
139
89
  collection,