Skip to content
Genom
Concepts

Formats, detection, registry

A format is one object. Recognising one is data, not code — which is the only reason lazy loading works.

The format module

A format used to arrive in pieces: a parser plugin registered here, a view plugin registered somewhere else, an extraction adapter known only to whatever dispatched extraction. Three registrations for one thing, and the application had to know all three existed.

A module is the whole format — how to recognise it, how to open it, how to read it out as content, and, in the browser half, how to draw it.

What a format package exports
import type { FormatModule } from '@genomdev/core';

export const docx: FormatModule<DocxDocument> = {
  id: 'docx',
  formats: ['docx', 'doc'],
  detect: [
    { container: 'zip', contentType: WORD_MAIN },
    { container: 'ole2' },              // a candidate; canOpen decides
    { extension: ['docx', 'docm', 'dotx', 'doc'] },
  ],
  async canOpen(source, detection) { … },
  async open(source, options) { … },
  walk: walkDocx,
};

How a file is recognised

In two passes, and the order matters. The cheap pass reads the first bytes, the file name and the MIME type. For PDF that settles it. For a ZIP it does not — .docx, .xlsx and .pptx are the same archive with the same signature — so the second pass opens the archive and reads the content type of the main part from [Content_Types].xml.

Both passes happen in @genomdev/core, which is why neither needs a parser to be loaded. The rules a module declares are matched against what the passes found, and exactly one module is fetched.

RuleStrengthExample
{ magic: [...] }certain%PDF- at offset 0
{ container: "zip", contentType }certainthe main part of an OPC package
{ container: "zip", entry }certaina file that must be in the archive
{ container: "ole2" }candidateshortlists the module; canOpen decides
{ extension: [...] }probablethe weakest evidence there is
{ mimeType: [...] }probablewhat the server said it was

The registry

A registry is a list of entries, and an entry is either a module or a promise of one. It is an instance rather than a global: one page may host several independently configured viewers, and tests must not see each other’s registrations.

registry.ts
import { FormatRegistry } from '@genomdev/core';
import { docx } from '@genomdev/docx';
import { pdf } from '@genomdev/genom/lazy';

const registry = new FormatRegistry().register(docx, pdf);

registry.formats();                 // ['docx', 'doc', 'pdf']
const found = await registry.resolve(source);
const document = await registry.open(source);

A format of your own

The contract lives in a small package with no formats in it, so a third-party format plugs in exactly the way the built-in ones do. Implement FormatModule, add view if it draws, and pass it where the others go.

@acme/genom-markdown
import type { ViewModule } from '@genomdev/core/dom';

export const markdown: ViewModule<MarkdownDocument> = {
  id: 'md',
  formats: ['md'],
  detect: [{ extension: ['md', 'markdown'] }],
  open: openMarkdown,
  walk: walkMarkdown,
  view: { mount: mountMarkdown },
};

// In an application:
<DocumentViewer file={file} formats={[docx, pdf, markdown]} />;