Peios Learn
Products
PePeios pkpekit PvProvium UDUniversal Directory TrTrail PrProject WiWispist
Using Peios Security Basics Technical Documentation Source
Using Peios Security Basics Technical Documentation Source
Wispist

Reference

Single-page view · as markdown

wispist.json reference

Wispist / Reference

wispist.json is an optional file at the site archive root. It declares the collections exposed to browser code by that release.

The parser is strict:

  • The file must be UTF-8 JSON no larger than 64 KiB.
  • Duplicate object members and unknown fields are errors.
  • version must be 1.
  • collections is required and may contain at most 32 entries.
  • Collection limits may lower, but never raise, host defaults.
  • Missing wispist.json means zero declared collections.

An invalid declaration rejects the complete upload. It never falls back to a permissive policy.

Top-level object #

MemberTypeRequiredMeaning
versionintegeryesProtocol declaration version; exactly 1.
collectionsobjectyesCollection name to collection declaration.

Collection names #

Names match:

[a-z][a-z0-9_-]{0,47}

They are ASCII, case-sensitive, and compared byte-for-byte.

Collection declaration #

MemberTypeRequiredMeaning
accessstring or objectyesA built-in profile or all six operation policies.
limitsobjectnoLower document count and byte limits.

Shared profile #

{
  "access": "shared"
}

shared expands to anyone for list, read, create, update, delete, and subscribe. Every visitor can read and change the collection.

Expanded access #

{
  "access": {
    "list": "anyone",
    "read": "anyone",
    "create": "nobody",
    "update": "nobody",
    "delete": "nobody",
    "subscribe": "anyone"
  }
}

Every operation must appear exactly once:

OperationControls
listListing collection documents.
readReading one document and receiving its body in a subscription.
createGenerated-ID POST and selected-ID create.
updateReplacing an existing document.
deleteDeleting an existing document.
subscribeOpening a change stream for the collection.

Each value is one of:

ValueMeaning
anyonePermit every principal.
authenticatedRequire an authenticated principal asserted by the host.
nobodyDeny the operation.

Wispdeck initially supplies anonymous principals to ordinary hosted-site and preview JavaScript. Preview authorisation grants access to the preview, not authenticated Wispist authority.

read and list are intentionally independent, as are read and subscribe.

Collection limits #

{
  "limits": {
    "maxDocuments": 250,
    "maxDocumentBytes": 4096
  }
}
MemberTypeRangeDefault
maxDocumentsinteger1 to host maximum1,000
maxDocumentBytesinteger2 to host maximum32 KiB

These limits apply to that collection. The namespace also has a total live or draft byte limit.

See also #

  • Limits and abuse controls — the installation defaults these declarations may lower.
  • Documents and collections — what a declared collection contains.
  • Forbidden — what a denied operation returns.

Browser API reference

Wispist / Reference

Wispdeck defines one non-enumerable, non-writable global before site-authored elements run:

wispist.version
wispist.mode
wispist.readOnly
wispist.collection(name)

There is no connect method. The client makes no request during evaluation.

Environment fields #

FieldTypeMeaning
versionnumberClient protocol major; 1.
modestringlive, draft, or live-preview.
readOnlybooleanWhether the host has forced mutations off.

mode and readOnly are synchronous presentation metadata. The server enforces the real binding independently.

Collection handle #

const items = wispist.collection("before-you-go");

collection(name) validates the collection-name grammar locally and returns a frozen handle. The server still requires the active release to declare it.

list(options?) #

Returns a promise for every document in creation order. options.limit selects the page size from 1 to 250; the client follows pagination automatically.

const documents = await items.list({ limit: 100 });

get(id) #

Returns one complete document envelope or rejects with not_found.

const document = await items.get("passport");

add(data) #

Creates a generated-ID document and returns it. data must be a non-null object and not an array. The client generates the required idempotency key.

const document = await items.add({ text: "Passport", done: false });

create(id, data) #

Creates exactly the selected ID and returns it. It uses create-only semantics and rejects with revision_conflict if that ID exists.

