@vielzeug/archive
v26.10.18
Published
Community content publishing for Cloudflare Workers + D1: a hardened Worker core over per-collection payload schemas
Downloads
1,085
Readme
@vielzeug/archive
Community content publishing for Cloudflare Workers + D1: a hardened Worker core over per-collection payload schemas
Installation
pnpm add @vielzeug/archive @vielzeug/spell
npm install @vielzeug/archive @vielzeug/spell
yarn add @vielzeug/archive @vielzeug/spellQuick Start
Define your content as one payload schema plus what to index, and export the Worker built from it:
import { defineArchiveCollection } from '@vielzeug/archive';
import { createArchiveWorker } from '@vielzeug/archive/worker';
import { s } from '@vielzeug/spell';
const payloadSchema = s.object({
cuisine: s.enum(['italian', 'japanese']),
ingredients: s.array(s.string().trim().min(1).max(64)).min(1).max(20),
name: s.string().trim().min(1).max(32),
});
export const recipesCollection = defineArchiveCollection({
// Ingredient order is not identity: sort it into one canonical form.
canonicalize: (payload) => ({ ...payload, ingredients: [...payload.ingredients].sort() }),
contentVersion: 1,
filters: ['italian', 'japanese'],
index: (payload) => ({
filter: payload.cuisine,
tags: [payload.ingredients.length <= 5 ? 'quick' : 'involved'],
title: payload.name,
}),
name: 'recipes',
payloadSchema,
tags: ['quick', 'involved'],
});
export default createArchiveWorker([recipesCollection]);The collection's contract bundle then drives your client:
recipesCollection.contract.entry, .page, .publishInput, and .updateInput
are the same schemas the Worker uses. Payloads are stable JSON objects with
sorted-key serialization; vocabularies are optional readonly arrays. The root
entry is browser-safe. Deploy the sibling @vielzeug/archive/auth issuer with its
required AUTH_ARCHIVE binding for coordinated account deletion, and bind the
catalog's required AUTH_ISSUER_SERVICE to that issuer for signing-key retrieval
and an uncached session-version check after local JWT verification.
JWT_JWKS_URL selects the issuer's JWKS route; the catalog fetches it through
the service binding, not global fetch, including same-zone deployments.
Issuer sessions carry the required sessionVersion claim backed by
auth_users.session_version. POST /api/v1/sign-out only clears the current
cookie; POST /api/v1/auth/sign-out-all advances the version and revokes all
sessions. GET /api/v1/auth/health exposes the typed, read-only
archiveAuthHealthSchema readiness response. Issuer admission is durable D1
state: 20 requests per minute, plus three sends per email per hour; no issuer
rate-limiter binding is required for sign-in admission, while the public verify
and session-check routes are rate-limited at the edge. Magic-link GETs show a confirmation page; only
a deliberate guarded POST redeems the token. Confirmation uses escaped APP_NAME
branding, nonce-protected inline styles/script and no external assets. Recovery
links to the fixed PUBLIC_ORIGIN/account sign-in entry; successful sign-in
navigates to PUBLIC_ORIGIN/signed-in. Your application must serve both pages.
The initial “Back to app” exit links to PUBLIC_ORIGIN/. The issuer
renders only the confirmation endpoint. Expired links require a new link, while
temporary failures offer manual retry. With valid issuer configuration, malformed
GET links return HTML 400 recovery; POST errors retain their JSON contract.
Full account deletion revokes old issuer/subject sessions; profile-only deletion
does not. Re-enrollment uses a fresh issuer subject. Both Worker factories expose
typed tap() events and never log directly. Invalid content derives to null;
collection faults throw ArchiveDerivationError. Shipped DDL initializes fresh
databases only. Index projections are persisted data; changing a definition does
not update existing rows.
Documentation
License
MIT © Helmuth Saatkamp: part of the Vielzeug monorepo.
