~ knotless Search/
↑↓ move↵ open⇧↵ all results

chadtmiller.com/pds.js

An atproto PDS in JavaScript, for Cloudflare Workers and Node

0
0
main
626 lines · 28.8 KB Raw History
# @pdsjs/deno

## 2.5.2

### Patch Changes

- Updated dependencies
  - @pdsjs/core@2.5.2
  - @pdsjs/sites@2.1.9
  - @pdsjs/git@0.1.5
  - @pdsjs/lexicon-resolver@2.5.2

## 2.5.1

### Patch Changes

- Updated dependencies [542c4dc]
  - @pdsjs/core@2.5.1
  - @pdsjs/sites@2.1.8
  - @pdsjs/git@0.1.4
  - @pdsjs/lexicon-resolver@2.5.1

## 2.5.0

### Patch Changes

- d66fcff: The default blob upload limit is 50 MiB. An upload streams to storage, so
  the limit bounds cost rather than memory, and 50 MiB stays under the 100 MB
  request body a Cloudflare Worker accepts on every plan. `PDS_BLOB_UPLOAD_LIMIT`
  still overrides it.

  git-remote-atproto uploads a bundle in 8 MiB chunks. A server that refuses
  a chunk as too large gets the push again at half the size, down to 64 KiB,
  so a reference PDS at 5 MB still takes it. Existing bundles keep their parts
  until a repack rewrites them.

- f79dd82: Feature packages mount on the server as extensions. `PersonalDataServer`
  takes an `extensions` option: factories core calls with an `ExtensionHost`,
  which carries the storage ports, the space ports when spaces are on, and
  late-binding `getDid`, `xrpc`, `createCommit`, `getSigner`,
  `reapOrphanedBlobs`, `accountApi` and the server's composed `writeGuard`.
  An extension contributes routes, a handler for paths under prefixes it
  declares, a handler for hostnames other than the server's, a write guard,
  a commit tap, account feature flags and rail counts, and declares the record
  collections it owns: the storage on its host refuses a record call naming
  any other, a commit refuses an op naming any other, and a self-addressed
  `xrpc` call is anonymous whatever credentials the request carried. Core
  merges the pieces: a route core already serves is refused,
  a prefix core serves under or that overlaps another extension's is
  refused, route handlers are called unbound, write guards compose over the
  union of their collections and apply to space writes as well as public
  ones, and every commit tap runs in its own try/catch. The types are
  `ExtensionFactory` and `PdsExtension` in `@pdsjs/core/ports`.

  Every feature package is on the seam, each from its `/extension` export:
  `createGitExtension({ http, browse })`, `createDriveExtension({ upload })`,
  `createSitesExtension({ domain, apexName, queries })`,
  `createSpacesExtension()`, `createOciExtension()` and
  `createNpmExtension()`. Each serves its own `/account/api` routes through
  the host, so those routes, their helpers and their tests move out of core's
  account handler into the packages. The git and drive packages build the
  space admin methods they use over the host's storage from `@pdsjs/spaces`,
  now an optional peer of each. `@pdsjs/deno` mounts the git and sites
  extensions whenever those packages are installed, the same as
  `@pdsjs/node`, and the sites extension mounts with or without a domain so
  the installs listing is always served.

  For a deployment nothing changes: `createServer` on node and deno takes the
  same `spaces` and `experimental` options, the Worker reads the same
  `PDS_ENABLE_SPACES` and `PDS_EXPERIMENTAL_*` vars, and every endpoint and
  account API path answers as before. What changes is the constructor of
  `PersonalDataServer` itself, for a platform that embeds core directly: the
  feature-wiring options `spaceRoutes`, `spaceBrowser`, `spaceAdmin`,
  `siteHandler`, `siteBrowser`, `siteInstaller`, `queryRoutes`,
  `gitBrowser`, `driveBrowser`, `driveWriter`, `gitHttpEnabled`,
  `ociEnabled` and `npmEnabled` are gone, replaced by `extensions`;
  `spaceStorage` stays, and core builds the space browser over it. Those
  options were the seam between core and its own adapters rather than a
  supported embedding surface, which is why this ships as a minor.

