@pro-laico/payload-seed 0.0.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 (71) hide show
  1. package/LICENSE.md +22 -0
  2. package/README.md +9 -0
  3. package/dist/bin/seed.d.ts +9 -0
  4. package/dist/bin/seed.d.ts.map +1 -0
  5. package/dist/bin/seed.js +36 -0
  6. package/dist/bin/seed.js.map +1 -0
  7. package/dist/components/SeedButton.d.ts +11 -0
  8. package/dist/components/SeedButton.d.ts.map +1 -0
  9. package/dist/components/SeedButton.js +82 -0
  10. package/dist/components/SeedButton.js.map +1 -0
  11. package/dist/defineSeed.d.ts +43 -0
  12. package/dist/defineSeed.d.ts.map +1 -0
  13. package/dist/defineSeed.js +51 -0
  14. package/dist/defineSeed.js.map +1 -0
  15. package/dist/endpoint.d.ts +10 -0
  16. package/dist/endpoint.d.ts.map +1 -0
  17. package/dist/endpoint.js +60 -0
  18. package/dist/endpoint.js.map +1 -0
  19. package/dist/engine/files.d.ts +15 -0
  20. package/dist/engine/files.d.ts.map +1 -0
  21. package/dist/engine/files.js +72 -0
  22. package/dist/engine/files.js.map +1 -0
  23. package/dist/engine/graph.d.ts +61 -0
  24. package/dist/engine/graph.d.ts.map +1 -0
  25. package/dist/engine/graph.js +0 -0
  26. package/dist/engine/graph.js.map +1 -0
  27. package/dist/engine/run.d.ts +46 -0
  28. package/dist/engine/run.d.ts.map +1 -0
  29. package/dist/engine/run.js +387 -0
  30. package/dist/engine/run.js.map +1 -0
  31. package/dist/engine/tokens.d.ts +16 -0
  32. package/dist/engine/tokens.d.ts.map +1 -0
  33. package/dist/engine/tokens.js +37 -0
  34. package/dist/engine/tokens.js.map +1 -0
  35. package/dist/engine/validate.d.ts +28 -0
  36. package/dist/engine/validate.d.ts.map +1 -0
  37. package/dist/engine/validate.js +79 -0
  38. package/dist/engine/validate.js.map +1 -0
  39. package/dist/guard.d.ts +11 -0
  40. package/dist/guard.d.ts.map +1 -0
  41. package/dist/guard.js +11 -0
  42. package/dist/guard.js.map +1 -0
  43. package/dist/index.d.ts +9 -0
  44. package/dist/index.d.ts.map +1 -0
  45. package/dist/index.js +13 -0
  46. package/dist/index.js.map +1 -0
  47. package/dist/options.d.ts +29 -0
  48. package/dist/options.d.ts.map +1 -0
  49. package/dist/options.js +10 -0
  50. package/dist/options.js.map +1 -0
  51. package/dist/plugin.d.ts +14 -0
  52. package/dist/plugin.d.ts.map +1 -0
  53. package/dist/plugin.js +73 -0
  54. package/dist/plugin.js.map +1 -0
  55. package/dist/refs.d.ts +56 -0
  56. package/dist/refs.d.ts.map +1 -0
  57. package/dist/refs.js +39 -0
  58. package/dist/refs.js.map +1 -0
  59. package/dist/registry.d.ts +31 -0
  60. package/dist/registry.d.ts.map +1 -0
  61. package/dist/registry.js +20 -0
  62. package/dist/registry.js.map +1 -0
  63. package/dist/typegen.d.ts +13 -0
  64. package/dist/typegen.d.ts.map +1 -0
  65. package/dist/typegen.js +40 -0
  66. package/dist/typegen.js.map +1 -0
  67. package/dist/types.d.ts +81 -0
  68. package/dist/types.d.ts.map +1 -0
  69. package/dist/types.js +3 -0
  70. package/dist/types.js.map +1 -0
  71. package/package.json +70 -0
