payload 3.84.1 → 4.0.0-internal.5b1e7cd
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.
- package/README.md +5 -5
- package/dist/utilities/formatAdminURL.js +15 -3
- package/dist/utilities/formatAdminURL.js.map +1 -1
- package/dist/utilities/formatAdminURL.spec.js +90 -1
- package/dist/utilities/formatAdminURL.spec.js.map +1 -1
- package/dist/utilities/handleEndpoints.d.ts.map +1 -1
- package/dist/utilities/handleEndpoints.js +4 -2
- package/dist/utilities/handleEndpoints.js.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -69,16 +69,16 @@ All-in-one on Vercel — one click to deploy Payload with a **Next.js** front en
|
|
|
69
69
|
|
|
70
70
|
Jumpstart your next project with a ready-to-go template. These are **production-ready, end-to-end solutions** designed to get you to market fast. Build any kind of **website**, **ecommerce store**, **blog**, or **portfolio** — complete with a modern front end built using **React Server Components** and **Tailwind**.
|
|
71
71
|
|
|
72
|
-
#### 🌐 [Website](https://github.com/payloadcms/payload/tree/
|
|
72
|
+
#### 🌐 [Website](https://github.com/payloadcms/payload/tree/main/templates/website)
|
|
73
73
|
|
|
74
|
-
#### 🛍️ [Ecommerce](https://github.com/payloadcms/payload/tree/
|
|
74
|
+
#### 🛍️ [Ecommerce](https://github.com/payloadcms/payload/tree/main/templates/ecommerce) 🎉 _**NEW**_ 🎉
|
|
75
75
|
|
|
76
|
-
We're constantly adding more templates to our [**Templates Directory**](https://github.com/payloadcms/payload/tree/
|
|
76
|
+
We're constantly adding more templates to our [**Templates Directory**](https://github.com/payloadcms/payload/tree/main/templates).
|
|
77
77
|
If you maintain your own, add the `payload-template` topic to your GitHub repo so others can discover it.
|
|
78
78
|
|
|
79
79
|
**🔗 Explore more:**
|
|
80
80
|
|
|
81
|
-
- [Official Templates](https://github.com/payloadcms/payload/tree/
|
|
81
|
+
- [Official Templates](https://github.com/payloadcms/payload/tree/main/templates)
|
|
82
82
|
- [Community Templates](https://github.com/topics/payload-template)
|
|
83
83
|
|
|
84
84
|
## ✨ Payload Features
|
|
@@ -106,7 +106,7 @@ If you maintain your own, add the `payload-template` topic to your GitHub repo s
|
|
|
106
106
|
|
|
107
107
|
Check out the [Payload website](https://payloadcms.com/docs/getting-started/what-is-payload) to find in-depth documentation for everything that Payload offers.
|
|
108
108
|
|
|
109
|
-
Migrating from v2 to v3? Check out the [3.0 Migration Guide](https://github.com/payloadcms/payload/blob/
|
|
109
|
+
Migrating from v2 to v3? Check out the [3.0 Migration Guide](https://github.com/payloadcms/payload/blob/main/docs/migration-guide/overview.mdx) on how to do it.
|
|
110
110
|
|
|
111
111
|
## 🙋 Contributing
|
|
112
112
|
|
|
@@ -11,12 +11,24 @@ export const formatAdminURL = (args)=>{
|
|
|
11
11
|
const includeBasePath = includeBasePathArg ?? (adminRoute ? false : true);
|
|
12
12
|
if (relative || !serverURL) {
|
|
13
13
|
if (includeBasePath && basePath) {
|
|
14
|
-
return pathnameWithBase;
|
|
14
|
+
return applyTrailingSlash(pathnameWithBase);
|
|
15
15
|
}
|
|
16
|
-
return pathname;
|
|
16
|
+
return applyTrailingSlash(pathname);
|
|
17
17
|
}
|
|
18
18
|
const serverURLObj = new URL(serverURL);
|
|
19
|
-
return new URL(pathnameWithBase, serverURLObj.origin).toString();
|
|
19
|
+
return applyTrailingSlash(new URL(pathnameWithBase, serverURLObj.origin).toString());
|
|
20
|
+
};
|
|
21
|
+
const applyTrailingSlash = (url)=>{
|
|
22
|
+
if (process.env.NEXT_TRAILING_SLASH !== 'true') {
|
|
23
|
+
return url;
|
|
24
|
+
}
|
|
25
|
+
const queryIndex = url.search(/[?#]/);
|
|
26
|
+
const pathPart = queryIndex === -1 ? url : url.slice(0, queryIndex);
|
|
27
|
+
const queryPart = queryIndex === -1 ? '' : url.slice(queryIndex);
|
|
28
|
+
if (pathPart.endsWith('/')) {
|
|
29
|
+
return url;
|
|
30
|
+
}
|
|
31
|
+
return `${pathPart}/${queryPart}`;
|
|
20
32
|
};
|
|
21
33
|
|
|
22
34
|
//# sourceMappingURL=formatAdminURL.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/utilities/formatAdminURL.ts"],"sourcesContent":["import type { Config } from '../config/types.js'\n\n/**\n * This function builds correct URLs for admin panel routing.\n * Its primary responsibilities are:\n * 1. Read from your `routes.admin` config and appropriately handle `\"/\"` admin paths\n * 2. Prepend the `basePath` from your Next.js config, if specified\n * 3. Return relative or absolute URLs, as needed\n */\ntype BaseFormatURLArgs = {\n /**\n * The subpath of your application, if specified.\n * @see https://nextjs.org/docs/app/api-reference/config/next-config-js/basePath\n * @example '/docs'\n */\n basePath?: string\n includeBasePath?: boolean\n path?: '' | `/${string}` | null\n /**\n * Return a relative URL, e.g. ignore `serverURL`.\n * Useful for route-matching, etc.\n */\n relative?: boolean\n} & Pick<Config, 'serverURL'>\n\ntype FormatURLArgs =\n | ({\n adminRoute: NonNullable<Config['routes']>['admin']\n apiRoute?: never\n } & BaseFormatURLArgs)\n | ({\n adminRoute?: never\n apiRoute: NonNullable<Config['routes']>['api']\n } & BaseFormatURLArgs)\n\nexport const formatAdminURL = (args: FormatURLArgs): string => {\n const {\n adminRoute,\n apiRoute,\n includeBasePath: includeBasePathArg,\n path = '',\n relative = false,\n serverURL,\n } = args\n const basePath = process.env.NEXT_BASE_PATH || args.basePath || ''\n const routePath = adminRoute || apiRoute\n const segments = [routePath && routePath !== '/' && routePath, path && path].filter(Boolean)\n const pathname = segments.join('') || '/'\n const pathnameWithBase = (basePath + pathname).replace(/\\/$/, '') || '/'\n const includeBasePath = includeBasePathArg ?? (adminRoute ? false : true)\n\n if (relative || !serverURL) {\n if (includeBasePath && basePath) {\n return pathnameWithBase\n }\n return pathname\n }\n\n const serverURLObj = new URL(serverURL)\n return new URL(pathnameWithBase, serverURLObj.origin).toString()\n}\n"],"names":["formatAdminURL","args","adminRoute","apiRoute","includeBasePath","includeBasePathArg","path","relative","serverURL","basePath","process","env","NEXT_BASE_PATH","routePath","segments","filter","Boolean","pathname","join","pathnameWithBase","replace","serverURLObj","URL","origin","toString"],"mappings":"AAmCA,OAAO,MAAMA,iBAAiB,CAACC;IAC7B,MAAM,EACJC,UAAU,EACVC,QAAQ,EACRC,iBAAiBC,kBAAkB,EACnCC,OAAO,EAAE,EACTC,WAAW,KAAK,EAChBC,SAAS,EACV,GAAGP;IACJ,MAAMQ,WAAWC,QAAQC,GAAG,CAACC,cAAc,IAAIX,KAAKQ,QAAQ,IAAI;IAChE,MAAMI,YAAYX,cAAcC;IAChC,MAAMW,WAAW;QAACD,aAAaA,cAAc,OAAOA;QAAWP,QAAQA;KAAK,CAACS,MAAM,CAACC;IACpF,MAAMC,WAAWH,SAASI,IAAI,CAAC,OAAO;IACtC,MAAMC,mBAAmB,AAACV,CAAAA,WAAWQ,QAAO,EAAGG,OAAO,CAAC,OAAO,OAAO;IACrE,MAAMhB,kBAAkBC,sBAAuBH,CAAAA,aAAa,QAAQ,IAAG;IAEvE,IAAIK,YAAY,CAACC,WAAW;QAC1B,IAAIJ,mBAAmBK,UAAU;YAC/B,
|
|
1
|
+
{"version":3,"sources":["../../src/utilities/formatAdminURL.ts"],"sourcesContent":["import type { Config } from '../config/types.js'\n\n/**\n * This function builds correct URLs for admin panel routing.\n * Its primary responsibilities are:\n * 1. Read from your `routes.admin` config and appropriately handle `\"/\"` admin paths\n * 2. Prepend the `basePath` from your Next.js config, if specified\n * 3. Return relative or absolute URLs, as needed\n */\ntype BaseFormatURLArgs = {\n /**\n * The subpath of your application, if specified.\n * @see https://nextjs.org/docs/app/api-reference/config/next-config-js/basePath\n * @example '/docs'\n */\n basePath?: string\n includeBasePath?: boolean\n path?: '' | `/${string}` | null\n /**\n * Return a relative URL, e.g. ignore `serverURL`.\n * Useful for route-matching, etc.\n */\n relative?: boolean\n} & Pick<Config, 'serverURL'>\n\ntype FormatURLArgs =\n | ({\n adminRoute: NonNullable<Config['routes']>['admin']\n apiRoute?: never\n } & BaseFormatURLArgs)\n | ({\n adminRoute?: never\n apiRoute: NonNullable<Config['routes']>['api']\n } & BaseFormatURLArgs)\n\nexport const formatAdminURL = (args: FormatURLArgs): string => {\n const {\n adminRoute,\n apiRoute,\n includeBasePath: includeBasePathArg,\n path = '',\n relative = false,\n serverURL,\n } = args\n const basePath = process.env.NEXT_BASE_PATH || args.basePath || ''\n const routePath = adminRoute || apiRoute\n const segments = [routePath && routePath !== '/' && routePath, path && path].filter(Boolean)\n const pathname = segments.join('') || '/'\n const pathnameWithBase = (basePath + pathname).replace(/\\/$/, '') || '/'\n const includeBasePath = includeBasePathArg ?? (adminRoute ? false : true)\n\n if (relative || !serverURL) {\n if (includeBasePath && basePath) {\n return applyTrailingSlash(pathnameWithBase)\n }\n return applyTrailingSlash(pathname)\n }\n\n const serverURLObj = new URL(serverURL)\n return applyTrailingSlash(new URL(pathnameWithBase, serverURLObj.origin).toString())\n}\n\nconst applyTrailingSlash = (url: string): string => {\n if (process.env.NEXT_TRAILING_SLASH !== 'true') {\n return url\n }\n const queryIndex = url.search(/[?#]/)\n const pathPart = queryIndex === -1 ? url : url.slice(0, queryIndex)\n const queryPart = queryIndex === -1 ? '' : url.slice(queryIndex)\n if (pathPart.endsWith('/')) {\n return url\n }\n return `${pathPart}/${queryPart}`\n}\n"],"names":["formatAdminURL","args","adminRoute","apiRoute","includeBasePath","includeBasePathArg","path","relative","serverURL","basePath","process","env","NEXT_BASE_PATH","routePath","segments","filter","Boolean","pathname","join","pathnameWithBase","replace","applyTrailingSlash","serverURLObj","URL","origin","toString","url","NEXT_TRAILING_SLASH","queryIndex","search","pathPart","slice","queryPart","endsWith"],"mappings":"AAmCA,OAAO,MAAMA,iBAAiB,CAACC;IAC7B,MAAM,EACJC,UAAU,EACVC,QAAQ,EACRC,iBAAiBC,kBAAkB,EACnCC,OAAO,EAAE,EACTC,WAAW,KAAK,EAChBC,SAAS,EACV,GAAGP;IACJ,MAAMQ,WAAWC,QAAQC,GAAG,CAACC,cAAc,IAAIX,KAAKQ,QAAQ,IAAI;IAChE,MAAMI,YAAYX,cAAcC;IAChC,MAAMW,WAAW;QAACD,aAAaA,cAAc,OAAOA;QAAWP,QAAQA;KAAK,CAACS,MAAM,CAACC;IACpF,MAAMC,WAAWH,SAASI,IAAI,CAAC,OAAO;IACtC,MAAMC,mBAAmB,AAACV,CAAAA,WAAWQ,QAAO,EAAGG,OAAO,CAAC,OAAO,OAAO;IACrE,MAAMhB,kBAAkBC,sBAAuBH,CAAAA,aAAa,QAAQ,IAAG;IAEvE,IAAIK,YAAY,CAACC,WAAW;QAC1B,IAAIJ,mBAAmBK,UAAU;YAC/B,OAAOY,mBAAmBF;QAC5B;QACA,OAAOE,mBAAmBJ;IAC5B;IAEA,MAAMK,eAAe,IAAIC,IAAIf;IAC7B,OAAOa,mBAAmB,IAAIE,IAAIJ,kBAAkBG,aAAaE,MAAM,EAAEC,QAAQ;AACnF,EAAC;AAED,MAAMJ,qBAAqB,CAACK;IAC1B,IAAIhB,QAAQC,GAAG,CAACgB,mBAAmB,KAAK,QAAQ;QAC9C,OAAOD;IACT;IACA,MAAME,aAAaF,IAAIG,MAAM,CAAC;IAC9B,MAAMC,WAAWF,eAAe,CAAC,IAAIF,MAAMA,IAAIK,KAAK,CAAC,GAAGH;IACxD,MAAMI,YAAYJ,eAAe,CAAC,IAAI,KAAKF,IAAIK,KAAK,CAACH;IACrD,IAAIE,SAASG,QAAQ,CAAC,MAAM;QAC1B,OAAOP;IACT;IACA,OAAO,GAAGI,SAAS,CAAC,EAAEE,WAAW;AACnC"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest';
|
|
1
|
+
import { afterEach, beforeEach, describe, it, expect } from 'vitest';
|
|
2
2
|
import { formatAdminURL } from './formatAdminURL.js';
|
|
3
3
|
describe('formatAdminURL', ()=>{
|
|
4
4
|
const serverURL = 'https://example.com';
|
|
@@ -163,6 +163,95 @@ describe('formatAdminURL', ()=>{
|
|
|
163
163
|
expect(result).toBe('/');
|
|
164
164
|
});
|
|
165
165
|
});
|
|
166
|
+
describe('trailing slash handling', ()=>{
|
|
167
|
+
const originalTrailingSlash = process.env.NEXT_TRAILING_SLASH;
|
|
168
|
+
beforeEach(()=>{
|
|
169
|
+
process.env.NEXT_TRAILING_SLASH = 'true';
|
|
170
|
+
});
|
|
171
|
+
afterEach(()=>{
|
|
172
|
+
if (originalTrailingSlash === undefined) {
|
|
173
|
+
delete process.env.NEXT_TRAILING_SLASH;
|
|
174
|
+
} else {
|
|
175
|
+
process.env.NEXT_TRAILING_SLASH = originalTrailingSlash;
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
it('should append trailing slash to relative admin URL', ()=>{
|
|
179
|
+
const result = formatAdminURL({
|
|
180
|
+
adminRoute: defaultAdminRoute,
|
|
181
|
+
path: dummyPath,
|
|
182
|
+
relative: true
|
|
183
|
+
});
|
|
184
|
+
expect(result).toBe(`${defaultAdminRoute}${dummyPath}/`);
|
|
185
|
+
});
|
|
186
|
+
it('should append trailing slash to relative api URL', ()=>{
|
|
187
|
+
const result = formatAdminURL({
|
|
188
|
+
apiRoute: '/api',
|
|
189
|
+
path: '/users',
|
|
190
|
+
relative: true
|
|
191
|
+
});
|
|
192
|
+
expect(result).toBe(`${process.env.NEXT_BASE_PATH || ''}/api/users/`);
|
|
193
|
+
});
|
|
194
|
+
it('should append trailing slash to absolute URL', ()=>{
|
|
195
|
+
const result = formatAdminURL({
|
|
196
|
+
adminRoute: defaultAdminRoute,
|
|
197
|
+
path: dummyPath,
|
|
198
|
+
serverURL
|
|
199
|
+
});
|
|
200
|
+
expect(result).toBe(`${serverURL}${process.env.NEXT_BASE_PATH || ''}${defaultAdminRoute}${dummyPath}/`);
|
|
201
|
+
});
|
|
202
|
+
it('should append trailing slash when basePath is set', ()=>{
|
|
203
|
+
const result = formatAdminURL({
|
|
204
|
+
apiRoute: '/api',
|
|
205
|
+
basePath: '/v1',
|
|
206
|
+
path: '/users',
|
|
207
|
+
serverURL
|
|
208
|
+
});
|
|
209
|
+
expect(result).toBe(`${serverURL}${process.env.NEXT_BASE_PATH || ''}/v1/api/users/`);
|
|
210
|
+
});
|
|
211
|
+
it('should not append trailing slash to root "/"', ()=>{
|
|
212
|
+
const result = formatAdminURL({
|
|
213
|
+
adminRoute: rootAdminRoute,
|
|
214
|
+
relative: true
|
|
215
|
+
});
|
|
216
|
+
expect(result).toBe('/');
|
|
217
|
+
});
|
|
218
|
+
it('should not double-slash when path already ends with /', ()=>{
|
|
219
|
+
const result = formatAdminURL({
|
|
220
|
+
adminRoute: defaultAdminRoute,
|
|
221
|
+
path: '/collections/posts',
|
|
222
|
+
relative: true
|
|
223
|
+
});
|
|
224
|
+
expect(result.endsWith('//')).toBe(false);
|
|
225
|
+
expect(result).toBe(`${defaultAdminRoute}/collections/posts/`);
|
|
226
|
+
});
|
|
227
|
+
it('should place trailing slash before query string', ()=>{
|
|
228
|
+
const path = `${dummyPath}?page=2`;
|
|
229
|
+
const result = formatAdminURL({
|
|
230
|
+
adminRoute: defaultAdminRoute,
|
|
231
|
+
path,
|
|
232
|
+
relative: true
|
|
233
|
+
});
|
|
234
|
+
expect(result).toBe(`${defaultAdminRoute}${dummyPath}/?page=2`);
|
|
235
|
+
});
|
|
236
|
+
it('should leave URLs unchanged when env var is not set', ()=>{
|
|
237
|
+
delete process.env.NEXT_TRAILING_SLASH;
|
|
238
|
+
const result = formatAdminURL({
|
|
239
|
+
adminRoute: defaultAdminRoute,
|
|
240
|
+
path: dummyPath,
|
|
241
|
+
relative: true
|
|
242
|
+
});
|
|
243
|
+
expect(result).toBe(`${defaultAdminRoute}${dummyPath}`);
|
|
244
|
+
});
|
|
245
|
+
it('should leave URLs unchanged when env var is "false"', ()=>{
|
|
246
|
+
process.env.NEXT_TRAILING_SLASH = 'false';
|
|
247
|
+
const result = formatAdminURL({
|
|
248
|
+
adminRoute: defaultAdminRoute,
|
|
249
|
+
path: dummyPath,
|
|
250
|
+
relative: true
|
|
251
|
+
});
|
|
252
|
+
expect(result).toBe(`${defaultAdminRoute}${dummyPath}`);
|
|
253
|
+
});
|
|
254
|
+
});
|
|
166
255
|
});
|
|
167
256
|
|
|
168
257
|
//# sourceMappingURL=formatAdminURL.spec.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/utilities/formatAdminURL.spec.ts"],"sourcesContent":["import { describe, it, expect } from 'vitest'\nimport { formatAdminURL } from './formatAdminURL.js'\n\ndescribe('formatAdminURL', () => {\n const serverURL = 'https://example.com'\n\n const defaultAdminRoute = '/admin'\n const rootAdminRoute = '/'\n\n const dummyPath = '/collections/posts'\n\n describe('relative URLs', () => {\n it('should ignore `serverURL` when relative=true', () => {\n const result = formatAdminURL({\n adminRoute: defaultAdminRoute,\n path: dummyPath,\n serverURL,\n relative: true,\n })\n\n expect(result).toBe(`${defaultAdminRoute}${dummyPath}`)\n })\n\n it('should force relative URL when `serverURL` is omitted', () => {\n const result = formatAdminURL({\n adminRoute: defaultAdminRoute,\n path: dummyPath,\n relative: false,\n })\n\n expect(result).toBe(`${defaultAdminRoute}${dummyPath}`)\n })\n })\n\n describe('absolute URLs', () => {\n it('should return absolute URL with serverURL', () => {\n const result = formatAdminURL({\n adminRoute: defaultAdminRoute,\n path: dummyPath,\n serverURL,\n })\n\n expect(result).toBe(\n `${serverURL}${process.env.NEXT_BASE_PATH || ''}${defaultAdminRoute}${dummyPath}`,\n )\n })\n\n it('should handle serverURL with trailing slash', () => {\n const result = formatAdminURL({\n adminRoute: defaultAdminRoute,\n path: '/collections/posts',\n serverURL: 'https://example.com/',\n })\n\n expect(result).toBe(\n `https://example.com${process.env.NEXT_BASE_PATH || ''}/admin/collections/posts`,\n )\n })\n\n it('should handle serverURL with subdirectory', () => {\n const result = formatAdminURL({\n adminRoute: defaultAdminRoute,\n path: '/collections/posts',\n serverURL: 'https://example.com/api/v1',\n })\n\n expect(result).toBe(\n `https://example.com${process.env.NEXT_BASE_PATH || ''}/admin/collections/posts`,\n )\n })\n })\n\n describe('admin route handling', () => {\n it('should return relative URL for adminRoute=\"/\", no path, no `serverURL`', () => {\n const result = formatAdminURL({\n adminRoute: rootAdminRoute,\n })\n\n expect(result).toBe('/')\n })\n\n it('should handle relative URL for adminRoute=\"/\", with path, no `serverURL`', () => {\n const result = formatAdminURL({\n adminRoute: rootAdminRoute,\n path: dummyPath,\n })\n\n expect(result).toBe(dummyPath)\n })\n\n it('should return absolute URL for adminRoute=\"/\", no path, with `serverURL`', () => {\n const result = formatAdminURL({\n adminRoute: rootAdminRoute,\n serverURL,\n })\n\n expect(result).toBe('https://example.com/')\n })\n\n it('should handle absolute URL for adminRoute=\"/\", with path and `serverURL`', () => {\n const result = formatAdminURL({\n adminRoute: rootAdminRoute,\n serverURL,\n path: dummyPath,\n })\n\n expect(result).toBe(`${serverURL}${process.env.NEXT_BASE_PATH || ''}${dummyPath}`)\n })\n })\n\n describe('base path handling', () => {\n it('should include basePath in URL', () => {\n const result = formatAdminURL({\n adminRoute: defaultAdminRoute,\n basePath: '/v1',\n path: dummyPath,\n serverURL,\n })\n\n expect(result).toBe(\n `${serverURL}${process.env.NEXT_BASE_PATH || ''}/v1${defaultAdminRoute}${dummyPath}`,\n )\n })\n\n it('should handle basePath with adminRoute=\"/\"', () => {\n const result = formatAdminURL({\n adminRoute: rootAdminRoute,\n basePath: '/v1',\n serverURL,\n })\n\n expect(result).toBe(`${serverURL}${process.env.NEXT_BASE_PATH || ''}/v1`)\n })\n\n it('should handle basePath with no adminRoute', () => {\n const result = formatAdminURL({\n adminRoute: undefined,\n basePath: '/v1',\n path: dummyPath,\n serverURL,\n })\n\n expect(result).toBe(`${serverURL}${process.env.NEXT_BASE_PATH || ''}/v1${dummyPath}`)\n })\n\n it('should handle empty basePath', () => {\n const result = formatAdminURL({\n adminRoute: defaultAdminRoute,\n basePath: '',\n path: dummyPath,\n serverURL,\n })\n\n expect(result).toBe(\n `${serverURL}${process.env.NEXT_BASE_PATH || ''}${defaultAdminRoute}${dummyPath}`,\n )\n })\n })\n\n describe('path handling', () => {\n it('should handle empty string path', () => {\n const result = formatAdminURL({\n adminRoute: defaultAdminRoute,\n path: '',\n serverURL,\n })\n\n expect(result).toBe(`${serverURL}${process.env.NEXT_BASE_PATH || ''}${defaultAdminRoute}`)\n })\n\n it('should handle null path', () => {\n const result = formatAdminURL({\n adminRoute: defaultAdminRoute,\n path: null,\n serverURL,\n })\n expect(result).toBe(`${serverURL}${process.env.NEXT_BASE_PATH || ''}${defaultAdminRoute}`)\n })\n\n it('should handle undefined path', () => {\n const result = formatAdminURL({\n adminRoute: defaultAdminRoute,\n path: undefined,\n serverURL,\n })\n\n expect(result).toBe(`${serverURL}${process.env.NEXT_BASE_PATH || ''}${defaultAdminRoute}`)\n })\n\n it('should handle path with query parameters', () => {\n const path = `${dummyPath}?page=2`\n\n const result = formatAdminURL({\n adminRoute: defaultAdminRoute,\n path,\n serverURL,\n })\n\n expect(result).toBe(\n `${serverURL}${process.env.NEXT_BASE_PATH || ''}${defaultAdminRoute}${path}`,\n )\n })\n })\n\n describe('edge cases', () => {\n it('should return \"/\" when given minimal args', () => {\n const result = formatAdminURL({\n adminRoute: undefined,\n basePath: '',\n path: '',\n relative: true,\n })\n\n expect(result).toBe('/')\n })\n })\n})\n"],"names":["describe","it","expect","formatAdminURL","serverURL","defaultAdminRoute","rootAdminRoute","dummyPath","result","adminRoute","path","relative","toBe","process","env","NEXT_BASE_PATH","basePath","undefined"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../src/utilities/formatAdminURL.spec.ts"],"sourcesContent":["import { afterEach, beforeEach, describe, it, expect } from 'vitest'\nimport { formatAdminURL } from './formatAdminURL.js'\n\ndescribe('formatAdminURL', () => {\n const serverURL = 'https://example.com'\n\n const defaultAdminRoute = '/admin'\n const rootAdminRoute = '/'\n\n const dummyPath = '/collections/posts'\n\n describe('relative URLs', () => {\n it('should ignore `serverURL` when relative=true', () => {\n const result = formatAdminURL({\n adminRoute: defaultAdminRoute,\n path: dummyPath,\n serverURL,\n relative: true,\n })\n\n expect(result).toBe(`${defaultAdminRoute}${dummyPath}`)\n })\n\n it('should force relative URL when `serverURL` is omitted', () => {\n const result = formatAdminURL({\n adminRoute: defaultAdminRoute,\n path: dummyPath,\n relative: false,\n })\n\n expect(result).toBe(`${defaultAdminRoute}${dummyPath}`)\n })\n })\n\n describe('absolute URLs', () => {\n it('should return absolute URL with serverURL', () => {\n const result = formatAdminURL({\n adminRoute: defaultAdminRoute,\n path: dummyPath,\n serverURL,\n })\n\n expect(result).toBe(\n `${serverURL}${process.env.NEXT_BASE_PATH || ''}${defaultAdminRoute}${dummyPath}`,\n )\n })\n\n it('should handle serverURL with trailing slash', () => {\n const result = formatAdminURL({\n adminRoute: defaultAdminRoute,\n path: '/collections/posts',\n serverURL: 'https://example.com/',\n })\n\n expect(result).toBe(\n `https://example.com${process.env.NEXT_BASE_PATH || ''}/admin/collections/posts`,\n )\n })\n\n it('should handle serverURL with subdirectory', () => {\n const result = formatAdminURL({\n adminRoute: defaultAdminRoute,\n path: '/collections/posts',\n serverURL: 'https://example.com/api/v1',\n })\n\n expect(result).toBe(\n `https://example.com${process.env.NEXT_BASE_PATH || ''}/admin/collections/posts`,\n )\n })\n })\n\n describe('admin route handling', () => {\n it('should return relative URL for adminRoute=\"/\", no path, no `serverURL`', () => {\n const result = formatAdminURL({\n adminRoute: rootAdminRoute,\n })\n\n expect(result).toBe('/')\n })\n\n it('should handle relative URL for adminRoute=\"/\", with path, no `serverURL`', () => {\n const result = formatAdminURL({\n adminRoute: rootAdminRoute,\n path: dummyPath,\n })\n\n expect(result).toBe(dummyPath)\n })\n\n it('should return absolute URL for adminRoute=\"/\", no path, with `serverURL`', () => {\n const result = formatAdminURL({\n adminRoute: rootAdminRoute,\n serverURL,\n })\n\n expect(result).toBe('https://example.com/')\n })\n\n it('should handle absolute URL for adminRoute=\"/\", with path and `serverURL`', () => {\n const result = formatAdminURL({\n adminRoute: rootAdminRoute,\n serverURL,\n path: dummyPath,\n })\n\n expect(result).toBe(`${serverURL}${process.env.NEXT_BASE_PATH || ''}${dummyPath}`)\n })\n })\n\n describe('base path handling', () => {\n it('should include basePath in URL', () => {\n const result = formatAdminURL({\n adminRoute: defaultAdminRoute,\n basePath: '/v1',\n path: dummyPath,\n serverURL,\n })\n\n expect(result).toBe(\n `${serverURL}${process.env.NEXT_BASE_PATH || ''}/v1${defaultAdminRoute}${dummyPath}`,\n )\n })\n\n it('should handle basePath with adminRoute=\"/\"', () => {\n const result = formatAdminURL({\n adminRoute: rootAdminRoute,\n basePath: '/v1',\n serverURL,\n })\n\n expect(result).toBe(`${serverURL}${process.env.NEXT_BASE_PATH || ''}/v1`)\n })\n\n it('should handle basePath with no adminRoute', () => {\n const result = formatAdminURL({\n adminRoute: undefined,\n basePath: '/v1',\n path: dummyPath,\n serverURL,\n })\n\n expect(result).toBe(`${serverURL}${process.env.NEXT_BASE_PATH || ''}/v1${dummyPath}`)\n })\n\n it('should handle empty basePath', () => {\n const result = formatAdminURL({\n adminRoute: defaultAdminRoute,\n basePath: '',\n path: dummyPath,\n serverURL,\n })\n\n expect(result).toBe(\n `${serverURL}${process.env.NEXT_BASE_PATH || ''}${defaultAdminRoute}${dummyPath}`,\n )\n })\n })\n\n describe('path handling', () => {\n it('should handle empty string path', () => {\n const result = formatAdminURL({\n adminRoute: defaultAdminRoute,\n path: '',\n serverURL,\n })\n\n expect(result).toBe(`${serverURL}${process.env.NEXT_BASE_PATH || ''}${defaultAdminRoute}`)\n })\n\n it('should handle null path', () => {\n const result = formatAdminURL({\n adminRoute: defaultAdminRoute,\n path: null,\n serverURL,\n })\n expect(result).toBe(`${serverURL}${process.env.NEXT_BASE_PATH || ''}${defaultAdminRoute}`)\n })\n\n it('should handle undefined path', () => {\n const result = formatAdminURL({\n adminRoute: defaultAdminRoute,\n path: undefined,\n serverURL,\n })\n\n expect(result).toBe(`${serverURL}${process.env.NEXT_BASE_PATH || ''}${defaultAdminRoute}`)\n })\n\n it('should handle path with query parameters', () => {\n const path = `${dummyPath}?page=2`\n\n const result = formatAdminURL({\n adminRoute: defaultAdminRoute,\n path,\n serverURL,\n })\n\n expect(result).toBe(\n `${serverURL}${process.env.NEXT_BASE_PATH || ''}${defaultAdminRoute}${path}`,\n )\n })\n })\n\n describe('edge cases', () => {\n it('should return \"/\" when given minimal args', () => {\n const result = formatAdminURL({\n adminRoute: undefined,\n basePath: '',\n path: '',\n relative: true,\n })\n\n expect(result).toBe('/')\n })\n })\n\n describe('trailing slash handling', () => {\n const originalTrailingSlash = process.env.NEXT_TRAILING_SLASH\n\n beforeEach(() => {\n process.env.NEXT_TRAILING_SLASH = 'true'\n })\n\n afterEach(() => {\n if (originalTrailingSlash === undefined) {\n delete process.env.NEXT_TRAILING_SLASH\n } else {\n process.env.NEXT_TRAILING_SLASH = originalTrailingSlash\n }\n })\n\n it('should append trailing slash to relative admin URL', () => {\n const result = formatAdminURL({\n adminRoute: defaultAdminRoute,\n path: dummyPath,\n relative: true,\n })\n\n expect(result).toBe(`${defaultAdminRoute}${dummyPath}/`)\n })\n\n it('should append trailing slash to relative api URL', () => {\n const result = formatAdminURL({\n apiRoute: '/api',\n path: '/users',\n relative: true,\n })\n\n expect(result).toBe(`${process.env.NEXT_BASE_PATH || ''}/api/users/`)\n })\n\n it('should append trailing slash to absolute URL', () => {\n const result = formatAdminURL({\n adminRoute: defaultAdminRoute,\n path: dummyPath,\n serverURL,\n })\n\n expect(result).toBe(\n `${serverURL}${process.env.NEXT_BASE_PATH || ''}${defaultAdminRoute}${dummyPath}/`,\n )\n })\n\n it('should append trailing slash when basePath is set', () => {\n const result = formatAdminURL({\n apiRoute: '/api',\n basePath: '/v1',\n path: '/users',\n serverURL,\n })\n\n expect(result).toBe(`${serverURL}${process.env.NEXT_BASE_PATH || ''}/v1/api/users/`)\n })\n\n it('should not append trailing slash to root \"/\"', () => {\n const result = formatAdminURL({\n adminRoute: rootAdminRoute,\n relative: true,\n })\n\n expect(result).toBe('/')\n })\n\n it('should not double-slash when path already ends with /', () => {\n const result = formatAdminURL({\n adminRoute: defaultAdminRoute,\n path: '/collections/posts',\n relative: true,\n })\n\n expect(result.endsWith('//')).toBe(false)\n expect(result).toBe(`${defaultAdminRoute}/collections/posts/`)\n })\n\n it('should place trailing slash before query string', () => {\n const path = `${dummyPath}?page=2`\n\n const result = formatAdminURL({\n adminRoute: defaultAdminRoute,\n path,\n relative: true,\n })\n\n expect(result).toBe(`${defaultAdminRoute}${dummyPath}/?page=2`)\n })\n\n it('should leave URLs unchanged when env var is not set', () => {\n delete process.env.NEXT_TRAILING_SLASH\n\n const result = formatAdminURL({\n adminRoute: defaultAdminRoute,\n path: dummyPath,\n relative: true,\n })\n\n expect(result).toBe(`${defaultAdminRoute}${dummyPath}`)\n })\n\n it('should leave URLs unchanged when env var is \"false\"', () => {\n process.env.NEXT_TRAILING_SLASH = 'false'\n\n const result = formatAdminURL({\n adminRoute: defaultAdminRoute,\n path: dummyPath,\n relative: true,\n })\n\n expect(result).toBe(`${defaultAdminRoute}${dummyPath}`)\n })\n })\n})\n"],"names":["afterEach","beforeEach","describe","it","expect","formatAdminURL","serverURL","defaultAdminRoute","rootAdminRoute","dummyPath","result","adminRoute","path","relative","toBe","process","env","NEXT_BASE_PATH","basePath","undefined","originalTrailingSlash","NEXT_TRAILING_SLASH","apiRoute","endsWith"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"handleEndpoints.d.ts","sourceRoot":"","sources":["../../src/utilities/handleEndpoints.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAA4B,eAAe,EAAE,MAAM,oBAAoB,CAAA;AA0BnF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,eAAO,MAAM,eAAe,kFAMzB;IACD,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,MAAM,EAAE,OAAO,CAAC,eAAe,CAAC,GAAG,eAAe,CAAA;IAClD,qCAAqC;IACrC,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,uBAAuB,CAAC,EAAE,MAAM,CAAA;IAChC,OAAO,EAAE,OAAO,CAAA;CACjB,KAAG,OAAO,CAAC,QAAQ,
|
|
1
|
+
{"version":3,"file":"handleEndpoints.d.ts","sourceRoot":"","sources":["../../src/utilities/handleEndpoints.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAA4B,eAAe,EAAE,MAAM,oBAAoB,CAAA;AA0BnF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,eAAO,MAAM,eAAe,kFAMzB;IACD,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,MAAM,EAAE,OAAO,CAAC,eAAe,CAAC,GAAG,eAAe,CAAA;IAClD,qCAAqC;IACrC,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,uBAAuB,CAAC,EAAE,MAAM,CAAA;IAChC,OAAO,EAAE,OAAO,CAAA;CACjB,KAAG,OAAO,CAAC,QAAQ,CAmNnB,CAAA"}
|
|
@@ -103,11 +103,13 @@ const notFoundResponse = (req, pathname)=>{
|
|
|
103
103
|
});
|
|
104
104
|
const { payload } = req;
|
|
105
105
|
const { config } = payload;
|
|
106
|
-
const
|
|
107
|
-
const
|
|
106
|
+
const rawPathname = path ?? new URL(req.url).pathname;
|
|
107
|
+
const pathname = rawPathname.length > 1 ? rawPathname.replace(/\/$/, '') : rawPathname;
|
|
108
|
+
const rawBaseAPIPath = formatAdminURL({
|
|
108
109
|
apiRoute: config.routes.api,
|
|
109
110
|
path: ''
|
|
110
111
|
});
|
|
112
|
+
const baseAPIPath = rawBaseAPIPath.length > 1 ? rawBaseAPIPath.replace(/\/$/, '') : rawBaseAPIPath;
|
|
111
113
|
if (!pathname.startsWith(baseAPIPath)) {
|
|
112
114
|
return notFoundResponse(req, pathname);
|
|
113
115
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/utilities/handleEndpoints.ts"],"sourcesContent":["import { status as httpStatus } from 'http-status'\nimport { match } from 'path-to-regexp'\n\nimport type { Collection } from '../collections/config/types.js'\nimport type { Endpoint, PayloadHandler, SanitizedConfig } from '../config/types.js'\nimport type { APIError } from '../errors/APIError.js'\nimport type { GlobalConfig } from '../globals/config/types.js'\nimport type { PayloadRequest } from '../types/index.js'\n\nimport { createPayloadRequest } from './createPayloadRequest.js'\nimport { formatAdminURL } from './formatAdminURL.js'\nimport { headersWithCors } from './headersWithCors.js'\nimport { mergeHeaders } from './mergeHeaders.js'\nimport { routeError } from './routeError.js'\n\nconst notFoundResponse = (req: PayloadRequest, pathname?: string) => {\n return Response.json(\n {\n message: `Route not found \"${pathname ?? new URL(req.url!).pathname}\"`,\n },\n {\n headers: headersWithCors({\n headers: new Headers(),\n req,\n }),\n status: httpStatus.NOT_FOUND,\n },\n )\n}\n\n/**\n * Attaches the Payload REST API to any backend framework that uses Fetch Request/Response\n * like Next.js (app router), Remix, Bun, Hono.\n *\n * ### Example: Using Hono\n * ```ts\n * import { handleEndpoints } from 'payload';\n * import { serve } from '@hono/node-server';\n * import { loadEnv } from 'payload/node';\n *\n * const port = 3001;\n * loadEnv();\n *\n * const { default: config } = await import('@payload-config');\n *\n * const server = serve({\n * fetch: async (request) => {\n * const response = await handleEndpoints({\n * config,\n * request: request.clone(),\n * });\n *\n * return response;\n * },\n * port,\n * });\n *\n * server.on('listening', () => {\n * console.log(`API server is listening on http://localhost:${port}/api`);\n * });\n * ```\n */\nexport const handleEndpoints = async ({\n basePath = '',\n config: incomingConfig,\n path,\n payloadInstanceCacheKey,\n request,\n}: {\n basePath?: string\n config: Promise<SanitizedConfig> | SanitizedConfig\n /** Override path from the request */\n path?: string\n payloadInstanceCacheKey?: string\n request: Request\n}): Promise<Response> => {\n let handler!: PayloadHandler\n let req: PayloadRequest\n let collection!: Collection\n\n // This can be used against GET request search params size limit.\n // Instead you can do POST request with a text body as search params.\n // We use this internally for relationships querying on the frontend\n // packages/ui/src/fields/Relationship/index.tsx\n if (\n request.method.toLowerCase() === 'post' &&\n (request.headers.get('X-Payload-HTTP-Method-Override') === 'GET' ||\n request.headers.get('X-HTTP-Method-Override') === 'GET')\n ) {\n let url = request.url\n let data: any = undefined\n\n if (request.headers.get('Content-Type') === 'application/x-www-form-urlencoded') {\n const search = await request.text()\n url = `${request.url}?${search}`\n } else if (request.headers.get('Content-Type') === 'application/json') {\n // May not be supported by every endpoint\n data = await request.json()\n\n // locale and fallbackLocale is read by createPayloadRequest to populate req.locale and req.fallbackLocale\n // => add to searchParams\n if (data?.locale) {\n url += `?locale=${data.locale}`\n }\n if (data?.fallbackLocale) {\n url += `&fallbackLocale=${data.depth}`\n }\n }\n\n const req = new Request(url, {\n // @ts-expect-error // TODO: check if this is required\n cache: request.cache,\n credentials: request.credentials,\n headers: request.headers,\n method: 'GET',\n signal: request.signal,\n })\n\n if (data) {\n // @ts-expect-error attach data to request - less overhead than using urlencoded\n req.data = data\n }\n\n const response = await handleEndpoints({\n basePath,\n config: incomingConfig,\n path,\n payloadInstanceCacheKey,\n request: req,\n })\n\n return response\n }\n\n try {\n req = await createPayloadRequest({\n canSetHeaders: true,\n config: incomingConfig,\n payloadInstanceCacheKey,\n request,\n })\n\n const { payload } = req\n const { config } = payload\n\n const pathname = path ?? new URL(req.url!).pathname\n const baseAPIPath = formatAdminURL({\n apiRoute: config.routes.api,\n path: '',\n })\n\n if (!pathname.startsWith(baseAPIPath)) {\n return notFoundResponse(req, pathname)\n }\n\n // /api/posts/route -> /posts/route\n let adjustedPathname = pathname.replace(baseAPIPath, '')\n\n let isGlobals = false\n\n // /globals/header/route -> /header/route\n if (adjustedPathname.startsWith('/globals')) {\n isGlobals = true\n adjustedPathname = adjustedPathname.replace('/globals', '')\n }\n\n const segments = adjustedPathname.split('/')\n // remove empty string first element\n segments.shift()\n\n const firstParam = segments[0]\n\n let globalConfig!: GlobalConfig\n\n // first param can be a global slug or collection slug, find the relevant config\n if (firstParam) {\n if (isGlobals) {\n globalConfig = payload.globals.config.find((each) => each.slug === firstParam)!\n } else if (payload.collections[firstParam]) {\n collection = payload.collections[firstParam]\n }\n }\n\n let endpoints: Endpoint[] | false = config.endpoints\n\n if (collection) {\n endpoints = collection.config.endpoints\n // /posts/route -> /route\n adjustedPathname = adjustedPathname.replace(`/${collection.config.slug}`, '')\n } else if (globalConfig) {\n // /header/route -> /route\n adjustedPathname = adjustedPathname.replace(`/${globalConfig.slug}`, '')\n endpoints = globalConfig.endpoints!\n }\n\n // sanitize when endpoint.path is '/'\n if (adjustedPathname === '') {\n adjustedPathname = '/'\n }\n\n if (endpoints === false) {\n return Response.json(\n {\n message: `Cannot ${req.method?.toUpperCase()} ${req.url}`,\n },\n {\n headers: headersWithCors({\n headers: new Headers(),\n req,\n }),\n status: httpStatus.NOT_IMPLEMENTED,\n },\n )\n }\n\n // Find the relevant endpoint configuration\n const endpoint = endpoints?.find((endpoint) => {\n if (endpoint.method !== req.method?.toLowerCase()) {\n return false\n }\n\n const pathMatchFn = match(endpoint.path, { decode: decodeURIComponent })\n\n const matchResult = pathMatchFn(adjustedPathname)\n\n if (!matchResult) {\n return false\n }\n\n req.routeParams = matchResult.params as Record<string, unknown>\n\n // Inject to routeParams the slug as well so it can be used later\n if (collection) {\n req.routeParams.collection = collection.config.slug\n } else if (globalConfig) {\n req.routeParams.global = globalConfig.slug\n }\n\n return true\n })\n\n if (endpoint) {\n handler = endpoint.handler\n }\n\n if (!handler) {\n // If no custom handler found and this is an OPTIONS request,\n // return default CORS response for preflight requests\n if (req.method?.toLowerCase() === 'options') {\n return Response.json(\n {},\n {\n headers: headersWithCors({\n headers: new Headers(),\n req,\n }),\n status: 200,\n },\n )\n }\n\n return notFoundResponse(req, pathname)\n }\n\n const response = await handler(req)\n\n return new Response(response.body, {\n headers: headersWithCors({\n headers: mergeHeaders(req.responseHeaders ?? new Headers(), response.headers),\n req,\n }),\n status: response.status,\n statusText: response.statusText,\n })\n } catch (_err) {\n const err = _err as APIError\n return routeError({\n collection,\n config: incomingConfig,\n err,\n req: req!,\n })\n }\n}\n"],"names":["status","httpStatus","match","createPayloadRequest","formatAdminURL","headersWithCors","mergeHeaders","routeError","notFoundResponse","req","pathname","Response","json","message","URL","url","headers","Headers","NOT_FOUND","handleEndpoints","basePath","config","incomingConfig","path","payloadInstanceCacheKey","request","handler","collection","method","toLowerCase","get","data","undefined","search","text","locale","fallbackLocale","depth","Request","cache","credentials","signal","response","canSetHeaders","payload","baseAPIPath","apiRoute","routes","api","startsWith","adjustedPathname","replace","isGlobals","segments","split","shift","firstParam","globalConfig","globals","find","each","slug","collections","endpoints","toUpperCase","NOT_IMPLEMENTED","endpoint","pathMatchFn","decode","decodeURIComponent","matchResult","routeParams","params","global","body","responseHeaders","statusText","_err","err"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../src/utilities/handleEndpoints.ts"],"sourcesContent":["import { status as httpStatus } from 'http-status'\nimport { match } from 'path-to-regexp'\n\nimport type { Collection } from '../collections/config/types.js'\nimport type { Endpoint, PayloadHandler, SanitizedConfig } from '../config/types.js'\nimport type { APIError } from '../errors/APIError.js'\nimport type { GlobalConfig } from '../globals/config/types.js'\nimport type { PayloadRequest } from '../types/index.js'\n\nimport { createPayloadRequest } from './createPayloadRequest.js'\nimport { formatAdminURL } from './formatAdminURL.js'\nimport { headersWithCors } from './headersWithCors.js'\nimport { mergeHeaders } from './mergeHeaders.js'\nimport { routeError } from './routeError.js'\n\nconst notFoundResponse = (req: PayloadRequest, pathname?: string) => {\n return Response.json(\n {\n message: `Route not found \"${pathname ?? new URL(req.url!).pathname}\"`,\n },\n {\n headers: headersWithCors({\n headers: new Headers(),\n req,\n }),\n status: httpStatus.NOT_FOUND,\n },\n )\n}\n\n/**\n * Attaches the Payload REST API to any backend framework that uses Fetch Request/Response\n * like Next.js (app router), Remix, Bun, Hono.\n *\n * ### Example: Using Hono\n * ```ts\n * import { handleEndpoints } from 'payload';\n * import { serve } from '@hono/node-server';\n * import { loadEnv } from 'payload/node';\n *\n * const port = 3001;\n * loadEnv();\n *\n * const { default: config } = await import('@payload-config');\n *\n * const server = serve({\n * fetch: async (request) => {\n * const response = await handleEndpoints({\n * config,\n * request: request.clone(),\n * });\n *\n * return response;\n * },\n * port,\n * });\n *\n * server.on('listening', () => {\n * console.log(`API server is listening on http://localhost:${port}/api`);\n * });\n * ```\n */\nexport const handleEndpoints = async ({\n basePath = '',\n config: incomingConfig,\n path,\n payloadInstanceCacheKey,\n request,\n}: {\n basePath?: string\n config: Promise<SanitizedConfig> | SanitizedConfig\n /** Override path from the request */\n path?: string\n payloadInstanceCacheKey?: string\n request: Request\n}): Promise<Response> => {\n let handler!: PayloadHandler\n let req: PayloadRequest\n let collection!: Collection\n\n // This can be used against GET request search params size limit.\n // Instead you can do POST request with a text body as search params.\n // We use this internally for relationships querying on the frontend\n // packages/ui/src/fields/Relationship/index.tsx\n if (\n request.method.toLowerCase() === 'post' &&\n (request.headers.get('X-Payload-HTTP-Method-Override') === 'GET' ||\n request.headers.get('X-HTTP-Method-Override') === 'GET')\n ) {\n let url = request.url\n let data: any = undefined\n\n if (request.headers.get('Content-Type') === 'application/x-www-form-urlencoded') {\n const search = await request.text()\n url = `${request.url}?${search}`\n } else if (request.headers.get('Content-Type') === 'application/json') {\n // May not be supported by every endpoint\n data = await request.json()\n\n // locale and fallbackLocale is read by createPayloadRequest to populate req.locale and req.fallbackLocale\n // => add to searchParams\n if (data?.locale) {\n url += `?locale=${data.locale}`\n }\n if (data?.fallbackLocale) {\n url += `&fallbackLocale=${data.depth}`\n }\n }\n\n const req = new Request(url, {\n // @ts-expect-error // TODO: check if this is required\n cache: request.cache,\n credentials: request.credentials,\n headers: request.headers,\n method: 'GET',\n signal: request.signal,\n })\n\n if (data) {\n // @ts-expect-error attach data to request - less overhead than using urlencoded\n req.data = data\n }\n\n const response = await handleEndpoints({\n basePath,\n config: incomingConfig,\n path,\n payloadInstanceCacheKey,\n request: req,\n })\n\n return response\n }\n\n try {\n req = await createPayloadRequest({\n canSetHeaders: true,\n config: incomingConfig,\n payloadInstanceCacheKey,\n request,\n })\n\n const { payload } = req\n const { config } = payload\n\n const rawPathname = path ?? new URL(req.url!).pathname\n const pathname = rawPathname.length > 1 ? rawPathname.replace(/\\/$/, '') : rawPathname\n const rawBaseAPIPath = formatAdminURL({\n apiRoute: config.routes.api,\n path: '',\n })\n const baseAPIPath =\n rawBaseAPIPath.length > 1 ? rawBaseAPIPath.replace(/\\/$/, '') : rawBaseAPIPath\n\n if (!pathname.startsWith(baseAPIPath)) {\n return notFoundResponse(req, pathname)\n }\n\n // /api/posts/route -> /posts/route\n let adjustedPathname = pathname.replace(baseAPIPath, '')\n\n let isGlobals = false\n\n // /globals/header/route -> /header/route\n if (adjustedPathname.startsWith('/globals')) {\n isGlobals = true\n adjustedPathname = adjustedPathname.replace('/globals', '')\n }\n\n const segments = adjustedPathname.split('/')\n // remove empty string first element\n segments.shift()\n\n const firstParam = segments[0]\n\n let globalConfig!: GlobalConfig\n\n // first param can be a global slug or collection slug, find the relevant config\n if (firstParam) {\n if (isGlobals) {\n globalConfig = payload.globals.config.find((each) => each.slug === firstParam)!\n } else if (payload.collections[firstParam]) {\n collection = payload.collections[firstParam]\n }\n }\n\n let endpoints: Endpoint[] | false = config.endpoints\n\n if (collection) {\n endpoints = collection.config.endpoints\n // /posts/route -> /route\n adjustedPathname = adjustedPathname.replace(`/${collection.config.slug}`, '')\n } else if (globalConfig) {\n // /header/route -> /route\n adjustedPathname = adjustedPathname.replace(`/${globalConfig.slug}`, '')\n endpoints = globalConfig.endpoints!\n }\n\n // sanitize when endpoint.path is '/'\n if (adjustedPathname === '') {\n adjustedPathname = '/'\n }\n\n if (endpoints === false) {\n return Response.json(\n {\n message: `Cannot ${req.method?.toUpperCase()} ${req.url}`,\n },\n {\n headers: headersWithCors({\n headers: new Headers(),\n req,\n }),\n status: httpStatus.NOT_IMPLEMENTED,\n },\n )\n }\n\n // Find the relevant endpoint configuration\n const endpoint = endpoints?.find((endpoint) => {\n if (endpoint.method !== req.method?.toLowerCase()) {\n return false\n }\n\n const pathMatchFn = match(endpoint.path, { decode: decodeURIComponent })\n\n const matchResult = pathMatchFn(adjustedPathname)\n\n if (!matchResult) {\n return false\n }\n\n req.routeParams = matchResult.params as Record<string, unknown>\n\n // Inject to routeParams the slug as well so it can be used later\n if (collection) {\n req.routeParams.collection = collection.config.slug\n } else if (globalConfig) {\n req.routeParams.global = globalConfig.slug\n }\n\n return true\n })\n\n if (endpoint) {\n handler = endpoint.handler\n }\n\n if (!handler) {\n // If no custom handler found and this is an OPTIONS request,\n // return default CORS response for preflight requests\n if (req.method?.toLowerCase() === 'options') {\n return Response.json(\n {},\n {\n headers: headersWithCors({\n headers: new Headers(),\n req,\n }),\n status: 200,\n },\n )\n }\n\n return notFoundResponse(req, pathname)\n }\n\n const response = await handler(req)\n\n return new Response(response.body, {\n headers: headersWithCors({\n headers: mergeHeaders(req.responseHeaders ?? new Headers(), response.headers),\n req,\n }),\n status: response.status,\n statusText: response.statusText,\n })\n } catch (_err) {\n const err = _err as APIError\n return routeError({\n collection,\n config: incomingConfig,\n err,\n req: req!,\n })\n }\n}\n"],"names":["status","httpStatus","match","createPayloadRequest","formatAdminURL","headersWithCors","mergeHeaders","routeError","notFoundResponse","req","pathname","Response","json","message","URL","url","headers","Headers","NOT_FOUND","handleEndpoints","basePath","config","incomingConfig","path","payloadInstanceCacheKey","request","handler","collection","method","toLowerCase","get","data","undefined","search","text","locale","fallbackLocale","depth","Request","cache","credentials","signal","response","canSetHeaders","payload","rawPathname","length","replace","rawBaseAPIPath","apiRoute","routes","api","baseAPIPath","startsWith","adjustedPathname","isGlobals","segments","split","shift","firstParam","globalConfig","globals","find","each","slug","collections","endpoints","toUpperCase","NOT_IMPLEMENTED","endpoint","pathMatchFn","decode","decodeURIComponent","matchResult","routeParams","params","global","body","responseHeaders","statusText","_err","err"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "payload",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.0.0-internal.5b1e7cd",
|
|
4
4
|
"description": "Node, React, Headless CMS and Application Framework built on Next.js",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"admin panel",
|
|
@@ -115,7 +115,7 @@
|
|
|
115
115
|
"undici": "7.24.4",
|
|
116
116
|
"uuid": "11.1.0",
|
|
117
117
|
"ws": "^8.16.0",
|
|
118
|
-
"@payloadcms/translations": "
|
|
118
|
+
"@payloadcms/translations": "4.0.0-internal.5b1e7cd"
|
|
119
119
|
},
|
|
120
120
|
"devDependencies": {
|
|
121
121
|
"@hyrious/esbuild-plugin-commonjs": "0.2.6",
|