Version v26.10.6 Size 0.2 KB gzip
Why Archive?
You can share content entries with ranked browsing, filters, likes and account erasure on Cloudflare Workers + D1. defineArchiveCollection supplies your JSON payload schema and indexing rules; the Worker supplies sessions, guards, idempotent publishing, cursors and rate limits.
// Before: each deployment repeats validation, indexing and route plumbing.
// After: the deployment is one collection definition plus one factory call.
import { defineArchiveCollection } from '@vielzeug/archive';
import { createArchiveWorker } from '@vielzeug/archive/worker';
import { s } from '@vielzeug/spell';
const notes = defineArchiveCollection({
name: 'notes', contentVersion: 1,
payloadSchema: s.object({ title: s.string().min(1).max(128) }),
index: ({ title }) => ({ title, filter: null, tags: [] }),
});
export default createArchiveWorker([notes]);| Feature | Archive | Hand-rolled Worker | BaaS (Firebase/Supabase) |
|---|---|---|---|
| Bundle size (core) | 0.2 KB | Depends on deployment code | Depends on selected SDK |
| Zero dependencies | No — Spell and jose | Depends on implementation | No — client SDK |
| Content knowledge in the core | |||
| Canonical fresh-database D1 schema | Deployment-owned | Not a D1 backend | |
| Idempotent publish w/ tombstones | |||
| GDPR erasure / export endpoints | |||
| Strict DTO re-validation on read |
Use Archive when an app needs a shareable, ranked, liked community space for content entries on Cloudflare Workers + D1, and can express its content as one strict payload schema. Consider alternatives when you need rich queries beyond filter/tag/search, live collaboration, or a backend you do not control — Archive is deliberately one narrow, hardened surface.
Installation
pnpm add @vielzeug/archive @vielzeug/spellnpm install @vielzeug/archive @vielzeug/spellyarn add @vielzeug/archive @vielzeug/spellQuick Start
A deployment is one collection definition plus one factory call. The definition brings the payload schema and what to index; everything else — the emitted-entry envelope, the content hash, the wire contract, the routes — is composed for you.
import { defineArchiveCollection } from '@vielzeug/archive';
import { createArchiveWorker } from '@vielzeug/archive/worker';
import { s } from '@vielzeug/spell';
// The collection's payload: one strict schema is the whole content contract.
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),
});
const recipesCollection = defineArchiveCollection({
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'],
});
// The whole Worker: routing, auth, guards, limits, SQL and DTO disciplines included.
export default createArchiveWorker([recipesCollection]);The browser side never imports worker machinery. The same collection's composed schemas parse what the routes emit:
import { defineArchiveCollection } from '@vielzeug/archive';
import { s } from '@vielzeug/spell';
const notes = defineArchiveCollection({
name: 'notes', contentVersion: 1,
payloadSchema: s.object({ title: s.string().min(1).max(128) }),
index: ({ title }) => ({ title, filter: null, tags: [] }),
});
const response = await fetch('/api/v1/collections/notes/entries?sort=liked');
if (!response.ok) throw new Error('Archive unavailable');
const parsed = notes.contract.page.parse(await response.json());
console.log(parsed.entries);Features
defineArchiveCollection— the one content seam: payload schema, canonicalization, filter/tag/title derivation, union-typed vocabulariescreateArchiveWorker— the whole routes/auth/limits/SQL stack from registered collectionscollection.contract— entry, page, publish/update input schemas shared by client and WorkerARCHIVE_LIMITS+archiveHealthSchema— the application bounds, advertised by health and validated on both sidesarchiveAuthHealthSchema+AUTH_SCHEMA_VERSION— typed issuer readiness checked against the issuer DDL stamparchiveErrorResponseSchema— the one error envelope, code-typed across the wirecreateAuthWorker— scanner-safe confirmation, live session-version enforcement, durable budgets, metadata export and coordinated erasureAUTH_ISSUER_SERVICE— required uncached issuer check after local catalog JWT verificationArchiveDerivationError— distinguishes faulty collection code from invalid submitted content- Moderation — reader reports, the operator soft-hide and the report queue, keyed on a verified
rolesclaim worker.tap()/issuer.tap()— typed sanitized runtime events; neither core logs by defaultmigrations/— canonical fresh-database DDL with the storage stamp health reads back; no upgrade framework
Documentation
- Usage Guide — stand up a collection, deploy the Worker, wire the client.
- API Reference — every export, all three entry points, the full route table.
- Examples — recipes from first deployment to GDPR erasure.
- Migration — breaking changes after the first release and fresh-database scope.