4.0.0: tree-form DocumentPackage, definitions tables, $schema versioning, LayoutDocument demotion - #23
Merged
Merged
Conversation
…by $schema URI The DocumentPackage envelope becomes the single hierarchical artefact (#20): the root carries kind, metadata, the document-level symbolTable, optional rendered pages, optional package-level styles/definitions tables, and children -- one group per top-level container, with the node vocabulary in src/package-node.ts following document-outline.js's phase-1 reference shape exactly. Section groups are mandatory, grouping never crosses container boundaries, and discrimination is structural on node+children rather than on the presence of a kind. The generic package-level definitions-table facility lands with styles as its first tenant (#21): style entries carry strict paragraph/run sub-objects of resolved canonical properties only -- the frames/sourcePath/styleId ban is enforced by schema shape (strictObject rejects those keys outright), never by convention -- tree groups carry style refs into the table, ContentDocument nodes carry none so the flat codec-exchange form stays fully materialised, and the pure overlay/resolve/apply helpers export for the documents.js package boundary. Minting stays documents.js's behaviour. Versioning moves entirely to the serialised-artefact boundary: both DocumentPackage's and ContentDocument's formatVersion literals retire, and the release-pinned $schema URI a dumper stamps is the version. documentFromJson is the enforcement point for untrusted input -- same major parses, an older major throws the named-change error (the retired formatVersion envelope and flat package shape), a newer major throws the upgrade pointer, and a layout-document URI throws the pdf-codec tombstone (#65's schema side). A bare DocumentPackageSchema parse structurally validates without version-discriminating; the dispatch contract is documented in src/schema-io.ts. Content hashes exclude $schema -- it is envelope metadata naming the dumper, not content. content-json-schema-defs.ts transcribes the recursive PackageNode set (descriptors, anchors, the seven group wrappers, sheet-image/vector leaves, the definitions tables), with live z.toJSONSchema() comparison coverage for every fragment that has a real z.object counterpart; the generator splices the one $defs block into both published schema files so each resolves its local pointers.
The layout item model (LayoutDocument, LayoutPage, LayoutItem and its text/image/rect/line/ellipse/path/link variants, LayoutImageAsset, LAYOUT_FORMAT_VERSION) moves to pdf-codec, where the only codec that ever read or wrote it owns it outright -- the family pattern every other format already follows. pdf-codec's own PR takes the file and flips its imports; its issue closes there. The LayoutCodec interface goes with it: it modelled the single format that produces layout cheaply on read (PDF), and a schema-package interface for one private implementation was an accident of pdf-codec predating the content pivot. ContentCodec stays, unchanged in shape. schema-io, the generator, and the smoke tests already carry the schema-file side of the demotion (two published .schema.json files, the URI-pattern tombstone recognising old layout-document dumps); this commit removes the model itself and retires the last stale comment references to it in geometry, metadata, and style. Dependents stay on document-schema.js 3.x via semver until their own majors, so this is not a cascade-breaker.
README gains "The package tree" (the group/leaf vocabulary, the container rules, and the three laws as the contract with document-outline.js), "Definitions tables and styles" (the tenant-generic facility with styles as first tenant, the schema-shape ban list, and the overlay chain), and "Versioning by $schema" (the release-pinned URI as the version, the documentFromJson dispatch contract, and the hash-exclusion rule). The Usage, Codecs, and JSON Schema sections are rewritten for the tree form, the two published schema files, and ContentCodec alone; the layout family is documented as pdf-codec-private.
…n bare package-tree leaves
The runtime guards now enforce what the published JSON Schema fragments
have always declared: every group wrapper fragment carries
additionalProperties: false over exactly { node, style, children }, so
isGroupWrapper rejects a wrapper with any fourth key instead of letting
an unknown key ride through unvalidated.
The leaf arms of the five child predicates reject a value carrying a
top-level style key before delegating to the content schemas. A style
ref is legal only on a group wrapper (resolution walks group
ancestors, never leaf payloads), and the shared content schemas
deliberately accept-and-ignore unknown keys -- tightening them to
strict would change flat ContentDocument parsing far beyond the
package tree -- so without this check a leaf-position ref parsed, sat
inert through resolution, and was still rejected by the published
.schema.json leaf fragments: a tree documentFromJson accepted that the
CDN-published schema forbids.
decompose emits a single ContentFormula and flatten requires exactly one (document-outline.js's phase-1 reference throws on any other count), so the schema states the cardinality the bijection needs instead of admitting trees that cannot round-trip. The generated document-package.schema.json picks up minItems/maxItems 1 alongside the existing items $ref, keeping the published face in agreement.
Nothing consumes the const or its ContentDocumentKind type: package.ts spells its own z.literal kind per discriminatedUnion arm, content.ts spells its own, and no test or script references either name, so the export was a third parallel copy of the five-kind list rather than the single shared declaration its comment promised. Reintroduce the day a real consumer exists.
|
🎉 This PR is included in version 4.0.0 🎉 The release is available on: Your semantic-release bot 📦🚀 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #20
Closes #21
The coordinated major. Four changes that share files, so they land together:
The tree.
DocumentPackageSchemais now the single hierarchical artefact: the root carrieskind(moved up from the retired flatcontentfield),metadata, the document-levelsymbolTable, optionalpages, optionalstyles/definitionstables, andchildren— one group per top-level container. The node vocabulary lives insrc/package-node.tsand follows document-outline.js's phase-1 reference implementation (itspackage-node.ts/decompose.ts/flatten.ts, merged on main) node for node:{ node, children }groups with container descriptors or full anchor paragraphs as the node payload, bare leaves carryingkindand neverchildren, section groups mandatory, grouping never crossing container boundaries, discrimination structural on node+children. Phase 2's port into documents.js (documents.js#623) should be mechanical.Deviations from the reference shape, all forced by this package's zod-first conventions or its untrusted-input role:
omit().extend().strict()from the content schemas, not TSOmittypes over hand guards — same single-sourcing intent, the schema package's native spelling. Strictness is deliberate: a flat container smuggled in as a descriptor (aContentShapestill carryingblocks) is rejected rather than silently stripped.isPackageGroupchecks children generically. The reference walks trees it constructed itself; this schema is the boundary validator for trees it did not, so an illegal child position rejects here.style?: stringsits on every group wrapper (the reference predates styles). No ref field exists anywhere on content nodes, per Package-local referenced styles and group inheritance: compression-only, materialised on flatten #21's errata.children's type says exactly which root group each kind takes — the reference expresses the same narrowing throughPackageRoot; zod says it through the union.Definitions tables + styles. The generic facility: a tenant-generic
definitionsrecord (entries taggedkind, open body — future link/footnote tenants, see #22 and markdown-codec#63) alongside the styles tenant's own table. Style entries are strict objects of resolved canonical properties only —frames/sourcePath/styleIdare rejected by schema shape, not documented away. Pure helpers (overlayStyleEntries,resolveStyleChain,applyParagraphStyleProperties,applyRunStyleProperties) export for documents.js's flatten; minting stays documents.js's (#21's errata).resolveStyleChainthrows on an unresolvable ref; ref/table consistency is producer responsibility, same as pages/frames always was.Versioning by
$schema.DOCUMENT_PACKAGE_FORMAT_VERSION,CONTENT_FORMAT_VERSION, and (with the demotion)LAYOUT_FORMAT_VERSIONretire. Today's CDN URIs were already release-pinned (@__PACKAGE_VERSION__), so no new path segment was needed — what changed is thatdocumentFromJsonnow enforces it: the URI's major must match the installed release's major (same major parses — semver-compatible generation; older major throwsSchemaVersionMismatchErrornaming the formatVersion/tree change; newer major throws the upgrade pointer). A bareDocumentPackageSchema.parsedoes not version-discriminate — that contract is documented insrc/schema-io.tsand the README. Content hashes exclude$schema(envelope metadata — noted on the stamping functions).Layout demotion (the schema side of pdf-codec#65 — that issue closes in pdf-codec's own PR, not here):
src/layout.ts+ test deleted,LayoutCodecgone fromcodec.ts, theLayoutDocumentkind gone from schema-io/generator/smoke, with the tombstone kept — a layout-document$schemaURI from any release throwsLayoutSchemaDemotedErrorpointing at pdf-codec. pdf-codec takes the file in W3; dependents stay on 3.x via semver until their own majors, so this is not a cascade-breaker.Also in:
content-json-schema-defs.tstranscribes the recursive PackageNode set and the new tables — 27 of the new fragments have real z.object counterparts and are live-compared in the regression test (52 entries now); only the seven group wrappers andContentEmbeddedObjectremain hand-verified alone. The generator splices the one$defsblock into both published files so each resolves its local pointers (the only cross-file$refstays the embedded-object cycle). document-outline.js#2 is the reference implementation this tracks.Review attention, in order of how much I'd want a second pair of eyes: the per-kind child predicates in
src/package-node.ts(they are new enforcement the reference doesn't have — check nothing legal got rejected), the version-gate semantics insrc/schema-io.ts(same-major acceptance is the semver reading of "URI → exact schema"; a stricter exact-match reading would refuse patch-release dumps), and the both-files$defssplice in the generator.