Skip to content
This repository was archived by the owner on Aug 20, 2026. It is now read-only.

Give the typed readers a DocumentPackage-native API - #66

Merged
Mearman merged 6 commits into
mainfrom
feat/document-package-native-api
Aug 19, 2026
Merged

Give the typed readers a DocumentPackage-native API#66
Mearman merged 6 commits into
mainfrom
feat/document-package-native-api

Conversation

@Mearman

@Mearman Mearman commented Aug 19, 2026

Copy link
Copy Markdown
Member

readOdt, readOdp, readOdg, readOds, and readOdfFormula now return document-schema.js's DocumentPackage rather than a flat, format-local shape. Each is its existing flat reader plus assemblePackage — decompose into the tree, splice the envelope onto the root, mint the styles table — which is the same call documents.js makes at every package it builds. Nothing is passed for pages: a reader runs before any layout pass, so there is no rendered page geometry to report.

The flat readers are untouched behaviourally and keep working under *Content names, which is still the right level for anything working in the flat codec-exchange form (documents.js's conversion pipeline reads there today). A package-native reader calls its own *Content sibling, so both levels are one read of the file and cannot disagree.

Breaking, hence the major: the bare names now belong to the package-native readers.

was now
readOdt readOdtContent
readOdp readOdpContent
readOdg readOdgContent
readOds readOdsContent
readOdfFormulaDocument readOdfFormulaContent
readOdfFormula readOdfFormulaMathMl

Every new bare name returns a DocumentPackage, which is assignable to none of the old return types, so an unmigrated call site is a compile error rather than a silent change of meaning. documents.js is the one in-family caller and will need the rename applied.

readOdm and most .odb readers are deliberately left alone: a master document's chapters are external file references and readOdbInventory/readOdbReport describe structure rather than content, so none of those three has a ContentDocument to decompose. readOdbForm does read an embedded ODF text document via readOdtContent — a form's sub-document is a genuine, complete ODF text document — but that result is nested inside the form's own control-tree return value rather than being the function's own top-level return, so it doesn't get a package-native form either.

On testing: each format gets a round trip over its real fixture rather than a no-throw check — DocumentPackageSchema.parse compared back against the input (proving the package satisfies document-schema.js's own tree guards, though only a stray key on a group node is actually rejected by this; a leaf's z.custom guard passes a stray key through unchanged), flattenPackage reproducing the flat reader's own output exactly, and factorStyles re-minting the identical table. That last pair is as close to bytes → package → bytes as this package can get, since it has readers and no writers. The workers suite exercises the package-native path too, because assemblePackage is a second package's code running in the same isolate and its Worker-safety shouldn't be assumed.

Typecheck, lint, unit, workers, build, and smoke are all green locally.

🤖 Generated with Claude Code

readOdt, readOdp, readOdg, readOds, and readOdfFormula now return
document-schema.js's DocumentPackage -- the hierarchical artefact where
structure, layout, and content are one tree -- instead of a flat,
format-local shape.

Each is its flat reader plus document-schema.js's assemblePackage:
decompose the content into the tree, splice the envelope onto the root,
mint the styles table.
assemblePackage rather than bare decompose because a reader is a package
construction site and owes its caller a whole package, which is the same
call documents.js's conversion pipeline already makes at every package
it builds.
No pages argument is passed: rendered page geometry only exists after a
layout pass, so a reader has none to report.

The flat readers keep their behaviour under *Content names and remain
the right level for a caller working in the flat codec-exchange form.
Each package-native reader calls its own *Content sibling, so the two
levels are one read of the file.

Round-trip tests per format assert schema validity with no stray fields,
flattenPackage reproducing the flat reader's own output exactly over
real fixture bytes, and factorStyles re-minting the identical table.
The workers suite covers the package-native path too, since
assemblePackage is a second package's code running in the same isolate.

BREAKING CHANGE: the flat typed readers move to *Content names, freeing
the bare names for the DocumentPackage-native readers: readOdt ->
readOdtContent, readOdp -> readOdpContent, readOdg -> readOdgContent,
readOds -> readOdsContent, readOdfFormulaDocument ->
readOdfFormulaContent, and readOdfFormula -> readOdfFormulaMathMl.
Behaviour is unchanged in every case; each new bare name returns a
DocumentPackage, which is assignable to none of the old return types, so
an unmigrated call site fails to compile rather than changing meaning
silently.
The README documented one reader per format returning a format-local
shape.
It now documents both levels each reader has -- the DocumentPackage the
bare name returns and the flat *Content function beneath it -- with a
per-format table, the flattenPackage equality that relates them, and a
migration table for the renamed flat functions.

Also corrects the schema dependency's description: LayoutDocument left
document-schema.js for pdf-codec, and DocumentPackage is what odf.js
shares with ooxml.js alongside ContentDocument.
…af nodes

DocumentPackageSchema.parse does not strip or reject an unrecognised key on a leaf node.
Group descriptors are .strict() and isGroupWrapper restricts a group node's own keys to
node/style/children, so a stray key on a GROUP node makes the parse throw. A leaf is
validated by a z.custom guard, which returns its input unchanged on success rather than a
stripped, reparsed value, so a stray leaf key survives undetected by
expect(parse(pkg)).toEqual(pkg).
…ation section

readOdbForm does read an embedded document via readOdtContent, since a form's
sub-document is a complete, ordinary ODF text document -- so the prior claim that none of
the untouched readers reads document content was wrong for this one. It still gets no
package-native form, because that OdtDocument is nested inside the form's own
control-tree return value rather than being the function's own top-level return.
Replace the "previously the bare readOdX name" / "before this landed" framing in
src/index.ts and src/typed/formula/read.ts with comments that state only the evergreen
fact: why each ladder exists and which level a caller should reach for. The old wording
described one specific rename rather than a lasting property of the code; the README's
migration table is already the correct home for the old-to-new name mapping.
… page

The "shapes as groups" assertion only inspected the fixture's first page, which has zero
shapes, so it passed even when shape-group assembly was mutated away entirely -- the
children-length check reduces to vectors.length alone when shapes.length is zero. Extend
the same assertions to the second page, which has one shape and no vectors, so a dropped
shape group now fails the length check.
@Mearman
Mearman merged commit e65ca2a into main Aug 19, 2026
10 checks passed
@Mearman
Mearman deleted the feat/document-package-native-api branch August 19, 2026 06:41
@github-actions

Copy link
Copy Markdown
Contributor

🎉 This PR is included in version 5.0.0 🎉

The release is available on:

Your semantic-release bot 📦🚀

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant