---
title: "Runtime"
description: "Serve translations straight from the latest commit of a translations repository, without a checkout or a file system."
canonical_url: "https://diglot.sh/store/runtime"
---
# Runtime

> Serve translations straight from the latest commit of a translations repository, without a checkout or a file system.

A documentation site can read its translations at build time, but then a new translation only appears with the next deploy. `@diglot/store/runtime` lets a server read the store straight from an archive of the translations repository instead, so it can serve the latest commit as soon as it lands. The entry imports no Node built-ins, so it runs in edge runtimes as well as in Node.js.

```ts
import { readArchive } from '@diglot/store/runtime'

const response = await fetch(`https://codeload.github.com/nuxt/translations/tar.gz/${sha}`)
const archive = await readArchive(response.body!, { locales: ['fr'] })

const fr = archive.records.get('fr') // Map<hash, unknown>, read each value with parseRecord
```

## Read an archive

`readArchive(input, { locales, manifests })` reads a `tar` or `tar.gz` archive, given as a stream, a `Uint8Array` or an `ArrayBuffer`. A stream is read as it arrives, and only the files of the store are kept: the records, the manifests in `.diglot/manifest`, the policy snapshot in `.diglot/policy.json`, and the YAML files at the root and in each locale directory. The archive's top-level directory is dropped, as GitHub puts every file under one. Use `locales` and `manifests` (by source name) to read less.

The result has these fields:

<table>
<thead>
  <tr>
    <th>
      Field
    </th>
    
    <th>
      Content
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        records
      </code>
    </td>
    
    <td>
      Record values by locale, then by hash, as parsed JSON. Only their <code>
        hash
      </code>
      
       is checked, so read each with <code>
        parseRecord
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        manifests
      </code>
    </td>
    
    <td>
      Source manifests by source name
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        policy
      </code>
    </td>
    
    <td>
      The policy snapshot, as parsed JSON, when the archive has one
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        files
      </code>
    </td>
    
    <td>
      The text of the YAML files, by path, such as <code>
        glossary.yml
      </code>
      
       or <code>
        fr/locale.yml
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        sha
      </code>
    </td>
    
    <td>
      The commit the archive was made from, from the header GitHub writes
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        invalid
      </code>
    </td>
    
    <td>
      Paths that were skipped: invalid JSON, a record whose <code>
        hash
      </code>
      
       doesn't match its file name, or a manifest of another version
    </td>
  </tr>
</tbody>
</table>

`readArchive` rejects when the archive is truncated or the stream fails.

## Apply under the same policy

Hashes only match when you extract with the policy the manifests were extracted with. Restore it from the snapshot in the archive and pass it to the adapter:

```ts
import { restorePolicy } from '@diglot/core'

if (!archive.policy)
  throw new Error('the archive has no .diglot/policy.json')
const policy = restorePolicy(archive.policy)
await apply(doc, translations, { policy })
```

`restorePolicy` throws when the policy has a custom `salt` or `codeLabel` function that you don't pass in. See [snapshots](https://diglot.sh/core/policy-and-config#snapshots).

## Locale files

`archiveGlossary(archive, locale, { shared })`, `archiveLocaleState(archive, locale)` and `archiveStyle(archive, locale)` read a locale's merged glossary, its `locale.yml` and its `style.yml` from `archive.files`, the same way the [loaders](https://diglot.sh/store/locale-files) read them from disk. `shared` defaults to `glossary.yml`, and `false` reads no shared glossary. All three throw on a malformed file.

## Coverage

`manifestCoverage(manifest, records, { visible })` returns, for each file of a manifest, the words of its segments by [status](https://diglot.sh/guide/concepts/review-and-status), and `fileCoverage(entries, records, { visible })` does the same for one file. `records` is a map or a lookup function by hash. Each record is validated against its manifest entry, so a text that fails validation counts as `rejected`, just as readers see the source text for it. Records for which `visible` returns `false` count as `untranslated`, and segments without words are left out.

## Changed records

When a push lands, you often want to know which pages to refresh. `changedRecords(before, after, fields)` returns the hashes whose record was added, removed or changed between two record sets, which can be maps or anything with `get` and `keys`. Records are compared on `text`, `review`, `origin` and `derived` by default, the fields that change what a reader sees, and `fields` overrides that list.

## Record files and paths

`parseRecordFile(text, hash)` parses the text of a record file and returns `undefined` when there is no text, when it isn't a valid record or when its `hash` differs. `parseRecordPath(path)` is the inverse of `recordPath`: it returns `{ locale, hash }` for `<locale>/segments/<hh>/<hash>.json` and `undefined` for any other path. Both are also exported from the main entry, together with `recordPath` and `serialiseRecord`.

## Cache records compactly

A server that keeps records in a key-value cache benefits from storing a locale as one compact string. `encodeRecords(records, { compression })` packs a locale's records, a `Map` or an object keyed by hash, into columns with repeated values stored once, then compresses and base64-encodes them. Values that fail `parseRecord`, or that sit under another hash, are left out.

<table>
<thead>
  <tr>
    <th>
      <code>
        compression
      </code>
    </th>
    
    <th>
      Description
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        gzip
      </code>
    </td>
    
    <td>
      The default, readable everywhere
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        zstd
      </code>
    </td>
    
    <td>
      Faster to write and read, but only where <code>
        node:zlib
      </code>
      
       supports zstd
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        none
      </code>
    </td>
    
    <td>
      No compression
    </td>
  </tr>
</tbody>
</table>

`decodeRecords(data)` reads the string back into a `Map<hash, record>`. `decodeRecordsView(data)` returns a read-only map instead, which builds each record the first time you read it and returns the same object afterwards. That suits a server that decodes a locale on every cold start but only reads some of its records. The pack's structure is checked up front, reading a malformed record throws, and iterating the view builds every record.

Where `process.getBuiltinModule` exists, as in Node.js, Deno and Bun, both use `node:zlib`. Elsewhere they use `CompressionStream` and `DecompressionStream`.


## Sitemap

See the full [sitemap](https://diglot.sh/sitemap.md) for all pages.
