@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
@@ -0,0 +1,46 @@
1
+ import { type Payload, type PayloadRequest } from 'payload';
2
+ import { type ResolvedSeedOptions, type SeedPluginOptions } from '../options';
3
+ import type { SeedDefinition } from '../types';
4
+ import { type DeferredField } from './graph';
5
+ export interface SeedResult {
6
+ /** Created doc counts keyed by collection slug. */
7
+ created: Record<string, number>;
8
+ /** The computed topological create order (doc node ids, `collection:_key`). */
9
+ order: string[];
10
+ /** Fields deferred to break a `ref` cycle: created null, then set in a second pass. */
11
+ deferred: DeferredField[];
12
+ /** Definitions skipped this run (their own `disabled`, or the collection's `custom.seedDisabled`). */
13
+ skipped: SkippedDefinition[];
14
+ }
15
+ export interface SkippedDefinition {
16
+ slug: string;
17
+ reason: string;
18
+ }
19
+ export interface RunSeedArgs {
20
+ payload: Payload;
21
+ req: PayloadRequest;
22
+ options: ResolvedSeedOptions;
23
+ /** Seed definitions. Falls back to `options.definitions` when omitted. */
24
+ definitions?: SeedDefinition[];
25
+ }
26
+ /**
27
+ * The seed engine. Takes the seed definitions, skips the disabled ones (their own `disabled`, or
28
+ * the collection's `custom.seedDisabled` — dropping optional refs that point at them), builds the
29
+ * model, validates references against the live config, topologically sorts the dependency graph,
30
+ * clears the seeded collections, then creates docs in order — resolving `ref` tokens to ids and
31
+ * delivering each doc's `_file` as a native upload (upload collections) or a source-field value
32
+ * (`custom.seedAsset` collections). A `ref` cycle is broken by deferring an optional field, which a
33
+ * second pass sets once every doc exists (a cycle with only required fields is a hard error).
34
+ * Globals are updated last.
35
+ */
36
+ export declare function runSeed({ payload, req, options, definitions }: RunSeedArgs): Promise<SeedResult>;
37
+ /**
38
+ * CLI / Local-API convenience: run the seed from a script (`payload run`) or test. Builds
39
+ * a local `req` if one isn't supplied and resolves the public plugin options.
40
+ */
41
+ export declare function seed(args: {
42
+ payload: Payload;
43
+ req?: PayloadRequest;
44
+ options?: SeedPluginOptions;
45
+ }): Promise<SeedResult>;
46
+ //# sourceMappingURL=run.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../../src/engine/run.ts"],"names":[],"mappings":"AAAA,OAAO,EAAuC,KAAK,OAAO,EAAE,KAAK,cAAc,EAAE,MAAM,SAAS,CAAA;AAChG,OAAO,EAAkB,KAAK,mBAAmB,EAAE,KAAK,iBAAiB,EAAE,MAAM,YAAY,CAAA;AAE7F,OAAO,KAAK,EAAmB,cAAc,EAAsB,MAAM,UAAU,CAAA;AACnF,OAAO,EAAyF,KAAK,aAAa,EAAE,MAAM,SAAS,CAAA;AAKnI,MAAM,WAAW,UAAU;IACzB,mDAAmD;IACnD,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC/B,+EAA+E;IAC/E,KAAK,EAAE,MAAM,EAAE,CAAA;IACf,uFAAuF;IACvF,QAAQ,EAAE,aAAa,EAAE,CAAA;IACzB,sGAAsG;IACtG,OAAO,EAAE,iBAAiB,EAAE,CAAA;CAC7B;AAED,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,MAAM,CAAA;CACf;AAED,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,OAAO,CAAA;IAChB,GAAG,EAAE,cAAc,CAAA;IACnB,OAAO,EAAE,mBAAmB,CAAA;IAC5B,0EAA0E;IAC1E,WAAW,CAAC,EAAE,cAAc,EAAE,CAAA;CAC/B;AA0JD;;;;;;;;;GASG;AACH,wBAAsB,OAAO,CAAC,EAAE,OAAO,EAAE,GAAG,EAAE,OAAO,EAAE,WAAW,EAAE,EAAE,WAAW,GAAG,OAAO,CAAC,UAAU,CAAC,CAkJtG;AAED;;;GAGG;AACH,wBAAsB,IAAI,CAAC,IAAI,EAAE;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,GAAG,CAAC,EAAE,cAAc,CAAC;IAAC,OAAO,CAAC,EAAE,iBAAiB,CAAA;CAAE,GAAG,OAAO,CAAC,UAAU,CAAC,CAG7H"}
@@ -0,0 +1,387 @@
1
+ import { createLocalReq } from "payload";
2
+ import { resolveOptions } from "../options.js";
3
+ import { file, isFileToken, isRef, ref } from "../refs.js";
4
+ import { buildGraph } from "./graph.js";
5
+ import { resolveFilePath, readFileAsUpload, searchedDirs } from "./files.js";
6
+ import { collectTokens, docNodeId, resolveTokens } from "./tokens.js";
7
+ import { SeedValidationError, validateModel } from "./validate.js";
8
+ const tokens = {
9
+ ref,
10
+ file
11
+ };
12
+ /** Discover asset collections from the live config: any collection whose `custom.seedAsset` is set.
13
+ * A `_file` on one of these is handed to the collection's ingest hook via `sourceField` instead of
14
+ * uploaded as bytes. Replaces the old `assetProviders` plugin option — declaration now lives on the
15
+ * collection, so the seed plugin needs no knowledge of the owning plugins. */ function discoverAssetCollections(payload) {
16
+ const map = new Map();
17
+ for (const slug of Object.keys(payload.collections)){
18
+ const marker = payload.collections[slug]?.config.custom?.seedAsset;
19
+ if (!marker) continue;
20
+ const m = marker === true ? {} : marker;
21
+ map.set(slug, {
22
+ sourceField: m.sourceField ?? 'source',
23
+ subdir: m.subdir
24
+ });
25
+ }
26
+ return map;
27
+ }
28
+ /** Split definitions by kind and build the concrete model (records + their `_file`, and globals). */ function buildModel(definitions) {
29
+ const collections = [];
30
+ const globals = [];
31
+ for (const def of definitions){
32
+ if (def.kind === 'collection') {
33
+ const records = def.build(tokens).map((rec)=>{
34
+ const { _key, _file, ...data } = rec;
35
+ return {
36
+ key: _key,
37
+ file: isFileToken(_file) ? _file : undefined,
38
+ data
39
+ };
40
+ });
41
+ collections.push({
42
+ slug: def.slug,
43
+ records
44
+ });
45
+ } else if (def.kind === 'global') {
46
+ globals.push({
47
+ slug: def.slug,
48
+ data: def.build(tokens)
49
+ });
50
+ }
51
+ }
52
+ return {
53
+ collections,
54
+ globals
55
+ };
56
+ }
57
+ /** Split definitions into runnable and skipped. A definition is skipped when its own `disabled` is
58
+ * set, or when its target collection declares `custom.seedDisabled` (e.g. a plugin detecting
59
+ * missing credentials at config time). Skipped definitions still shaped the generated seed-ref
60
+ * types — only the run drops them, so types stay stable across environments. */ function partitionDefinitions(payload, defs) {
61
+ const active = [];
62
+ const skipped = [];
63
+ for (const def of defs){
64
+ const fromCollection = def.kind === 'collection' ? payload.collections[def.slug]?.config.custom?.seedDisabled : undefined;
65
+ const flag = def.disabled || fromCollection;
66
+ if (!flag) {
67
+ active.push(def);
68
+ continue;
69
+ }
70
+ const reason = typeof flag === 'string' ? flag : 'disabled';
71
+ skipped.push({
72
+ slug: def.slug,
73
+ reason
74
+ });
75
+ payload.logger.warn(`[payload-seed] skipping '${def.slug}': ${reason}`);
76
+ }
77
+ return {
78
+ active,
79
+ skipped
80
+ };
81
+ }
82
+ /** Drop every optional field whose value contains a `ref()` into a skipped collection (warning per
83
+ * drop — the doc won't exist this run), and hard-error when such a ref sits on a required field.
84
+ * Runs before validation, so the remaining model checks clean. */ function stripRefsToSkipped(payload, model, skipped, requiredFields) {
85
+ if (!skipped.length) return;
86
+ const reasonBySlug = new Map(skipped.map((s)=>[
87
+ s.slug,
88
+ s.reason
89
+ ]));
90
+ const issues = [];
91
+ const strip = (where, slug, data)=>{
92
+ for (const [field, value] of Object.entries(data)){
93
+ const hit = collectTokens(value).find((t)=>isRef(t) && reasonBySlug.has(t.collection));
94
+ if (!hit || !isRef(hit)) continue;
95
+ if (slug && requiredFields.get(slug)?.has(field)) {
96
+ issues.push(`${where}.${field}: required, but ref('${hit.collection}', '${hit.key}') targets a skipped definition (${reasonBySlug.get(hit.collection)}).`);
97
+ continue;
98
+ }
99
+ delete data[field];
100
+ payload.logger.warn(`[payload-seed] dropping entire field '${field}' on ${where} (contains ref('${hit.collection}', '${hit.key}') to skipped '${hit.collection}': ${reasonBySlug.get(hit.collection)}).`);
101
+ }
102
+ };
103
+ for (const coll of model.collections)for (const rec of coll.records)strip(docNodeId(coll.slug, rec.key), coll.slug, rec.data);
104
+ for (const g of model.globals)strip(`global:${g.slug}`, undefined, g.data);
105
+ if (issues.length) throw new SeedValidationError(issues);
106
+ }
107
+ async function clearCollection(payload, req, collection) {
108
+ const config = payload.collections[collection]?.config;
109
+ if (!config) return;
110
+ payload.logger.info(`[payload-seed] clearing ${collection}`);
111
+ // Delete via the Local API (firing hooks) when clearing must cascade — an upload collection (to
112
+ // remove stored bytes) or any collection with a before/after-delete hook (e.g. `mux-video`'s Mux
113
+ // cleanup, `font`'s cascade to its originals + optimized). Otherwise wipe rows directly.
114
+ const withHooks = Boolean(config.upload || config.hooks?.beforeDelete?.length || config.hooks?.afterDelete?.length);
115
+ if (withHooks) {
116
+ const result = await payload.delete({
117
+ collection: collection,
118
+ where: {
119
+ id: {
120
+ exists: true
121
+ }
122
+ },
123
+ req,
124
+ overrideAccess: true,
125
+ context: {
126
+ disableRevalidate: true
127
+ },
128
+ disableTransaction: true
129
+ });
130
+ // Payload's bulk delete does NOT throw on per-doc failures — it returns them in `errors`.
131
+ // Retry each once, then warn LOUDLY with the underlying reasons — a silent partial wipe
132
+ // would leave stale docs sitting beside the fresh seed.
133
+ const failed = [];
134
+ for (const e of result?.errors ?? []){
135
+ if (e.id == null) continue;
136
+ try {
137
+ await payload.delete({
138
+ collection: collection,
139
+ id: e.id,
140
+ req,
141
+ overrideAccess: true,
142
+ context: {
143
+ disableRevalidate: true
144
+ },
145
+ disableTransaction: true
146
+ });
147
+ } catch (err) {
148
+ // Surface the DEEPEST cause: the ORM wraps the driver error ("Failed query: …"), which
149
+ // hides the actionable part (e.g. "NOT NULL constraint failed: projects_gallery.image_id").
150
+ let deepest = err instanceof Error ? err : undefined;
151
+ while(deepest?.cause instanceof Error)deepest = deepest.cause;
152
+ const reason = (deepest?.message ?? e.message ?? String(err)).replace(/\s+/g, ' ').slice(0, 300);
153
+ failed.push({
154
+ id: e.id,
155
+ reason
156
+ });
157
+ }
158
+ }
159
+ if (failed.length) {
160
+ const detail = failed.map((f)=>`${f.id}: ${f.reason}`).join(' | ');
161
+ payload.logger.warn(`[payload-seed] could not clear ${failed.length} doc(s) in '${collection}' — these STALE docs now sit beside the fresh seed; re-run the seed or delete them in the admin. Reasons: ${detail}`);
162
+ }
163
+ } else {
164
+ await payload.db.deleteMany({
165
+ collection: collection,
166
+ req,
167
+ where: {}
168
+ });
169
+ }
170
+ if (config.versions) await payload.db.deleteVersions({
171
+ collection: collection,
172
+ req,
173
+ where: {}
174
+ });
175
+ }
176
+ /**
177
+ * The seed engine. Takes the seed definitions, skips the disabled ones (their own `disabled`, or
178
+ * the collection's `custom.seedDisabled` — dropping optional refs that point at them), builds the
179
+ * model, validates references against the live config, topologically sorts the dependency graph,
180
+ * clears the seeded collections, then creates docs in order — resolving `ref` tokens to ids and
181
+ * delivering each doc's `_file` as a native upload (upload collections) or a source-field value
182
+ * (`custom.seedAsset` collections). A `ref` cycle is broken by deferring an optional field, which a
183
+ * second pass sets once every doc exists (a cycle with only required fields is a hard error).
184
+ * Globals are updated last.
185
+ */ export async function runSeed({ payload, req, options, definitions }) {
186
+ const defs = definitions ?? options.definitions ?? [];
187
+ if (defs.length === 0) payload.logger.warn('[payload-seed] no seed definitions: pass `definitions` to seedPlugin() or seed().');
188
+ // Drop disabled definitions (their own `disabled`, or the collection's `custom.seedDisabled` —
189
+ // e.g. payload-mux without credentials). They still shaped the generated seed-ref types.
190
+ const { active, skipped } = partitionDefinitions(payload, defs);
191
+ const model = buildModel(active);
192
+ const collectionSlugs = new Set(Object.keys(payload.collections));
193
+ const isUpload = (slug)=>Boolean(payload.collections[slug]?.config.upload);
194
+ const assetBySlug = discoverAssetCollections(payload);
195
+ // Collections a `_file` may sit on: every upload collection plus every `custom.seedAsset` collection.
196
+ const fileCollections = new Set([
197
+ ...collectionSlugs
198
+ ].filter(isUpload));
199
+ for (const slug of assetBySlug.keys())fileCollections.add(slug);
200
+ // Valid top-level field names per node (for unknown-field detection) plus the required ones (so a
201
+ // ref cycle can only be broken by deferring an optional field) — read from the live config.
202
+ const fieldNames = new Map();
203
+ const requiredFields = new Map();
204
+ for (const coll of model.collections){
205
+ const cfg = payload.collections[coll.slug]?.config;
206
+ if (!cfg) continue;
207
+ fieldNames.set(coll.slug, new Set(cfg.flattenedFields.map((f)=>f.name)));
208
+ requiredFields.set(coll.slug, new Set(cfg.flattenedFields.filter((f)=>f.required).map((f)=>f.name)));
209
+ }
210
+ for (const g of model.globals){
211
+ const cfg = payload.config.globals.find((gc)=>gc.slug === g.slug);
212
+ if (cfg) fieldNames.set(`global:${g.slug}`, new Set(cfg.flattenedFields.map((f)=>f.name)));
213
+ }
214
+ // Refs into a skipped definition come out of the data before validation: dropped when the field
215
+ // is optional (the run warns; re-seed once the skip is lifted and they fill in), fatal when it's
216
+ // required (the doc can't be created without it).
217
+ stripRefsToSkipped(payload, model, skipped, requiredFields);
218
+ const globalSlugs = new Set(payload.config.globals.map((g)=>g.slug));
219
+ validateModel({
220
+ model,
221
+ collectionSlugs,
222
+ globalSlugs,
223
+ fileCollections,
224
+ fieldNames
225
+ });
226
+ const isRequired = (collection, field)=>requiredFields.get(collection)?.has(field) ?? false;
227
+ const { order, deferred } = buildGraph(model, {
228
+ isRequired
229
+ });
230
+ // Fields nulled at create time (their refs point into a cycle) and set in a second pass below.
231
+ const deferredByNode = new Map();
232
+ for (const d of deferred){
233
+ const set = deferredByNode.get(d.node) ?? new Set();
234
+ set.add(d.field);
235
+ deferredByNode.set(d.node, set);
236
+ }
237
+ const baseArgs = {
238
+ depth: 0,
239
+ overrideAccess: true,
240
+ context: {
241
+ disableRevalidate: true
242
+ },
243
+ req
244
+ };
245
+ const docIds = new Map();
246
+ const recordIndex = new Map();
247
+ for (const coll of model.collections)for (const rec of coll.records)recordIndex.set(docNodeId(coll.slug, rec.key), {
248
+ slug: coll.slug,
249
+ record: rec
250
+ });
251
+ // Clear every seeded collection — dependents BEFORE their dependencies (the REVERSE of creation
252
+ // order). Clearing in creation order deletes referenced docs while the previous run's
253
+ // referencing rows still exist, and on SQL adapters a relationship column can make that delete
254
+ // FAIL outright (e.g. sqlite generates a required in-array upload as NOT NULL with an
255
+ // ON DELETE SET NULL foreign key — nulling it violates the constraint), stranding stale docs
256
+ // beside the fresh seed. `clearCollection` fires delete hooks when the collection needs a
257
+ // cascade (uploads / external-asset cleanup); plain collections are wiped directly.
258
+ const seededCollections = [
259
+ ...new Set(model.collections.map((c)=>c.slug))
260
+ ];
261
+ const creationOrder = [];
262
+ const seen = new Set();
263
+ for (const nodeId of order){
264
+ const slug = recordIndex.get(nodeId)?.slug;
265
+ if (slug && !seen.has(slug)) {
266
+ seen.add(slug);
267
+ creationOrder.push(slug);
268
+ }
269
+ }
270
+ // Definitions whose records were all skipped/empty still get cleared (after the ordered ones).
271
+ for (const slug of seededCollections)if (!seen.has(slug)) creationOrder.push(slug);
272
+ payload.logger.info('[payload-seed] clearing collections...');
273
+ for (const slug of [
274
+ ...creationOrder
275
+ ].reverse())await clearCollection(payload, req, slug);
276
+ // Create docs in dependency order, resolving ref tokens to ids and delivering each `_file`.
277
+ const created = {};
278
+ payload.logger.info('[payload-seed] seeding documents...');
279
+ for (const nodeId of order){
280
+ const entry = recordIndex.get(nodeId);
281
+ if (!entry) continue;
282
+ const { slug, record } = entry;
283
+ // Drop any deferred fields before resolving: their refs point into a cycle and aren't created
284
+ // yet. The second pass below sets them once every doc exists.
285
+ const deferFields = deferredByNode.get(nodeId);
286
+ const source = deferFields ? Object.fromEntries(Object.entries(record.data).filter(([k])=>!deferFields.has(k))) : record.data;
287
+ let data = resolveTokens(source, {
288
+ docs: docIds,
289
+ where: nodeId
290
+ });
291
+ let uploadFile;
292
+ if (record.file) {
293
+ // Both branches resolve the file the same way — under the per-collection subdir (a
294
+ // `custom.seedAsset` `subdir`, else `assetSubDirs`, else the slug), then the assets root.
295
+ const asset = assetBySlug.get(slug);
296
+ const subdir = asset?.subdir ?? options.assetSubDirs[slug] ?? slug;
297
+ const subdirs = [
298
+ subdir,
299
+ ''
300
+ ];
301
+ const path = await resolveFilePath(record.file.name, options.assetsDir, subdirs);
302
+ if (!path) {
303
+ const searched = searchedDirs(record.file.name, options.assetsDir, subdirs).join(', ');
304
+ payload.logger.warn({
305
+ msg: `[payload-seed] ${nodeId}: _file '${record.file.name}' not found - skipped. Searched: ${searched}`
306
+ });
307
+ } else if (asset) {
308
+ // Hand the resolved path + options to the collection's ingest hook via its source field
309
+ // instead of uploading bytes.
310
+ data = {
311
+ ...data,
312
+ [asset.sourceField]: {
313
+ file: path,
314
+ ...record.file.options
315
+ }
316
+ };
317
+ } else if (isUpload(slug)) {
318
+ uploadFile = await readFileAsUpload(path);
319
+ }
320
+ }
321
+ payload.logger.info(`[payload-seed] seeding '${nodeId}'`);
322
+ const doc = await payload.create({
323
+ collection: slug,
324
+ data: data,
325
+ ...uploadFile ? {
326
+ file: uploadFile
327
+ } : {},
328
+ ...baseArgs
329
+ });
330
+ docIds.set(nodeId, doc.id);
331
+ created[slug] = (created[slug] ?? 0) + 1;
332
+ }
333
+ // Second pass: now every doc exists, resolve and set the fields deferred to break cycles.
334
+ if (deferred.length) {
335
+ payload.logger.info(`[payload-seed] resolving ${deferred.length} deferred reference(s)...`);
336
+ for (const { node, field } of deferred){
337
+ const entry = recordIndex.get(node);
338
+ const id = docIds.get(node);
339
+ if (!entry || id === undefined) continue;
340
+ const value = resolveTokens(entry.record.data[field], {
341
+ docs: docIds,
342
+ where: `${node}.${field}`
343
+ });
344
+ await payload.update({
345
+ collection: entry.slug,
346
+ id,
347
+ data: {
348
+ [field]: value
349
+ },
350
+ ...baseArgs
351
+ });
352
+ }
353
+ }
354
+ // Update globals after all docs exist.
355
+ for (const g of model.globals){
356
+ payload.logger.info(`[payload-seed] seeding global '${g.slug}'`);
357
+ const data = resolveTokens(g.data, {
358
+ docs: docIds,
359
+ where: `global:${g.slug}`
360
+ });
361
+ await payload.updateGlobal({
362
+ slug: g.slug,
363
+ data: data,
364
+ ...baseArgs
365
+ });
366
+ }
367
+ payload.logger.info('[payload-seed] seed complete.');
368
+ return {
369
+ created,
370
+ order,
371
+ deferred,
372
+ skipped
373
+ };
374
+ }
375
+ /**
376
+ * CLI / Local-API convenience: run the seed from a script (`payload run`) or test. Builds
377
+ * a local `req` if one isn't supplied and resolves the public plugin options.
378
+ */ export async function seed(args) {
379
+ const req = args.req ?? await createLocalReq({}, args.payload);
380
+ return runSeed({
381
+ payload: args.payload,
382
+ req,
383
+ options: resolveOptions(args.options)
384
+ });
385
+ }
386
+
387
+ //# sourceMappingURL=run.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/engine/run.ts"],"sourcesContent":["import { type CollectionSlug, createLocalReq, type Payload, type PayloadRequest } from 'payload'\nimport { resolveOptions, type ResolvedSeedOptions, type SeedPluginOptions } from '../options'\nimport { file, isFileToken, isRef, ref } from '../refs'\nimport type { SeedAssetMarker, SeedDefinition, SeedDisabledMarker } from '../types'\nimport { type BuiltCollection, type BuiltGlobal, type BuiltModel, type BuiltRecord, buildGraph, type DeferredField } from './graph'\nimport { resolveFilePath, readFileAsUpload, searchedDirs } from './files'\nimport { collectTokens, docNodeId, resolveTokens } from './tokens'\nimport { SeedValidationError, validateModel } from './validate'\n\nexport interface SeedResult {\n /** Created doc counts keyed by collection slug. */\n created: Record<string, number>\n /** The computed topological create order (doc node ids, `collection:_key`). */\n order: string[]\n /** Fields deferred to break a `ref` cycle: created null, then set in a second pass. */\n deferred: DeferredField[]\n /** Definitions skipped this run (their own `disabled`, or the collection's `custom.seedDisabled`). */\n skipped: SkippedDefinition[]\n}\n\nexport interface SkippedDefinition {\n slug: string\n reason: string\n}\n\nexport interface RunSeedArgs {\n payload: Payload\n req: PayloadRequest\n options: ResolvedSeedOptions\n /** Seed definitions. Falls back to `options.definitions` when omitted. */\n definitions?: SeedDefinition[]\n}\n\nconst tokens = { ref, file }\n\n/** A `custom.seedAsset` collection, resolved to its effective source field (subdir defaults are\n * applied at lookup, alongside `assetSubDirs`, so they match native uploads). */\ninterface AssetCollection {\n sourceField: string\n subdir?: string\n}\n\n/** Discover asset collections from the live config: any collection whose `custom.seedAsset` is set.\n * A `_file` on one of these is handed to the collection's ingest hook via `sourceField` instead of\n * uploaded as bytes. Replaces the old `assetProviders` plugin option — declaration now lives on the\n * collection, so the seed plugin needs no knowledge of the owning plugins. */\nfunction discoverAssetCollections(payload: Payload): Map<string, AssetCollection> {\n const map = new Map<string, AssetCollection>()\n for (const slug of Object.keys(payload.collections)) {\n const marker = payload.collections[slug as CollectionSlug]?.config.custom?.seedAsset as SeedAssetMarker | undefined\n if (!marker) continue\n const m = marker === true ? {} : marker\n map.set(slug, { sourceField: m.sourceField ?? 'source', subdir: m.subdir })\n }\n return map\n}\n\n/** Split definitions by kind and build the concrete model (records + their `_file`, and globals). */\nfunction buildModel(definitions: SeedDefinition[]): BuiltModel {\n const collections: BuiltCollection[] = []\n const globals: BuiltGlobal[] = []\n\n for (const def of definitions) {\n if (def.kind === 'collection') {\n const records: BuiltRecord[] = def.build(tokens).map((rec) => {\n const { _key, _file, ...data } = rec as { _key: string; _file?: unknown } & Record<string, unknown>\n return { key: _key, file: isFileToken(_file) ? _file : undefined, data }\n })\n collections.push({ slug: def.slug, records })\n } else if (def.kind === 'global') {\n globals.push({ slug: def.slug, data: def.build(tokens) as Record<string, unknown> })\n }\n }\n\n return { collections, globals }\n}\n\n/** Split definitions into runnable and skipped. A definition is skipped when its own `disabled` is\n * set, or when its target collection declares `custom.seedDisabled` (e.g. a plugin detecting\n * missing credentials at config time). Skipped definitions still shaped the generated seed-ref\n * types — only the run drops them, so types stay stable across environments. */\nfunction partitionDefinitions(payload: Payload, defs: SeedDefinition[]): { active: SeedDefinition[]; skipped: SkippedDefinition[] } {\n const active: SeedDefinition[] = []\n const skipped: SkippedDefinition[] = []\n for (const def of defs) {\n const fromCollection =\n def.kind === 'collection'\n ? (payload.collections[def.slug as CollectionSlug]?.config.custom?.seedDisabled as SeedDisabledMarker | undefined)\n : undefined\n const flag = def.disabled || fromCollection\n if (!flag) {\n active.push(def)\n continue\n }\n const reason = typeof flag === 'string' ? flag : 'disabled'\n skipped.push({ slug: def.slug, reason })\n payload.logger.warn(`[payload-seed] skipping '${def.slug}': ${reason}`)\n }\n return { active, skipped }\n}\n\n/** Drop every optional field whose value contains a `ref()` into a skipped collection (warning per\n * drop — the doc won't exist this run), and hard-error when such a ref sits on a required field.\n * Runs before validation, so the remaining model checks clean. */\nfunction stripRefsToSkipped(payload: Payload, model: BuiltModel, skipped: SkippedDefinition[], requiredFields: Map<string, Set<string>>): void {\n if (!skipped.length) return\n const reasonBySlug = new Map(skipped.map((s) => [s.slug, s.reason]))\n const issues: string[] = []\n\n const strip = (where: string, slug: string | undefined, data: Record<string, unknown>) => {\n for (const [field, value] of Object.entries(data)) {\n const hit = collectTokens(value).find((t) => isRef(t) && reasonBySlug.has(t.collection))\n if (!hit || !isRef(hit)) continue\n if (slug && requiredFields.get(slug)?.has(field)) {\n issues.push(\n `${where}.${field}: required, but ref('${hit.collection}', '${hit.key}') targets a skipped definition (${reasonBySlug.get(hit.collection)}).`,\n )\n continue\n }\n delete data[field]\n payload.logger.warn(\n `[payload-seed] dropping entire field '${field}' on ${where} (contains ref('${hit.collection}', '${hit.key}') to skipped '${hit.collection}': ${reasonBySlug.get(hit.collection)}).`,\n )\n }\n }\n\n for (const coll of model.collections) for (const rec of coll.records) strip(docNodeId(coll.slug, rec.key), coll.slug, rec.data)\n for (const g of model.globals) strip(`global:${g.slug}`, undefined, g.data)\n\n if (issues.length) throw new SeedValidationError(issues)\n}\n\nasync function clearCollection(payload: Payload, req: PayloadRequest, collection: string): Promise<void> {\n const config = payload.collections[collection as CollectionSlug]?.config\n if (!config) return\n payload.logger.info(`[payload-seed] clearing ${collection}`)\n // Delete via the Local API (firing hooks) when clearing must cascade — an upload collection (to\n // remove stored bytes) or any collection with a before/after-delete hook (e.g. `mux-video`'s Mux\n // cleanup, `font`'s cascade to its originals + optimized). Otherwise wipe rows directly.\n const withHooks = Boolean(config.upload || config.hooks?.beforeDelete?.length || config.hooks?.afterDelete?.length)\n if (withHooks) {\n const result = (await payload.delete({\n collection: collection as CollectionSlug,\n where: { id: { exists: true } },\n req,\n overrideAccess: true,\n context: { disableRevalidate: true },\n disableTransaction: true,\n })) as { errors?: Array<{ id?: string | number; message?: string }> }\n // Payload's bulk delete does NOT throw on per-doc failures — it returns them in `errors`.\n // Retry each once, then warn LOUDLY with the underlying reasons — a silent partial wipe\n // would leave stale docs sitting beside the fresh seed.\n const failed: Array<{ id: string | number; reason: string }> = []\n for (const e of result?.errors ?? []) {\n if (e.id == null) continue\n try {\n await payload.delete({\n collection: collection as CollectionSlug,\n id: e.id,\n req,\n overrideAccess: true,\n context: { disableRevalidate: true },\n disableTransaction: true,\n })\n } catch (err) {\n // Surface the DEEPEST cause: the ORM wraps the driver error (\"Failed query: …\"), which\n // hides the actionable part (e.g. \"NOT NULL constraint failed: projects_gallery.image_id\").\n let deepest = err instanceof Error ? err : undefined\n while (deepest?.cause instanceof Error) deepest = deepest.cause\n const reason = (deepest?.message ?? e.message ?? String(err)).replace(/\\s+/g, ' ').slice(0, 300)\n failed.push({ id: e.id, reason })\n }\n }\n if (failed.length) {\n const detail = failed.map((f) => `${f.id}: ${f.reason}`).join(' | ')\n payload.logger.warn(\n `[payload-seed] could not clear ${failed.length} doc(s) in '${collection}' — these STALE docs now sit beside the fresh seed; re-run the seed or delete them in the admin. Reasons: ${detail}`,\n )\n }\n } else {\n await payload.db.deleteMany({ collection: collection as CollectionSlug, req, where: {} })\n }\n if (config.versions) await payload.db.deleteVersions({ collection: collection as CollectionSlug, req, where: {} })\n}\n\n/**\n * The seed engine. Takes the seed definitions, skips the disabled ones (their own `disabled`, or\n * the collection's `custom.seedDisabled` — dropping optional refs that point at them), builds the\n * model, validates references against the live config, topologically sorts the dependency graph,\n * clears the seeded collections, then creates docs in order — resolving `ref` tokens to ids and\n * delivering each doc's `_file` as a native upload (upload collections) or a source-field value\n * (`custom.seedAsset` collections). A `ref` cycle is broken by deferring an optional field, which a\n * second pass sets once every doc exists (a cycle with only required fields is a hard error).\n * Globals are updated last.\n */\nexport async function runSeed({ payload, req, options, definitions }: RunSeedArgs): Promise<SeedResult> {\n const defs = definitions ?? options.definitions ?? []\n if (defs.length === 0) payload.logger.warn('[payload-seed] no seed definitions: pass `definitions` to seedPlugin() or seed().')\n\n // Drop disabled definitions (their own `disabled`, or the collection's `custom.seedDisabled` —\n // e.g. payload-mux without credentials). They still shaped the generated seed-ref types.\n const { active, skipped } = partitionDefinitions(payload, defs)\n\n const model = buildModel(active)\n const collectionSlugs = new Set(Object.keys(payload.collections))\n\n const isUpload = (slug: string): boolean => Boolean(payload.collections[slug as CollectionSlug]?.config.upload)\n const assetBySlug = discoverAssetCollections(payload)\n\n // Collections a `_file` may sit on: every upload collection plus every `custom.seedAsset` collection.\n const fileCollections = new Set<string>([...collectionSlugs].filter(isUpload))\n for (const slug of assetBySlug.keys()) fileCollections.add(slug)\n\n // Valid top-level field names per node (for unknown-field detection) plus the required ones (so a\n // ref cycle can only be broken by deferring an optional field) — read from the live config.\n const fieldNames = new Map<string, Set<string>>()\n const requiredFields = new Map<string, Set<string>>()\n for (const coll of model.collections) {\n const cfg = payload.collections[coll.slug as CollectionSlug]?.config\n if (!cfg) continue\n fieldNames.set(coll.slug, new Set(cfg.flattenedFields.map((f) => f.name)))\n requiredFields.set(coll.slug, new Set(cfg.flattenedFields.filter((f) => (f as { required?: boolean }).required).map((f) => f.name)))\n }\n for (const g of model.globals) {\n const cfg = payload.config.globals.find((gc) => gc.slug === g.slug)\n if (cfg) fieldNames.set(`global:${g.slug}`, new Set(cfg.flattenedFields.map((f) => f.name)))\n }\n\n // Refs into a skipped definition come out of the data before validation: dropped when the field\n // is optional (the run warns; re-seed once the skip is lifted and they fill in), fatal when it's\n // required (the doc can't be created without it).\n stripRefsToSkipped(payload, model, skipped, requiredFields)\n\n const globalSlugs = new Set(payload.config.globals.map((g) => g.slug))\n validateModel({ model, collectionSlugs, globalSlugs, fileCollections, fieldNames })\n const isRequired = (collection: string, field: string): boolean => requiredFields.get(collection)?.has(field) ?? false\n const { order, deferred } = buildGraph(model, { isRequired })\n\n // Fields nulled at create time (their refs point into a cycle) and set in a second pass below.\n const deferredByNode = new Map<string, Set<string>>()\n for (const d of deferred) {\n const set = deferredByNode.get(d.node) ?? new Set<string>()\n set.add(d.field)\n deferredByNode.set(d.node, set)\n }\n\n const baseArgs = { depth: 0, overrideAccess: true, context: { disableRevalidate: true }, req } as const\n\n const docIds = new Map<string, string | number>()\n const recordIndex = new Map<string, { slug: string; record: BuiltRecord }>()\n for (const coll of model.collections)\n for (const rec of coll.records) recordIndex.set(docNodeId(coll.slug, rec.key), { slug: coll.slug, record: rec })\n\n // Clear every seeded collection — dependents BEFORE their dependencies (the REVERSE of creation\n // order). Clearing in creation order deletes referenced docs while the previous run's\n // referencing rows still exist, and on SQL adapters a relationship column can make that delete\n // FAIL outright (e.g. sqlite generates a required in-array upload as NOT NULL with an\n // ON DELETE SET NULL foreign key — nulling it violates the constraint), stranding stale docs\n // beside the fresh seed. `clearCollection` fires delete hooks when the collection needs a\n // cascade (uploads / external-asset cleanup); plain collections are wiped directly.\n const seededCollections = [...new Set(model.collections.map((c) => c.slug))]\n const creationOrder: string[] = []\n const seen = new Set<string>()\n for (const nodeId of order) {\n const slug = recordIndex.get(nodeId)?.slug\n if (slug && !seen.has(slug)) {\n seen.add(slug)\n creationOrder.push(slug)\n }\n }\n // Definitions whose records were all skipped/empty still get cleared (after the ordered ones).\n for (const slug of seededCollections) if (!seen.has(slug)) creationOrder.push(slug)\n payload.logger.info('[payload-seed] clearing collections...')\n for (const slug of [...creationOrder].reverse()) await clearCollection(payload, req, slug)\n\n // Create docs in dependency order, resolving ref tokens to ids and delivering each `_file`.\n const created: Record<string, number> = {}\n\n payload.logger.info('[payload-seed] seeding documents...')\n for (const nodeId of order) {\n const entry = recordIndex.get(nodeId)\n if (!entry) continue\n const { slug, record } = entry\n // Drop any deferred fields before resolving: their refs point into a cycle and aren't created\n // yet. The second pass below sets them once every doc exists.\n const deferFields = deferredByNode.get(nodeId)\n const source = deferFields ? Object.fromEntries(Object.entries(record.data).filter(([k]) => !deferFields.has(k))) : record.data\n let data = resolveTokens(source, { docs: docIds, where: nodeId }) as Record<string, unknown>\n let uploadFile: Awaited<ReturnType<typeof readFileAsUpload>> | undefined\n\n if (record.file) {\n // Both branches resolve the file the same way — under the per-collection subdir (a\n // `custom.seedAsset` `subdir`, else `assetSubDirs`, else the slug), then the assets root.\n const asset = assetBySlug.get(slug)\n const subdir = asset?.subdir ?? options.assetSubDirs[slug] ?? slug\n const subdirs = [subdir, '']\n const path = await resolveFilePath(record.file.name, options.assetsDir, subdirs)\n if (!path) {\n const searched = searchedDirs(record.file.name, options.assetsDir, subdirs).join(', ')\n payload.logger.warn({ msg: `[payload-seed] ${nodeId}: _file '${record.file.name}' not found - skipped. Searched: ${searched}` })\n } else if (asset) {\n // Hand the resolved path + options to the collection's ingest hook via its source field\n // instead of uploading bytes.\n data = { ...data, [asset.sourceField]: { file: path, ...record.file.options } }\n } else if (isUpload(slug)) {\n uploadFile = await readFileAsUpload(path)\n }\n }\n\n payload.logger.info(`[payload-seed] seeding '${nodeId}'`)\n const doc = (await payload.create({\n collection: slug as CollectionSlug,\n data: data as never,\n ...(uploadFile ? { file: uploadFile } : {}),\n ...baseArgs,\n })) as { id: string | number }\n docIds.set(nodeId, doc.id)\n created[slug] = (created[slug] ?? 0) + 1\n }\n\n // Second pass: now every doc exists, resolve and set the fields deferred to break cycles.\n if (deferred.length) {\n payload.logger.info(`[payload-seed] resolving ${deferred.length} deferred reference(s)...`)\n for (const { node, field } of deferred) {\n const entry = recordIndex.get(node)\n const id = docIds.get(node)\n if (!entry || id === undefined) continue\n const value = resolveTokens(entry.record.data[field], { docs: docIds, where: `${node}.${field}` })\n await payload.update({ collection: entry.slug as CollectionSlug, id, data: { [field]: value } as never, ...baseArgs })\n }\n }\n\n // Update globals after all docs exist.\n for (const g of model.globals) {\n payload.logger.info(`[payload-seed] seeding global '${g.slug}'`)\n const data = resolveTokens(g.data, { docs: docIds, where: `global:${g.slug}` }) as Record<string, unknown>\n await payload.updateGlobal({ slug: g.slug as never, data: data as never, ...baseArgs })\n }\n\n payload.logger.info('[payload-seed] seed complete.')\n return { created, order, deferred, skipped }\n}\n\n/**\n * CLI / Local-API convenience: run the seed from a script (`payload run`) or test. Builds\n * a local `req` if one isn't supplied and resolves the public plugin options.\n */\nexport async function seed(args: { payload: Payload; req?: PayloadRequest; options?: SeedPluginOptions }): Promise<SeedResult> {\n const req = args.req ?? (await createLocalReq({}, args.payload))\n return runSeed({ payload: args.payload, req, options: resolveOptions(args.options) })\n}\n"],"names":["createLocalReq","resolveOptions","file","isFileToken","isRef","ref","buildGraph","resolveFilePath","readFileAsUpload","searchedDirs","collectTokens","docNodeId","resolveTokens","SeedValidationError","validateModel","tokens","discoverAssetCollections","payload","map","Map","slug","Object","keys","collections","marker","config","custom","seedAsset","m","set","sourceField","subdir","buildModel","definitions","globals","def","kind","records","build","rec","_key","_file","data","key","undefined","push","partitionDefinitions","defs","active","skipped","fromCollection","seedDisabled","flag","disabled","reason","logger","warn","stripRefsToSkipped","model","requiredFields","length","reasonBySlug","s","issues","strip","where","field","value","entries","hit","find","t","has","collection","get","coll","g","clearCollection","req","info","withHooks","Boolean","upload","hooks","beforeDelete","afterDelete","result","delete","id","exists","overrideAccess","context","disableRevalidate","disableTransaction","failed","e","errors","err","deepest","Error","cause","message","String","replace","slice","detail","f","join","db","deleteMany","versions","deleteVersions","runSeed","options","collectionSlugs","Set","isUpload","assetBySlug","fileCollections","filter","add","fieldNames","cfg","flattenedFields","name","required","gc","globalSlugs","isRequired","order","deferred","deferredByNode","d","node","baseArgs","depth","docIds","recordIndex","record","seededCollections","c","creationOrder","seen","nodeId","reverse","created","entry","deferFields","source","fromEntries","k","docs","uploadFile","asset","assetSubDirs","subdirs","path","assetsDir","searched","msg","doc","create","update","updateGlobal","seed","args"],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ import { type AnyRef } from '../refs';
2
+ /** The map key for a seeded doc node: `collection:_key`. */
3
+ export declare const docNodeId: (collection: string, key: string) => string;
4
+ /** Walk a seed-data value and collect every ref token it contains (drives the dependency graph). */
5
+ export declare function collectTokens(value: unknown, out?: AnyRef[]): AnyRef[];
6
+ export interface ResolveContext {
7
+ /** `collection:_key` → created doc id. */
8
+ docs: Map<string, string | number>;
9
+ /** For error messages. */
10
+ where: string;
11
+ }
12
+ /** Deep-clone a seed-data value, replacing every ref token with its resolved id. Throws a
13
+ * contextual error if a ref can't be resolved (should be impossible after validation +
14
+ * topo-sort, but guards against engine bugs). */
15
+ export declare function resolveTokens(value: unknown, ctx: ResolveContext): unknown;
16
+ //# sourceMappingURL=tokens.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tokens.d.ts","sourceRoot":"","sources":["../../src/engine/tokens.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,MAAM,EAA+B,MAAM,SAAS,CAAA;AAElE,4DAA4D;AAC5D,eAAO,MAAM,SAAS,GAAI,YAAY,MAAM,EAAE,KAAK,MAAM,KAAG,MAAgC,CAAA;AAE5F,oGAAoG;AACpG,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,EAAE,GAAG,GAAE,MAAM,EAAO,GAAG,MAAM,EAAE,CAW1E;AAED,MAAM,WAAW,cAAc;IAC7B,0CAA0C;IAC1C,IAAI,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,CAAA;IAClC,0BAA0B;IAC1B,KAAK,EAAE,MAAM,CAAA;CACd;AAED;;kDAEkD;AAClD,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,EAAE,GAAG,EAAE,cAAc,GAAG,OAAO,CAU1E"}
@@ -0,0 +1,37 @@
1
+ import { isAnyToken, isRef } from "../refs.js";
2
+ /** The map key for a seeded doc node: `collection:_key`. */ export const docNodeId = (collection, key)=>`${collection}:${key}`;
3
+ /** Walk a seed-data value and collect every ref token it contains (drives the dependency graph). */ export function collectTokens(value, out = []) {
4
+ if (isRef(value)) {
5
+ out.push(value);
6
+ return out;
7
+ }
8
+ if (Array.isArray(value)) {
9
+ for (const item of value)collectTokens(item, out);
10
+ } else if (value && typeof value === 'object') {
11
+ for (const v of Object.values(value))collectTokens(v, out);
12
+ }
13
+ return out;
14
+ }
15
+ /** Deep-clone a seed-data value, replacing every ref token with its resolved id. Throws a
16
+ * contextual error if a ref can't be resolved (should be impossible after validation +
17
+ * topo-sort, but guards against engine bugs). */ export function resolveTokens(value, ctx) {
18
+ if (isRef(value)) return resolveRef(value, ctx);
19
+ if (isAnyToken(value)) return value // any non-ref token passes through untouched
20
+ ;
21
+ if (Array.isArray(value)) return value.map((item)=>resolveTokens(item, ctx));
22
+ if (value && typeof value === 'object') {
23
+ const out = {};
24
+ for (const [k, v] of Object.entries(value))out[k] = resolveTokens(v, ctx);
25
+ return out;
26
+ }
27
+ return value;
28
+ }
29
+ function resolveRef(token, ctx) {
30
+ const id = ctx.docs.get(docNodeId(token.collection, token.key));
31
+ if (id === undefined) {
32
+ throw new Error(`[payload-seed] ${ctx.where}: unresolved ref('${token.collection}', '${token.key}') - no seeded doc with that _key.`);
33
+ }
34
+ return id;
35
+ }
36
+
37
+ //# sourceMappingURL=tokens.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/engine/tokens.ts"],"sourcesContent":["import { type AnyRef, isAnyToken, isRef, type Ref } from '../refs'\n\n/** The map key for a seeded doc node: `collection:_key`. */\nexport const docNodeId = (collection: string, key: string): string => `${collection}:${key}`\n\n/** Walk a seed-data value and collect every ref token it contains (drives the dependency graph). */\nexport function collectTokens(value: unknown, out: AnyRef[] = []): AnyRef[] {\n if (isRef(value)) {\n out.push(value)\n return out\n }\n if (Array.isArray(value)) {\n for (const item of value) collectTokens(item, out)\n } else if (value && typeof value === 'object') {\n for (const v of Object.values(value)) collectTokens(v, out)\n }\n return out\n}\n\nexport interface ResolveContext {\n /** `collection:_key` → created doc id. */\n docs: Map<string, string | number>\n /** For error messages. */\n where: string\n}\n\n/** Deep-clone a seed-data value, replacing every ref token with its resolved id. Throws a\n * contextual error if a ref can't be resolved (should be impossible after validation +\n * topo-sort, but guards against engine bugs). */\nexport function resolveTokens(value: unknown, ctx: ResolveContext): unknown {\n if (isRef(value)) return resolveRef(value, ctx)\n if (isAnyToken(value)) return value // any non-ref token passes through untouched\n if (Array.isArray(value)) return value.map((item) => resolveTokens(item, ctx))\n if (value && typeof value === 'object') {\n const out: Record<string, unknown> = {}\n for (const [k, v] of Object.entries(value)) out[k] = resolveTokens(v, ctx)\n return out\n }\n return value\n}\n\nfunction resolveRef(token: Ref, ctx: ResolveContext): string | number {\n const id = ctx.docs.get(docNodeId(token.collection, token.key))\n if (id === undefined) {\n throw new Error(`[payload-seed] ${ctx.where}: unresolved ref('${token.collection}', '${token.key}') - no seeded doc with that _key.`)\n }\n return id\n}\n"],"names":["isAnyToken","isRef","docNodeId","collection","key","collectTokens","value","out","push","Array","isArray","item","v","Object","values","resolveTokens","ctx","resolveRef","map","k","entries","token","id","docs","get","undefined","Error","where"],"mappings":"AAAA,SAAsBA,UAAU,EAAEC,KAAK,QAAkB,aAAS;AAElE,0DAA0D,GAC1D,OAAO,MAAMC,YAAY,CAACC,YAAoBC,MAAwB,GAAGD,WAAW,CAAC,EAAEC,KAAK,CAAA;AAE5F,kGAAkG,GAClG,OAAO,SAASC,cAAcC,KAAc,EAAEC,MAAgB,EAAE;IAC9D,IAAIN,MAAMK,QAAQ;QAChBC,IAAIC,IAAI,CAACF;QACT,OAAOC;IACT;IACA,IAAIE,MAAMC,OAAO,CAACJ,QAAQ;QACxB,KAAK,MAAMK,QAAQL,MAAOD,cAAcM,MAAMJ;IAChD,OAAO,IAAID,SAAS,OAAOA,UAAU,UAAU;QAC7C,KAAK,MAAMM,KAAKC,OAAOC,MAAM,CAACR,OAAQD,cAAcO,GAAGL;IACzD;IACA,OAAOA;AACT;AASA;;gDAEgD,GAChD,OAAO,SAASQ,cAAcT,KAAc,EAAEU,GAAmB;IAC/D,IAAIf,MAAMK,QAAQ,OAAOW,WAAWX,OAAOU;IAC3C,IAAIhB,WAAWM,QAAQ,OAAOA,MAAM,6CAA6C;;IACjF,IAAIG,MAAMC,OAAO,CAACJ,QAAQ,OAAOA,MAAMY,GAAG,CAAC,CAACP,OAASI,cAAcJ,MAAMK;IACzE,IAAIV,SAAS,OAAOA,UAAU,UAAU;QACtC,MAAMC,MAA+B,CAAC;QACtC,KAAK,MAAM,CAACY,GAAGP,EAAE,IAAIC,OAAOO,OAAO,CAACd,OAAQC,GAAG,CAACY,EAAE,GAAGJ,cAAcH,GAAGI;QACtE,OAAOT;IACT;IACA,OAAOD;AACT;AAEA,SAASW,WAAWI,KAAU,EAAEL,GAAmB;IACjD,MAAMM,KAAKN,IAAIO,IAAI,CAACC,GAAG,CAACtB,UAAUmB,MAAMlB,UAAU,EAAEkB,MAAMjB,GAAG;IAC7D,IAAIkB,OAAOG,WAAW;QACpB,MAAM,IAAIC,MAAM,CAAC,eAAe,EAAEV,IAAIW,KAAK,CAAC,kBAAkB,EAAEN,MAAMlB,UAAU,CAAC,IAAI,EAAEkB,MAAMjB,GAAG,CAAC,kCAAkC,CAAC;IACtI;IACA,OAAOkB;AACT"}
@@ -0,0 +1,28 @@
1
+ import type { BuiltModel } from './graph';
2
+ export declare class SeedValidationError extends Error {
3
+ issues: string[];
4
+ constructor(issues: string[]);
5
+ }
6
+ export interface ValidateArgs {
7
+ model: BuiltModel;
8
+ /** Slugs of collections that actually exist in the Payload config. */
9
+ collectionSlugs: Set<string>;
10
+ /** Slugs of globals that actually exist in the Payload config. */
11
+ globalSlugs: Set<string>;
12
+ /** Slugs that can carry a `_file`: upload collections plus `custom.seedAsset` collections. */
13
+ fileCollections: Set<string>;
14
+ /** Valid top-level field names per node (`collection` slug, or `global:<slug>`), from the
15
+ * live config's `flattenedFields`. When provided, unknown record fields are flagged —
16
+ * the runtime counterpart to the compile-time exactness check. */
17
+ fieldNames?: Map<string, Set<string>>;
18
+ }
19
+ /**
20
+ * Validate the built model against the declared docs and the live config: every definition's own
21
+ * slug exists in the config (as a collection or global, matching its kind); every `ref()`
22
+ * resolves to a seeded doc and references a real collection; every `_file` sits on a collection
23
+ * that can take one (an upload or `custom.seedAsset` collection); no duplicate `_key` within a collection
24
+ * (across every definition sharing the slug); and (when `fieldNames` is supplied) every record field
25
+ * exists in the schema. Collects ALL issues and throws once. (Cycle detection happens in the graph topo-sort.)
26
+ */
27
+ export declare function validateModel({ model, collectionSlugs, globalSlugs, fileCollections, fieldNames }: ValidateArgs): void;
28
+ //# sourceMappingURL=validate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../../src/engine/validate.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,SAAS,CAAA;AAGzC,qBAAa,mBAAoB,SAAQ,KAAK;IACzB,MAAM,EAAE,MAAM,EAAE;gBAAhB,MAAM,EAAE,MAAM,EAAE;CAIpC;AAED,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,UAAU,CAAA;IACjB,sEAAsE;IACtE,eAAe,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;IAC5B,kEAAkE;IAClE,WAAW,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;IACxB,8FAA8F;IAC9F,eAAe,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;IAC5B;;uEAEmE;IACnE,UAAU,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC,CAAA;CACtC;AAKD;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,EAAE,KAAK,EAAE,eAAe,EAAE,WAAW,EAAE,eAAe,EAAE,UAAU,EAAE,EAAE,YAAY,GAAG,IAAI,CA+DtH"}