replace(document, data) #

Conditionally replaces the observed document with data and returns the new envelope. A concurrent change rejects with revision_conflict.

update(document, changes) #

Shallow-merges changes into document.data locally, then performs replace.

const updated = await items.update(document, { done: true });

An undefined change is omitted by JSON serialisation but does not delete the old property from the local merge. To remove properties or make nested changes, construct the complete new object and call replace.

delete(document) #

Conditionally deletes the observed document. The promise resolves with no value.

Subscribe #

const unsubscribe = items.subscribe((documents, event) => {
  render(documents);
}, {
  limit: 250,
  onError(error) {
    showError(error);
  },
});

The callback runs once with event.type === "initial" and after each applied change. Documents remain in creation order, with ID as the deterministic tie-breaker.

The client reconnects after recoverable EventSource failures with bounded exponential backoff and jitter. A server reset causes a fresh list. onError receives initial or terminal errors; ordinary reconnects do not create unhandled promise rejections.

subscribe returns the unsubscribe function immediately.

WispistError #

Rejected operations use an Error subclass with:

FieldMeaning
nameAlways WispistError.
typeRFC 9457 problem type URI, when an HTTP problem was returned.
codeErgonomic code mapped from a known problem type.
titleStable problem title.
detailSafe occurrence-specific explanation.
statusHTTP status, when available.
instanceProblem occurrence URI, when available.
requestIdX-Request-ID value, when available.
problemComplete decoded Problem Details object.

The wire protocol does not contain a separate code. type is the stable machine identifier; code is client convenience. Network failures use network_error without a type or HTTP status. An invalid non-problem response uses invalid_response.

Known codes are listed in Problem Details.

Browser dependencies and side effects #

The client uses fetch, EventSource, and Web Crypto. It has no package dependency, local-storage use, service worker, analytics, or telemetry. Requests are same-origin, use same-origin credentials, and disable browser caching.

See also #

  • HTTP API reference — the routes these methods call.
  • Problem Details — every code a WispistError can carry.
  • Live updates — how subscribe behaves across disconnects and resets.

HTTP API reference

Wispist / Reference

Wispist v1 is served from the hosted site's own origin under /_wispist/:

GET    /_wispist/client/v1.js
GET    /_wispist/v1
GET    /_wispist/v1/collections/{collection}/documents
POST   /_wispist/v1/collections/{collection}/documents
GET    /_wispist/v1/collections/{collection}/documents/{document}
PUT    /_wispist/v1/collections/{collection}/documents/{document}
DELETE /_wispist/v1/collections/{collection}/documents/{document}
GET    /_wispist/v1/changes

Use the browser client unless direct HTTP control is necessary.

General rules #

  • API responses use Cache-Control: no-store and X-Content-Type-Options: nosniff.
  • JSON responses use application/json; charset=utf-8.
  • JSON request bodies require Content-Type: application/json.
  • Every response includes X-Request-ID. A caller-provided UUID may be retained.
  • Errors use RFC 9457 application/problem+json.
  • Unsupported methods on known resources return 405 with Allow.
  • Paths and identifiers must be decoded, canonical, and free of encoded separators or dot segments.
  • No CORS permission is emitted.

Unsafe requests require one exact same-origin Origin header. If Sec-Fetch-Site is present, it must be same-origin.

Service description #

GET /_wispist/v1
{
  "name": "Wispist",
  "version": 1,
  "mode": "live",
  "readOnly": false,
  "collections": ["before-you-go"]
}

List documents #

GET /_wispist/v1/collections/before-you-go/documents?limit=100&after=opaque

limit defaults to 100 and may be 1 to 250. after is an opaque pagination cursor.

{
  "documents": [
    {
      "id": "passport",
      "revision": "opaque",
      "createdAt": "2026-07-13T12:34:56.123Z",
      "updatedAt": "2026-07-13T12:34:56.123Z",
      "data": { "text": "Pack passport", "done": false }
    }
  ],
  "after": null,
  "changes": "opaque-change-cursor"
}

