ENG-2019: Extract shared cross-space node discovery into packages/database#1216
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
|
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:
|
sid597
left a comment
There was a problem hiding this comment.
Reviewer orientation — the four places to read, in order.
| }; | ||
| }; | ||
|
|
||
| export const listGroupSharedNodes = async ({ |
There was a problem hiding this comment.
Entry point. Everything below this file's exports is Roam's ENG-1855 code moved verbatim; the only additions are the three extra my_contents columns (author_id, created, metadata) and the optional groupIds (caller-supplied to avoid a duplicate getAvailableGroupIds query — Obsidian passes it, Roam doesn't).
| ); | ||
| }; | ||
|
|
||
| export const buildSharedNodeCandidates = ({ |
There was a problem hiding this comment.
Pure builder = the shared semantics both apps now use: contract gate (instance concept with schema_id + direct + typed full), exact ResourceAccess identity match, current-space exclusion, newest-first sort. This is the semantics reconciliation — Obsidian previously had none of these gates.
| concepts, | ||
| contents, | ||
| currentSpaceId, | ||
| export const toDiscoveredSharedNodes = ({ |
There was a problem hiding this comment.
Roam swap: behavior-identical output. What stays app-side is the imported-RID lookup (roamAlphaAPI block props) and this mapper to the existing DiscoveredSharedNode shape (sourceSpaceId was always the space URI — the candidate now carries it directly).
| .neq("space_id", currentSpaceId); | ||
|
|
||
| if (error) { | ||
| const candidates = await listGroupSharedNodes({ |
There was a problem hiding this comment.
Obsidian swap — the PR's one behavior change lands here (see description). Same PublishedNode shape out; timestamp conversions (+ "Z") preserved from the old query. Two hardening deltas vs the old code: null-metadata guard (old code threw on metadata: null) and re-wrapping PostgrestError so the modal Notice shows a real message.
sid597
left a comment
There was a problem hiding this comment.
Moved-to map for the deleted code — each comment marks where it landed in packages/database/src/lib/sharedNodes.ts and whether it changed.
| spaces: SharedSpace[]; | ||
| }; | ||
|
|
||
| const getResourceKey = ({ |
There was a problem hiding this comment.
→ sharedNodes.ts:61. getResourceKey, getLatestTimestamp (L65), chunk (L235), and the row types are all verbatim copies — except SharedContent gains author_id, created, metadata (Obsidian consumer needs them).
| ); | ||
| }; | ||
|
|
||
| export const buildDiscoveredSharedNodes = ({ |
There was a problem hiding this comment.
→ sharedNodes.ts:80 as buildSharedNodeCandidates. Gate/sort/identity logic unchanged. Modified: drops importedSourceRids/alreadyImported (now mapped app-side, L21 of this file) and outputs the neutral candidate shape (rid, platform, lastModified, + spaceUri/created/authorId/directMetadata pass-throughs).
| ); | ||
| }; | ||
|
|
||
| const getGroupSharedResources = async ( |
There was a problem hiding this comment.
→ sharedNodes.ts:210. Verbatim except a new optional groupIds param — callers that already fetched group ids (Obsidian modal) skip the internal getAvailableGroupIds refetch.
| values.slice(index * size, (index + 1) * size), | ||
| ); | ||
|
|
||
| const getSharedNodeRows = async ({ |
There was a problem hiding this comment.
→ sharedNodes.ts:246. Verbatim except the my_contents select adds author_id, created, metadata and it threads groupIds through.
|
|
||
| // Query my_contents (RLS applied); exclude current space. Get both variants so we can use | ||
| // the latest last_modified per node and prefer "direct" for text (title). | ||
| const { data, error } = await client |
There was a problem hiding this comment.
Not moved — replaced. This query's semantics (any cross-space row, RLS-only, unpaginated) are superseded by the shared module's gated listing; the variant-grouping/direct-preference logic it did by hand is what buildSharedNodeCandidates does behind the gate.
|
ENG-1855 — addressed in this PR ENG-2019 — follow-up in #1216 |
Note
Stacked on #1214 (ENG-1855). Base is
eng-1855-add-roam-shared-node-import-discovery-v2, so this diff shows only the extraction. Merge after #1214.Closes ENG-2019.
What
One shared module —
@repo/database/lib/sharedNodes— now lists group-visible shared nodes cross-space for both apps:discoverSharedNodes.ts:getGroupSharedResources,getSharedNodeRows,buildDiscoveredSharedNodes,chunk,getLatestTimestamppackages/database/src/lib/sharedNodes.ts(listGroupSharedNodes+ purebuildSharedNodeCandidates)getPublishedNodesForGroups: inlinemy_contentsquerylistGroupSharedNodes, maps to the existingPublishedNodeshapeRoam keeps its
DiscoveredSharedNodetype,discoverSharedNodesentry point, and app-side imported-RID detection (roamAlphaAPI); it maps candidates through a small exportedtoDiscoveredSharedNodes. Already-imported detection stays app-side in both apps by design.The one behavior change (Obsidian)
Obsidian's import listing previously returned any cross-space
my_contentsrow (RLS-trust only — itsgroupIdsparameter was dead code — no pagination, no contract check). It now uses the shared semantics extracted from Roam:schema_idset), adirectcontent, and a typedfullcontent are listed.ResourceAccess∩getAvailableGroupIds(RLS stays as defense-in-depth). Whether to move to RLS-trust instead is deliberately out of scope.getAllPages+ 100-id chunked reads (the old query silently capped at the PostgREST row limit).modifiedAtnow takes the latest timestamp across the concept and both content variants (was: content rows only).Deviations from a pure code move
my_contentsselect gainsauthor_id, created, metadata— fields the Obsidian consumer needs.metadata !== nullguard in the Obsidian mapper: the old code crashed on adirectrow with null metadata (typeof null === "object"passes, then.filePaththrows); Roam-origin rows make that case likely now.Error(the shared module throws rawPostgrestError, which the modal's Notice would render as[object Object]).listGroupSharedNodestakes optionalgroupIdsso the Obsidian modal (which already fetches them for its empty-groups notice) doesn't trigger a secondgetAvailableGroupIdsquery. Roam omits it.Infra
packages/databasegets a minimal vitest setup mirroringpackages/content-model; script istest:unitbecause cucumber ownstest. Roam's pure-builder tests moved over (9 tests); Roam keeps 2 mapper tests.turbo.json+ CI gain atest:unitstep so the moved tests stay enforced (they previously ran via Roam's CI-filtered suite).pnpm-lock.yamlis a 3-line hand edit reusing the catalog's existing vitest resolution — a regenerated lockfile re-resolved unrelated packages and broke Obsidian's typecheck, so re-resolution was avoided entirely.Out of scope, unchanged on purpose: the 4 other
chunkcopies,importPreview.ts(preview-time schema checks are a different concern), and any RLS-vs-explicit gating redesign.Verification
tsc --noEmitclean inpackages/database,apps/roam,apps/obsidian; eslint/prettier clean on all touched files (importNodes.ts keeps its 21 pre-existing warnings, count unchanged).turbo run test:unitverified locally.SUPABASE_USE_DB=local; dist staged atdg-test-eng-2019for manual verification.