package/LICENSE.md ADDED
@@ -0,0 +1,22 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024-2026 Pro Laico LLC <contact@prolaico.com>
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ 'Software'), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
20
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
21
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
22
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,9 @@
1
+ # @pro-laico/payload-seed
2
+
3
+ Type-safe database seeding for [Payload CMS](https://payloadcms.com/) — write seed data in `seed.ts` files, reference other docs with typed tokens instead of raw ids, attach upload files inline, and the plugin orders dependencies and creates everything.
4
+
5
+ ```bash
6
+ pnpm add @pro-laico/payload-seed
7
+ ```
8
+
9
+ **[Documentation →](https://payload-plugins.prolaico.com/docs/plugins/payload-seed)**
@@ -0,0 +1,9 @@
1
+ import { type SanitizedConfig } from 'payload';
2
+ /**
3
+ * Payload custom-bin entry. `seedPlugin` registers this under `config.bin` as the `seed`
4
+ * command, so `payload seed` runs the seed via the Local API — no per-project runner
5
+ * script. Behind the `ENABLE_SEED` guard (the seed is destructive). Reads the plugin
6
+ * options the project passed to `seedPlugin` from `config.custom.payloadSeed`.
7
+ */
8
+ export declare const script: (config: SanitizedConfig) => Promise<void>;
9
+ //# sourceMappingURL=seed.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"seed.d.ts","sourceRoot":"","sources":["../../src/bin/seed.ts"],"names":[],"mappings":"AAAA,OAAO,EAAc,KAAK,eAAe,EAAE,MAAM,SAAS,CAAA;AAK1D;;;;;GAKG;AACH,eAAO,MAAM,MAAM,GAAU,QAAQ,eAAe,KAAG,OAAO,CAAC,IAAI,CAoBlE,CAAA"}
@@ -0,0 +1,36 @@
1
+ import { getPayload } from "payload";
2
+ import { seed } from "../engine/run.js";
3
+ import { SEED_DISABLED_MESSAGE, seedingEnabled } from "../guard.js";
4
+ /**
5
+ * Payload custom-bin entry. `seedPlugin` registers this under `config.bin` as the `seed`
6
+ * command, so `payload seed` runs the seed via the Local API — no per-project runner
7
+ * script. Behind the `ENABLE_SEED` guard (the seed is destructive). Reads the plugin
8
+ * options the project passed to `seedPlugin` from `config.custom.payloadSeed`.
9
+ */ export const script = async (config)=>{
10
+ if (!seedingEnabled()) {
11
+ console.error(SEED_DISABLED_MESSAGE);
12
+ process.exitCode = 1;
13
+ return;
14
+ }
15
+ const options = config.custom?.payloadSeed?.options ?? {};
16
+ const payload = await getPayload({
17
+ config
18
+ });
19
+ try {
20
+ const result = await seed({
21
+ payload,
22
+ options
23
+ });
24
+ // One human-sized summary line — the full result (created/order/deferred/skipped) is for
25
+ // programmatic consumers of `seed()`, not the terminal.
26
+ const total = Object.values(result.created).reduce((sum, n)=>sum + n, 0);
27
+ const skipped = result.skipped.length ? ` (${result.skipped.length} definition${result.skipped.length === 1 ? '' : 's'} skipped)` : '';
28
+ payload.logger.info(`[payload-seed] created ${total} docs across ${Object.keys(result.created).length} collections${skipped}`);
29
+ } finally{
30
+ // Close adapter connections so the process doesn't hang on open handles.
31
+ await payload.db.destroy?.();
32
+ }
33
+ process.exit(0);
34
+ };
35
+
36
+ //# sourceMappingURL=seed.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/bin/seed.ts"],"sourcesContent":["import { getPayload, type SanitizedConfig } from 'payload'\nimport { seed } from '../engine/run'\nimport { SEED_DISABLED_MESSAGE, seedingEnabled } from '../guard'\nimport type { SeedPluginOptions } from '../options'\n\n/**\n * Payload custom-bin entry. `seedPlugin` registers this under `config.bin` as the `seed`\n * command, so `payload seed` runs the seed via the Local API — no per-project runner\n * script. Behind the `ENABLE_SEED` guard (the seed is destructive). Reads the plugin\n * options the project passed to `seedPlugin` from `config.custom.payloadSeed`.\n */\nexport const script = async (config: SanitizedConfig): Promise<void> => {\n if (!seedingEnabled()) {\n console.error(SEED_DISABLED_MESSAGE)\n process.exitCode = 1\n return\n }\n const options = (config.custom?.payloadSeed?.options ?? {}) as SeedPluginOptions\n const payload = await getPayload({ config })\n try {\n const result = await seed({ payload, options })\n // One human-sized summary line — the full result (created/order/deferred/skipped) is for\n // programmatic consumers of `seed()`, not the terminal.\n const total = Object.values(result.created).reduce((sum, n) => sum + n, 0)\n const skipped = result.skipped.length ? ` (${result.skipped.length} definition${result.skipped.length === 1 ? '' : 's'} skipped)` : ''\n payload.logger.info(`[payload-seed] created ${total} docs across ${Object.keys(result.created).length} collections${skipped}`)\n } finally {\n // Close adapter connections so the process doesn't hang on open handles.\n await payload.db.destroy?.()\n }\n process.exit(0)\n}\n"],"names":["getPayload","seed","SEED_DISABLED_MESSAGE","seedingEnabled","script","config","console","error","process","exitCode","options","custom","payloadSeed","payload","result","total","Object","values","created","reduce","sum","n","skipped","length","logger","info","keys","db","destroy","exit"],"mappings":"AAAA,SAASA,UAAU,QAA8B,UAAS;AAC1D,SAASC,IAAI,QAAQ,mBAAe;AACpC,SAASC,qBAAqB,EAAEC,cAAc,QAAQ,cAAU;AAGhE;;;;;CAKC,GACD,OAAO,MAAMC,SAAS,OAAOC;IAC3B,IAAI,CAACF,kBAAkB;QACrBG,QAAQC,KAAK,CAACL;QACdM,QAAQC,QAAQ,GAAG;QACnB;IACF;IACA,MAAMC,UAAWL,OAAOM,MAAM,EAAEC,aAAaF,WAAW,CAAC;IACzD,MAAMG,UAAU,MAAMb,WAAW;QAAEK;IAAO;IAC1C,IAAI;QACF,MAAMS,SAAS,MAAMb,KAAK;YAAEY;YAASH;QAAQ;QAC7C,yFAAyF;QACzF,wDAAwD;QACxD,MAAMK,QAAQC,OAAOC,MAAM,CAACH,OAAOI,OAAO,EAAEC,MAAM,CAAC,CAACC,KAAKC,IAAMD,MAAMC,GAAG;QACxE,MAAMC,UAAUR,OAAOQ,OAAO,CAACC,MAAM,GAAG,CAAC,EAAE,EAAET,OAAOQ,OAAO,CAACC,MAAM,CAAC,WAAW,EAAET,OAAOQ,OAAO,CAACC,MAAM,KAAK,IAAI,KAAK,IAAI,SAAS,CAAC,GAAG;QACpIV,QAAQW,MAAM,CAACC,IAAI,CAAC,CAAC,uBAAuB,EAAEV,MAAM,aAAa,EAAEC,OAAOU,IAAI,CAACZ,OAAOI,OAAO,EAAEK,MAAM,CAAC,YAAY,EAAED,SAAS;IAC/H,SAAU;QACR,yEAAyE;QACzE,MAAMT,QAAQc,EAAE,CAACC,OAAO;IAC1B;IACApB,QAAQqB,IAAI,CAAC;AACf,EAAC"}
@@ -0,0 +1,11 @@
1
+ import type React from 'react';
2
+ export interface SeedButtonProps {
3
+ /** Endpoint URL the button POSTs to. Defaults to `<routes.api>/seed` from the admin config. */
4
+ endpoint?: string;
5
+ }
6
+ /** Admin dashboard button that triggers `POST /api/seed`. Injected via the plugin's
7
+ * `adminButton` option, or registered manually in `admin.components.beforeDashboard`.
8
+ * The seed wipes seeded collections, so the first click arms a confirm and the second runs. */
9
+ export declare const SeedButton: React.FC<SeedButtonProps>;
10
+ export default SeedButton;
11
+ //# sourceMappingURL=SeedButton.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SeedButton.d.ts","sourceRoot":"","sources":["../../src/components/SeedButton.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AA0B9B,MAAM,WAAW,eAAe;IAC9B,+FAA+F;IAC/F,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AAED;;gGAEgG;AAChG,eAAO,MAAM,UAAU,EAAE,KAAK,CAAC,EAAE,CAAC,eAAe,CAwDhD,CAAA;AAED,eAAe,UAAU,CAAA"}
@@ -0,0 +1,82 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { toast, useConfig } from "@payloadcms/ui";
4
+ import { useCallback, useState } from "react";
5
+ const SuccessMessage = ()=>/*#__PURE__*/ _jsxs("div", {
6
+ children: [
7
+ "Database seeded! You can now",
8
+ ' ',
9
+ /*#__PURE__*/ _jsx("a", {
10
+ target: "_blank",
11
+ href: "/",
12
+ rel: "noreferrer",
13
+ children: "visit your website"
14
+ })
15
+ ]
16
+ });
17
+ const MAX_SHOWN_ISSUES = 5;
18
+ const summarizeIssues = (issues)=>{
19
+ const shown = issues.slice(0, MAX_SHOWN_ISSUES);
20
+ const more = issues.length - shown.length;
21
+ return shown.join('\n') + (more > 0 ? `\n…and ${more} more` : '');
22
+ };
23
+ /** Admin dashboard button that triggers `POST /api/seed`. Injected via the plugin's
24
+ * `adminButton` option, or registered manually in `admin.components.beforeDashboard`.
25
+ * The seed wipes seeded collections, so the first click arms a confirm and the second runs. */ export const SeedButton = ({ endpoint })=>{
26
+ const { config } = useConfig();
27
+ const url = endpoint ?? `${config.serverURL ?? ''}${config.routes.api}/seed`;
28
+ const [seeded, setSeeded] = useState(false);
29
+ const [loading, setLoading] = useState(false);
30
+ const [error, setError] = useState(null);
31
+ const [confirming, setConfirming] = useState(false);
32
+ const handleClick = useCallback((e)=>{
33
+ e.preventDefault();
34
+ if (loading) return toast.info('Seeding already in progress.');
35
+ if (!confirming) return setConfirming(true);
36
+ setConfirming(false);
37
+ setError(null);
38
+ setLoading(true);
39
+ const run = fetch(url, {
40
+ method: 'POST',
41
+ credentials: 'include'
42
+ }).then(async (res)=>{
43
+ const body = await res.json().catch(()=>({}));
44
+ if (!res.ok) {
45
+ const base = body.error ?? 'An error occurred while seeding.';
46
+ throw new Error(body.issues?.length ? `${base}\n${summarizeIssues(body.issues)}` : base);
47
+ }
48
+ setSeeded(true);
49
+ return body;
50
+ }).catch((err)=>{
51
+ const wrapped = err instanceof Error ? err : new Error(String(err));
52
+ setError(wrapped.message);
53
+ throw wrapped;
54
+ }).finally(()=>setLoading(false));
55
+ toast.promise(run, {
56
+ loading: 'Seeding with data....',
57
+ success: (body)=>body.message ?? /*#__PURE__*/ _jsx(SuccessMessage, {}),
58
+ error: (err)=>err instanceof Error ? err.message : 'An error occurred while seeding.'
59
+ });
60
+ }, [
61
+ loading,
62
+ confirming,
63
+ url
64
+ ]);
65
+ let message = '';
66
+ if (seeded) message = ' (done — click to reseed)';
67
+ if (error) message = ` (error: ${error} — click to retry)`;
68
+ if (confirming) message = ' — click again to confirm: this wipes seeded collections';
69
+ if (loading) message = ' (seeding...)';
70
+ return /*#__PURE__*/ _jsxs("button", {
71
+ type: "button",
72
+ onClick: handleClick,
73
+ disabled: loading,
74
+ children: [
75
+ "Seed your database",
76
+ message
77
+ ]
78
+ });
79
+ };
80
+ export default SeedButton;
81
+
82
+ //# sourceMappingURL=SeedButton.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/components/SeedButton.tsx"],"sourcesContent":["'use client'\n\nimport { toast, useConfig } from '@payloadcms/ui'\nimport type React from 'react'\nimport { useCallback, useState } from 'react'\n\nconst SuccessMessage: React.FC = () => (\n <div>\n Database seeded! You can now{' '}\n <a target=\"_blank\" href=\"/\" rel=\"noreferrer\">\n visit your website\n </a>\n </div>\n)\n\nconst MAX_SHOWN_ISSUES = 5\n\nconst summarizeIssues = (issues: string[]): string => {\n const shown = issues.slice(0, MAX_SHOWN_ISSUES)\n const more = issues.length - shown.length\n return shown.join('\\n') + (more > 0 ? `\\n…and ${more} more` : '')\n}\n\ninterface SeedResponseBody {\n error?: string\n issues?: string[]\n message?: string\n}\n\nexport interface SeedButtonProps {\n /** Endpoint URL the button POSTs to. Defaults to `<routes.api>/seed` from the admin config. */\n endpoint?: string\n}\n\n/** Admin dashboard button that triggers `POST /api/seed`. Injected via the plugin's\n * `adminButton` option, or registered manually in `admin.components.beforeDashboard`.\n * The seed wipes seeded collections, so the first click arms a confirm and the second runs. */\nexport const SeedButton: React.FC<SeedButtonProps> = ({ endpoint }) => {\n const { config } = useConfig()\n const url = endpoint ?? `${config.serverURL ?? ''}${config.routes.api}/seed`\n const [seeded, setSeeded] = useState(false)\n const [loading, setLoading] = useState(false)\n const [error, setError] = useState<null | string>(null)\n const [confirming, setConfirming] = useState(false)\n\n const handleClick = useCallback(\n (e: React.MouseEvent<HTMLButtonElement>) => {\n e.preventDefault()\n\n if (loading) return toast.info('Seeding already in progress.')\n if (!confirming) return setConfirming(true)\n\n setConfirming(false)\n setError(null)\n setLoading(true)\n\n const run = fetch(url, { method: 'POST', credentials: 'include' })\n .then(async (res) => {\n const body = (await res.json().catch(() => ({}))) as SeedResponseBody\n if (!res.ok) {\n const base = body.error ?? 'An error occurred while seeding.'\n throw new Error(body.issues?.length ? `${base}\\n${summarizeIssues(body.issues)}` : base)\n }\n setSeeded(true)\n return body\n })\n .catch((err) => {\n const wrapped = err instanceof Error ? err : new Error(String(err))\n setError(wrapped.message)\n throw wrapped\n })\n .finally(() => setLoading(false))\n\n toast.promise(run, {\n loading: 'Seeding with data....',\n success: (body) => body.message ?? <SuccessMessage />,\n error: (err) => (err instanceof Error ? err.message : 'An error occurred while seeding.'),\n })\n },\n [loading, confirming, url],\n )\n\n let message = ''\n if (seeded) message = ' (done — click to reseed)'\n if (error) message = ` (error: ${error} — click to retry)`\n if (confirming) message = ' — click again to confirm: this wipes seeded collections'\n if (loading) message = ' (seeding...)'\n\n return (\n <button type=\"button\" onClick={handleClick} disabled={loading}>\n Seed your database{message}\n </button>\n )\n}\n\nexport default SeedButton\n"],"names":["toast","useConfig","useCallback","useState","SuccessMessage","div","a","target","href","rel","MAX_SHOWN_ISSUES","summarizeIssues","issues","shown","slice","more","length","join","SeedButton","endpoint","config","url","serverURL","routes","api","seeded","setSeeded","loading","setLoading","error","setError","confirming","setConfirming","handleClick","e","preventDefault","info","run","fetch","method","credentials","then","res","body","json","catch","ok","base","Error","err","wrapped","String","message","finally","promise","success","button","type","onClick","disabled"],"mappings":"AAAA;;AAEA,SAASA,KAAK,EAAEC,SAAS,QAAQ,iBAAgB;AAEjD,SAASC,WAAW,EAAEC,QAAQ,QAAQ,QAAO;AAE7C,MAAMC,iBAA2B,kBAC/B,MAACC;;YAAI;YAC0B;0BAC7B,KAACC;gBAAEC,QAAO;gBAASC,MAAK;gBAAIC,KAAI;0BAAa;;;;AAMjD,MAAMC,mBAAmB;AAEzB,MAAMC,kBAAkB,CAACC;IACvB,MAAMC,QAAQD,OAAOE,KAAK,CAAC,GAAGJ;IAC9B,MAAMK,OAAOH,OAAOI,MAAM,GAAGH,MAAMG,MAAM;IACzC,OAAOH,MAAMI,IAAI,CAAC,QAASF,CAAAA,OAAO,IAAI,CAAC,OAAO,EAAEA,KAAK,KAAK,CAAC,GAAG,EAAC;AACjE;AAaA;;8FAE8F,GAC9F,OAAO,MAAMG,aAAwC,CAAC,EAAEC,QAAQ,EAAE;IAChE,MAAM,EAAEC,MAAM,EAAE,GAAGnB;IACnB,MAAMoB,MAAMF,YAAY,GAAGC,OAAOE,SAAS,IAAI,KAAKF,OAAOG,MAAM,CAACC,GAAG,CAAC,KAAK,CAAC;IAC5E,MAAM,CAACC,QAAQC,UAAU,GAAGvB,SAAS;IACrC,MAAM,CAACwB,SAASC,WAAW,GAAGzB,SAAS;IACvC,MAAM,CAAC0B,OAAOC,SAAS,GAAG3B,SAAwB;IAClD,MAAM,CAAC4B,YAAYC,cAAc,GAAG7B,SAAS;IAE7C,MAAM8B,cAAc/B,YAClB,CAACgC;QACCA,EAAEC,cAAc;QAEhB,IAAIR,SAAS,OAAO3B,MAAMoC,IAAI,CAAC;QAC/B,IAAI,CAACL,YAAY,OAAOC,cAAc;QAEtCA,cAAc;QACdF,SAAS;QACTF,WAAW;QAEX,MAAMS,MAAMC,MAAMjB,KAAK;YAAEkB,QAAQ;YAAQC,aAAa;QAAU,GAC7DC,IAAI,CAAC,OAAOC;YACX,MAAMC,OAAQ,MAAMD,IAAIE,IAAI,GAAGC,KAAK,CAAC,IAAO,CAAA,CAAC,CAAA;YAC7C,IAAI,CAACH,IAAII,EAAE,EAAE;gBACX,MAAMC,OAAOJ,KAAKd,KAAK,IAAI;gBAC3B,MAAM,IAAImB,MAAML,KAAK/B,MAAM,EAAEI,SAAS,GAAG+B,KAAK,EAAE,EAAEpC,gBAAgBgC,KAAK/B,MAAM,GAAG,GAAGmC;YACrF;YACArB,UAAU;YACV,OAAOiB;QACT,GACCE,KAAK,CAAC,CAACI;YACN,MAAMC,UAAUD,eAAeD,QAAQC,MAAM,IAAID,MAAMG,OAAOF;YAC9DnB,SAASoB,QAAQE,OAAO;YACxB,MAAMF;QACR,GACCG,OAAO,CAAC,IAAMzB,WAAW;QAE5B5B,MAAMsD,OAAO,CAACjB,KAAK;YACjBV,SAAS;YACT4B,SAAS,CAACZ,OAASA,KAAKS,OAAO,kBAAI,KAAChD;YACpCyB,OAAO,CAACoB,MAASA,eAAeD,QAAQC,IAAIG,OAAO,GAAG;QACxD;IACF,GACA;QAACzB;QAASI;QAAYV;KAAI;IAG5B,IAAI+B,UAAU;IACd,IAAI3B,QAAQ2B,UAAU;IACtB,IAAIvB,OAAOuB,UAAU,CAAC,SAAS,EAAEvB,MAAM,kBAAkB,CAAC;IAC1D,IAAIE,YAAYqB,UAAU;IAC1B,IAAIzB,SAASyB,UAAU;IAEvB,qBACE,MAACI;QAAOC,MAAK;QAASC,SAASzB;QAAa0B,UAAUhC;;YAAS;YAC1CyB;;;AAGzB,EAAC;AAED,eAAelC,WAAU"}
@@ -0,0 +1,43 @@
1
+ import type { CollectionSlug, GlobalSlug } from 'payload';
2
+ import { file, ref } from './refs';
3
+ import type { CollectionSeedData, CollectionSeedDefinition, GlobalSeedData, GlobalSeedDefinition, SeedDisabledMarker, SeedTokens } from './types';
4
+ declare const tokens: {
5
+ ref: typeof ref;
6
+ file: typeof file;
7
+ };
8
+ type Exact<T, Shape> = T & Record<Exclude<keyof T, keyof Shape>, never>;
9
+ type ExactEach<T extends readonly unknown[], Shape> = {
10
+ [I in keyof T]: Exact<T[I], Shape>;
11
+ };
12
+ type ShapeFor<TSlug> = TSlug extends CollectionSlug ? ReadonlyArray<CollectionSeedData<TSlug>> : TSlug extends GlobalSlug ? GlobalSeedData<TSlug> : never;
13
+ type ExactFor<T, Shape> = Shape extends readonly unknown[] ? T extends readonly unknown[] ? ExactEach<T, Shape[number]> : never : Exact<T, Shape>;
14
+ type DefinitionFor<TSlug> = TSlug extends CollectionSlug ? CollectionSeedDefinition<TSlug> : TSlug extends GlobalSlug ? GlobalSeedDefinition<TSlug> : never;
15
+ /**
16
+ * Define seed data for a collection or a global. Pass the slug and a builder; the second parameter
17
+ * types itself from the slug — an array of records for a collection (each with a local `_key` handle
18
+ * and an optional `_file` attaching a source file), or a single data object for a global (no `_key`).
19
+ * Records are typed against the app's generated Payload types, relationship fields accept `ref()`
20
+ * tokens, and unknown fields are rejected. Default-export one per `seed.ts` file, then wire it into
21
+ * `seedPlugin({ definitions })`.
22
+ *
23
+ * This is a single call signature (not an overload set), so a bad field reports against the resolved
24
+ * record type — the same specific error the collection/global shapes give on their own.
25
+ *
26
+ * export default defineSeed('images', ({ file }) => [
27
+ * { _key: 'hero', _file: file('hero.jpg'), alt: 'A lighthouse at dusk' },
28
+ * ])
29
+ *
30
+ * export default defineSeed('site-settings', ({ ref }) => ({
31
+ * featured: ref('services', 'consulting'),
32
+ * }))
33
+ *
34
+ * `opts.disabled` skips the definition at seed time without removing it (so the generated seed-ref
35
+ * types stay stable) — the engine warns, and optional refs pointing at it are dropped. Collections
36
+ * that declare `custom.seedDisabled` (e.g. a plugin detecting missing credentials) are skipped the
37
+ * same way automatically, so you usually don't need to set this yourself.
38
+ */
39
+ export declare function defineSeed<TSlug extends CollectionSlug | GlobalSlug, const T extends ShapeFor<TSlug>>(slug: TSlug, build: (tokens: SeedTokens) => ExactFor<T, ShapeFor<TSlug>>, opts?: {
40
+ disabled?: SeedDisabledMarker;
41
+ }): DefinitionFor<TSlug>;
42
+ export { tokens };
43
+ //# sourceMappingURL=defineSeed.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"defineSeed.d.ts","sourceRoot":"","sources":["../src/defineSeed.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,SAAS,CAAA;AACzD,OAAO,EAAE,IAAI,EAAE,GAAG,EAAE,MAAM,QAAQ,CAAA;AAClC,OAAO,KAAK,EACV,kBAAkB,EAClB,wBAAwB,EACxB,cAAc,EACd,oBAAoB,EACpB,kBAAkB,EAClB,UAAU,EACX,MAAM,SAAS,CAAA;AAEhB,QAAA,MAAM,MAAM;;;CAAgB,CAAA;AAM5B,KAAK,KAAK,CAAC,CAAC,EAAE,KAAK,IAAI,CAAC,GAAG,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,MAAM,KAAK,CAAC,EAAE,KAAK,CAAC,CAAA;AACvE,KAAK,SAAS,CAAC,CAAC,SAAS,SAAS,OAAO,EAAE,EAAE,KAAK,IAAI;KAAG,CAAC,IAAI,MAAM,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC;CAAE,CAAA;AAK5F,KAAK,QAAQ,CAAC,KAAK,IAAI,KAAK,SAAS,cAAc,GAC/C,aAAa,CAAC,kBAAkB,CAAC,KAAK,CAAC,CAAC,GACxC,KAAK,SAAS,UAAU,GACtB,cAAc,CAAC,KAAK,CAAC,GACrB,KAAK,CAAA;AAIX,KAAK,QAAQ,CAAC,CAAC,EAAE,KAAK,IAAI,KAAK,SAAS,SAAS,OAAO,EAAE,GACtD,CAAC,SAAS,SAAS,OAAO,EAAE,GAC1B,SAAS,CAAC,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC,GAC3B,KAAK,GACP,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAA;AAGnB,KAAK,aAAa,CAAC,KAAK,IAAI,KAAK,SAAS,cAAc,GACpD,wBAAwB,CAAC,KAAK,CAAC,GAC/B,KAAK,SAAS,UAAU,GACtB,oBAAoB,CAAC,KAAK,CAAC,GAC3B,KAAK,CAAA;AAEX;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,UAAU,CAAC,KAAK,SAAS,cAAc,GAAG,UAAU,EAAE,KAAK,CAAC,CAAC,SAAS,QAAQ,CAAC,KAAK,CAAC,EACnG,IAAI,EAAE,KAAK,EACX,KAAK,EAAE,CAAC,MAAM,EAAE,UAAU,KAAK,QAAQ,CAAC,CAAC,EAAE,QAAQ,CAAC,KAAK,CAAC,CAAC,EAC3D,IAAI,CAAC,EAAE;IAAE,QAAQ,CAAC,EAAE,kBAAkB,CAAA;CAAE,GACvC,aAAa,CAAC,KAAK,CAAC,CAYtB;AAED,OAAO,EAAE,MAAM,EAAE,CAAA"}
@@ -0,0 +1,51 @@
1
+ import { file, ref } from "./refs.js";
2
+ const tokens = {
3
+ ref,
4
+ file
5
+ };
6
+ /**
7
+ * Define seed data for a collection or a global. Pass the slug and a builder; the second parameter
8
+ * types itself from the slug — an array of records for a collection (each with a local `_key` handle
9
+ * and an optional `_file` attaching a source file), or a single data object for a global (no `_key`).
10
+ * Records are typed against the app's generated Payload types, relationship fields accept `ref()`
11
+ * tokens, and unknown fields are rejected. Default-export one per `seed.ts` file, then wire it into
12
+ * `seedPlugin({ definitions })`.
13
+ *
14
+ * This is a single call signature (not an overload set), so a bad field reports against the resolved
15
+ * record type — the same specific error the collection/global shapes give on their own.
16
+ *
17
+ * export default defineSeed('images', ({ file }) => [
18
+ * { _key: 'hero', _file: file('hero.jpg'), alt: 'A lighthouse at dusk' },
19
+ * ])
20
+ *
21
+ * export default defineSeed('site-settings', ({ ref }) => ({
22
+ * featured: ref('services', 'consulting'),
23
+ * }))
24
+ *
25
+ * `opts.disabled` skips the definition at seed time without removing it (so the generated seed-ref
26
+ * types stay stable) — the engine warns, and optional refs pointing at it are dropped. Collections
27
+ * that declare `custom.seedDisabled` (e.g. a plugin detecting missing credentials) are skipped the
28
+ * same way automatically, so you usually don't need to set this yourself.
29
+ */ export function defineSeed(slug, build, opts) {
30
+ // Classify collection vs global from the builder's shape (collection → array, global → object).
31
+ // The ref/file tokens are pure constructors, so this eager call has no side effects and resolves
32
+ // no refs; it only decides `kind`. The engine calls `build` again at seed time.
33
+ let built;
34
+ try {
35
+ built = build(tokens);
36
+ } catch (e) {
37
+ throw new Error(`[payload-seed] defineSeed('${slug}'): builder threw during classification: ${e instanceof Error ? e.message : String(e)}`);
38
+ }
39
+ const kind = Array.isArray(built) ? 'collection' : 'global';
40
+ return {
41
+ kind,
42
+ slug,
43
+ build,
44
+ ...opts?.disabled !== undefined ? {
45
+ disabled: opts.disabled
46
+ } : {}
47
+ };
48
+ }
49
+ export { tokens };
50
+
51
+ //# sourceMappingURL=defineSeed.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/defineSeed.ts"],"sourcesContent":["import type { CollectionSlug, GlobalSlug } from 'payload'\nimport { file, ref } from './refs'\nimport type {\n CollectionSeedData,\n CollectionSeedDefinition,\n GlobalSeedData,\n GlobalSeedDefinition,\n SeedDisabledMarker,\n SeedTokens,\n} from './types'\n\nconst tokens = { ref, file }\n\n// Re-enable excess-property checking, which TS disables for our mapped/intersection record\n// types: any key on `T` not present in `Shape` is forced to `never`, so a misspelled or\n// bogus field fails to type-check. (Wrong-*typed* values are already caught by the `extends`\n// constraint; this adds the missing unknown-*field* check.)\ntype Exact<T, Shape> = T & Record<Exclude<keyof T, keyof Shape>, never>\ntype ExactEach<T extends readonly unknown[], Shape> = { [I in keyof T]: Exact<T[I], Shape> }\n\n// The seed shape for a slug: an array of records for a collection, a single data object for a\n// global. Keyed on the slug, which is already pinned from the first argument by the time `build`\n// is checked, so the conditional collapses to one concrete branch.\ntype ShapeFor<TSlug> = TSlug extends CollectionSlug\n ? ReadonlyArray<CollectionSeedData<TSlug>>\n : TSlug extends GlobalSlug\n ? GlobalSeedData<TSlug>\n : never\n\n// Apply the excess-field check to whichever branch `ShapeFor` resolved to (per-record for a\n// collection array, whole-object for a global).\ntype ExactFor<T, Shape> = Shape extends readonly unknown[]\n ? T extends readonly unknown[]\n ? ExactEach<T, Shape[number]>\n : never\n : Exact<T, Shape>\n\n// The definition a given slug produces, so the return type discriminates like the data does.\ntype DefinitionFor<TSlug> = TSlug extends CollectionSlug\n ? CollectionSeedDefinition<TSlug>\n : TSlug extends GlobalSlug\n ? GlobalSeedDefinition<TSlug>\n : never\n\n/**\n * Define seed data for a collection or a global. Pass the slug and a builder; the second parameter\n * types itself from the slug — an array of records for a collection (each with a local `_key` handle\n * and an optional `_file` attaching a source file), or a single data object for a global (no `_key`).\n * Records are typed against the app's generated Payload types, relationship fields accept `ref()`\n * tokens, and unknown fields are rejected. Default-export one per `seed.ts` file, then wire it into\n * `seedPlugin({ definitions })`.\n *\n * This is a single call signature (not an overload set), so a bad field reports against the resolved\n * record type — the same specific error the collection/global shapes give on their own.\n *\n * export default defineSeed('images', ({ file }) => [\n * { _key: 'hero', _file: file('hero.jpg'), alt: 'A lighthouse at dusk' },\n * ])\n *\n * export default defineSeed('site-settings', ({ ref }) => ({\n * featured: ref('services', 'consulting'),\n * }))\n *\n * `opts.disabled` skips the definition at seed time without removing it (so the generated seed-ref\n * types stay stable) — the engine warns, and optional refs pointing at it are dropped. Collections\n * that declare `custom.seedDisabled` (e.g. a plugin detecting missing credentials) are skipped the\n * same way automatically, so you usually don't need to set this yourself.\n */\nexport function defineSeed<TSlug extends CollectionSlug | GlobalSlug, const T extends ShapeFor<TSlug>>(\n slug: TSlug,\n build: (tokens: SeedTokens) => ExactFor<T, ShapeFor<TSlug>>,\n opts?: { disabled?: SeedDisabledMarker },\n): DefinitionFor<TSlug> {\n // Classify collection vs global from the builder's shape (collection → array, global → object).\n // The ref/file tokens are pure constructors, so this eager call has no side effects and resolves\n // no refs; it only decides `kind`. The engine calls `build` again at seed time.\n let built: unknown\n try {\n built = (build as (t: SeedTokens) => unknown)(tokens)\n } catch (e) {\n throw new Error(`[payload-seed] defineSeed('${slug}'): builder threw during classification: ${e instanceof Error ? e.message : String(e)}`)\n }\n const kind = Array.isArray(built) ? 'collection' : 'global'\n return { kind, slug, build, ...(opts?.disabled !== undefined ? { disabled: opts.disabled } : {}) } as unknown as DefinitionFor<TSlug>\n}\n\nexport { tokens }\n"],"names":["file","ref","tokens","defineSeed","slug","build","opts","built","e","Error","message","String","kind","Array","isArray","disabled","undefined"],"mappings":"AACA,SAASA,IAAI,EAAEC,GAAG,QAAQ,YAAQ;AAUlC,MAAMC,SAAS;IAAED;IAAKD;AAAK;AAiC3B;;;;;;;;;;;;;;;;;;;;;;;CAuBC,GACD,OAAO,SAASG,WACdC,IAAW,EACXC,KAA2D,EAC3DC,IAAwC;IAExC,gGAAgG;IAChG,iGAAiG;IACjG,gFAAgF;IAChF,IAAIC;IACJ,IAAI;QACFA,QAAQ,AAACF,MAAqCH;IAChD,EAAE,OAAOM,GAAG;QACV,MAAM,IAAIC,MAAM,CAAC,2BAA2B,EAAEL,KAAK,yCAAyC,EAAEI,aAAaC,QAAQD,EAAEE,OAAO,GAAGC,OAAOH,IAAI;IAC5I;IACA,MAAMI,OAAOC,MAAMC,OAAO,CAACP,SAAS,eAAe;IACnD,OAAO;QAAEK;QAAMR;QAAMC;QAAO,GAAIC,MAAMS,aAAaC,YAAY;YAAED,UAAUT,KAAKS,QAAQ;QAAC,IAAI,CAAC,CAAC;IAAE;AACnG;AAEA,SAASb,MAAM,GAAE"}
@@ -0,0 +1,10 @@
1
+ import type { Endpoint } from 'payload';
2
+ import type { ResolvedSeedOptions } from './options';
3
+ /**
4
+ * Builds `POST /api/seed`. Gated by the `ENABLE_SEED` runtime guard and requires an
5
+ * authenticated user (any user — not necessarily an admin). Each write sets
6
+ * `context.disableRevalidate`, so app revalidate hooks skip during the run; the engine
7
+ * does no final revalidation.
8
+ */
9
+ export declare function createSeedEndpoint(options: ResolvedSeedOptions): Endpoint;
10
+ //# sourceMappingURL=endpoint.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"endpoint.d.ts","sourceRoot":"","sources":["../src/endpoint.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAA;AAIvC,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,WAAW,CAAA;AAEpD;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,mBAAmB,GAAG,QAAQ,CAoBzE"}
@@ -0,0 +1,60 @@
1
+ import { runSeed } from "./engine/run.js";
2
+ import { SeedValidationError } from "./engine/validate.js";
3
+ import { SEED_DISABLED_MESSAGE, seedingEnabled } from "./guard.js";
4
+ /**
5
+ * Builds `POST /api/seed`. Gated by the `ENABLE_SEED` runtime guard and requires an
6
+ * authenticated user (any user — not necessarily an admin). Each write sets
7
+ * `context.disableRevalidate`, so app revalidate hooks skip during the run; the engine
8
+ * does no final revalidation.
9
+ */ export function createSeedEndpoint(options) {
10
+ return {
11
+ path: '/seed',
12
+ method: 'post',
13
+ handler: async (req)=>{
14
+ if (!seedingEnabled()) return Response.json({
15
+ error: SEED_DISABLED_MESSAGE
16
+ }, {
17
+ status: 403
18
+ });
19
+ if (!req.user) return Response.json({
20
+ error: 'Seeding requires an authenticated Payload user (any user) - log in first.'
21
+ }, {
22
+ status: 403
23
+ });
24
+ try {
25
+ const result = await runSeed({
26
+ payload: req.payload,
27
+ req,
28
+ options,
29
+ definitions: options.definitions
30
+ });
31
+ const message = options.definitions?.length ? undefined : '0 documents created — no seed definitions registered';
32
+ return Response.json({
33
+ success: true,
34
+ ...message ? {
35
+ message
36
+ } : {},
37
+ ...result
38
+ });
39
+ } catch (e) {
40
+ req.payload.logger.error({
41
+ err: e,
42
+ msg: 'Error seeding data'
43
+ });
44
+ if (e instanceof SeedValidationError) return Response.json({
45
+ error: 'Seed validation failed.',
46
+ issues: e.issues
47
+ }, {
48
+ status: 400
49
+ });
50
+ return Response.json({
51
+ error: 'Error seeding data.'
52
+ }, {
53
+ status: 500
54
+ });
55
+ }
56
+ }
57
+ };
58
+ }
59
+
60
+ //# sourceMappingURL=endpoint.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/endpoint.ts"],"sourcesContent":["import type { Endpoint } from 'payload'\nimport { runSeed } from './engine/run'\nimport { SeedValidationError } from './engine/validate'\nimport { SEED_DISABLED_MESSAGE, seedingEnabled } from './guard'\nimport type { ResolvedSeedOptions } from './options'\n\n/**\n * Builds `POST /api/seed`. Gated by the `ENABLE_SEED` runtime guard and requires an\n * authenticated user (any user — not necessarily an admin). Each write sets\n * `context.disableRevalidate`, so app revalidate hooks skip during the run; the engine\n * does no final revalidation.\n */\nexport function createSeedEndpoint(options: ResolvedSeedOptions): Endpoint {\n return {\n path: '/seed',\n method: 'post',\n handler: async (req) => {\n if (!seedingEnabled()) return Response.json({ error: SEED_DISABLED_MESSAGE }, { status: 403 })\n if (!req.user)\n return Response.json({ error: 'Seeding requires an authenticated Payload user (any user) - log in first.' }, { status: 403 })\n\n try {\n const result = await runSeed({ payload: req.payload, req, options, definitions: options.definitions })\n const message = options.definitions?.length ? undefined : '0 documents created — no seed definitions registered'\n return Response.json({ success: true, ...(message ? { message } : {}), ...result })\n } catch (e) {\n req.payload.logger.error({ err: e, msg: 'Error seeding data' })\n if (e instanceof SeedValidationError) return Response.json({ error: 'Seed validation failed.', issues: e.issues }, { status: 400 })\n return Response.json({ error: 'Error seeding data.' }, { status: 500 })\n }\n },\n }\n}\n"],"names":["runSeed","SeedValidationError","SEED_DISABLED_MESSAGE","seedingEnabled","createSeedEndpoint","options","path","method","handler","req","Response","json","error","status","user","result","payload","definitions","message","length","undefined","success","e","logger","err","msg","issues"],"mappings":"AACA,SAASA,OAAO,QAAQ,kBAAc;AACtC,SAASC,mBAAmB,QAAQ,uBAAmB;AACvD,SAASC,qBAAqB,EAAEC,cAAc,QAAQ,aAAS;AAG/D;;;;;CAKC,GACD,OAAO,SAASC,mBAAmBC,OAA4B;IAC7D,OAAO;QACLC,MAAM;QACNC,QAAQ;QACRC,SAAS,OAAOC;YACd,IAAI,CAACN,kBAAkB,OAAOO,SAASC,IAAI,CAAC;gBAAEC,OAAOV;YAAsB,GAAG;gBAAEW,QAAQ;YAAI;YAC5F,IAAI,CAACJ,IAAIK,IAAI,EACX,OAAOJ,SAASC,IAAI,CAAC;gBAAEC,OAAO;YAA4E,GAAG;gBAAEC,QAAQ;YAAI;YAE7H,IAAI;gBACF,MAAME,SAAS,MAAMf,QAAQ;oBAAEgB,SAASP,IAAIO,OAAO;oBAAEP;oBAAKJ;oBAASY,aAAaZ,QAAQY,WAAW;gBAAC;gBACpG,MAAMC,UAAUb,QAAQY,WAAW,EAAEE,SAASC,YAAY;gBAC1D,OAAOV,SAASC,IAAI,CAAC;oBAAEU,SAAS;oBAAM,GAAIH,UAAU;wBAAEA;oBAAQ,IAAI,CAAC,CAAC;oBAAG,GAAGH,MAAM;gBAAC;YACnF,EAAE,OAAOO,GAAG;gBACVb,IAAIO,OAAO,CAACO,MAAM,CAACX,KAAK,CAAC;oBAAEY,KAAKF;oBAAGG,KAAK;gBAAqB;gBAC7D,IAAIH,aAAarB,qBAAqB,OAAOS,SAASC,IAAI,CAAC;oBAAEC,OAAO;oBAA2Bc,QAAQJ,EAAEI,MAAM;gBAAC,GAAG;oBAAEb,QAAQ;gBAAI;gBACjI,OAAOH,SAASC,IAAI,CAAC;oBAAEC,OAAO;gBAAsB,GAAG;oBAAEC,QAAQ;gBAAI;YACvE;QACF;IACF;AACF"}
@@ -0,0 +1,15 @@
1
+ import type { File } from 'payload';
2
+ /** The directories `resolveFilePath` searches for a name, so a missing-file warning can list them. */
3
+ export declare const searchedDirs: (name: string, assetsRoot: string, subdirs: string[]) => string[];
4
+ /**
5
+ * Resolve a `_file` name to an absolute path on disk. `subdirs` is the ordered list of directories
6
+ * under the assets root to search — typically a collection's subdir then the root (`['media', '']`).
7
+ * The name may include a relative subpath (`portraits/jane.jpg`), resolved under each search dir, so
8
+ * a collection folder can be subdivided further. Tolerates an extension mismatch for image files (a
9
+ * name of `foo.png` picks up `foo.jpg`). Returns null when nothing matches; an absolute name is
10
+ * returned as-is.
11
+ */
12
+ export declare function resolveFilePath(name: string, assetsRoot: string, subdirs: string[]): Promise<string | null>;
13
+ /** Read a resolved path into a Payload `File` (for a native upload's `file` param). */
14
+ export declare function readFileAsUpload(path: string): Promise<File>;
15
+ //# sourceMappingURL=files.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"files.d.ts","sourceRoot":"","sources":["../../src/engine/files.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,SAAS,CAAA;AAyBnC,sGAAsG;AACtG,eAAO,MAAM,YAAY,GAAI,MAAM,MAAM,EAAE,YAAY,MAAM,EAAE,SAAS,MAAM,EAAE,KAAG,MAAM,EAC7B,CAAA;AAE5D;;;;;;;GAOG;AACH,wBAAsB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAmBjH;AAED,uFAAuF;AACvF,wBAAsB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAKlE"}
@@ -0,0 +1,72 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import { basename, dirname, extname, isAbsolute, join } from "node:path";
3
+ // MIME type derived from the file's real extension — we upload whatever the source is
4
+ // (JPEG/PNG/WebP/SVG/WOFF2/…), not an assumed format.
5
+ const MIME_BY_EXT = {
6
+ '.png': 'image/png',
7
+ '.jpg': 'image/jpeg',
8
+ '.jpeg': 'image/jpeg',
9
+ '.webp': 'image/webp',
10
+ '.avif': 'image/avif',
11
+ '.gif': 'image/gif',
12
+ '.svg': 'image/svg+xml',
13
+ '.woff2': 'font/woff2',
14
+ '.woff': 'font/woff',
15
+ '.ttf': 'font/ttf',
16
+ '.otf': 'font/otf'
17
+ };
18
+ // Extensions eligible for the same-basename sibling fallback in `resolveFilePath` — image formats
19
+ // only, where `foo.png` picking up `foo.jpg` is a convenience. Fonts are matched by exact name so a
20
+ // requested format is never silently swapped for another.
21
+ const TOLERANT_EXTS = new Set([
22
+ '.png',
23
+ '.jpg',
24
+ '.jpeg',
25
+ '.webp',
26
+ '.avif',
27
+ '.gif',
28
+ '.svg'
29
+ ]);
30
+ const stripExt = (name)=>name.slice(0, name.length - extname(name).length);
31
+ /** The directories `resolveFilePath` searches for a name, so a missing-file warning can list them. */ export const searchedDirs = (name, assetsRoot, subdirs)=>subdirs.map((sub)=>join(assetsRoot, sub, dirname(name)));
32
+ /**
33
+ * Resolve a `_file` name to an absolute path on disk. `subdirs` is the ordered list of directories
34
+ * under the assets root to search — typically a collection's subdir then the root (`['media', '']`).
35
+ * The name may include a relative subpath (`portraits/jane.jpg`), resolved under each search dir, so
36
+ * a collection folder can be subdivided further. Tolerates an extension mismatch for image files (a
37
+ * name of `foo.png` picks up `foo.jpg`). Returns null when nothing matches; an absolute name is
38
+ * returned as-is.
39
+ */ export async function resolveFilePath(name, assetsRoot, subdirs) {
40
+ if (isAbsolute(name)) return name;
41
+ const { readdir } = await import("node:fs/promises");
42
+ const nameDir = dirname(name) // '.' for a bare filename, else the subpath (e.g. 'portraits')
43
+ ;
44
+ const fileName = basename(name);
45
+ const wantBase = stripExt(fileName);
46
+ for (const sub of subdirs){
47
+ const dir = join(assetsRoot, sub, nameDir);
48
+ let entries;
49
+ try {
50
+ entries = await readdir(dir);
51
+ } catch {
52
+ continue;
53
+ }
54
+ if (entries.includes(fileName)) return join(dir, fileName);
55
+ const sibling = entries.find((e)=>stripExt(e) === wantBase && TOLERANT_EXTS.has(extname(e).toLowerCase()));
56
+ if (sibling) return join(dir, sibling);
57
+ }
58
+ return null;
59
+ }
60
+ /** Read a resolved path into a Payload `File` (for a native upload's `file` param). */ export async function readFileAsUpload(path) {
61
+ const data = await readFile(path);
62
+ const name = path.split(/[\\/]/).pop() ?? path;
63
+ const mimetype = MIME_BY_EXT[extname(path).toLowerCase()] ?? 'application/octet-stream';
64
+ return {
65
+ name,
66
+ data,
67
+ mimetype,
68
+ size: data.byteLength
69
+ };
70
+ }
71
+
72
+ //# sourceMappingURL=files.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/engine/files.ts"],"sourcesContent":["import { readFile } from 'node:fs/promises'\nimport { basename, dirname, extname, isAbsolute, join } from 'node:path'\nimport type { File } from 'payload'\n\n// MIME type derived from the file's real extension — we upload whatever the source is\n// (JPEG/PNG/WebP/SVG/WOFF2/…), not an assumed format.\nconst MIME_BY_EXT: Record<string, string> = {\n '.png': 'image/png',\n '.jpg': 'image/jpeg',\n '.jpeg': 'image/jpeg',\n '.webp': 'image/webp',\n '.avif': 'image/avif',\n '.gif': 'image/gif',\n '.svg': 'image/svg+xml',\n '.woff2': 'font/woff2',\n '.woff': 'font/woff',\n '.ttf': 'font/ttf',\n '.otf': 'font/otf',\n}\n\n// Extensions eligible for the same-basename sibling fallback in `resolveFilePath` — image formats\n// only, where `foo.png` picking up `foo.jpg` is a convenience. Fonts are matched by exact name so a\n// requested format is never silently swapped for another.\nconst TOLERANT_EXTS = new Set(['.png', '.jpg', '.jpeg', '.webp', '.avif', '.gif', '.svg'])\n\nconst stripExt = (name: string) => name.slice(0, name.length - extname(name).length)\n\n/** The directories `resolveFilePath` searches for a name, so a missing-file warning can list them. */\nexport const searchedDirs = (name: string, assetsRoot: string, subdirs: string[]): string[] =>\n subdirs.map((sub) => join(assetsRoot, sub, dirname(name)))\n\n/**\n * Resolve a `_file` name to an absolute path on disk. `subdirs` is the ordered list of directories\n * under the assets root to search — typically a collection's subdir then the root (`['media', '']`).\n * The name may include a relative subpath (`portraits/jane.jpg`), resolved under each search dir, so\n * a collection folder can be subdivided further. Tolerates an extension mismatch for image files (a\n * name of `foo.png` picks up `foo.jpg`). Returns null when nothing matches; an absolute name is\n * returned as-is.\n */\nexport async function resolveFilePath(name: string, assetsRoot: string, subdirs: string[]): Promise<string | null> {\n if (isAbsolute(name)) return name\n const { readdir } = await import('node:fs/promises')\n const nameDir = dirname(name) // '.' for a bare filename, else the subpath (e.g. 'portraits')\n const fileName = basename(name)\n const wantBase = stripExt(fileName)\n for (const sub of subdirs) {\n const dir = join(assetsRoot, sub, nameDir)\n let entries: string[]\n try {\n entries = await readdir(dir)\n } catch {\n continue\n }\n if (entries.includes(fileName)) return join(dir, fileName)\n const sibling = entries.find((e) => stripExt(e) === wantBase && TOLERANT_EXTS.has(extname(e).toLowerCase()))\n if (sibling) return join(dir, sibling)\n }\n return null\n}\n\n/** Read a resolved path into a Payload `File` (for a native upload's `file` param). */\nexport async function readFileAsUpload(path: string): Promise<File> {\n const data = await readFile(path)\n const name = path.split(/[\\\\/]/).pop() ?? path\n const mimetype = MIME_BY_EXT[extname(path).toLowerCase()] ?? 'application/octet-stream'\n return { name, data, mimetype, size: data.byteLength }\n}\n"],"names":["readFile","basename","dirname","extname","isAbsolute","join","MIME_BY_EXT","TOLERANT_EXTS","Set","stripExt","name","slice","length","searchedDirs","assetsRoot","subdirs","map","sub","resolveFilePath","readdir","nameDir","fileName","wantBase","dir","entries","includes","sibling","find","e","has","toLowerCase","readFileAsUpload","path","data","split","pop","mimetype","size","byteLength"],"mappings":"AAAA,SAASA,QAAQ,QAAQ,mBAAkB;AAC3C,SAASC,QAAQ,EAAEC,OAAO,EAAEC,OAAO,EAAEC,UAAU,EAAEC,IAAI,QAAQ,YAAW;AAGxE,sFAAsF;AACtF,sDAAsD;AACtD,MAAMC,cAAsC;IAC1C,QAAQ;IACR,QAAQ;IACR,SAAS;IACT,SAAS;IACT,SAAS;IACT,QAAQ;IACR,QAAQ;IACR,UAAU;IACV,SAAS;IACT,QAAQ;IACR,QAAQ;AACV;AAEA,kGAAkG;AAClG,oGAAoG;AACpG,0DAA0D;AAC1D,MAAMC,gBAAgB,IAAIC,IAAI;IAAC;IAAQ;IAAQ;IAAS;IAAS;IAAS;IAAQ;CAAO;AAEzF,MAAMC,WAAW,CAACC,OAAiBA,KAAKC,KAAK,CAAC,GAAGD,KAAKE,MAAM,GAAGT,QAAQO,MAAME,MAAM;AAEnF,oGAAoG,GACpG,OAAO,MAAMC,eAAe,CAACH,MAAcI,YAAoBC,UAC7DA,QAAQC,GAAG,CAAC,CAACC,MAAQZ,KAAKS,YAAYG,KAAKf,QAAQQ,QAAO;AAE5D;;;;;;;CAOC,GACD,OAAO,eAAeQ,gBAAgBR,IAAY,EAAEI,UAAkB,EAAEC,OAAiB;IACvF,IAAIX,WAAWM,OAAO,OAAOA;IAC7B,MAAM,EAAES,OAAO,EAAE,GAAG,MAAM,MAAM,CAAC;IACjC,MAAMC,UAAUlB,QAAQQ,MAAM,+DAA+D;;IAC7F,MAAMW,WAAWpB,SAASS;IAC1B,MAAMY,WAAWb,SAASY;IAC1B,KAAK,MAAMJ,OAAOF,QAAS;QACzB,MAAMQ,MAAMlB,KAAKS,YAAYG,KAAKG;QAClC,IAAII;QACJ,IAAI;YACFA,UAAU,MAAML,QAAQI;QAC1B,EAAE,OAAM;YACN;QACF;QACA,IAAIC,QAAQC,QAAQ,CAACJ,WAAW,OAAOhB,KAAKkB,KAAKF;QACjD,MAAMK,UAAUF,QAAQG,IAAI,CAAC,CAACC,IAAMnB,SAASmB,OAAON,YAAYf,cAAcsB,GAAG,CAAC1B,QAAQyB,GAAGE,WAAW;QACxG,IAAIJ,SAAS,OAAOrB,KAAKkB,KAAKG;IAChC;IACA,OAAO;AACT;AAEA,qFAAqF,GACrF,OAAO,eAAeK,iBAAiBC,IAAY;IACjD,MAAMC,OAAO,MAAMjC,SAASgC;IAC5B,MAAMtB,OAAOsB,KAAKE,KAAK,CAAC,SAASC,GAAG,MAAMH;IAC1C,MAAMI,WAAW9B,WAAW,CAACH,QAAQ6B,MAAMF,WAAW,GAAG,IAAI;IAC7D,OAAO;QAAEpB;QAAMuB;QAAMG;QAAUC,MAAMJ,KAAKK,UAAU;IAAC;AACvD"}
@@ -0,0 +1,61 @@
1
+ import type { FileToken } from '../refs';
2
+ export interface BuiltRecord {
3
+ key: string;
4
+ /** The record's `_file` meta-key, if it attaches a source file. */
5
+ file?: FileToken;
6
+ data: Record<string, unknown>;
7
+ }
8
+ export interface BuiltCollection {
9
+ slug: string;
10
+ records: BuiltRecord[];
11
+ }
12
+ export interface BuiltGlobal {
13
+ slug: string;
14
+ data: Record<string, unknown>;
15
+ }
16
+ export interface BuiltModel {
17
+ collections: BuiltCollection[];
18
+ globals: BuiltGlobal[];
19
+ }
20
+ export type GraphNodeType = 'doc' | 'global';
21
+ export interface GraphNode {
22
+ id: string;
23
+ type: GraphNodeType;
24
+ label: string;
25
+ collection?: string;
26
+ slug?: string;
27
+ key?: string;
28
+ }
29
+ export interface GraphEdge {
30
+ from: string;
31
+ to: string;
32
+ }
33
+ /** A doc field whose refs were deferred to break a dependency cycle: created null, then set in a
34
+ * second pass once every doc exists. `node` is the owning doc (`collection:_key`); `field` its top-level field. */
35
+ export interface DeferredField {
36
+ node: string;
37
+ field: string;
38
+ }
39
+ export interface SeedGraph {
40
+ nodes: GraphNode[];
41
+ edges: GraphEdge[];
42
+ /** Doc node ids in dependency-first create order. */
43
+ order: string[];
44
+ /** Fields deferred to break cycles (empty when the graph is acyclic). */
45
+ deferred: DeferredField[];
46
+ }
47
+ /** Whether a top-level field is required (so its refs can't be deferred). Defaults to treating every
48
+ * field as required — i.e. no field is deferrable, so any cycle is a hard error (the strict default).
49
+ * The engine passes a real lookup (from the live config) to enable optional-field cycle breaking. */
50
+ export type RequiredLookup = (collection: string, field: string) => boolean;
51
+ /**
52
+ * Build the dependency graph: doc/global nodes, edges from each doc/global to the docs it
53
+ * references, and the topologically sorted doc create order. When a `ref` cycle exists, an optional
54
+ * field on one edge of the cycle is **deferred** (its refs set in a second pass) to break it; a cycle
55
+ * with no optional field to break is a hard error. Pass `isRequired` to enable this — without it every
56
+ * field is treated as required, so any cycle throws.
57
+ */
58
+ export declare function buildGraph(model: BuiltModel, opts?: {
59
+ isRequired?: RequiredLookup;
60
+ }): SeedGraph;
61
+ //# sourceMappingURL=graph.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"graph.d.ts","sourceRoot":"","sources":["../../src/engine/graph.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,SAAS,CAAA;AAGxC,MAAM,WAAW,WAAW;IAC1B,GAAG,EAAE,MAAM,CAAA;IACX,mEAAmE;IACnE,IAAI,CAAC,EAAE,SAAS,CAAA;IAChB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAC9B;AACD,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,WAAW,EAAE,CAAA;CACvB;AACD,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAC9B;AACD,MAAM,WAAW,UAAU;IACzB,WAAW,EAAE,eAAe,EAAE,CAAA;IAC9B,OAAO,EAAE,WAAW,EAAE,CAAA;CACvB;AAED,MAAM,MAAM,aAAa,GAAG,KAAK,GAAG,QAAQ,CAAA;AAC5C,MAAM,WAAW,SAAS;IACxB,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,aAAa,CAAA;IACnB,KAAK,EAAE,MAAM,CAAA;IACb,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,GAAG,CAAC,EAAE,MAAM,CAAA;CACb;AACD,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAA;IACZ,EAAE,EAAE,MAAM,CAAA;CACX;AAED;oHACoH;AACpH,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,MAAM,CAAA;CACd;AAED,MAAM,WAAW,SAAS;IACxB,KAAK,EAAE,SAAS,EAAE,CAAA;IAClB,KAAK,EAAE,SAAS,EAAE,CAAA;IAClB,qDAAqD;IACrD,KAAK,EAAE,MAAM,EAAE,CAAA;IACf,yEAAyE;IACzE,QAAQ,EAAE,aAAa,EAAE,CAAA;CAC1B;AAED;;sGAEsG;AACtG,MAAM,MAAM,cAAc,GAAG,CAAC,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,OAAO,CAAA;AAK3E;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,UAAU,EAAE,IAAI,GAAE;IAAE,UAAU,CAAC,EAAE,cAAc,CAAA;CAAO,GAAG,SAAS,CA8CnG"}
Binary file
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/engine/graph.ts"],"sourcesContent":["import type { FileToken } from '../refs'\nimport { collectTokens, docNodeId } from './tokens'\n\nexport interface BuiltRecord {\n key: string\n /** The record's `_file` meta-key, if it attaches a source file. */\n file?: FileToken\n data: Record<string, unknown>\n}\nexport interface BuiltCollection {\n slug: string\n records: BuiltRecord[]\n}\nexport interface BuiltGlobal {\n slug: string\n data: Record<string, unknown>\n}\nexport interface BuiltModel {\n collections: BuiltCollection[]\n globals: BuiltGlobal[]\n}\n\nexport type GraphNodeType = 'doc' | 'global'\nexport interface GraphNode {\n id: string\n type: GraphNodeType\n label: string\n collection?: string\n slug?: string\n key?: string\n}\nexport interface GraphEdge {\n from: string\n to: string\n}\n\n/** A doc field whose refs were deferred to break a dependency cycle: created null, then set in a\n * second pass once every doc exists. `node` is the owning doc (`collection:_key`); `field` its top-level field. */\nexport interface DeferredField {\n node: string\n field: string\n}\n\nexport interface SeedGraph {\n nodes: GraphNode[]\n edges: GraphEdge[]\n /** Doc node ids in dependency-first create order. */\n order: string[]\n /** Fields deferred to break cycles (empty when the graph is acyclic). */\n deferred: DeferredField[]\n}\n\n/** Whether a top-level field is required (so its refs can't be deferred). Defaults to treating every\n * field as required — i.e. no field is deferrable, so any cycle is a hard error (the strict default).\n * The engine passes a real lookup (from the live config) to enable optional-field cycle breaking. */\nexport type RequiredLookup = (collection: string, field: string) => boolean\n\n/** Collection slug portion of a `collection:_key` node id (slugs never contain `:`). */\nconst collectionOf = (nodeId: string): string => nodeId.slice(0, nodeId.indexOf(':'))\n\n/**\n * Build the dependency graph: doc/global nodes, edges from each doc/global to the docs it\n * references, and the topologically sorted doc create order. When a `ref` cycle exists, an optional\n * field on one edge of the cycle is **deferred** (its refs set in a second pass) to break it; a cycle\n * with no optional field to break is a hard error. Pass `isRequired` to enable this — without it every\n * field is treated as required, so any cycle throws.\n */\nexport function buildGraph(model: BuiltModel, opts: { isRequired?: RequiredLookup } = {}): SeedGraph {\n const isRequired = opts.isRequired ?? (() => true)\n const nodes: GraphNode[] = []\n const edges: GraphEdge[] = []\n const docIds = new Set<string>()\n // Per doc: field name → the doc nodes it references (only refs that land on another seeded doc).\n const fieldTargets = new Map<string, Map<string, Set<string>>>()\n\n for (const coll of model.collections) {\n for (const rec of coll.records) {\n const id = docNodeId(coll.slug, rec.key)\n docIds.add(id)\n fieldTargets.set(id, new Map())\n nodes.push({ id, type: 'doc', label: `${coll.slug}:${rec.key}`, collection: coll.slug, key: rec.key })\n }\n }\n\n // Record edges per top-level field so a cycle can be broken at a specific (doc, field). `track`\n // is false for globals — they're never part of the doc create order (updated after every doc).\n const recordRefs = (fromId: string, data: Record<string, unknown>, track: boolean) => {\n for (const [field, value] of Object.entries(data)) {\n for (const token of collectTokens(value)) {\n const to = docNodeId(token.collection, token.key)\n edges.push({ from: fromId, to })\n if (!track || !docIds.has(to)) continue\n const byField = fieldTargets.get(fromId)\n if (!byField) continue\n let targets = byField.get(field)\n if (!targets) {\n targets = new Set()\n byField.set(field, targets)\n }\n targets.add(to)\n }\n }\n }\n\n for (const coll of model.collections) for (const rec of coll.records) recordRefs(docNodeId(coll.slug, rec.key), rec.data, true)\n for (const g of model.globals) {\n const id = `global:${g.slug}`\n nodes.push({ id, type: 'global', label: g.slug, slug: g.slug })\n recordRefs(id, g.data, false)\n }\n\n const { order, deferred } = planOrder(docIds, fieldTargets, isRequired)\n return { nodes, edges, order, deferred }\n}\n\n/** Break cycles by deferring optional fields, then depth-first topo-sort the resulting acyclic graph. */\nfunction planOrder(\n docIds: Set<string>,\n fieldTargets: Map<string, Map<string, Set<string>>>,\n isRequired: RequiredLookup,\n): { order: string[]; deferred: DeferredField[] } {\n // dependency adjacency (dependent → dependencies), mutated as cycles are broken\n const deps = new Map<string, Set<string>>()\n for (const id of docIds) deps.set(id, new Set())\n for (const [node, byField] of fieldTargets) {\n for (const targets of byField.values()) for (const to of targets) deps.get(node)?.add(to)\n }\n\n const deferred: DeferredField[] = []\n const seen = new Set<string>()\n for (let cycle = findCycle(docIds, deps); cycle !== null; cycle = findCycle(docIds, deps)) {\n if (!breakCycle(cycle, fieldTargets, deps, isRequired, deferred, seen)) {\n throw new Error(\n `[payload-seed] dependency cycle detected: ${cycle.join(' -> ')} - every ref in the cycle is on a required field; make one optional to break it.`,\n )\n }\n }\n return { order: topoSortDocs(docIds, deps), deferred }\n}\n\n/**\n * Break one edge of a detected cycle by deferring an **optional** field. Walks the cycle's edges and\n * defers the first `(from, field)` whose field is optional and references the next node — removing all\n * of that field's edges from the graph. Returns false when no edge in the cycle is breakable.\n */\nfunction breakCycle(\n cycle: string[],\n fieldTargets: Map<string, Map<string, Set<string>>>,\n deps: Map<string, Set<string>>,\n isRequired: RequiredLookup,\n deferred: DeferredField[],\n seen: Set<string>,\n): boolean {\n for (let i = 0; i < cycle.length - 1; i++) {\n const from = cycle[i]\n const to = cycle[i + 1]\n if (from === undefined || to === undefined) continue\n const byField = fieldTargets.get(from)\n if (!byField) continue\n for (const [field, targets] of byField) {\n if (!targets.has(to) || isRequired(collectionOf(from), field)) continue\n // Defer the whole field: remove every edge it contributes (safe — the second pass sets it\n // once all docs exist), which breaks this cycle and any other routed through the same field.\n for (const t of targets) deps.get(from)?.delete(t)\n const key = `${from}\u0000${field}`\n if (!seen.has(key)) {\n seen.add(key)\n deferred.push({ node: from, field })\n }\n return true\n }\n }\n return false\n}\n\n/** Find one dependency cycle as `[start, …, start]`, or null if the graph is acyclic. */\nfunction findCycle(docIds: Set<string>, deps: Map<string, Set<string>>): string[] | null {\n const state = new Map<string, 'visiting' | 'done'>()\n const stack: string[] = []\n let cycle: string[] | null = null\n\n const visit = (id: string) => {\n if (cycle) return\n const s = state.get(id)\n if (s === 'done') return\n if (s === 'visiting') {\n cycle = [...stack.slice(stack.indexOf(id)), id]\n return\n }\n state.set(id, 'visiting')\n stack.push(id)\n for (const dep of deps.get(id) ?? []) {\n if (!docIds.has(dep)) continue\n visit(dep)\n if (cycle) break\n }\n stack.pop()\n state.set(id, 'done')\n }\n\n for (const id of docIds) {\n if (cycle) break\n visit(id)\n }\n return cycle\n}\n\n/** Depth-first topological sort (dependencies first). Throws on a cycle — a safety assertion, since\n * `planOrder` breaks every cycle before calling this. */\nfunction topoSortDocs(docIds: Set<string>, deps: Map<string, Set<string>>): string[] {\n const order: string[] = []\n const state = new Map<string, 'visiting' | 'done'>()\n const stack: string[] = []\n\n const visit = (id: string) => {\n const s = state.get(id)\n if (s === 'done') return\n if (s === 'visiting') {\n const cycle = [...stack.slice(stack.indexOf(id)), id].join(' -> ')\n throw new Error(`[payload-seed] dependency cycle detected: ${cycle}`)\n }\n state.set(id, 'visiting')\n stack.push(id)\n for (const dep of deps.get(id) ?? []) {\n if (!docIds.has(dep)) continue\n visit(dep)\n }\n stack.pop()\n state.set(id, 'done')\n order.push(id)\n }\n\n for (const id of docIds) visit(id)\n return order\n}\n"],"names":["collectTokens","docNodeId","collectionOf","nodeId","slice","indexOf","buildGraph","model","opts","isRequired","nodes","edges","docIds","Set","fieldTargets","Map","coll","collections","rec","records","id","slug","key","add","set","push","type","label","collection","recordRefs","fromId","data","track","field","value","Object","entries","token","to","from","has","byField","get","targets","g","globals","order","deferred","planOrder","deps","node","values","seen","cycle","findCycle","breakCycle","Error","join","topoSortDocs","i","length","undefined","t","delete","state","stack","visit","s","dep","pop"],"mappings":"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"}