The changes cursor is captured by the first page and repeated by later pages. List all pages, then consume changes after it to reconcile concurrent mutations.

Create a generated ID #

POST /_wispist/v1/collections/before-you-go/documents
Origin: https://site.example
Idempotency-Key: a-strong-random-value
Content-Type: application/json

{"data":{"text":"Buy insurance","done":false}}

The key is required and contains 16 to 128 visible ASCII characters. The same namespace, key, and request fingerprint returns the original 201 Created response for at least 24 hours. Reusing the key for a different request returns idempotency conflict.

The response includes Location, the document ETag, and the complete envelope.

Read #

GET /_wispist/v1/collections/before-you-go/documents/passport

The ETag is the quoted opaque revision. If-None-Match supports wildcard, lists, and weak comparison for GET/HEAD and may return 304 Not Modified.

Create or replace a selected ID #

Create only:

PUT /_wispist/v1/collections/before-you-go/documents/passport
Origin: https://site.example
If-None-Match: *
Content-Type: application/json

{"data":{"text":"Pack passport","done":false}}

Replace:

PUT /_wispist/v1/collections/before-you-go/documents/passport
Origin: https://site.example
If-Match: "observed-revision"
Content-Type: application/json

{"data":{"text":"Pack passport","done":true}}

Exactly one precondition is required. Create returns 201; replacement returns 200. Both return a new document and ETag.

Delete #

DELETE /_wispist/v1/collections/before-you-go/documents/passport
Origin: https://site.example
If-Match: "observed-revision"

Success is 204 No Content. The deletion appends a change containing ID and deleted revision but no document body.

Change stream #

GET /_wispist/v1/changes?collections=before-you-go&after=opaque
Accept: text/event-stream

collections is repeatable with 1 to 8 distinct declared values. Every collection must permit subscribe and read.

id: opaque-change-cursor
event: change
data: {"collection":"before-you-go","operation":"update","document":{...}}

A deletion supplies id and revision instead of document. A reset event means the client must list again. If after is absent, the stream begins after the current high-water mark and does not replay the collection.

The server sends heartbeat comments while otherwise idle. Delivery is at least once; de-duplicate by cursor.

See also #

  • Browser API reference — the supported client over these routes.
  • Problem Details — the error shape every route uses.
  • Limits and abuse controls — body, pagination, stream, and rate bounds.

Limits and abuse controls

Wispist / Reference

Wispist applies hard resource limits and bounded rate buckets before expensive work. A release may lower collection limits but cannot raise installation policy.

Resource defaults #

ResourceDefault
Collections per release32
Documents per collection1,000
Document JSON32 KiB
Live document data per site10 MiB
Draft document data per site5 MiB
List page100; maximum 250
Collections per SSE stream8
SSE streams per site and client6
SSE streams per site100
Pending events per stream128
Request envelope allowancedocument limit plus 4 KiB
Live idempotency records per namespace10,000
Retained changes per namespace10,000
Change age7 days
Idempotency retentionat least 24 hours

The document-data quota counts compact document JSON, not release files. Change and idempotency tables are separately bounded.

Initial Wispdeck rate buckets #

BucketSustainedBurst
Reads per site and client600/minute100
Mutations per site and client60/minute20
Mutations across one site300/minute60
Generated-ID creates across one site10,000/day100
All Wispist requests6,000/minute1,000

Client keys come from Wispdeck's trusted-proxy-aware address resolution. Untrusted forwarding headers are ignored.

Buckets use bounded memory and expire after inactivity. Concurrent streams use counters rather than token rates. A rejected request returns rate limited with Retry-After.

Rate limits are availability controls, not authorisation. A public shared collection remains publicly writable below the rate allowance.

See also #

  • wispist.json reference — how a release lowers collection limits.
  • Quota exceeded, Request too large, Rate limited — the problems these bounds produce.

Peios Learn — documentation for the Peios project.

Built with Trail.