- 411b47b: The git extension serves its reads and writes as XRPC methods under
  `dev.pdsjs.git.*`, each described by a lexicon: `listRepos`, `getRepo`,
  `getTree`, `listFiles`, `getFile`, `getRaw`, `listCommits`, `getCommit`,
  `getLastCommit`, `listChecks`, `getAuthors`, `createRepo`,
  `updateSettings` and `requestCheck`. A read of the bundle chain keeps the last four repositories
  opened and downloads a chain without object indexes whole only up to
  `eagerLimit`, answering `RepositoryTooLarge` past it. A read of
  the public repo is anonymous and answers under the record cid as its ETag; a
  read of a space repository and every write take the owner's account session,
  or for a write a bearer token with the `repo:` scopes of the collections it
  writes. `community.lexicon.service.describe` lists the methods a server
  serves. The `/account/api/git` routes and `/git/<account>/<repo>/last-commit`
  are gone; the methods replace them.

  An extension host gains `readAccountSession` and `readJsonBounded`, so a
  route that answers in its own shape can take the account session alongside a
  bearer token.

  The account application's repositories pages call these methods, with the
  raw file view served through `getRaw`.

- 9149663: A migrated account announces its identity until a relay hears it. An AppView
  takes an account's signing key from its own identity store, filled by the
  `#identity` event on the firehose, and the `forceRefresh` its token verifier
  asks for is not propagated. So the announcement `activateAccount` makes at the
  end of a migration reaches nobody when no relay has subscribed yet, and nothing
  retries it: every service token the account mints is then rejected against the
  key it used to have, and feeds, notifications and profiles fail with
  `jwt signature does not match jwt issuer`.

  The hourly maintenance tick now calls `maybeAnnounceIdentity`. It asks the
  configured relay whether that relay reads this server, and announces again
  while the answer is no, so the event lands once a subscription exists. It stops
  as soon as the relay reports it is reading. `activateAccount` also asks for the
  crawl before it announces, rather than after.

