Skip to content

Commit f06912e

Browse files
committed
fix(core): the import treats as a reference only a field that declares one, reading its target through referenceTargetOf; pin the user-field control (#22739)
Claude-Session: https://claude.ai/code/session_01JfJfBUC3cQ6hhgm9MQK76T Co-authored-by: Claude <noreply@anthropic.com>
1 parent 0d326bf commit f06912e

2 files changed

Lines changed: 38 additions & 6 deletions

File tree

‎packages/core/src/utils/import-field-meta.ts‎

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,11 @@ export interface ExportFieldMeta {
1818
label?: string;
1919
options?: Array<{ label?: string; value?: unknown; color?: string }>;
2020
/**
21-
* Target object of a reference-typed field, as the spec's one arbiter
22-
* `referenceTargetOf` answers it (a `user` field targets `sys_user` with no
23-
* `reference` written). The import's reference resolution matches cells
24-
* against it and asks it its exposure first (#22739).
21+
* Target object of a field that declares a `reference` string, read through
22+
* the spec's one arbiter `referenceTargetOf` (so a non-reference type names
23+
* none). The import's reference resolution matches cells against it and asks
24+
* it its exposure first (#22739). Only a declared `reference` makes a field
25+
* one: a `user` field written without it keeps no target here, as before.
2526
*/
2627
reference?: string;
2728
/** Field on the referenced record to show as its label. */
@@ -86,7 +87,7 @@ export function buildFieldMetaMap(schema: unknown): Map<string, ExportFieldMeta>
8687
type: typeof f.type === 'string' ? f.type : undefined,
8788
label: typeof f.label === 'string' ? f.label : undefined,
8889
options: Array.isArray(f.options) ? f.options : undefined,
89-
reference: referenceTargetOf(f),
90+
reference: typeof f.reference === 'string' ? referenceTargetOf(f) : undefined,
9091
displayField: typeof f.displayField === 'string' ? f.displayField : undefined,
9192
multiple: f.multiple === true,
9293
});

‎packages/core/src/utils/import-runner-reference-exposure.test.ts‎

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515

1616
import { describe, it, expect, vi } from 'vitest';
1717
import { runImport, type ImportProtocolLike } from './import-runner';
18-
import type { ExportFieldMeta } from './import-field-meta.js';
18+
import { buildFieldMetaMap, type ExportFieldMeta } from './import-field-meta.js';
1919

2020
type FindArgs = Parameters<ImportProtocolLike['findData']>[0];
2121

@@ -89,3 +89,34 @@ describe('[#22739] import reference resolution asks the target its declared expo
8989
expect(getMetaItem).toHaveBeenCalledTimes(1);
9090
});
9191
});
92+
93+
/**
94+
* CONTROL — which fields the import treats as references is unchanged: a field
95+
* declaring a `reference` string. A `user` field written without one is NOT
96+
* resolved (its raw cell reaches the write, as on `main`), although the spec's
97+
* arbiter gives it `sys_user`; adopting that target widens the accept set and
98+
* is not this change. A non-reference type declaring `reference` names no target.
99+
*/
100+
describe('[#22739] the reference field set stays the declared-reference set', () => {
101+
it('a user field without reference passes its cell through unresolved; one declaring it is resolved', async () => {
102+
const meta = buildFieldMetaMap({ fields: {
103+
owner: { type: 'user', label: 'Owner' },
104+
assignee: { type: 'user', label: 'Assignee', reference: 'sys_user' },
105+
amount: { type: 'number', label: 'Amount', reference: 'rx_target' },
106+
} });
107+
expect([meta.get('owner')!.reference, meta.get('assignee')!.reference, meta.get('amount')!.reference]).toEqual([undefined, 'sys_user', undefined]);
108+
109+
const findData = vi.fn(async (args: FindArgs) => (args.object === 'sys_user' && Object.values(args.query!.where!)[0] === 'Ann' ? [{ id: 'u_ann' }] : []));
110+
const getMetaItem = vi.fn(async ({ name }: { type: string; name: string }) => ({ type: 'object', name, item: { name } }));
111+
const createData = vi.fn(async (a: { data: Record<string, unknown> }) => ({ id: 'new', ...a.data }));
112+
const p: ImportProtocolLike = { findData, getMetaItem, createData, updateData: vi.fn() };
113+
const summary = await runImport({
114+
p, objectName: 'rx_source', metaMap: meta, writeMode: 'insert', matchFields: [], dryRun: false,
115+
runAutomations: false, trimWhitespace: true, createMissingOptions: false, skipBlankMatchKey: false,
116+
rows: [{ owner: 'Ann', assignee: 'Ann' }],
117+
});
118+
expect(summary.results.map(verdict)).toEqual([{ ok: true, code: undefined, field: undefined }]);
119+
expect(createData.mock.calls[0]![0].data).toMatchObject({ owner: 'Ann', assignee: 'u_ann' });
120+
expect(getMetaItem.mock.calls.map(([r]) => r.name)).toEqual(['sys_user']);
121+
});
122+
});

0 commit comments

Comments
 (0)