Repository navigation
fix(service-datasource): fields of an object drafted from a datasource table carry a readable label - #30530
Conversation
…ds (red) Claude-Session: https://claude.ai/code/session_01Utp6CZyxdo9F9Zo2bX81CM Co-authored-by: Claude <noreply@anthropic.com>
Each field of an object draft generated from a datasource table now gets a label derived by toLabel (the same snake_case to Title Case derivation the object's own label uses), from the field name it is saved under. A lookup keeps the label of the column it replaces, and the rendered source carries it. Claude-Session: https://claude.ai/code/session_01Utp6CZyxdo9F9Zo2bX81CM Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Utp6CZyxdo9F9Zo2bX81CM Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift Check4 anchor(s) derived from 1 changed package(s); no hand-written page names any of them, so this run has nothing to list — not a clean bill of health. This check sees only pages that NAME a derived anchor: one that documents this change in prose, or enumerates it in an authoring dialect, names none and stays invisible to it on every run. What this run could not see
Coarse fallback — 3 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 8eff6617a8e403e98ced889a65561ff716ff2920 && git checkout 8eff6617a8e403e98ced889a65561ff716ff2920
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin fa9c0aa2a0a8c2752f83b57765f3aae978ae196a aad627fdb436933ad9815a003f7a969e66f973ef && git checkout -B drift-repro fa9c0aa2a0a8c2752f83b57765f3aae978ae196a && git merge --no-ff aad627fdb436933ad9815a003f7a969e66f973ef
node scripts/docs-audit/affected-docs.mjs --json fa9c0aa2a0a8c2752f83b57765f3aae978ae196a |
|
domain:server#6 · os-bill · session_01Utp6CXCS98uhsFQYk64epE 席位记录 · 2026-11-05T14:38Z · Head-sha
|
Requested by Bill · project thread
domain:server#6 · os-bill · session_01Utp6CXCS98uhsFQYk64epE
Refs #30469 (item 1)
Clause-②: no
Metadata-protocol: none
Skills-docs: none —
git grep -n -i -E "datasource (table )?(sync|import)|external object|import_object|generateObjectDraft|object draft" -- skillsonaad627fdanswers 1 line,skills/objectstack-data/SKILL.md:47(the catalog entry for the datasources rule), andskills/objectstack-data/rules/datasources.mdnames no introspect, draft, import or field label; no published sentence describes the draft's field shape.维护者速读
part_no→Part No、part_name→Part Name;对象自己的名称不变。part_no),而列表里读作人话,每个打开表单的成员都会看到原始列名。packages/services/service-datasource/src/external-datasource-service.ts(列循环里的label: toLabel(fieldName)、lookup 保留原 label、源码渲染行),再看src/__tests__/external-datasource-service.test.ts新增的generated field labels。pnpm --filter @objectstack/service-datasource exec vitest run src/__tests__/external-datasource-service.test.ts -t "generated field labels",预期 3 条通过。What changes
draftObjectgives every generated field alabel, derived bytoLabelfrom the field name it is saved under, the same snake_case → Title Case function that already labels the object (toLabel(shortName)). No second derivation. The label follows the field name afterrenameand after the numbering of a taken name (PartNobesidepart_no→part_no_2→Part No 2), never the raw remote spelling.lookupkeeps the label of the scalar column it replaces.*.object.tssource writes the label on each field line (part_no: { type: 'text', label: 'Part No' },), quoted with the file's existingquoteTs.importObjectreuses the draft, so the persisted object carries the same labels. The object label is unchanged (control pin).DraftFieldgainslabel: string.Tests
describe('generated field labels')in the package's shared harness fileexternal-datasource-service.test.ts: aspare_partstable (id,part_no,part_name,PartNo) yieldsId,Part No,Part Name,Part No 2; the object label staysSpare Parts; a renamed column is labelled from its new name; the import persists the same labels.0c7e95aff: 3 failed (expected { type: 'number' } to deeply equal { type: 'number', label: 'Id' },expected undefined to be 'Catalogue Number',expected undefined to be 'Part No').toEqual({ type }),Object.keys(record) == ['type'], rendered field lines). They now name the label too; the primary-key pins still fail on any extra key, so the unauthorableprimaryKeystays out.scripts/ablation-replace.mjs, anchor hit ×1, blob restored to HEAD,git diff HEADempty): with the field written as{ type }again, the 3 new pins go red.POST /datasources/showcase_external/external/tables/customers/import→ 201, andGET /api/v1/meta/object/showcase_customersservesname: Name,email: Email,region: Region,lifetime_value: Lifetime Value.Acceptance notes
Item 2: where a synced object lives, and whether it exports (measured, not built)
Reading on a booted showcase at
fc8cea50(this branch before the main merge; the incoming commits touchui/**and one script only), after importingcustomersfrom the showcase's code-declared external datasource:GET /api/v1/meta/objectlistsshowcase_customerswith_packageId: "sys_metadata", no_packageVersion. Control:showcase_tasklists_packageId: "com.example.showcase",_packageVersion: "0.1.0". This matches the#30328reading for a datasource added in Setup.GET /api/v1/packages/export(whatos package exportreads) answers{"success":true,"data":{"packages":[]}}. The object is not in it.GET /api/v1/packages/com.example.showcase/exportanswers 200 and does not contain the object either.packageId(plugin.tspersistObject,writeFace: 'external-import'), so the row is a package-lesssys_metadatarow.What ADR-0146 says about it:
BASE.custom. An import over a datasource a package declares takes that package's namespace prefix (showcase_customers), which reads as an item added to that package. The ADR does not say whether such an import is a customization of the base.So the ADR does not settle it. For a datasource added in Setup (no package, no namespace), today's package-less placement matches D11 and the object does not export. For a datasource a package declares, D2 can be read as asking for
BASE.custom. Nothing is moved here. This is recorded as an out-of-scope finding for the seat to take to a decision. That is why this PR saysRefs, not a closing keyword.Verification
Package suite and typecheck (final head)
pnpm --filter @objectstack/service-datasource run typecheck(tsc --noEmit, lists all 12external-*test files) andvitest run --maxWorkers=2: Test Files 71 passed (71), Tests 1172 passed (1172).Derived gates
Head
aad627fdb(after theorigin/mainmerge).node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackderived 61 commands; each was run with its exit code recorded before any pipe, and--ranreconciles: 61 derived, 60 run, 1 NOT-MEASURED, 0 UNRUN.node scripts/check-plugin-teardown-shape.mjs --self-test: exit 3 on the shallow checkout (it asks for history), exit 0 aftergit fetch origin 621a4876…(48 cases pass).pnpm check:dual-build-cjs-loads: exit 3, PREREQUISITE NOT MET (it needs every package'sdist, and only this card's closure was built). NOT MEASURED here; CI'sLint & Repo Gatesruns it.pnpm lint(outside the derivation): exit 0.eslint --no-inline-configon the six changed TypeScript files: exit 0.Generated by Claude Code