ENG-1977 Add schema import data layer for Obsidian - #1264
Conversation
|
This pull request has been ignored for the connected project Preview Branches by Supabase. |
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
3af9adf to
f364d5a
Compare
23c69c1 to
d417d43
Compare
d417d43 to
bc0e07d
Compare
f364d5a to
09bb0e7
Compare
bc0e07d to
34254ad
Compare
5119839 to
cfff9bf
Compare
34254ad to
9073b20
Compare
cfff9bf to
3820532
Compare
9073b20 to
26d1ad2
Compare
26d1ad2 to
6b684dc
Compare
b0abbd4 to
2a39a08
Compare
PR size/scope checkThis PR is over our review-size guideline.
Please split this into smaller PRs unless there is a clear reason the changes need to land together. If keeping it as one PR, please add a brief justification covering:
|
… set, not schema intersection
…types Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…lySchemaImportSelection Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The remote-space import and the schema-file import both had to answer "does this incoming type already exist locally?", and answered it differently: the Supabase path compared names and labels case-sensitively while specImport lowercased them. The same vault reached through the two paths would dedupe differently. Extracts schemaMatching.ts with the id-then-name/label fallback, the triple identity check, and buildSchemaRid — which pins the "schema" RID subtype so both paths emit byte-identical RIDs. Node instance and relation instance RIDs keep their own "note"/"relation" subtypes and are untouched. Matching is now case-insensitive on both paths. This is a behavior change to the Supabase import: importing a "Claim" type into a vault holding "claim" now reuses the local type instead of creating a near-duplicate. specImport sets importedFromRid from the file's vaultId, so schema imported from a file and content imported from that same vault via Supabase resolve to the same RID. Like the Supabase path, this records the immediate source vault rather than preserving an older origin. Also fixes a duplicate-triple hole the case-insensitive matching widens: the apply loop guarded against triples that existed at plan time but not against ones created earlier in the same run, so two schema node types collapsing onto one local type produced duplicate triples. The check now runs against live settings. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
31a387c to
83e0548
Compare
2a39a08 to
2f98215
Compare
A schema file holding both "Event" and "event" created two node types that matching then treats as one, because the existing-check only compared against the vault's types as they were before the import. Same hole for relation types by label. Fixed in the planner rather than at apply time: the known-set grows as types are planned, so the second type resolves to the first the same way it would resolve to a pre-existing local type. Keeping it in the planner means nodeTypeIdMapping stays correct — skipping the duplicate at apply time would leave discourse relations pointing at an id that was never created. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Re: size/scope checkCurrent: +532 −44 across 4 files — within the file-count preference, over the 400-line limit. Single problem: make an exported schema file importable into a vault, resolving incoming ids against what already exists there. Why the changes are coupled: That said, a clean split does exist and I don't want to overstate the coupling:
Splitting this way would actually improve review quality: the behavior change to the shipped Supabase import currently sits underneath 400+ lines of new file, which is exactly where it's easiest to miss. The cost is one more PR in an already 5-deep stack and a re-stack of #1265. Happy to do the split if a reviewer prefers it — say the word. |
https://www.loom.com/share/7771ec3f58e749599a5bf406debdb101
Summary
Adds the import data layer — file parsing, match planning, and apply logic. The UI modal is in the next PR.
specImport.tsbuildSchemaImportMatchPlan— pure, no side effects. Maps every id in the schema file to a local id, id match first and name/label fallback second, and records which items already exist locally.applySchemaImportSelection— creates new relation types and relations asprovisional, skips existing matches, preserves a node type's template reference when that template is selected for import or already exists locally.parseDgSchemaFile(inspecValidation.ts) does full Zod validation againstdgSchemaFileSchema, including aversionliteral guard.schemaMatching.ts— matching primitives shared with the Supabase import path (see below).Planning is deliberately separated from applying. The Supabase import path interleaves them —
mapNodeTypeIdToLocalandmapRelationTypeToLocalboth find-or-create inline, so side effects happen during what reads like a lookup. Keeping the plan pure means the preview in #1265 can show exact counts without touching settings.Shared matching with the Supabase import path
Both import paths have to answer "does this incoming type already exist locally?" — and they answered it differently. The Supabase path compared names and labels case-sensitively;
specImportlowercased them. The same vault reached through the two paths would dedupe differently.schemaMatching.tsnow holds the shared primitives:findLocalNodeTypeMatchmapNodeTypeIdToLocal,buildSchemaImportMatchPlanfindLocalRelationTypeMatchmapRelationTypeToLocal,buildSchemaImportMatchPlanfindExistingTriplefindOrCreateTriple,buildSchemaImportMatchPlanbuildSchemaRidmapNodeTypeIdToLocal,mapRelationTypeToLocal,applySchemaImportSelectionbuildSchemaRidpins the"schema"RID subtype in one place so the two paths cannot drift. Node instance ("note") and relation instance ("relation") RIDs are a different concern and are untouched.Only the finder half of
findOrCreateTriplewas extracted, not the create half — it callsplugin.saveSettings()per triple, whileapplySchemaImportSelectionbatches a single save at the end. Sharing the whole function would have turned one write into N.Name and label matching is now case-insensitive on both paths. Importing a
Claimnode type into a vault that already holdsclaimnow reuses the local type instead of creating a near-duplicate. This is a change to an already-shipped code path and is the one thing in this PR worth reviewing on its own merits.importedFromRidprovenanceapplySchemaImportSelectionsetsimportedFromRidon created node types, relation types, and relations, derived from the file'svaultId(added in #1263) asorn:obsidian.schema:<appId>/<localId>.This is byte-identical to what the Supabase path produces for the same vault and local id, so schema imported from a file and content imported from that same vault over Supabase resolve to the same RID.
Like the Supabase path, this records the immediate source vault. If vault B exports a type it originally imported from vault C, the RID points at B, not C — consistent with existing behavior, but it means RIDs are one hop of provenance, not a full chain.
Bug fixed along the way
The apply loop guarded discourse relations against triples that existed at plan time, but not against triples created earlier in the same run. Two schema node types collapsing onto one local type therefore produced duplicate triples. The guard now runs against live settings.
This was latent before this PR, but case-insensitive matching widens the window:
Claimandclaimin one schema file now collapse onto a single local type where previously they did not.Stack
PR 4 of 5 for FEE-840. Stacks on #1263.
Test plan
Verified:
pnpm --filter @discourse-graphs/obsidian check-typespassespnpm --filter @discourse-graphs/obsidian lint— 0 errors (75 pre-existing warnings, none in touched files)ridToSpaceUriAndLocalId(buildSchemaRid(...))recovers the originalspaceUri/localIdpairfindLocalNodeTypeMatch/findLocalRelationTypeMatch— id match beats a differing name; name/label fallback folds case and trims whitespace; genuinely new items returnundefinedfindExistingTriple— matches on endpoints while ignoring the triple's own id; reversed endpoints and a different relation type both correctly miss