@focus-reactive/payload-plugin-translator 0.4.0 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. package/README.md +44 -29
  2. package/dist/client/shared/ui/Button/Button.d.ts +4 -4
  3. package/dist/client/shared/ui/Button/Button.js +18 -18
  4. package/dist/client/shared/ui/Button/styles.module.scss +1 -1
  5. package/dist/client/shared/ui/Popup/Popup.d.ts +10 -4
  6. package/dist/client/shared/ui/Popup/Popup.js +4 -3
  7. package/dist/client/shared/ui/Select/Select.d.ts +9 -7
  8. package/dist/client/shared/ui/Select/Select.js +18 -17
  9. package/dist/client/shared/ui/Select/styles.module.scss +5 -0
  10. package/dist/index.d.ts +16 -14
  11. package/dist/index.js +9 -7
  12. package/dist/plugin.d.ts +15 -4
  13. package/dist/plugin.js +34 -57
  14. package/dist/server/features/cancel/handler.d.ts +2 -2
  15. package/dist/server/features/cancel/handler.js +3 -1
  16. package/dist/server/features/cancel/route.d.ts +4 -4
  17. package/dist/server/features/cancel/route.js +4 -4
  18. package/dist/server/features/cancel-by-collection/handler.d.ts +3 -3
  19. package/dist/server/features/cancel-by-collection/handler.js +1 -1
  20. package/dist/server/features/cancel-by-collection/route.d.ts +5 -5
  21. package/dist/server/features/cancel-by-collection/route.js +4 -4
  22. package/dist/server/features/createTranslationRoutes.d.ts +23 -0
  23. package/dist/server/features/createTranslationRoutes.js +27 -0
  24. package/dist/server/features/enqueue-translation/handler.d.ts +3 -3
  25. package/dist/server/features/enqueue-translation/handler.js +1 -1
  26. package/dist/server/features/enqueue-translation/route.d.ts +5 -5
  27. package/dist/server/features/enqueue-translation/route.js +4 -4
  28. package/dist/server/features/get-collection-status/handler.d.ts +3 -3
  29. package/dist/server/features/get-collection-status/handler.js +1 -1
  30. package/dist/server/features/get-collection-status/route.d.ts +5 -5
  31. package/dist/server/features/get-collection-status/route.js +4 -4
  32. package/dist/server/features/get-document-status/handler.d.ts +3 -3
  33. package/dist/server/features/get-document-status/handler.js +1 -1
  34. package/dist/server/features/get-document-status/route.d.ts +5 -5
  35. package/dist/server/features/get-document-status/route.js +4 -4
  36. package/dist/server/features/index.d.ts +7 -6
  37. package/dist/server/features/index.js +7 -6
  38. package/dist/server/features/run-translation/handler.d.ts +2 -2
  39. package/dist/server/features/run-translation/route.d.ts +4 -4
  40. package/dist/server/features/run-translation/route.js +4 -4
  41. package/dist/server/features/translate-document/handler.js +7 -11
  42. package/dist/server/features/translate-field/resolveFieldSubtree.d.ts +31 -0
  43. package/dist/server/features/translate-field/resolveFieldSubtree.js +45 -0
  44. package/dist/server/modules/task-runner/TaskRunnerProvider.interface.d.ts +18 -1
  45. package/dist/server/modules/task-runner/index.d.ts +6 -6
  46. package/dist/server/modules/task-runner/index.js +2 -2
  47. package/dist/server/modules/task-runner/payload-jobs-runner/PayloadJobsRunnerProvider.d.ts +16 -1
  48. package/dist/server/modules/task-runner/payload-jobs-runner/PayloadJobsRunnerProvider.js +16 -1
  49. package/dist/server/modules/task-runner/sync-runner/SyncRunnerProvider.d.ts +18 -10
  50. package/dist/server/modules/task-runner/sync-runner/SyncRunnerProvider.js +24 -14
  51. package/dist/server/modules/translation-levels/PluginConfigBuilder.d.ts +46 -0
  52. package/dist/server/modules/translation-levels/PluginConfigBuilder.js +108 -0
  53. package/dist/server/modules/translation-levels/collectionLevel.d.ts +15 -0
  54. package/dist/server/modules/translation-levels/collectionLevel.js +24 -0
  55. package/dist/server/modules/translation-levels/documentLevel.d.ts +19 -0
  56. package/dist/server/modules/translation-levels/documentLevel.js +28 -0
  57. package/dist/server/modules/translation-levels/index.d.ts +3 -0
  58. package/dist/server/modules/translation-levels/index.js +4 -0
  59. package/dist/server/modules/translation-levels/types.d.ts +42 -0
  60. package/dist/server/modules/translation-levels/types.js +10 -0
  61. package/dist/server/modules/translation-levels/useDocTranslationApi.d.ts +8 -0
  62. package/dist/server/modules/translation-levels/useDocTranslationApi.js +18 -0
  63. package/dist/server/modules/translation-pipeline/index.d.ts +5 -3
  64. package/dist/server/modules/translation-pipeline/index.js +3 -2
  65. package/dist/server/modules/translation-pipeline/stages/data-reconciler/DataReconciler.d.ts +3 -9
  66. package/dist/server/modules/translation-pipeline/stages/data-reconciler/DataReconciler.js +71 -78
  67. package/dist/server/modules/translation-pipeline/stages/field-collector/FieldChunkCollector.d.ts +11 -22
  68. package/dist/server/modules/translation-pipeline/stages/field-collector/FieldChunkCollector.js +85 -106
  69. package/dist/server/modules/translation-pipeline/translateContent.d.ts +35 -0
  70. package/dist/server/modules/translation-pipeline/translateContent.js +30 -0
  71. package/dist/server/modules/translation-providers/OpenAITranslation.provider.d.ts +4 -3
  72. package/dist/server/modules/translation-providers/OpenAITranslation.provider.js +15 -15
  73. package/dist/server/shared/field-traversal/findFieldByPath.d.ts +40 -0
  74. package/dist/server/shared/field-traversal/findFieldByPath.js +88 -0
  75. package/dist/server/shared/field-traversal/index.d.ts +5 -0
  76. package/dist/server/shared/field-traversal/index.js +5 -0
  77. package/dist/server/shared/field-traversal/kernel.d.ts +85 -0
  78. package/dist/server/shared/field-traversal/kernel.js +145 -0
  79. package/dist/server/shared/field-traversal/types.d.ts +191 -0
  80. package/dist/server/shared/field-traversal/types.js +41 -0
  81. package/dist/server/shared/field-traversal/walkFields.d.ts +51 -0
  82. package/dist/server/shared/field-traversal/walkFields.js +164 -0
  83. package/dist/server/shared/utils/filterLocalizedFields.d.ts +3 -3
  84. package/dist/server/shared/utils/filterLocalizedFields.js +52 -63
  85. package/dist/types/AccessGuard.d.ts +24 -1
  86. package/dist/types/AccessGuard.js +12 -1
  87. package/package.json +2 -2
