/*! * Copyright (c) 2026 Interop Alliance. All rights reserved. */ /** * `createWasSyncPort`: the {@link WasSyncPort} implementation over a signed * {@link WasClient}, bound to one Space + Collection. * * Writes and single-resource reads ride the raw, signed `was.request()` escape * hatch, which moves the stored body VERBATIM (bypassing the encryption codec). * The change feed already ships opaque stored bodies -- plaintext for a * plaintext collection, the EDV envelope for an encrypted one -- and push must * write those same bytes back unchanged; running them through `resource.put()` * would re-encrypt an already-encrypted envelope. Encrypt/decrypt therefore * stays a read/write-time concern above the port, and the port itself is * collection-agnostic and never touches keys. * * The pull path rides the client's `Collection.changes()` feed, bound to the * same Space + Collection, which produces the byte-identical signed * `POST /space/:s/:c/query` (profile `changes`) as a root invocation and, like * the raw writes, ships the stored bodies verbatim without decrypting. * * Conditional writes ride the server's monotonic content `version` (`ETag`), * enforced uniformly for plaintext and encrypted resources, so there is no * plaintext-vs-encrypted fork. `putContent`/`deleteContent` return the server- * acked `version` (parsed from the write's `ETag`, re-read only if the backend * sent none), so a caller can record acked revisions immediately. */ import type { WasClient } from '../WasClient.js'; import { KEY_EPOCH_HEADER } from '../internal/conditional.js'; import type { IZcap } from '../types.js'; import type { WasSyncPort } from './types.js'; /** * The request header the server reads a content write's key-epoch id from, * stamping it onto the Resource's metadata. Defined next to the header * assembly it drives (`internal/conditional.ts`) and re-exported here as part * of the sync subpath's public surface. */ export { KEY_EPOCH_HEADER }; /** * Formats a numeric content `version` as the quoted strong `ETag` an * update-if-unchanged write passes as its `ifMatch` precondition (e.g. `3` to * `"3"`). Inverse of {@link parseEtag}. * * @param version {number} * @returns {string} */ export declare function formatEtag(version: number): string; /** * Parses a quoted strong `ETag` (`"3"`) into its numeric revision, or * `undefined` when the header is absent or non-numeric (no such revision yet). * * @param etag {string | null} * @returns {number | undefined} */ export declare function parseEtag(etag: string | null): number | undefined; /** * Builds a {@link WasSyncPort} bound to one Space + Collection, backed by the * caller's signed {@link WasClient}. With no `capability`, requests invoke the * client's own root capability. * * `mapAuthErrors` exists because a WAS server MASKS an authorization failure as * `404` ("not found or invalid authorization") rather than `403`, so an * unauthorized caller cannot probe which resources exist. A replica that * already synced its Space and Collection knows they exist, so on its sync * paths a `404` can only mean the invocation itself was rejected -- the * expired- or revoked-grant signal it needs in order to stop retrying and * prompt for a reconnect. The reading is only safe with that knowledge, so it * is opt-in: off (the default), every status behaves exactly as before. * * Two paths keep their own `404` semantics even when it is on, because there a * `404` is a modeled outcome rather than an anomaly: `deleteContent` resolves * (the tombstone's goal state already holds -- an idempotent delete), and `get` * resolves `null` (absent or tombstoned -- the deletion-wins input its callers * depend on). Revoked access still surfaces within one poll on `query` and on * the content/metadata writes. * * @param options {object} * @param options.was {WasClient} the session client (holds the signer) * @param options.spaceId {string} the WAS Space id * @param options.collectionId {string} the WAS collection id * @param [options.capability] {IZcap} a delegated capability to invoke on * every request this port makes (pull, writes, and reads alike); omit to * invoke the client's own root capability * @param [options.mapAuthErrors] {boolean} map `401` / `403` / the masked * `404` to {@link WasSyncAuthError} (default `false`) * @returns {WasSyncPort} */ export declare function createWasSyncPort({ was, spaceId, collectionId, capability, mapAuthErrors }: { was: WasClient; spaceId: string; collectionId: string; capability?: IZcap; mapAuthErrors?: boolean; }): WasSyncPort; //# sourceMappingURL=port.d.ts.map