Obsidian vault transform module (pure functions + fixture vault) #116
Labels
No Label
area:auth
area:docs
area:export
area:ops
area:storage
area:supply-chain
auth
backend
blocked
collab
deployment
docs
effort:L
effort:M
effort:S
frontend
plugins
qa
vs-nfd
vs-nfd:blocker
No Milestone
No project
No Assignees
1 Participants
Notifications
Due Date
No due date set.
Dependencies
No dependencies set.
Reference: stwaidele/dorfteich#116
Loading…
Reference in New Issue
Block a user
No description provided.
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Context
The vault import (see the milestone) needs a deterministic, DB-free transform from "uploaded ZIP" to "per-note rewritten Markdown plus metadata" that can be unit-tested in isolation. Key constraint: Dorfteich wikilinks resolve by SLUG within a pond, while Obsidian links reference note NAMES — and the shared
wikilinkRuleparses[[target]]verbatim, so all rewriting must happen as a text pre-pass beforemarkdownToDoc.Scope
New server-only module
apps/api/src/import-export/obsidian-vault.tswith pure functions:unzipSyncwith theplugin-package.service.tsprotections (safeRelativePathagainst zip-slip, incremental unpacked-size ceilingMAX_VAULT_UNPACKED_BYTES = 256 MiBagainst zip bombs)..md) / assets (everything else), case-insensitive name index.MAX_PAGE_DEPTH(6); merge the DEEPEST folder levels, merged container titles read likea/b.--- … ---block; extracttags:(scalar, inline array, and list forms — hand-rolled, no yaml dependency); modestripdrops the block, modepreservere-emits it as a fenced ```yaml code block at the top.#tag/#nested/tagoccurrences (skipping code fences, inline code, and headings' leading#) and remove them from the text.[[Name]],[[Name|Display]],[[Name#Heading]](strip the heading, keep display),[[folder/Name]](full-path match wins over basename);![[image.ext]]→ standard image markdown against the resolved vault asset;![[note]]→ plain wikilink; unresolvable targets →[[slugify(target)]](a phantom link).-2/-3suffixing againstexisting pond slugs ∪ batch; ambiguous basenames resolve to the lexicographically first vault path (documented rule).Fixture vault under
fixtures/import/obsidian-vault/covering folders, umlauts, duplicate basenames, nested tags, both frontmatter modes,![[img]],[[a/Note]], heading links; colocatedobsidian-vault.test.ts.Acceptance criteria
slugify, duplicate basenames, nested tags, both frontmatter modes, depth merge beyond 6, zip-slip and unpacked-ceiling rejection.Technical notes
packages/shared/src/ponds.ts(slugify),packages/shared/src/editor-schema/markdown.ts(wikilinkRuleconstraints: no nesting/newlines, verbatim target),apps/api/src/plugins/plugin-package.service.ts(unzip safety model).Dependencies
None (parallel to the 404 issue).
Size
~2 days.
Implemented in
09abda3(+269b36cfixing the gates that commit skipped past).apps/api/src/import-export/obsidian-vault.tsholds the whole transform as pure functions: safe unzip (zip-slip + 256 MiB unpacked ceiling,.obsidian/skipped), frontmatter handling (strip / preserve as a yaml code block,tags:in all three Obsidian forms), fence-aware inline#tagextraction and removal, the link rewrite ([[Name]],[[Name|Display]],[[Name#Heading]],[[folder/Name]]→ the FINAL slug; unresolvable → slugified phantom;![[img]]and relative→ asset placeholders; note embeds degrade to links), folder depth-merge againstMAX_PAGE_DEPTH, and batch slug planning (-nsuffixes against pond ∪ batch, duplicate basenames resolved to the lexicographically first vault path). Fixture vault underfixtures/import/obsidian-vault/with 13 unit tests.