@@ -0,0 +1,88 @@
1
+ import { classifyField, tabScopes } from "./kernel";
2
+ /**
3
+ * Navigate a field schema by a path of segment NAMES, descending one matching branch at a time
4
+ * with early-exit (targeted navigation, not an exhaustive walk). Presentational containers
5
+ * (`row`/`collapsible`/unnamed `group`) and unnamed tabs are transparent — searched in the same
6
+ * path scope. Built on {@link classifyField} / {@link tabScopes} so the structural dispatch lives
7
+ * in one place.
8
+ *
9
+ * The caller supplies already-prepared name segments (split, trimmed, with array indices dropped —
10
+ * a field's config is shared across array items, so indices never appear in the schema).
11
+ *
12
+ * @param fields - The schema level to search.
13
+ * @param segments - Remaining path segments (names) to match, head-first.
14
+ * @returns A {@link FieldPathResult}.
15
+ * @public
16
+ */ export function findFieldByPath(fields, segments) {
17
+ const [head, ...rest] = segments;
18
+ if (head === undefined) return {
19
+ status: "not-found"
20
+ };
21
+ for (const field of fields){
22
+ const structure = classifyField(field);
23
+ switch(structure.kind){
24
+ case "tabs":
25
+ {
26
+ for (const scope of tabScopes(structure.field)){
27
+ if (scope.named) {
28
+ if (scope.tab.name === head) {
29
+ return rest.length === 0 ? {
30
+ status: "container"
31
+ } : findFieldByPath(scope.tab.fields, rest);
32
+ }
33
+ } else {
34
+ const found = findFieldByPath(scope.fields, segments); // unnamed tab → same path scope
35
+ if (found.status !== "not-found") return found;
36
+ }
37
+ }
38
+ break;
39
+ }
40
+ case "transparent":
41
+ {
42
+ const found = findFieldByPath(structure.fields, segments); // row/collapsible/unnamed group → same scope
43
+ if (found.status !== "not-found") return found;
44
+ break;
45
+ }
46
+ case "presentational":
47
+ break;
48
+ case "group":
49
+ case "array":
50
+ {
51
+ if (structure.name !== head) break;
52
+ return rest.length === 0 ? {
53
+ status: "container"
54
+ } : findFieldByPath(structure.fields, rest);
55
+ }
56
+ case "blocks":
57
+ {
58
+ if (structure.name !== head) break;
59
+ return rest.length === 0 ? {
60
+ status: "container"
61
+ } : {
62
+ status: "inside-blocks"
63
+ };
64
+ }
65
+ case "leaf":
66
+ {
67
+ if (structure.name !== head) break;
68
+ return rest.length === 0 ? {
69
+ status: "leaf",
70
+ field: structure.field
71
+ } : {
72
+ status: "not-found"
73
+ };
74
+ }
75
+ default:
76
+ {
77
+ // Exhaustiveness guard: a new FieldStructure kind would error here at compile time.
78
+ const exhaustive = structure;
79
+ throw new Error(`unhandled field structure: ${String(exhaustive)}`);
80
+ }
81
+ }
82
+ }
83
+ return {
84
+ status: "not-found"
85
+ };
86
+ }
87
+
88
+ //# sourceMappingURL=findFieldByPath.js.map
@@ -0,0 +1,5 @@
1
+ export { findFieldByPath } from "./findFieldByPath";
2
+ export type { FieldPathResult } from "./findFieldByPath";
3
+ export { classifyField, resolveBlockFields, tabScopes } from "./kernel";
4
+ export type { ChildCursor, ChildOutput, ContainerInfo, FieldStructure, FieldWalker, LeafField, TabScope, WalkSignal } from "./types";
5
+ export { walkFields } from "./walkFields";
@@ -0,0 +1,5 @@
1
+ export { findFieldByPath } from "./findFieldByPath";
2
+ export { classifyField, resolveBlockFields, tabScopes } from "./kernel";
3
+ export { walkFields } from "./walkFields";
4
+
5
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,85 @@
1
+ import type { BlocksField, Field, TabsField } from "payload";
2
+ import type { FieldStructure, TabScope } from "./types";
3
+ /**
4
+ * Classify a single Payload {@link Field} into a discriminated {@link FieldStructure} — the
5
+ * one place that encodes how Payload field types map onto data boundaries.
6
+ *
7
+ * Traversals switch on the result instead of re-deriving the predicate chain
8
+ * (`fieldAffectsData` / `fieldIsGroupType` / …), so the dispatch — and any future Payload
9
+ * field type — lives in exactly one place.
10
+ *
11
+ * @param field - Any field from a collection/global `fields` array (original, un-sanitized
12
+ * config).
13
+ * @returns The field's {@link FieldStructure}:
14
+ * - `tabs` — a `tabs` field (expand with {@link tabScopes}).
15
+ * - `transparent` — a presentational container that opens **no** data boundary (`row`,
16
+ * `collapsible`, unnamed `group`); its `fields` live in the parent's data scope.
17
+ * - `presentational` — a `ui` field: no data, no subfields.
18
+ * - `group` / `array` / `blocks` — named data boundaries (carry `name` + child `fields`,
19
+ * except `blocks` whose child fields are per-element via {@link resolveBlockFields}).
20
+ * - `leaf` — a data-affecting field with no subfields.
21
+ *
22
+ * Dispatch order is significant: some fields satisfy more than one predicate and only the
23
+ * order disambiguates them.
24
+ * - `tabs` is checked first — it carries `.tabs` (not `.fields`) and is non-data-affecting,
25
+ * so a later check would mislabel it `presentational`.
26
+ * - `fieldAffectsData` is resolved before the `group` check, so an unnamed group resolves to
27
+ * `transparent`, never a `group` with `name: undefined`.
28
+ *
29
+ * @example
30
+ * ```ts
31
+ * const structure = classifyField(field);
32
+ * switch (structure.kind) {
33
+ * case "group": descend(structure.fields, data[structure.name]); break;
34
+ * case "leaf": translate(structure.field, structure.name); break;
35
+ * // ...handle the remaining kinds
36
+ * }
37
+ * ```
38
+ *
39
+ * @see {@link walkFields} — the recursive engine built on this classification.
40
+ * @public
41
+ */
42
+ export declare function classifyField(field: Field): FieldStructure;
43
+ /**
44
+ * Flatten a `tabs` field's tabs into {@link TabScope}s, absorbing the named/unnamed +
45
+ * `hasFields` handling that field traversals would otherwise each repeat.
46
+ *
47
+ * @param field - A Payload `tabs` field.
48
+ * @returns One {@link TabScope} per tab that has fields, in declaration order:
49
+ * - a **named** tab → `{ named: true, tab }` — it opens a data boundary at `tab.name`, and
50
+ * the `NamedTab` is surfaced so callers can read its `name`/`fields`/config;
51
+ * - an **unnamed** tab → `{ named: false, fields }` — its fields live in the parent's data
52
+ * scope, so only the `fields` are needed.
53
+ *
54
+ * @example
55
+ * ```ts
56
+ * for (const scope of tabScopes(tabsField)) {
57
+ * if (scope.named) descendInto(data[scope.tab.name], scope.tab.fields);
58
+ * else descendInto(data, scope.fields); // unnamed tab — same data scope as the parent
59
+ * }
60
+ * ```
61
+ * @public
62
+ */
63
+ export declare function tabScopes(field: TabsField): TabScope[];
64
+ /**
65
+ * Resolve the child `fields` for a single `blocks` element by matching its `blockType`
66
+ * against the field's block definitions.
67
+ *
68
+ * @param field - A Payload `blocks` field carrying inline block definitions.
69
+ * @param item - The data for one block element (expected to carry a `blockType` string).
70
+ * @returns The matched block's `fields`, or `null` when `item` is not a block object or its
71
+ * `blockType` matches no defined block.
72
+ *
73
+ * Assumes the **original, un-sanitized** schema, where blocks are inline `Block` objects on
74
+ * `field.blocks`. Payload's sanitized config may instead carry `blockReferences` resolved
75
+ * against the top-level config — not handled here by design, since this package traverses
76
+ * the original schema to preserve nested `localized` flags.
77
+ *
78
+ * @example
79
+ * ```ts
80
+ * const fields = resolveBlockFields(blocksField, item);
81
+ * if (fields) descendInto(item, fields); // null → unknown blockType / not a block → skip
82
+ * ```
83
+ * @public
84
+ */
85
+ export declare function resolveBlockFields(field: BlocksField, item: unknown): Field[] | null;
@@ -0,0 +1,145 @@
1
+ import { fieldAffectsData, fieldIsArrayType, fieldIsBlockType, fieldIsGroupType, tabHasName } from "payload/shared";
2
+ import { hasFields, isBlockItem, isTabsField } from "../guards";
3
+ /**
4
+ * Classify a single Payload {@link Field} into a discriminated {@link FieldStructure} — the
5
+ * one place that encodes how Payload field types map onto data boundaries.
6
+ *
7
+ * Traversals switch on the result instead of re-deriving the predicate chain
8
+ * (`fieldAffectsData` / `fieldIsGroupType` / …), so the dispatch — and any future Payload
9
+ * field type — lives in exactly one place.
10
+ *
11
+ * @param field - Any field from a collection/global `fields` array (original, un-sanitized
12
+ * config).
13
+ * @returns The field's {@link FieldStructure}:
14
+ * - `tabs` — a `tabs` field (expand with {@link tabScopes}).
15
+ * - `transparent` — a presentational container that opens **no** data boundary (`row`,
16
+ * `collapsible`, unnamed `group`); its `fields` live in the parent's data scope.
17
+ * - `presentational` — a `ui` field: no data, no subfields.
18
+ * - `group` / `array` / `blocks` — named data boundaries (carry `name` + child `fields`,
19
+ * except `blocks` whose child fields are per-element via {@link resolveBlockFields}).
20
+ * - `leaf` — a data-affecting field with no subfields.
21
+ *
22
+ * Dispatch order is significant: some fields satisfy more than one predicate and only the
23
+ * order disambiguates them.
24
+ * - `tabs` is checked first — it carries `.tabs` (not `.fields`) and is non-data-affecting,
25
+ * so a later check would mislabel it `presentational`.
26
+ * - `fieldAffectsData` is resolved before the `group` check, so an unnamed group resolves to
27
+ * `transparent`, never a `group` with `name: undefined`.
28
+ *
29
+ * @example
30
+ * ```ts
31
+ * const structure = classifyField(field);
32
+ * switch (structure.kind) {
33
+ * case "group": descend(structure.fields, data[structure.name]); break;
34
+ * case "leaf": translate(structure.field, structure.name); break;
35
+ * // ...handle the remaining kinds
36
+ * }
37
+ * ```
38
+ *
39
+ * @see {@link walkFields} — the recursive engine built on this classification.
40
+ * @public
41
+ */ export function classifyField(field) {
42
+ if (isTabsField(field)) {
43
+ return {
44
+ kind: "tabs",
45
+ field
46
+ };
47
+ }
48
+ if (!fieldAffectsData(field)) {
49
+ return hasFields(field) ? {
50
+ kind: "transparent",
51
+ fields: field.fields
52
+ } : {
53
+ kind: "presentational"
54
+ };
55
+ }
56
+ if (fieldIsGroupType(field)) {
57
+ return {
58
+ kind: "group",
59
+ name: field.name,
60
+ fields: field.fields,
61
+ field
62
+ };
63
+ }
64
+ if (fieldIsArrayType(field)) {
65
+ return {
66
+ kind: "array",
67
+ name: field.name,
68
+ fields: field.fields,
69
+ field
70
+ };
71
+ }
72
+ if (fieldIsBlockType(field)) {
73
+ return {
74
+ kind: "blocks",
75
+ name: field.name,
76
+ field
77
+ };
78
+ }
79
+ const leaf = field;
80
+ return {
81
+ kind: "leaf",
82
+ name: leaf.name,
83
+ field: leaf
84
+ };
85
+ }
86
+ /**
87
+ * Flatten a `tabs` field's tabs into {@link TabScope}s, absorbing the named/unnamed +
88
+ * `hasFields` handling that field traversals would otherwise each repeat.
89
+ *
90
+ * @param field - A Payload `tabs` field.
91
+ * @returns One {@link TabScope} per tab that has fields, in declaration order:
92
+ * - a **named** tab → `{ named: true, tab }` — it opens a data boundary at `tab.name`, and
93
+ * the `NamedTab` is surfaced so callers can read its `name`/`fields`/config;
94
+ * - an **unnamed** tab → `{ named: false, fields }` — its fields live in the parent's data
95
+ * scope, so only the `fields` are needed.
96
+ *
97
+ * @example
98
+ * ```ts
99
+ * for (const scope of tabScopes(tabsField)) {
100
+ * if (scope.named) descendInto(data[scope.tab.name], scope.tab.fields);
101
+ * else descendInto(data, scope.fields); // unnamed tab — same data scope as the parent
102
+ * }
103
+ * ```
104
+ * @public
105
+ */ export function tabScopes(field) {
106
+ const scopes = [];
107
+ for (const tab of field.tabs){
108
+ if (tabHasName(tab)) scopes.push({
109
+ named: true,
110
+ tab
111
+ });
112
+ else if (hasFields(tab)) scopes.push({
113
+ named: false,
114
+ fields: tab.fields
115
+ });
116
+ }
117
+ return scopes;
118
+ }
119
+ /**
120
+ * Resolve the child `fields` for a single `blocks` element by matching its `blockType`
121
+ * against the field's block definitions.
122
+ *
123
+ * @param field - A Payload `blocks` field carrying inline block definitions.
124
+ * @param item - The data for one block element (expected to carry a `blockType` string).
125
+ * @returns The matched block's `fields`, or `null` when `item` is not a block object or its
126
+ * `blockType` matches no defined block.
127
+ *
128
+ * Assumes the **original, un-sanitized** schema, where blocks are inline `Block` objects on
129
+ * `field.blocks`. Payload's sanitized config may instead carry `blockReferences` resolved
130
+ * against the top-level config — not handled here by design, since this package traverses
131
+ * the original schema to preserve nested `localized` flags.
132
+ *
133
+ * @example
134
+ * ```ts
135
+ * const fields = resolveBlockFields(blocksField, item);
136
+ * if (fields) descendInto(item, fields); // null → unknown blockType / not a block → skip
137
+ * ```
138
+ * @public
139
+ */ export function resolveBlockFields(field, item) {
140
+ if (!isBlockItem(item)) return null;
141
+ const block = field.blocks.find((candidate)=>candidate.slug === item.blockType);
142
+ return block ? block.fields : null;
143
+ }
144
+
145
+ //# sourceMappingURL=kernel.js.map
@@ -0,0 +1,191 @@
1
+ import type { ArrayField, BlocksField, Field, FieldAffectingData, NamedGroupField, NamedTab, TabAsField, TabsField } from "payload";
2
+ /**
3
+ * A data-affecting field the engine routes to `leaf`: a scalar/relational leaf, never a
4
+ * container (`group`/`array`/`blocks`) and never a `TabAsField`. Narrowing `FieldAffectingData`
5
+ * down to these members is what guarantees `name: string` — the raw union does NOT, because
6
+ * an unnamed `TabAsField` carries `name?: string`.
7
+ *
8
+ * @public
9
+ */
10
+ export type LeafField = Exclude<FieldAffectingData, ArrayField | BlocksField | NamedGroupField | TabAsField>;
11
+ /**
12
+ * Structural classification of a single Payload field — the one place that encodes
13
+ * how Payload field types map onto data boundaries (the dispatch order
14
+ * `tabs → transparent → group → array → blocks → leaf`).
15
+ *
16
+ * Consumed by {@link FieldWalker}/`walkFields` (the exhaustive, data-parallel walks)
17
+ * and by path-navigation; the four hand-rolled traversals collapse onto it so the
18
+ * dispatch lives in exactly one place.
19
+ *
20
+ * - `transparent` — presentational container that does NOT open a data boundary
21
+ * (row, collapsible, unnamed group). Its `fields` live in the SAME data scope as
22
+ * the parent.
23
+ * - `presentational` — a `ui` leaf: no data, no subfields.
24
+ * - `group`/`array`/`blocks` — open a data boundary at `name`.
25
+ * - `leaf` — a data-affecting field with no subfields.
26
+ *
27
+ * @see {@link classifyField} — produces this; {@link walkFields} — consumes it.
28
+ * @public
29
+ */
30
+ export type FieldStructure = {
31
+ kind: "tabs";
32
+ field: TabsField;
33
+ } | {
34
+ kind: "transparent";
35
+ fields: Field[];
36
+ } | {
37
+ kind: "presentational";
38
+ } | {
39
+ kind: "group";
40
+ name: string;
41
+ fields: Field[];
42
+ field: NamedGroupField;
43
+ } | {
44
+ kind: "array";
45
+ name: string;
46
+ fields: Field[];
47
+ field: ArrayField;
48
+ } | {
49
+ kind: "blocks";
50
+ name: string;
51
+ field: BlocksField;
52
+ } | {
53
+ kind: "leaf";
54
+ name: string;
55
+ field: LeafField;
56
+ };
57
+ /**
58
+ * A tab flattened by `tabScopes`. A named tab opens a data boundary, so its `NamedTab`
59
+ * is surfaced (the walker passes it to `enterObject`, and `tab.name` is the data key).
60
+ * An unnamed tab flattens into the parent data scope, so only its `fields` are needed.
61
+ *
62
+ * @see {@link tabScopes}
63
+ * @public
64
+ */
65
+ export type TabScope = {
66
+ named: true;
67
+ tab: NamedTab;
68
+ } | {
69
+ named: false;
70
+ fields: Field[];
71
+ };
72
+ /**
73
+ * Control signal a walker callback can return instead of descending.
74
+ * - `skip` — do not descend this branch; continue with siblings.
75
+ * - `stop` — halt the whole walk (used for path-style navigation / early-exit).
76
+ *
77
+ * @public
78
+ */
79
+ export type WalkSignal = "skip" | "stop";
80
+ /**
81
+ * One child to recurse under an array/blocks boundary. The caller derives these from its own
82
+ * data (it alone knows how many elements there are and their shape) and returns them from
83
+ * {@link FieldWalker.enterList}.
84
+ *
85
+ * @template Cursor - The walker's data-position type (see {@link FieldWalker}).
86
+ * @public
87
+ */
88
+ export interface ChildCursor<Cursor extends object> {
89
+ cursor: Cursor;
90
+ /** Child fields to walk under this item. For `blocks`, the resolved `block.fields`. */
91
+ fields: Field[];
92
+ /** Index/key this child sits at; surfaced back to `combine`. */
93
+ key: string | number;
94
+ }
95
+ /**
96
+ * The container the engine asks the walker to assemble, bottom-up.
97
+ *
98
+ * - `root` — the top-level field list (`field` is `null`).
99
+ * - `object` — a `group` or a named tab.
100
+ * - `list` — an `array` or `blocks` collection (children are its elements, keyed by index).
101
+ * - `element` — a single element of a list (children are its fields). `field` is the
102
+ * parent list field; distinguish array vs blocks via `field.type`.
103
+ *
104
+ * @see {@link FieldWalker.combine}
105
+ * @public
106
+ */
107
+ export type ContainerInfo = {
108
+ kind: "root";
109
+ field: null;
110
+ } | {
111
+ kind: "object";
112
+ field: NamedGroupField | NamedTab;
113
+ key: string;
114
+ } | {
115
+ kind: "list";
116
+ field: ArrayField | BlocksField;
117
+ key: string;
118
+ } | {
119
+ kind: "element";
120
+ field: ArrayField | BlocksField;
121
+ key: string | number;
122
+ };
123
+ /**
124
+ * A child's assembled output, tagged with the key it sat at within its parent. Passed to
125
+ * {@link FieldWalker.combine} as the `children` list.
126
+ *
127
+ * @template Out - The walker's per-node output type (see {@link FieldWalker}).
128
+ * @public
129
+ */
130
+ export interface ChildOutput<Out> {
131
+ key: string | number;
132
+ out: Out;
133
+ }
134
+ /**
135
+ * Behaviour a caller plugs into {@link walkFields}. The engine owns structural dispatch and
136
+ * recursion over the **schema**; the caller owns the **data** via the opaque `Cursor`
137
+ * (1..n parallel data trees plus any path live there) and decides what to produce.
138
+ *
139
+ * Shapes this is designed to subsume (one engine, many callers):
140
+ * - build-tree (filter / reconcile) — reconstruct values in `combine`.
141
+ * - collect + mutate (field-chunk collector) — push/mutate in `leaf`, `combine` is a no-op.
142
+ * - navigate + early-exit (path resolver) — `enter*` returns `'skip'`/`'stop'`.
143
+ *
144
+ * @template Cursor - The data position threaded through the walk. The engine never reads it;
145
+ * it only passes it to your callbacks and stores the cursors you return. Constrained to
146
+ * `object` so a cursor can never collide with a {@link WalkSignal} string (`'skip'`/`'stop'`).
147
+ * @template Out - What you produce per leaf and assemble per container. Use `void` for a
148
+ * collect-only walk (accumulate in a closure, return `undefined` from `combine`).
149
+ *
150
+ * @example
151
+ * A collect-only walker that records the path of every localized leaf:
152
+ * ```ts
153
+ * type Cursor = { data: Record<string, unknown>; path: string[] };
154
+ * const found: string[] = [];
155
+ *
156
+ * const walker: FieldWalker<Cursor, void> = {
157
+ * enterObject: (field, c) =>
158
+ * isRecord(c.data[field.name])
159
+ * ? { data: c.data[field.name], path: [...c.path, field.name] }
160
+ * : "skip",
161
+ * enterList: (field, c) => toItemCursors(field, c),
162
+ * leaf: (field, c) => {
163
+ * if (field.localized) found.push([...c.path, field.name].join("."));
164
+ * return undefined;
165
+ * },
166
+ * combine: () => undefined, // collect-only: nothing to assemble
167
+ * };
168
+ * ```
169
+ *
170
+ * @see {@link walkFields}
171
+ * @public
172
+ */
173
+ export interface FieldWalker<Cursor extends object, Out> {
174
+ /** Enter a single-object boundary (named `group` or named tab). Return the child cursor, or a signal. */
175
+ enterObject(field: NamedGroupField | NamedTab, cursor: Cursor): Cursor | WalkSignal;
176
+ /** Enter an `array`/`blocks` boundary. Return one {@link ChildCursor} per element, or a signal. */
177
+ enterList(field: ArrayField | BlocksField, cursor: Cursor): ChildCursor<Cursor>[] | WalkSignal;
178
+ /**
179
+ * Visit a data-affecting leaf. `field` is a {@link LeafField} — the engine has already
180
+ * resolved it via the `fieldAffectsData` guard and excluded containers/tabs, so `field.name`
181
+ * is always present and callers never touch the raw `Field` union. Return its output, or
182
+ * `undefined` to drop it.
183
+ */
184
+ leaf(field: LeafField, cursor: Cursor): Out | undefined;
185
+ /**
186
+ * Assemble a container from its children's outputs (called bottom-up). Build-tree
187
+ * callers reconstruct here; collectors return `undefined`. Return `undefined` to drop
188
+ * the container from its parent (e.g. an empty object after filtering).
189
+ */
190
+ combine(container: ContainerInfo, children: ChildOutput<Out>[], cursor: Cursor): Out | undefined;
191
+ }
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Behaviour a caller plugs into {@link walkFields}. The engine owns structural dispatch and
3
+ * recursion over the **schema**; the caller owns the **data** via the opaque `Cursor`
4
+ * (1..n parallel data trees plus any path live there) and decides what to produce.
5
+ *
6
+ * Shapes this is designed to subsume (one engine, many callers):
7
+ * - build-tree (filter / reconcile) — reconstruct values in `combine`.
8
+ * - collect + mutate (field-chunk collector) — push/mutate in `leaf`, `combine` is a no-op.
9
+ * - navigate + early-exit (path resolver) — `enter*` returns `'skip'`/`'stop'`.
10
+ *
11
+ * @template Cursor - The data position threaded through the walk. The engine never reads it;
12
+ * it only passes it to your callbacks and stores the cursors you return. Constrained to
13
+ * `object` so a cursor can never collide with a {@link WalkSignal} string (`'skip'`/`'stop'`).
14
+ * @template Out - What you produce per leaf and assemble per container. Use `void` for a
15
+ * collect-only walk (accumulate in a closure, return `undefined` from `combine`).
16
+ *
17
+ * @example
18
+ * A collect-only walker that records the path of every localized leaf:
19
+ * ```ts
20
+ * type Cursor = { data: Record<string, unknown>; path: string[] };
21
+ * const found: string[] = [];
22
+ *
23
+ * const walker: FieldWalker<Cursor, void> = {
24
+ * enterObject: (field, c) =>
25
+ * isRecord(c.data[field.name])
26
+ * ? { data: c.data[field.name], path: [...c.path, field.name] }
27
+ * : "skip",
28
+ * enterList: (field, c) => toItemCursors(field, c),
29
+ * leaf: (field, c) => {
30
+ * if (field.localized) found.push([...c.path, field.name].join("."));
31
+ * return undefined;
32
+ * },
33
+ * combine: () => undefined, // collect-only: nothing to assemble
34
+ * };
35
+ * ```
36
+ *
37
+ * @see {@link walkFields}
38
+ * @public
39
+ */ export { };
40
+
41
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1,51 @@
1
+ import type { Field } from "payload";
2
+ import type { FieldWalker } from "./types";
3
+ /**
4
+ * Depth-first walk of a Payload field **schema**, driving a caller-supplied
5
+ * {@link FieldWalker}. The single traversal engine this package's data operations build on
6
+ * (filtering, reconciling, collecting, navigating).
7
+ *
8
+ * The engine owns structural dispatch (via {@link classifyField} / {@link tabScopes}) and the
9
+ * recursion; the caller owns the **data** through the opaque `Cursor` and decides what to
10
+ * produce. The engine never reads the cursor — so the caller can thread one, two, or more
11
+ * parallel data trees (plus a path) inside it. Containers are assembled bottom-up via
12
+ * `combine`, after their children.
13
+ *
14
+ * @template Cursor - The caller's data position threaded through the walk (e.g. the current
15
+ * data object, a `{ source, target }` pair, plus a path). Constrained to `object` so a cursor
16
+ * can never collide with a {@link WalkSignal} string (`'skip'`/`'stop'`).
17
+ * @template Out - What the walker produces per node and assembles per container (e.g. a
18
+ * rebuilt data subtree, or `void` for a collect-only walk).
19
+ *
20
+ * @param fields - The schema to walk (a collection/global/group `fields` array).
21
+ * @param root - The initial `Cursor` paired with the top-level `fields`.
22
+ * @param walker - The behavior to drive: `enterObject` / `enterList` derive child cursors (or
23
+ * return a {@link WalkSignal} to `'skip'` a branch or `'stop'` the whole walk), `leaf`
24
+ * produces a value per data-affecting leaf, and `combine` assembles each container from its
25
+ * children.
26
+ * @returns The root `combine` output. `undefined` if the walk was halted by `'stop'` before the
27
+ * root assembled, OR if the root `combine` itself returned `undefined` (e.g. a collect-only
28
+ * walker). To distinguish "stopped" from "empty result", track it via the cursor, not this value.
29
+ *
30
+ * @example
31
+ * Keep only localized leaves and rebuild the data tree (a "filter" walk):
32
+ * ```ts
33
+ * type Cursor = { data: Record<string, unknown> };
34
+ *
35
+ * const filtered = walkFields<Cursor, unknown>(schema, { data }, {
36
+ * enterObject: (field, c) =>
37
+ * isRecord(c.data[field.name]) ? { data: c.data[field.name] } : "skip",
38
+ * enterList: (field, c) => toItemCursors(field, c), // one ChildCursor per element
39
+ * leaf: (field, c) => (field.localized ? c.data[field.name] : undefined),
40
+ * combine: (container, children) => {
41
+ * if (children.length === 0) return undefined; // drop empty containers
42
+ * if (container.kind === "list") return children.map((ch) => ch.out);
43
+ * return Object.fromEntries(children.map((ch) => [ch.key, ch.out]));
44
+ * },
45
+ * });
46
+ * ```
47
+ *
48
+ * @see {@link FieldWalker} for the full visitor contract and the caller shapes it subsumes.
49
+ * @public
50
+ */
51
+ export declare function walkFields<Cursor extends object, Out>(fields: Field[], root: Cursor, walker: FieldWalker<Cursor, Out>): Out | undefined;