@vielzeug/codex 26.10.9 → 26.10.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/data/catalog.json +43 -43
- package/data/llms-full.txt +286 -170
- package/data/llms.txt +1 -1
- package/data/manifest.json +1 -1
- package/data/packages/archive.json +2 -2
- package/data/refine.json +207 -207
- package/data/search.json +2 -2
- package/package.json +2 -2
package/data/llms-full.txt
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Vielzeug: Full Documentation
|
|
2
2
|
|
|
3
|
-
> Complete documentation for 42 packages. Version: 26.10.
|
|
3
|
+
> Complete documentation for 42 packages. Version: 26.10.9
|
|
4
4
|
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -129,26 +129,26 @@ console.log(parsed.entries);
|
|
|
129
129
|
|
|
130
130
|
## API Overview
|
|
131
131
|
|
|
132
|
-
| Symbol
|
|
133
|
-
|
|
|
134
|
-
| `defineArchiveCollection`
|
|
135
|
-
| `createArchiveWorker`
|
|
136
|
-
| `createAuthWorker`
|
|
137
|
-
| `worker.tap`, `issuer.tap`
|
|
138
|
-
| `ArchiveDerivationError`
|
|
139
|
-
| `ARCHIVE_LIMITS`
|
|
140
|
-
| `archiveErrorResponseSchema`, `archiveAuthHealthSchema` | Validate error and issuer-health responses
|
|
141
|
-
| `ArchiveError`, `ArchiveDefinitionError`
|
|
142
|
-
| `sessionCookieValue`
|
|
143
|
-
| `isWellFormedText`
|
|
132
|
+
| Symbol | Purpose | Execution mode | Common gotcha |
|
|
133
|
+
| ------------------------------------------------------- | ------------------------------------------------------- | -------------- | --------------------------------------------------------------- |
|
|
134
|
+
| `defineArchiveCollection` | Compose validation, indexing, identity and wire schemas | Sync | Invalid content derives to `null`; callback faults throw |
|
|
135
|
+
| `createArchiveWorker` | Build the catalog Worker | Sync | Collection names must be unique |
|
|
136
|
+
| `createAuthWorker` | Build the passwordless issuer | Sync | `AUTH_ARCHIVE` is required |
|
|
137
|
+
| `worker.tap`, `issuer.tap` | Observe sanitized runtime outcomes | Sync | Neither core logs by default; observer exceptions are swallowed |
|
|
138
|
+
| `ArchiveDerivationError` | Identify a collection implementation/runtime fault | Sync | Faults become sanitized 500s, not content-validation 422s |
|
|
139
|
+
| `ARCHIVE_LIMITS` | Shared application bounds | Sync | Page bounds do not cap total exports |
|
|
140
|
+
| `archiveErrorResponseSchema`, `archiveAuthHealthSchema` | Validate error and issuer-health responses | Sync | Unknown error codes and health states reject |
|
|
141
|
+
| `ArchiveError`, `ArchiveDefinitionError` | Public base and invalid-definition errors | Sync | Thrown locally, not transmitted as error objects |
|
|
142
|
+
| `sessionCookieValue` | Compose shared session-cookie attributes | Sync | Deployments must choose `secure` from their environment |
|
|
143
|
+
| `isWellFormedText` | Check control characters and lone surrogates | Sync | Does not trim or impose a length bound |
|
|
144
144
|
|
|
145
145
|
## Package Entry Point
|
|
146
146
|
|
|
147
|
-
| Import
|
|
148
|
-
|
|
|
149
|
-
| `@vielzeug/archive`
|
|
147
|
+
| Import | Purpose |
|
|
148
|
+
| -------------------------- | --------------------------------------------------------------------------- |
|
|
149
|
+
| `@vielzeug/archive` | Browser-safe collections, constants, schemas and errors |
|
|
150
150
|
| `@vielzeug/archive/worker` | Catalog factory, issuer signing-key/session service binding and event types |
|
|
151
|
-
| `@vielzeug/archive/auth`
|
|
151
|
+
| `@vielzeug/archive/auth` | Issuer factory and binding types |
|
|
152
152
|
|
|
153
153
|
## Collections
|
|
154
154
|
|
|
@@ -160,14 +160,14 @@ Creates a frozen collection and its contract; malformed names, versions or vocab
|
|
|
160
160
|
function defineArchiveCollection(definition: ArchiveCollectionDefinition): ArchiveCollection;
|
|
161
161
|
```
|
|
162
162
|
|
|
163
|
-
| Parameter
|
|
164
|
-
|
|
|
165
|
-
| `name`
|
|
166
|
-
| `contentVersion`
|
|
167
|
-
| `payloadSchema`
|
|
168
|
-
| `filters`, `tags` | Optional readonly arrays; omitted means empty vocabulary
|
|
169
|
-
| `canonicalize`
|
|
170
|
-
| `index`
|
|
163
|
+
| Parameter | Contract |
|
|
164
|
+
| ----------------- | --------------------------------------------------------------------------- |
|
|
165
|
+
| `name` | Lowercase `[a-z0-9-]{1,32}` |
|
|
166
|
+
| `contentVersion` | Positive safe integer |
|
|
167
|
+
| `payloadSchema` | Spell schema whose output is a stable JSON object |
|
|
168
|
+
| `filters`, `tags` | Optional readonly arrays; omitted means empty vocabulary |
|
|
169
|
+
| `canonicalize` | Optional pure, idempotent, schema-valid normalization |
|
|
170
|
+
| `index` | Derive `filter`, `tags` and searchable `title` without mutating the payload |
|
|
171
171
|
|
|
172
172
|
**Returns:** `ArchiveCollection`.
|
|
173
173
|
|
|
@@ -188,14 +188,14 @@ if (derived === null) throw new Error('Invalid recipe');
|
|
|
188
188
|
console.log(derived.contentHash);
|
|
189
189
|
```
|
|
190
190
|
|
|
191
|
-
| Method / property
|
|
192
|
-
|
|
|
193
|
-
| `derive(raw)`
|
|
194
|
-
| `contract.entry`
|
|
195
|
-
| `contract.ownedEntry`, `ownedEntries`
|
|
196
|
-
| `contract.page`
|
|
191
|
+
| Method / property | Result |
|
|
192
|
+
| -------------------------------------- | --------------------------------------------------- |
|
|
193
|
+
| `derive(raw)` | `Promise` |
|
|
194
|
+
| `contract.entry` | Public entry schema |
|
|
195
|
+
| `contract.ownedEntry`, `ownedEntries` | Owner view with `clientKey` and `hidden` |
|
|
196
|
+
| `contract.page` | Entries and the forward cursor |
|
|
197
197
|
| `contract.publishInput`, `updateInput` | Publish and revision-guarded update request schemas |
|
|
198
|
-
| `filters`, `tags`
|
|
198
|
+
| `filters`, `tags` | Frozen vocabulary snapshots |
|
|
199
199
|
|
|
200
200
|
`derive` returns `null` for invalid JSON objects, schema-invalid input, invalid titles, too many tags and oversized canonical payloads. Unexpected schema, callback or hashing failures throw `ArchiveDerivationError`. Non-idempotent or schema-invalid canonicalization, unstable schema transformations, index mutation and out-of-vocabulary projections are also derivation faults. Routes map `null` to 422 `invalid-payload` and faults to a sanitized 500 `internal`.
|
|
201
201
|
Schema output that is lossy or no longer JSON is a derivation fault, not invalid
|
|
@@ -229,18 +229,19 @@ import { createArchiveWorker, type ArchiveWorkerEnv } from '@vielzeug/archive/wo
|
|
|
229
229
|
import { s } from '@vielzeug/spell';
|
|
230
230
|
|
|
231
231
|
const notes = defineArchiveCollection({
|
|
232
|
-
name: 'notes',
|
|
232
|
+
name: 'notes',
|
|
233
|
+
contentVersion: 1,
|
|
233
234
|
payloadSchema: s.object({ title: s.string().min(1).max(128) }),
|
|
234
235
|
index: ({ title }) => ({ title, filter: null, tags: [] }),
|
|
235
236
|
});
|
|
236
237
|
export default createArchiveWorker([notes]);
|
|
237
238
|
```
|
|
238
239
|
|
|
239
|
-
| Method
|
|
240
|
-
|
|
|
241
|
-
| `fetch(request, env, ctx)`
|
|
240
|
+
| Method | Contract |
|
|
241
|
+
| --------------------------------- | -------------------------------------------------------------------------------- |
|
|
242
|
+
| `fetch(request, env, ctx)` | Returns `Promise` |
|
|
242
243
|
| `scheduled(controller, env, ctx)` | Sweeps expired retry keys and revocation tombstones; emits failures and rethrows |
|
|
243
|
-
| `tap(handler, { signal? }?)`
|
|
244
|
+
| `tap(handler, { signal? }?)` | Returns unsubscribe; a pre-aborted signal registers nothing |
|
|
244
245
|
|
|
245
246
|
`AUTH_ISSUER_SERVICE` is a required `fetch`-shaped service binding to the issuer.
|
|
246
247
|
The catalog fetches signing keys from the configured `JWT_JWKS_URL` through that
|
|
@@ -300,11 +301,11 @@ import { createAuthWorker, type AuthWorkerEnv } from '@vielzeug/archive/auth';
|
|
|
300
301
|
export default createAuthWorker();
|
|
301
302
|
```
|
|
302
303
|
|
|
303
|
-
| Method
|
|
304
|
-
|
|
|
305
|
-
| `fetch(request, env, ctx?)`
|
|
304
|
+
| Method | Contract |
|
|
305
|
+
| ---------------------------------- | ------------------------------------------------------------------------------- |
|
|
306
|
+
| `fetch(request, env, ctx?)` | Returns `Promise` |
|
|
306
307
|
| `scheduled(controller, env, ctx?)` | Removes expired links, closed send windows and unverified unreferenced accounts |
|
|
307
|
-
| `tap(handler, { signal? }?)`
|
|
308
|
+
| `tap(handler, { signal? }?)` | Observes typed `AuthEvent`; returns unsubscribe; pre-aborted signals are inert |
|
|
308
309
|
|
|
309
310
|
Magic links expire after 15 minutes and work once. The durable per-email budget allows three sends per hour; consumption, expiration and send failure do not refund it. Ordinary sessions last at most 30 days. Operator sessions use `OPERATOR_SESSION_MAX_AGE_SECONDS` (900 seconds). `OPERATOR_EMAILS` controls newly minted role claims.
|
|
310
311
|
|
|
@@ -331,12 +332,44 @@ contract; `/auth` exports the factory and issuer binding types.
|
|
|
331
332
|
Opening a magic link performs a safe GET that displays a click-confirmation page,
|
|
332
333
|
not redemption. Only a marked POST from an exact allowed origin or
|
|
333
334
|
`PUBLIC_ORIGIN` consumes `{ token }` and returns 204 with the session cookie. The
|
|
334
|
-
confirmation page navigates to
|
|
335
|
-
|
|
336
|
-
|
|
335
|
+
confirmation page navigates to the fixed `${PUBLIC_ORIGIN}/signed-in` destination;
|
|
336
|
+
your application must serve that page because the issuer serves only the
|
|
337
|
+
confirmation endpoint. The POST is not a redirect. Confirmation responses are
|
|
338
|
+
`no-store` with no-referrer and a restrictive CSP; inline script and styles
|
|
339
|
+
require a fresh response nonce, with no
|
|
340
|
+
`unsafe-inline`. The page removes the token from the address bar before rendering
|
|
341
|
+
the controls and loads no external assets. `APP_NAME` supplies escaped page and
|
|
342
|
+
email branding; the confirmation has keyboard focus, live status announcements,
|
|
343
|
+
44-pixel minimum action heights, a warm near-white paper and copper light scheme,
|
|
344
|
+
a deep neutral dark scheme, serif headings, sans-serif body text and reduced-motion
|
|
345
|
+
styles. Without JavaScript, it links to `${PUBLIC_ORIGIN}/account` as “Request a
|
|
346
|
+
new sign-in link” but cannot redeem a link.
|
|
337
347
|
GET displays confirmation for any syntactically valid token, including a used
|
|
338
348
|
token; only POST establishes whether the link is still redeemable. POST accepts
|
|
339
|
-
exactly `{ token }` and rejects query parameters.
|
|
349
|
+
exactly `{ token }` and rejects query parameters. With valid issuer configuration,
|
|
350
|
+
invalid or missing GET tokens, duplicate token parameters and unsupported query
|
|
351
|
+
parameters return HTTP 400 with branded HTML recovery, not a JSON error. POST
|
|
352
|
+
errors retain the JSON `ArchiveErrorResponse` schema and their HTTP status.
|
|
353
|
+
|
|
354
|
+
The button announces pending sign-in and blocks duplicate submissions. HTTP 410
|
|
355
|
+
replaces it with a new-link action; HTTP 400 also disables resubmission. Every
|
|
356
|
+
failure offers the fixed `${PUBLIC_ORIGIN}/account` recovery link labelled
|
|
357
|
+
“Request a new sign-in link”. Like `/signed-in`, `/account` is a fixed reference
|
|
358
|
+
issuer convention: the application must serve its sign-in entry there, not the issuer.
|
|
359
|
+
Recovery is secondary while retry remains possible, and becomes the primary action
|
|
360
|
+
for invalid or expired links. It replaces the footer exit link to avoid duplicate
|
|
361
|
+
navigation. The initial “Back to app” footer links to `${PUBLIC_ORIGIN}/`.
|
|
362
|
+
HTTP 429 delays manual retry using unsigned integer seconds or a syntactically valid
|
|
363
|
+
HTTP-date `Retry-After`, rounded up
|
|
364
|
+
and bounded to 1-300 seconds (30 seconds for missing or invalid values). HTTP 503
|
|
365
|
+
and network failures offer explicit retry; no failure starts an automatic request.
|
|
366
|
+
All returned response bodies, including discarded stale responses, are cancelled
|
|
367
|
+
without delaying recovery; cancellation failures are ignored. Each attempt aborts
|
|
368
|
+
its controller on completion. Stalled requests abort after 15 seconds with a
|
|
369
|
+
timeout-specific message, distinct from connection failures. Leaving the page aborts its request and
|
|
370
|
+
clears timers; bfcache restoration resumes only the remaining cooldown, not a
|
|
371
|
+
request. Navigation after exact 204 success is fixed at
|
|
372
|
+
`${PUBLIC_ORIGIN}/signed-in`; query input never controls either destination.
|
|
340
373
|
|
|
341
374
|
The issuer never logs directly. `tap` emits sanitized request and scheduled
|
|
342
375
|
outcomes; observer failures are swallowed, abort detaches the observer and failed
|
|
@@ -346,39 +379,39 @@ scheduled work emits then rethrows. `AuthDiagnostic` reports only a category.
|
|
|
346
379
|
|
|
347
380
|
Every route below is mounted under `/api/v1`. Mutation guards require an exact allowed `Origin` and `x-archive-request: 1`. Registered publishers pin `x-archive-publisher` to the identity captured when preparing private requests. The header is a freshness precondition, not authority.
|
|
348
381
|
|
|
349
|
-
| Route
|
|
350
|
-
|
|
|
351
|
-
| `GET /health`
|
|
352
|
-
| `POST /sign-out`
|
|
353
|
-
| `GET /collections/:collection`
|
|
354
|
-
| `GET /collections/:collection/entries`
|
|
355
|
-
| `POST /collections/:collection/entries`
|
|
356
|
-
| `GET /collections/:collection/entries/:id`
|
|
357
|
-
| `PUT /collections/:collection/entries/:id`
|
|
358
|
-
| `DELETE /collections/:collection/entries/:id`
|
|
359
|
-
| `PUT` / `DELETE /collections/:collection/entries/:id/like`
|
|
360
|
-
| `POST /collections/:collection/entries/:id/report`
|
|
361
|
-
| `PUT` / `DELETE /collections/:collection/entries/:id/hidden` | Operator hide / unhide; 204
|
|
362
|
-
| `GET /collections/:collection/reports`
|
|
363
|
-
| `PUT /collections/:collection/reports/:id/resolved`
|
|
364
|
-
| `DELETE /publishers/:id`
|
|
365
|
-
| `GET /moderation-log`
|
|
366
|
-
| `GET` / `POST` / `PUT /me`
|
|
367
|
-
| `DELETE /me`
|
|
368
|
-
| `DELETE /me/account`
|
|
369
|
-
| `GET /me/publications?collection=…`
|
|
370
|
-
| `GET /me/likes`
|
|
371
|
-
| `GET /me/likes?ids=…`
|
|
372
|
-
| `GET /me/reports`
|
|
373
|
-
| `GET /auth/health`
|
|
374
|
-
| `GET /auth/session`
|
|
375
|
-
| `POST /auth/sign-out-all`
|
|
376
|
-
| `GET /auth/jwks.json`
|
|
377
|
-
| `POST /auth/sign-in`
|
|
378
|
-
| `GET /auth/verify?token=…`
|
|
379
|
-
| `POST /auth/verify`
|
|
380
|
-
| `GET /auth/account`
|
|
381
|
-
| `DELETE /auth/account`
|
|
382
|
+
| Route | Contract |
|
|
383
|
+
| ------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
384
|
+
| `GET /health` | Public catalog readiness; 200 `ok` or 503 `degraded` |
|
|
385
|
+
| `POST /sign-out` | Catalog-local sign-out; `{ ok: true }` and expired session cookie, without issuer access |
|
|
386
|
+
| `GET /collections/:collection` | Collection metadata |
|
|
387
|
+
| `GET /collections/:collection/entries` | `author`, `filter`, `tag`, `search`, `sort`, `cursor`, `limit`; forward entry page |
|
|
388
|
+
| `POST /collections/:collection/entries` | `{ clientKey, payload }`; 201 create or 200 replay |
|
|
389
|
+
| `GET /collections/:collection/entries/:id` | Public entry; always `no-store` |
|
|
390
|
+
| `PUT /collections/:collection/entries/:id` | Owner-only `{ expectedRevision, payload }` |
|
|
391
|
+
| `DELETE /collections/:collection/entries/:id` | Owner or operator removal; 204 |
|
|
392
|
+
| `PUT` / `DELETE /collections/:collection/entries/:id/like` | Like / unlike visible entries only; guarded write and response; like-state response |
|
|
393
|
+
| `POST /collections/:collection/entries/:id/report` | `{ reason }`; idempotent per entry/reporter; 204 |
|
|
394
|
+
| `PUT` / `DELETE /collections/:collection/entries/:id/hidden` | Operator hide / unhide; 204 |
|
|
395
|
+
| `GET /collections/:collection/reports` | Operator page; `status=unresolved` (default) or `resolved`, `cursor`, `limit` |
|
|
396
|
+
| `PUT /collections/:collection/reports/:id/resolved` | Operator resolves report by its unique report ID; idempotent 204 |
|
|
397
|
+
| `DELETE /publishers/:id` | Operator erases Archive profile; not issuer account |
|
|
398
|
+
| `GET /moderation-log` | Operator forward page; `cursor`, `limit` |
|
|
399
|
+
| `GET` / `POST` / `PUT /me` | Identity, register with optional `{ name }`, rename with required `{ name }` |
|
|
400
|
+
| `DELETE /me` | Profile-only cascade; 204; does not clear cookie |
|
|
401
|
+
| `DELETE /me/account` | Revoke issuer/subject and atomically erase profile; 204; coordinated issuer-erasure seam |
|
|
402
|
+
| `GET /me/publications?collection=…` | Owner entries including hidden state and retry keys |
|
|
403
|
+
| `GET /me/likes` | Forward page `{ ids, cursor }`; `cursor`, `limit` |
|
|
404
|
+
| `GET /me/likes?ids=…` | Subset lookup `{ ids }`; at most `likeStateIds` input IDs |
|
|
405
|
+
| `GET /me/reports` | Own forward report page across collections; `cursor`, `limit` |
|
|
406
|
+
| `GET /auth/health` | Public issuer readiness; typed `AuthHealth`, 200 `ok` or 503 `degraded`; no email or peer probe |
|
|
407
|
+
| `GET /auth/session` | Internal issuer-session check for the catalog service binding; exact `{ ok: true }`, `no-store`, no PII; 401 for an invalid, stale or absent-account session |
|
|
408
|
+
| `POST /auth/sign-out-all` | Requires marker, exact origin, session and captured `ACCOUNT_HEADER`; atomically increments `session_version`, deletes outstanding links, preserves account and catalog data; 200 `{ ok: true }` with an expired cookie only on durable success |
|
|
409
|
+
| `GET /auth/jwks.json` | Active and configured retired public verification keys |
|
|
410
|
+
| `POST /auth/sign-in` | `{ email }` (trimmed and lowercased); 202 `{ ok: true }` |
|
|
411
|
+
| `GET /auth/verify?token=…` | Safe no-store click-confirmation HTML; with valid issuer configuration, invalid queries return 400 recovery HTML; no token consumption or session change |
|
|
412
|
+
| `POST /auth/verify` | JSON `{ token }`, marker and exact allowed origin or `PUBLIC_ORIGIN`; consume once; 204 with session cookie |
|
|
413
|
+
| `GET /auth/account` | `{ id, email, createdAt, lastSignInAt }`; no tokens or budget data |
|
|
414
|
+
| `DELETE /auth/account` | Requires `ACCOUNT_HEADER` with the captured issuer account UUID; catalog `/me/account` via `AUTH_ARCHIVE`, require exact 204, then issuer user and cookie clear; 204 |
|
|
382
415
|
|
|
383
416
|
Forward pages are newest first with opaque context-bound keyset cursors. Keep the same query context when following a cursor; end at `cursor: null`. `maxPageSize` bounds each page, not the total number of liked IDs, reports or audit actions you can export. The subset-like query is not a page and cannot combine `ids` with `cursor` or `limit`.
|
|
384
417
|
|
|
@@ -434,37 +467,37 @@ Effective hide, unhide, operator removal and publisher erasure append audit rows
|
|
|
434
467
|
|
|
435
468
|
## Constants and Schemas
|
|
436
469
|
|
|
437
|
-
| Export
|
|
438
|
-
|
|
|
439
|
-
| `ARCHIVE_CONTRACT_VERSION`, `ARCHIVE_SCHEMA_VERSION`
|
|
440
|
-
| `AUTH_SCHEMA_VERSION`
|
|
441
|
-
| `ARCHIVE_COLLECTION_NAME_PATTERN`
|
|
442
|
-
| `ARCHIVE_OPERATOR_ROLE`
|
|
443
|
-
| `SESSION_COOKIE_NAME`, `SESSION_COOKIE_PATH`
|
|
444
|
-
| `SESSION_MAX_AGE_SECONDS`
|
|
445
|
-
| `OPERATOR_SESSION_MAX_AGE_SECONDS`
|
|
446
|
-
| `ACCOUNT_HEADER`
|
|
447
|
-
| `PUBLISHER_HEADER`, `REQUEST_MARKER_HEADER`
|
|
448
|
-
| `ILL_FORMED_TEXT`, `isWellFormedText(value: string): boolean` | Reject control characters and lone UTF-16 surrogates
|
|
449
|
-
| `uuidSchema`, `isoTimestampSchema`, `hex64Schema`
|
|
450
|
-
| `archiveAliasSchema`
|
|
451
|
-
| `archiveAuthorSchema`
|
|
452
|
-
| `archiveIdentitySchema`
|
|
453
|
-
| `archiveAccountSchema`
|
|
454
|
-
| `archiveLimitsSchema`
|
|
455
|
-
| `archiveHealthSchema`
|
|
456
|
-
| `archiveAuthHealthSchema`
|
|
457
|
-
| `archiveErrorResponseSchema`
|
|
458
|
-
| `archiveOkSchema`
|
|
459
|
-
| `archiveCollectionMetaSchema`
|
|
460
|
-
| `archiveLikedIdsSchema`
|
|
461
|
-
| `archiveLikesPageSchema`
|
|
462
|
-
| `archiveLikeStateSchema`
|
|
463
|
-
| `archiveReportsSchema`, `archiveOwnReportsSchema`
|
|
464
|
-
| `archiveModerationLogSchema`
|
|
465
|
-
| `archiveSortSchema`
|
|
466
|
-
| `ARCHIVE_ERROR_CODES`
|
|
467
|
-
| `ARCHIVE_MODERATION_ACTIONS`
|
|
470
|
+
| Export | Value or parsed output |
|
|
471
|
+
| ------------------------------------------------------------- | -------------------------------------------------------------------------- |
|
|
472
|
+
| `ARCHIVE_CONTRACT_VERSION`, `ARCHIVE_SCHEMA_VERSION` | Both debut at `1`; wire and storage versions are independent |
|
|
473
|
+
| `AUTH_SCHEMA_VERSION` | `1`; issuer DDL stamp checked by `/auth/health` |
|
|
474
|
+
| `ARCHIVE_COLLECTION_NAME_PATTERN` | `/^[a-z0-9-]{1,32}$/` |
|
|
475
|
+
| `ARCHIVE_OPERATOR_ROLE` | `'archive-operator'` |
|
|
476
|
+
| `SESSION_COOKIE_NAME`, `SESSION_COOKIE_PATH` | `'archive_session'`, `'/api/v1'` |
|
|
477
|
+
| `SESSION_MAX_AGE_SECONDS` | `2592000` |
|
|
478
|
+
| `OPERATOR_SESSION_MAX_AGE_SECONDS` | `900`; shared root contract |
|
|
479
|
+
| `ACCOUNT_HEADER` | `'X-Archive-Account'`; required issuer-account UUID on coordinated erasure |
|
|
480
|
+
| `PUBLISHER_HEADER`, `REQUEST_MARKER_HEADER` | `'x-archive-publisher'`, `'x-archive-request'` |
|
|
481
|
+
| `ILL_FORMED_TEXT`, `isWellFormedText(value: string): boolean` | Reject control characters and lone UTF-16 surrogates |
|
|
482
|
+
| `uuidSchema`, `isoTimestampSchema`, `hex64Schema` | UUID, ISO timestamp, lowercase SHA-256 hex strings |
|
|
483
|
+
| `archiveAliasSchema` | Trimmed, well-formed alias of at most 24 characters |
|
|
484
|
+
| `archiveAuthorSchema` | `{ id: string; name: string }` |
|
|
485
|
+
| `archiveIdentitySchema` | `ArchiveIdentity` |
|
|
486
|
+
| `archiveAccountSchema` | `ArchiveAccount`; issuer metadata, excluding private fields |
|
|
487
|
+
| `archiveLimitsSchema` | All numeric `ARCHIVE_LIMITS` fields |
|
|
488
|
+
| `archiveHealthSchema` | `ArchiveHealth` |
|
|
489
|
+
| `archiveAuthHealthSchema` | `AuthHealth` |
|
|
490
|
+
| `archiveErrorResponseSchema` | `ArchiveErrorResponse` |
|
|
491
|
+
| `archiveOkSchema` | `{ ok: true }` |
|
|
492
|
+
| `archiveCollectionMetaSchema` | `ArchiveCollectionMeta` |
|
|
493
|
+
| `archiveLikedIdsSchema` | `{ ids: string[] }` subset response |
|
|
494
|
+
| `archiveLikesPageSchema` | `ArchiveLikesPage` |
|
|
495
|
+
| `archiveLikeStateSchema` | `{ id: string; likeCount: number; liked: boolean }` |
|
|
496
|
+
| `archiveReportsSchema`, `archiveOwnReportsSchema` | `ArchiveReports`, `ArchiveOwnReports` |
|
|
497
|
+
| `archiveModerationLogSchema` | `ArchiveModerationLog` |
|
|
498
|
+
| `archiveSortSchema` | `ArchiveSort` |
|
|
499
|
+
| `ARCHIVE_ERROR_CODES` | Values of `ArchiveErrorCode` below |
|
|
500
|
+
| `ARCHIVE_MODERATION_ACTIONS` | `['hide', 'unhide', 'remove', 'erase-publisher']` |
|
|
468
501
|
|
|
469
502
|
### `sessionCookieValue`
|
|
470
503
|
|
|
@@ -474,11 +507,11 @@ Composes a session cookie using the shared security attributes.
|
|
|
474
507
|
function sessionCookieValue(token: string, options: { maxAgeSeconds: number; secure: boolean }): string;
|
|
475
508
|
```
|
|
476
509
|
|
|
477
|
-
| Parameter
|
|
478
|
-
|
|
|
479
|
-
| `token`
|
|
480
|
-
| `options.maxAgeSeconds` | Cookie lifetime; `0` clears it
|
|
481
|
-
| `options.secure`
|
|
510
|
+
| Parameter | Contract |
|
|
511
|
+
| ----------------------- | -------------------------------------------- |
|
|
512
|
+
| `token` | Session token, or empty string when clearing |
|
|
513
|
+
| `options.maxAgeSeconds` | Cookie lifetime; `0` clears it |
|
|
514
|
+
| `options.secure` | Include `Secure` for production HTTPS |
|
|
482
515
|
|
|
483
516
|
**Returns:** the `Set-Cookie` value with `HttpOnly`, `SameSite=Strict`, the shared path and optional `Secure`.
|
|
484
517
|
|
|
@@ -517,11 +550,22 @@ console.log(isWellFormedText('Rice'), isWellFormedText('\u0000'));
|
|
|
517
550
|
|
|
518
551
|
```ts
|
|
519
552
|
const limits = {
|
|
520
|
-
aliasChars: 24,
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
553
|
+
aliasChars: 24,
|
|
554
|
+
cursorChars: 1024,
|
|
555
|
+
idempotencyRetentionMs: 172800000,
|
|
556
|
+
likeStateIds: 50,
|
|
557
|
+
maxPageSize: 50,
|
|
558
|
+
maxPublications: 50,
|
|
559
|
+
pageSize: 10,
|
|
560
|
+
payloadBytes: 8192,
|
|
561
|
+
publicCacheSeconds: 30,
|
|
562
|
+
reportReasonChars: 256,
|
|
563
|
+
requestBytes: 12288,
|
|
564
|
+
searchChars: 64,
|
|
565
|
+
searchTerms: 6,
|
|
566
|
+
tagsPerEntry: 16,
|
|
567
|
+
titleChars: 128,
|
|
568
|
+
vocabChars: 64,
|
|
525
569
|
};
|
|
526
570
|
```
|
|
527
571
|
|
|
@@ -592,16 +636,35 @@ type ArchiveSort = 'liked' | 'recent';
|
|
|
592
636
|
type ArchiveDerivationStage = 'canonicalize' | 'hash' | 'index' | 'schema';
|
|
593
637
|
type ArchiveIdentity = { id: string; name: string; operator: boolean } | null;
|
|
594
638
|
type ArchiveAccount = {
|
|
595
|
-
id: string;
|
|
639
|
+
id: string;
|
|
640
|
+
email: string;
|
|
641
|
+
createdAt: string;
|
|
642
|
+
lastSignInAt: string | null;
|
|
596
643
|
};
|
|
597
644
|
type ArchiveCollectionMeta = { filters: string[]; name: string; tags: string[] };
|
|
598
645
|
type ArchiveLikesPage = { cursor: string | null; ids: string[] };
|
|
599
646
|
type ArchiveErrorCode =
|
|
600
|
-
| 'account-changed'
|
|
601
|
-
| '
|
|
602
|
-
| '
|
|
603
|
-
| '
|
|
604
|
-
| '
|
|
647
|
+
| 'account-changed'
|
|
648
|
+
| 'auth-unavailable'
|
|
649
|
+
| 'bad-request'
|
|
650
|
+
| 'conflict'
|
|
651
|
+
| 'forbidden'
|
|
652
|
+
| 'gone'
|
|
653
|
+
| 'internal'
|
|
654
|
+
| 'invalid-payload'
|
|
655
|
+
| 'likes-disabled'
|
|
656
|
+
| 'method-not-allowed'
|
|
657
|
+
| 'not-found'
|
|
658
|
+
| 'publisher-changed'
|
|
659
|
+
| 'publishing-disabled'
|
|
660
|
+
| 'quota'
|
|
661
|
+
| 'rate-limited'
|
|
662
|
+
| 'record-unavailable'
|
|
663
|
+
| 'timeout'
|
|
664
|
+
| 'too-large'
|
|
665
|
+
| 'unauthenticated'
|
|
666
|
+
| 'unavailable'
|
|
667
|
+
| 'unsupported-media';
|
|
605
668
|
type ArchiveErrorResponse = {
|
|
606
669
|
error: { code: ArchiveErrorCode; message: string; requestId: string };
|
|
607
670
|
};
|
|
@@ -609,11 +672,22 @@ type ArchiveHealth = {
|
|
|
609
672
|
dbSchemaVersion: number | null;
|
|
610
673
|
features: { likes: boolean; publishing: boolean };
|
|
611
674
|
limits: {
|
|
612
|
-
aliasChars: number;
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
675
|
+
aliasChars: number;
|
|
676
|
+
cursorChars: number;
|
|
677
|
+
idempotencyRetentionMs: number;
|
|
678
|
+
likeStateIds: number;
|
|
679
|
+
maxPageSize: number;
|
|
680
|
+
maxPublications: number;
|
|
681
|
+
pageSize: number;
|
|
682
|
+
payloadBytes: number;
|
|
683
|
+
publicCacheSeconds: number;
|
|
684
|
+
reportReasonChars: number;
|
|
685
|
+
requestBytes: number;
|
|
686
|
+
searchChars: number;
|
|
687
|
+
searchTerms: number;
|
|
688
|
+
tagsPerEntry: number;
|
|
689
|
+
titleChars: number;
|
|
690
|
+
vocabChars: number;
|
|
617
691
|
};
|
|
618
692
|
schemaVersion: 1;
|
|
619
693
|
status: 'ok' | 'degraded';
|
|
@@ -628,23 +702,36 @@ type AuthHealth = {
|
|
|
628
702
|
type ArchiveReports = {
|
|
629
703
|
cursor: string | null;
|
|
630
704
|
reports: {
|
|
631
|
-
id: string;
|
|
632
|
-
|
|
705
|
+
id: string;
|
|
706
|
+
createdAt: string;
|
|
707
|
+
entryId: string;
|
|
708
|
+
entryTitle: string;
|
|
709
|
+
reason: string;
|
|
710
|
+
reporter: { id: string; name: string };
|
|
711
|
+
resolvedAt: string | null;
|
|
633
712
|
}[];
|
|
634
713
|
};
|
|
635
714
|
type ArchiveOwnReports = {
|
|
636
715
|
cursor: string | null;
|
|
637
716
|
reports: {
|
|
638
|
-
collection: string;
|
|
639
|
-
|
|
717
|
+
collection: string;
|
|
718
|
+
createdAt: string;
|
|
719
|
+
entryId: string;
|
|
720
|
+
id: string;
|
|
721
|
+
reason: string;
|
|
722
|
+
resolvedAt: string | null;
|
|
640
723
|
}[];
|
|
641
724
|
};
|
|
642
725
|
type ArchiveModerationAction = 'hide' | 'unhide' | 'remove' | 'erase-publisher';
|
|
643
726
|
type ArchiveModerationLog = {
|
|
644
727
|
cursor: string | null;
|
|
645
728
|
actions: {
|
|
646
|
-
id: string;
|
|
647
|
-
|
|
729
|
+
id: string;
|
|
730
|
+
action: ArchiveModerationAction;
|
|
731
|
+
collection: string | null;
|
|
732
|
+
createdAt: string;
|
|
733
|
+
operatorId: string;
|
|
734
|
+
targetId: string;
|
|
648
735
|
}[];
|
|
649
736
|
};
|
|
650
737
|
```
|
|
@@ -743,7 +830,7 @@ interface AuthWorkerHandler {
|
|
|
743
830
|
tap(handler: (event: AuthEvent) => void, options?: { readonly signal?: AbortSignal }): () => void;
|
|
744
831
|
}
|
|
745
832
|
type AuthDiagnostic = {
|
|
746
|
-
readonly kind: 'configuration' | 'database' | 'email' | 'catalog' | 'internal';
|
|
833
|
+
readonly kind: 'configuration' | 'database' | 'schema' | 'email' | 'catalog' | 'internal';
|
|
747
834
|
};
|
|
748
835
|
type AuthEvent =
|
|
749
836
|
| {
|
|
@@ -773,10 +860,10 @@ window resets atomically on a later sign-in request.
|
|
|
773
860
|
|
|
774
861
|
## Errors
|
|
775
862
|
|
|
776
|
-
| Exported class
|
|
777
|
-
|
|
|
778
|
-
| `ArchiveError`
|
|
779
|
-
| `ArchiveDefinitionError` | Invalid collection definition or duplicate registered collection names
|
|
863
|
+
| Exported class | Trigger | Properties |
|
|
864
|
+
| ------------------------ | ------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------- |
|
|
865
|
+
| `ArchiveError` | Base for public package errors | `name`, `message`, optional `cause` |
|
|
866
|
+
| `ArchiveDefinitionError` | Invalid collection definition or duplicate registered collection names | Extends `ArchiveError` |
|
|
780
867
|
| `ArchiveDerivationError` | Unexpected schema/canonicalization/index/hash fault or violated collection invariant | Extends `ArchiveError`; readonly `stage: ArchiveDerivationStage`; optional `cause` |
|
|
781
868
|
|
|
782
869
|
`ArchiveError` and `ArchiveDefinitionError` accept `(message: string, opts?: ErrorOptions)`.
|
|
@@ -851,7 +938,8 @@ if (!meResponse.ok) throw new Error('Identity unavailable');
|
|
|
851
938
|
const me = archiveIdentitySchema.parse(await meResponse.json());
|
|
852
939
|
if (me === null) throw new Error('Register a publisher first');
|
|
853
940
|
const mineResponse = await fetch('/api/v1/me/publications?collection=recipes', {
|
|
854
|
-
credentials: 'same-origin',
|
|
941
|
+
credentials: 'same-origin',
|
|
942
|
+
headers: { [PUBLISHER_HEADER]: me.id },
|
|
855
943
|
});
|
|
856
944
|
if (!mineResponse.ok) throw new Error('Own publications unavailable');
|
|
857
945
|
const owned = recipesCollection.contract.ownedEntries.parse(await mineResponse.json());
|
|
@@ -930,15 +1018,15 @@ anonymous read budget or call the issuer.
|
|
|
930
1018
|
The Worker reads its environment fail-closed — every binding must be present and
|
|
931
1019
|
well-formed, or the route answers 503 without naming what is missing:
|
|
932
1020
|
|
|
933
|
-
| Variable
|
|
934
|
-
|
|
|
935
|
-
| `APP_ENV`
|
|
936
|
-
| `ALLOWED_ORIGINS`
|
|
937
|
-
| `JWT_ISSUER` / `JWT_AUDIENCE` / `JWT_JWKS_URL` | Exact issuer claim, audience and issuer JWKS endpoint URL
|
|
938
|
-
| `AUTH_ISSUER_SERVICE`
|
|
939
|
-
| `LIKES_ENABLED` / `PUBLISHING_ENABLED`
|
|
940
|
-
| `RATE_LIMIT_KEY_SECRET`
|
|
941
|
-
| `DB` / `READ_LIMIT` / `WRITE_LIMIT`
|
|
1021
|
+
| Variable | Meaning |
|
|
1022
|
+
| ---------------------------------------------- | ----------------------------------------------------------------------------------------- |
|
|
1023
|
+
| `APP_ENV` | `local` or `production` (transport rules and the pepper policy) |
|
|
1024
|
+
| `ALLOWED_ORIGINS` | Exact canonical origins; never wildcards |
|
|
1025
|
+
| `JWT_ISSUER` / `JWT_AUDIENCE` / `JWT_JWKS_URL` | Exact issuer claim, audience and issuer JWKS endpoint URL |
|
|
1026
|
+
| `AUTH_ISSUER_SERVICE` | Required issuer service binding for signing-key retrieval and live session-version checks |
|
|
1027
|
+
| `LIKES_ENABLED` / `PUBLISHING_ENABLED` | Feature flags |
|
|
1028
|
+
| `RATE_LIMIT_KEY_SECRET` | HMAC pepper. Required ≥ 32 chars in production; local may fall back |
|
|
1029
|
+
| `DB` / `READ_LIMIT` / `WRITE_LIMIT` | D1 + rate-limit bindings |
|
|
942
1030
|
|
|
943
1031
|
Both limiter bindings must run 60-second windows (`simple` limiters:
|
|
944
1032
|
`period: 60`): the platform binding does not expose its period at runtime, so the
|
|
@@ -969,19 +1057,19 @@ sign-in time and claims the token. The issuer's D1 schema ships at
|
|
|
969
1057
|
`migrations_dir` there. The catalog's `JWT_ISSUER` and `JWT_JWKS_URL` must identify
|
|
970
1058
|
the issuer, and its audience must match the issuer's `AUDIENCE`.
|
|
971
1059
|
|
|
972
|
-
| Variable
|
|
973
|
-
|
|
|
974
|
-
| `APP_NAME`
|
|
975
|
-
| `ALLOWED_ORIGINS`
|
|
976
|
-
| `APP_ENV`
|
|
977
|
-
| `AUDIENCE` / `AUTH_ISSUER`
|
|
978
|
-
| `AUTH_DB` / `AUTH_EMAIL`
|
|
979
|
-
| `AUTH_ARCHIVE`
|
|
980
|
-
| `AUTH_JWT_PRIVATE_JWK`
|
|
981
|
-
| `AUTH_JWT_PREVIOUS_PUBLIC_JWKS` | Optional retired public RS256 JWKS; publish during rotation until old sessions expire
|
|
982
|
-
| `OPERATOR_EMAILS`
|
|
983
|
-
| `PUBLIC_ORIGIN`
|
|
984
|
-
| `SENDER_ADDRESS`
|
|
1060
|
+
| Variable | Meaning |
|
|
1061
|
+
| ------------------------------- | ----------------------------------------------------------------------------------------- |
|
|
1062
|
+
| `APP_NAME` | The app's display name in magic-link email and escaped confirmation-page branding |
|
|
1063
|
+
| `ALLOWED_ORIGINS` | Exact origins allowed to trigger unsafe routes (the app origin(s)) |
|
|
1064
|
+
| `APP_ENV` | `local` or `production` — production requires HTTPS and sets Secure cookies |
|
|
1065
|
+
| `AUDIENCE` / `AUTH_ISSUER` | The claims every minted session carries; the catalog verifies the same pair |
|
|
1066
|
+
| `AUTH_DB` / `AUTH_EMAIL` | The issuer's D1 and send-email bindings |
|
|
1067
|
+
| `AUTH_ARCHIVE` | Required service binding to the catalog Worker; deletion calls its profile-erasure route |
|
|
1068
|
+
| `AUTH_JWT_PRIVATE_JWK` | The RS256 private key as a JWK JSON string: a wrangler secret in production |
|
|
1069
|
+
| `AUTH_JWT_PREVIOUS_PUBLIC_JWKS` | Optional retired public RS256 JWKS; publish during rotation until old sessions expire |
|
|
1070
|
+
| `OPERATOR_EMAILS` | Comma-separated emails whose newly minted sessions carry the operator role |
|
|
1071
|
+
| `PUBLIC_ORIGIN` | App origin for links, confirmation POST origin checks and client navigation after sign-in |
|
|
1072
|
+
| `SENDER_ADDRESS` | The address magic links come from: a domain onboarded to Cloudflare Email Service |
|
|
985
1073
|
|
|
986
1074
|
Under `wrangler dev` the send-email binding is simulated — the link prints to the
|
|
987
1075
|
issuer's console, so that console is the inbox. Demo-only Alice/Bob routes are
|
|
@@ -1010,10 +1098,38 @@ Opening `GET /auth/verify?token=…` renders a no-store confirmation page and do
|
|
|
1010
1098
|
not consume the link or change the session, so email scanners cannot sign in by
|
|
1011
1099
|
fetching it. A deliberate click sends `POST /auth/verify` with JSON `{ token }`,
|
|
1012
1100
|
the request marker and an exact allowed origin or `PUBLIC_ORIGIN`.
|
|
1013
|
-
Success is 204 with Set-Cookie; the page then navigates to
|
|
1014
|
-
|
|
1101
|
+
Success is 204 with Set-Cookie; the page then navigates to the fixed
|
|
1102
|
+
`${PUBLIC_ORIGIN}/signed-in` destination. Your application must serve that page;
|
|
1103
|
+
the issuer only serves the confirmation endpoint. The redemption update and
|
|
1104
|
+
account sign-in stamp are trigger-backed and atomic.
|
|
1015
1105
|
The page removes the token from the address bar, loads no external assets, and
|
|
1016
|
-
sets a restrictive CSP and `Referrer-Policy: no-referrer`.
|
|
1106
|
+
sets a restrictive CSP and `Referrer-Policy: no-referrer`. Its inline CSS and
|
|
1107
|
+
script use a fresh nonce, never `unsafe-inline`. The page follows light/dark
|
|
1108
|
+
preferences, supports reduced motion, focuses the confirmation action and
|
|
1109
|
+
announces pending, success and recovery states. Without JavaScript it links to
|
|
1110
|
+
`${PUBLIC_ORIGIN}/account` as “Request a new sign-in link” and does not pretend
|
|
1111
|
+
to complete sign-in.
|
|
1112
|
+
|
|
1113
|
+
Archive only serves the confirmation endpoint. Your application must serve its
|
|
1114
|
+
sign-in entry at the fixed `${PUBLIC_ORIGIN}/account` recovery destination and
|
|
1115
|
+
the success page at `${PUBLIC_ORIGIN}/signed-in`. These are reference issuer
|
|
1116
|
+
route conventions, not configurable destinations. The initial “Back to app”
|
|
1117
|
+
exit links to `${PUBLIC_ORIGIN}/`. No query parameter supplies a destination.
|
|
1118
|
+
Expired or already-used links (POST 410) require a new link rather than a retry.
|
|
1119
|
+
HTTP 429 enables manual retry after a `Retry-After` cooldown bounded to 1-300
|
|
1120
|
+
seconds, accepting unsigned integer seconds or valid HTTP-date syntax and
|
|
1121
|
+
defaulting to 30 seconds for missing or invalid values, including negative or
|
|
1122
|
+
fractional numbers. HTTP 503 and network failures offer a retry button and a
|
|
1123
|
+
secondary “Request a new sign-in link” action; invalid or expired links make
|
|
1124
|
+
that recovery action primary. The page never
|
|
1125
|
+
automatically retries, aborts stalled requests after 15 seconds and releases
|
|
1126
|
+
requests/timers when you leave it. Response bodies, including stale responses,
|
|
1127
|
+
are cancelled without waiting, and completed attempts abort their controllers.
|
|
1128
|
+
|
|
1129
|
+
With valid issuer configuration, malformed GET links return HTTP 400 with branded
|
|
1130
|
+
recovery HTML rather than the JSON error envelope. POST errors retain the JSON
|
|
1131
|
+
error schema. A well-formed expired token still gets GET 200 confirmation: its
|
|
1132
|
+
redemption status is determined only by the deliberate POST.
|
|
1017
1133
|
|
|
1018
1134
|
`GET /auth/account` exports account metadata only. For full erasure, call
|
|
1019
1135
|
`DELETE /auth/account` with `ACCOUNT_HEADER` (`X-Archive-Account`) set to the
|
|
@@ -1069,9 +1185,9 @@ against the same file. Point wrangler at the directory:
|
|
|
1069
1185
|
{
|
|
1070
1186
|
"binding": "DB",
|
|
1071
1187
|
"database_name": "my-app",
|
|
1072
|
-
"migrations_dir": "./node_modules/@vielzeug/archive/migrations"
|
|
1073
|
-
}
|
|
1074
|
-
]
|
|
1188
|
+
"migrations_dir": "./node_modules/@vielzeug/archive/migrations",
|
|
1189
|
+
},
|
|
1190
|
+
],
|
|
1075
1191
|
}
|
|
1076
1192
|
```
|
|
1077
1193
|
|