- 93816a1: A space governs reads and writes separately, tracking
  [atproto PR #5187](https://github.com/bluesky-social/atproto/pull/5187).
  `createSpace` and `updateSpace` take a `readPolicy` and a `writePolicy`,
  `getSpace` answers with both, and `com.atproto.simplespace.putMember` sets a
  member's `read` and `write` rights, which `listMembers` reports. The read
  policy decides who may hold a space credential. The write policy decides whose
  `notifyWrite` the authority records, so a writer the space does not admit stays
  out of the writer set and no syncer pulls their repo. The authority passes
  `access` to a managing app's `checkUserAccess`, and omits `clientId` on a write
  check: that notice comes from a PDS, not an app.

  The earlier spellings still answer. A single `policy` sets both sides,
  `addMember` adds a member who reads and writes, and `getSpace` sends the two
  unions, the single one, and the `#spaceConfig` object together. A space created
  before the split keeps its policy on both sides, and an existing member keeps
  both rights.

  A delegation token and a client attestation are each spent once. Both must
  carry a `jti`, and an authority that has honoured one refuses it again, so a
  captured credential exchange cannot be replayed.

  A client attestation is verified rather than read. The authority resolves the
  `iss` to its `client-metadata.json`, fetches the key set that document
  publishes, and checks the signature against the key its `kid` names. An
  `appAccess` allow list rests on that check, and until now stated a rule the
  authority could not enforce.

  `SpaceStoragePort` replaces `addMember` and `isMember` with `putMember` and
  `getMember`, `listMembers` answers with `members` rather than `dids`, and
  `SpaceRow` carries `readPolicy`, `readManagingApp`, `writePolicy` and
  `writeManagingApp`. Both SQLite adapters add the columns to a database they
  already hold. `@pdsjs/core/oauth` exports `fetchClientMetadata`,
  `resolveClientJwk` and `verifyEs256WithJwk`, which the OAuth token endpoint and
  the space authority both use.

- 3d475f8: Permissioned data tracks the October 1 spaces alpha of the reference
  implementation (`0.0.0-spaces-alpha-20261001173819`, atproto
  `permissioned-data-alpha` at `79d6307e1`).

  A space credential is bound to its holder's key with an RFC 9421 HTTP message
  signature instead of DPoP. The application signs the `getSpaceCredential`
  request's `Authorization` header with a fresh P-256 key, named as a did:key in
  `keyid`, and the authority binds the credential to it as `cnf.kid`. A request
  presenting the credential uses the `Atproto-Space` scheme and an
  `Atproto-Space-Audience` header naming the account it is addressed to, and
  signs both. A host refuses a signature made for another account
  (`BadSpaceAudience`) or by another key (`BadSpaceSignature`). DPoP credentials,
  the `dpopJkt` field and the `DPoP` presentation scheme are no longer accepted.
  `@pdsjs/spaces/http-signature` signs and verifies both shapes, and the git
  remote helper and the spaces inspector sign with it.

  A credential lives ten minutes by default and never more than an hour, and must
  carry a finite `iat`, an `exp` after it and a `jti`. A repo host serves
  `com.atproto.space.notifyCredentialRevoked`: a space's authority names up to 100
  credential ids, and the host refuses those credentials with
  `CredentialRevoked` for an hour and ten seconds.

  The authority sequences the writer set. Each accepted `notifyWrite` gets a
  `spaceRev` that increases across the space, and the notice forwarded to a
  syncer carries `repoRev`, `spaceRev` and `prevSpaceRev`. A repeated or older
  repo rev is not forwarded again, and a repo rev more than five minutes ahead is
  refused with `FutureRev`. `listRepos` entries carry `repoRev` and `spaceRev`,
  are ordered by `spaceRev`, and page on a space-revision cursor that every
  non-empty page ends on. A writer recorded before this release is sequenced at
  its repo rev. The authority accepts a notice addressed to
  `<authority>#atproto_space_host` as well as to its bare DID, and a body naming
  `rev` instead of `repoRev`.

  A repo host stores each write notice it owes an authority before sending it,
  and retries a transient failure with backoff for up to a day, keeping only the
  newest notice per space. Extensions may declare a `tick`, which core runs
  through `runExtensionTicks`: Node and Deno every ten seconds, Cloudflare at its
  alarm, which an extension brings forward with the host's new `scheduleWakeup`.

  `createSpace` and `listSpaces` take `spaceType`, and still read `type`.

  `SpaceStoragePort` changes: `putSpaceWriter` takes a `spaceRev`, writer rows
  carry `repoRev` and `spaceRev`, and the port gains `getSpaceWriter`,
  `getSpaceRevHead`, the revocation methods and the notice outbox methods. The
  schema adds `space_writer.space_rev`, `space_credential_revocation` and
  `space_notify_outbox`, migrated in place.

- Updated dependencies [a67a568]
- Updated dependencies [d66fcff]
- Updated dependencies [05a11b4]
- Updated dependencies [f79dd82]
- Updated dependencies [c68858b]
- Updated dependencies [04b15d0]
- Updated dependencies [288063a]
- Updated dependencies [c3c4caa]
- Updated dependencies [bcb5758]
- Updated dependencies [e0b38cd]
- Updated dependencies [4fb8f23]
- Updated dependencies [1cf113d]
- Updated dependencies [04b15d0]
- Updated dependencies [c4af198]
- Updated dependencies [f777a2c]
- Updated dependencies [a981627]
- Updated dependencies [3febe1c]
- Updated dependencies [ac5c8eb]
- Updated dependencies [00bd461]
- Updated dependencies [83083a5]
- Updated dependencies [2c68bdc]
- Updated dependencies [fe7ffe0]
- Updated dependencies [211c7c8]
- Updated dependencies [6cf84d5]
- Updated dependencies [35f794f]
- Updated dependencies [522071f]
- Updated dependencies [32db640]
- Updated dependencies [04b15d0]
- Updated dependencies [cc4577b]
- Updated dependencies [cc4577b]
- Updated dependencies [f5aae7c]
- Updated dependencies [cc4577b]
- Updated dependencies [fe3c9ac]
- Updated dependencies [b99acf8]
- Updated dependencies [c1ec3d6]
- Updated dependencies [04b15d0]
- Updated dependencies [7c06024]
- Updated dependencies [6935a55]
- Updated dependencies [15c69af]
- Updated dependencies [a56be28]
- Updated dependencies [673ec05]
- Updated dependencies [74819af]
- Updated dependencies [4dfdbfa]
- Updated dependencies [04b15d0]
- Updated dependencies [aeb61f9]
- Updated dependencies [04b15d0]
- Updated dependencies [04b15d0]
- Updated dependencies [f27dcf7]
- Updated dependencies [c9e3550]
- Updated dependencies [c01859b]
- Updated dependencies [41df38b]
- Updated dependencies [411b47b]
- Updated dependencies [ca47f96]
- Updated dependencies [861663f]
- Updated dependencies [617df4a]
- Updated dependencies [154e7d7]
- Updated dependencies [bda4794]
- Updated dependencies [10c91f6]
- Updated dependencies [0e1bf66]
- Updated dependencies [77eb9db]
- Updated dependencies [76f5187]
- Updated dependencies [b51250e]
- Updated dependencies [799286b]
- Updated dependencies [4adc49e]
- Updated dependencies [e83838c]
- Updated dependencies [9149663]
- Updated dependencies [16ea1fe]
- Updated dependencies [fd401a4]
- Updated dependencies [abae790]
- Updated dependencies [93816a1]
- Updated dependencies [3d475f8]
- Updated dependencies [078c2df]
- Updated dependencies [ec85961]
- Updated dependencies [d5f111c]
  - @pdsjs/sites@2.1.7
  - @pdsjs/core@2.5.0
  - @pdsjs/git@0.1.3
  - @pdsjs/lexicon-resolver@2.5.0

## 2.4.3

### Patch Changes

- Updated dependencies [d307f57]
- Updated dependencies [2d0d653]
  - @pdsjs/core@2.4.3
  - @pdsjs/sites@2.1.6
  - @pdsjs/lexicon-resolver@2.4.3

## 2.4.2

### Patch Changes

- Updated dependencies [c235e96]
  - @pdsjs/core@2.4.2
  - @pdsjs/sites@2.1.5
  - @pdsjs/lexicon-resolver@2.4.2

## 2.4.1

### Patch Changes

- Updated dependencies [fa1558c]
  - @pdsjs/core@2.4.1
  - @pdsjs/sites@2.1.4
  - @pdsjs/lexicon-resolver@2.4.1

## 2.4.0

### Patch Changes

- 6b79149: Video-ready blob hosting. Blob uploads and downloads stream: `uploadBlob` hashes and stages bytes chunk by chunk instead of buffering the body, and `getBlob` answers byte ranges from a streamed read, so a 300 MB video fits through a Durable Object and seeks in a browser. The blob port gains optional `createWriter` and `getStream`; the fs, R2, S3 and Deno stores implement both, and each platform injects its native SHA-256. Drive stores a file as one blob of its own type when the store streams, takes large files across several requests through upload sessions, and offers a Link dialog with the blob ref any record can embed. The Worker serves public blobs through the edge cache, and a site's `/.blobs/` route serves ranges and now applies the same space-privacy filter as `getBlob`. A new `videos.mjs` example app is a gallery over the drive's videos. The node server accepts a `blobUploadLimit` option (`PDS_BLOB_UPLOAD_LIMIT` in the example), which Bluesky video posting needs.
- Updated dependencies [ee6d696]
- Updated dependencies [54bec84]
- Updated dependencies [f716d9d]
- Updated dependencies [4c334b3]
- Updated dependencies [6b79149]
- Updated dependencies [8616855]
- Updated dependencies [af8b1fd]
  - @pdsjs/core@2.4.0
  - @pdsjs/sites@2.1.3
  - @pdsjs/lexicon-resolver@2.4.0

## 2.3.0

### Patch Changes

- d8cae21: Hold resolved DID documents, so a space request stops reading the directory.

  Every space credential names its issuer, and verifying one reads that issuer's
  key. plc.directory sends no `Cache-Control`, so nothing below the resolver held
  the answer and every space request paid a round trip to it.
  `createCachedDidResolver` holds an answer for an hour, an unresolvable DID for a
  minute, and serves what it holds for up to a day when the directory stops
  answering. The platform packages build one at startup and share it between the
  PDS and the space routes.

  A held document can name a key its issuer has rotated away from. Verification
  now reads past the held answer and tries once more when a signature fails,
  which is how `getSigningKey` gained its `forceRefresh` argument. The service
  token that authorizes `com.atproto.server.createAccount` always reads past it:
  adopting a DID is worth a fresh read, and it happens once per account.

- Updated dependencies [232a280]
- Updated dependencies [c541c98]
- Updated dependencies [f8e3dff]
- Updated dependencies [bdad8c4]
- Updated dependencies [51d6402]
- Updated dependencies [d8cae21]
- Updated dependencies [d8cae21]
- Updated dependencies [24c9e1e]
- Updated dependencies [035450d]
- Updated dependencies [3920c52]
- Updated dependencies [585139b]
- Updated dependencies [89b33a7]
- Updated dependencies [5125583]
  - @pdsjs/core@2.3.0
  - @pdsjs/lexicon-resolver@2.3.0
  - @pdsjs/sites@2.1.2

## 2.2.0

### Minor Changes

- b368c19: New package: host git repositories natively in an atproto account. Ships a
  `dev.pdsjs.git.repo` lexicon and a `git-remote-atproto` remote helper that
  stores repository history as chunked git bundles in PDS blob storage, with
  compare-and-swap ref updates through putRecord. Clone, fetch, push, force
  push, tags, deletions, and automatic bundle-chain repacking all work against
  any stock PDS with no server changes.

  The package also ships a read-only git smart HTTP handler so stock git can
  clone these repositories without the helper. It advertises refs from the
  record and serves fetches by merging the bundle chain's packfiles without
  inflating objects, so it runs fine on Cloudflare Workers. Opt in with
  `experimental: { git: { http: true } }` in the Node adapter or
  `PDS_EXPERIMENTAL_GIT_HTTP = "true"` in the Worker's vars; clone URLs look
  like `https://<pds-host>/git/<handle-or-did>/<repo>`.

  Server adapters gain an `experimental` option group, exempt from semver:
  anything in it may change shape in any release. It holds one entry per
  feature experiment, `git: { http, browse }` and
  `sites: { domain, apexName, queries }` (the Deno adapter takes the same
  `sites` group), replacing the flat `siteDomain`, `siteApexName`, and
  `enableQueries` props. Experimental env vars carry the same signal as a
  prefix: `PDS_EXPERIMENTAL_GIT_HTTP`, `PDS_EXPERIMENTAL_GIT_BROWSE`,
  `PDS_EXPERIMENTAL_SITE_DOMAIN`, `PDS_EXPERIMENTAL_SITE_APEX_NAME`, and
  `PDS_EXPERIMENTAL_QUERIES`. The git package itself versions independently
  at 0.x until it stabilizes.

  The account page grows a Repositories section under the Beta heading, next
  to Sites: each hosted repository with its branches, tags, size, and last
  push, linking to a repository page with copyable clone commands, a ref
  picker, and a browsable file tree with text previews. Browsing is served by
  a pure-JS pack object reader (no git binary server-side) exposed through
  `/account/api/git/repos`, `/git/tree`, and `/git/file` endpoints, with
  clone URLs including the plain-git HTTP one when the platform serves it.

- 72965f8: Installable apps: a repo-hosted site, published once, installed anywhere.

  A developer publishes a `dev.pdsjs.app.manifest` record naming their app
  and pinning its site record by CID, with an icon, the query definitions the
  app reads, declared settings ({key, label, type, default, options}, data
  and never code), a suggested subdomain, and an `external` flag for apps
  that talk beyond the user's own PDS. The pinned record key is an artifact
  address with no naming meaning.

  Installing copies the app into the user's own repo: every blob is fetched
  from the developer's PDS and hashed locally against the CID the record
  names, the site record copies verbatim so its CID stays the one the
  manifest pinned, query defs and a `dev.pdsjs.app.install` record land with
  it in one commit. The install record snapshots the name, icon, settings
  schema, and chosen values, so the account page needs no cross-server read
  and the app survives its developer's repo disappearing. A drifted pin
  refuses to install; a manifest requesting scopes refuses until grants
  exist.

  Two ways in: `pdsjs-site install <at-uri>` over stock XRPC, or the account
  page's install sheet, which previews the manifest before anything happens.
  Installed apps list with their icon and name, carry a generated settings
  form, move to a new subdomain without touching their content, and show an
  update action when the developer publishes a new manifest version; updates
  keep the user's settings and subdomain. Apps discover their host from
  their own origin at load, so one published file serves every installer's
  data.

- 098af68: Materialized queries: a `dev.pdsjs.query.def` record names a live view over
  one collection of the repo, with equality filters and one sort field, and
  `GET /xrpc/dev.pdsjs.query.run` returns the whole view as JSON in a single
  request. Reading a thousand-record collection through `listRecords` costs a
  serial cursor walk; through a query it is one round trip. The engine keeps a
  decoded copy of each queried collection in memory, replayed from storage on
  first use and maintained from every commit, so results reflect a write
  immediately. Off unless `PDS_EXPERIMENTAL_QUERIES` (the `experimental.sites.queries` option on
  Node and Deno) is set.

  Behind it, `@pdsjs/core` gains the app platform's live-data seams, both
  inert until a platform package fills them: an optional `onCommit` callback
  called after each successful commit (an error there is logged and never
  fails the write), and a `queryRoutes` table composed like `spaceRoutes`.

  Site hostnames also now answer `/.blobs/<cid>` with that blob from the repo,
  immutable and edge-cacheable, sandboxed by CSP, media mimes only. A
  client-rendered site can query its data live and show repo media without a
  manifest entry per file.

- 224cb48: Scheduled backups, with a Backups page in the account UI.

  A backup is a snapshot in a new `BackupTargetPort` — a directory
  (`createFsBackupTarget`), an S3-compatible bucket (`createS3BackupTarget`), or
  on Cloudflare an R2 bucket bound as `BACKUPS`. Each snapshot holds the full
  signed repo CAR (the same bytes `com.atproto.sync.getRepo` serves) and a
  manifest, written last so an interrupted snapshot never counts; blobs are
  stored content-addressed at `blobs/<cid>` and shared across snapshots, so a
  run only uploads what the target doesn't already hold. Retention keeps the
  newest N snapshots and reaps blobs no kept manifest references.

  Scheduling rides the maintenance tick each runtime already had: the hourly
  interval on Node and Deno, and the Durable Object alarm on Cloudflare, which
  now fires hourly to match. Settings and run history live in the existing
  `metadata` table as one JSON document, read through new optional
  `getBackupState`/`setBackupState` methods on `ActorStoragePort`.

  The signing key stays out of snapshots unless opted in: entering a passphrase
  on the Backups page seals a copy under PBKDF2 → AES-256-GCM (`key.enc` in each
  snapshot), and the page warns when the key has rotated since it was sealed.
  The page also offers one-click manual runs, run history, schedule and
  retention settings, and a direct `.car` download that works even with no
  target configured.

- 5a39d42: Sign with secp256k1 account keys, the curve the reference PDS gives every
  account.

  Signing was P-256 only. That is spec-legal, but services that verify service-auth
  JWTs on secp256k1 alone reject it — a Go feed generator answers a bare `400`, so
  `app.bsky.feed.sendInteractions` silently never lands and a feed keeps serving
  posts the reader has already seen. WebCrypto cannot sign secp256k1, so the
  implementation is injected as a `Secp256k1SignerPort`, mirroring the verifier
  port that already existed on the verification side: core stays free of the
  dependency and the platform packages supply `@noble/curves`.

  A stored key now describes its own curve, so an existing bare 32-byte key still
  reads as P-256 and every P-256 deployment is unaffected. Where a signer is
  injected, new accounts are minted on secp256k1, and the JWT `alg` (`ES256K`),
  JWKS entry, `did:key` multicodec, published verification method, and DPoP
  self-verification all follow the key's curve rather than assuming one.

  Rotating an existing account's key is worth planning: services that have cached
  the DID document keep verifying against the old key until they re-resolve it,
  so expect proxied calls to fail until an `#identity` event reaches them.

- e628859: Static sites served from the repo, in a new `@pdsjs/sites` package.

  A site's files are ordinary blobs and a site is one `dev.pdsjs.site.deploy`
  record holding the path→blob manifest, so a deploy is a stock atproto write:
  it rides the firehose, survives migration, and lands in backups with
  everything else. `pdsjs-site deploy ./dist --site home` uploads only the files
  whose CID changed and writes the record.

  Serving is by hostname. The record key is a DNS label, so `blog` serves at
  `blog.<site domain>` and the site named by `PDS_EXPERIMENTAL_SITE_APEX_NAME` (default
  `home`) serves at the apex. Responses carry an ETag derived from the file's
  CID, so conditional requests answer 304, and a missing path falls back to
  `index.html` for single-page apps.

  Caching is per file: `pdsjs-site deploy --cache-control "[glob:]value"` sets
  the Cache-Control each matched path serves with, and content addressing makes
  `immutable` safe for fingerprinted assets. On Cloudflare, site responses are
  also held in the edge cache for their `max-age`, so repeat requests are
  answered at the visitor's edge location instead of crossing to the Durable
  Object's region. The PDS's own hostname never enters that cache.

  The feature is off until a site domain is configured: `PDS_EXPERIMENTAL_SITE_DOMAIN` on the
  Worker, `experimental: { sites: { domain } }` on the Node and Deno `createServer`. Without it no
  hostname is inspected and dispatch is untouched. `@pdsjs/core` gains two
  optional ports, `SiteHandlerPort` and `SiteBrowserPort`, and never imports the
  sites package itself; a platform package builds both and passes them in. On
  Node and Deno `@pdsjs/sites` is an optional peer dependency, so a deployment
  that does not want sites does not install it.

  Site content is only ever served on the site domain, never on the PDS's own
  hostname. That keeps repo-controlled HTML on a separate origin from the
  account session cookie.

  Every servable site hostname also answers `/.blobs/<cid>` with that blob from
  the repo, immutable and sandboxed, so a client-rendered site can show repo
  media without a manifest entry per file. `www` with no site named `www`
  deployed redirects to the apex.

  The account interface grows a Sites page listing what is deployed, with a
  delete that unlinks the record's blobs and reaps the orphans. It appears only
  when the server composes the feature in, and it warns when the PDS's own
  hostname shadows the apex site.

### Patch Changes

- Updated dependencies [261d4f4]
- Updated dependencies [dfce920]
- Updated dependencies [a4cae1f]
- Updated dependencies [dfcf1cc]
- Updated dependencies [99c414f]
- Updated dependencies [261d4f4]
- Updated dependencies [218f25c]
- Updated dependencies [1b8cd1e]
- Updated dependencies [f2df586]
- Updated dependencies [e761cdb]
- Updated dependencies [d679bf1]
- Updated dependencies [fd33b13]
- Updated dependencies [d88b21d]
- Updated dependencies [8ce9621]
- Updated dependencies [faceaf7]
- Updated dependencies [bc5314a]
- Updated dependencies [f800d5a]
- Updated dependencies [eeb7976]
- Updated dependencies [b368c19]
- Updated dependencies [44bd2b5]
- Updated dependencies [6e47bc6]
- Updated dependencies [5a39d42]
- Updated dependencies [72965f8]
- Updated dependencies [09e110d]
- Updated dependencies [098af68]
- Updated dependencies [7898894]
- Updated dependencies [378e2bb]
- Updated dependencies [4363389]
- Updated dependencies [011e28d]
- Updated dependencies [4169189]
- Updated dependencies [ee386d7]
- Updated dependencies [8cbae75]
- Updated dependencies [4363389]
- Updated dependencies [4a9943d]
- Updated dependencies [9abf6d6]
- Updated dependencies [9524139]
- Updated dependencies [62dc25a]
- Updated dependencies [8d62485]
- Updated dependencies [f1c6639]
- Updated dependencies [224cb48]
- Updated dependencies [5a39d42]
- Updated dependencies [fd54c7d]
- Updated dependencies [67387fe]
- Updated dependencies [91e52aa]
- Updated dependencies [1852faf]
- Updated dependencies [9ceb6ad]
- Updated dependencies [e628859]
  - @pdsjs/core@2.2.0
  - @pdsjs/sites@2.1.1
  - @pdsjs/lexicon-resolver@2.2.0

## 2.1.0

### Patch Changes

- Updated dependencies [8ac38c1]
- Updated dependencies [f5335a0]
  - @pdsjs/core@2.1.0
  - @pdsjs/lexicon-resolver@2.1.0

## 2.0.0

### Patch Changes

- Updated dependencies [62c6521]
- Updated dependencies [13e3049]
  - @pdsjs/core@2.0.0
  - @pdsjs/lexicon-resolver@2.0.0

## 1.0.2

### Patch Changes

- Updated dependencies
  - @pdsjs/core@1.0.2
  - @pdsjs/lexicon-resolver@1.0.2

## 1.0.1

### Patch Changes

- Updated dependencies [de2ceed]
  - @pdsjs/core@1.0.1
  - @pdsjs/lexicon-resolver@1.0.1

## 1.0.0

### Patch Changes

- Updated dependencies [3b7895c]
  - @pdsjs/core@1.0.0
  - @pdsjs/lexicon-resolver@1.0.0