/** * Builder-side client for the Personal Server Write API. * * @remarks * A builder holding a write-grant (a grant whose scope entries carry the * `write:` prefix, see {@link formatScopeEntry}) writes into a user's Personal * Server in two steps: * * 1. {@link openWriteSession}: `POST /v1/write/session` with a Web3Signed * handshake that carries the grant id as a signed claim. The Personal * Server verifies the builder key against the grant and mints a * short-lived bearer token bound to `{ builder, grantId }`. * 2. {@link writeData}: `POST /v1/data/:scope` with that bearer plus an * `X-Vana-Write-Signature` proof, a second Web3Signed signature over the * representation the Personal Server will store, again carrying the grant * id as a signed claim. The Personal Server stores the proof with the * record under the reserved `$writtenBy` key so anyone holding the record * can verify who wrote it. * * What the proof covers: a JSON write signs the request body, which must be * the compact `JSON.stringify` form (the server rejects anything else with * `WRITE_BODY_NOT_CANONICAL`); a binary write signs the `$binary` record the * server stores for the bytes and their representation headers * ({@link binaryWriteSignedBytes}), not the raw bytes. Every proof is * single-use: a retry after a lost response signs a fresh proof. * * Derivatives name their sources through `lineage` (see * {@link deriveDataPointId}): for a JSON write the ids are the top-level * `lineage` field of the body (inside the signed bytes); for a binary write * they are the `lineage` field of the `X-Vana-Metadata` JSON (inside the * signed `$binary` record). The server validates them and mirrors them under * the reserved `$lineage` key. Callers never send `$writtenBy` or `$lineage`. * * @category Protocol */ import { type Address, type Hex } from "viem"; import { type IngestResponse } from "./data-file.js"; import { type WriteTransportRetryOptions } from "./write-request.js"; import { type ResolveWriteSignerOptions, type WriteSigner, type WriteSignerSource } from "./write-signer.js"; export type { WriteTransportRetryOptions }; /** Path of the write-session handshake. */ export declare const WRITE_SESSION_PATH = "/v1/write/session"; /** Header carrying the builder's per-write payload proof. */ export declare const WRITE_SIGNATURE_HEADER = "X-Vana-Write-Signature"; /** Header carrying caller metadata (and `lineage`) for a binary write. */ export declare const WRITE_METADATA_HEADER = "X-Vana-Metadata"; /** Field the lineage source ids travel in (body for JSON, metadata for binary). */ export declare const LINEAGE_FIELD = "lineage"; /** The most sources one record may cite. */ export declare const MAX_LINEAGE_SOURCES = 256; /** Header carrying the filename of a binary write (printable ASCII names). */ export declare const WRITE_FILENAME_HEADER = "X-Filename"; /** * Header carrying a filename the `X-Filename` header cannot: the Personal * Server percent-decodes `filename*=UTF-8''...` (RFC 5987). */ export declare const WRITE_CONTENT_DISPOSITION_HEADER = "Content-Disposition"; /** Reserved record key the Personal Server stamps builder attribution into. */ export declare const WRITER_ATTRIBUTION_KEY = "$writtenBy"; /** Reserved record key the Personal Server stamps lineage sources into. */ export declare const LINEAGE_KEY = "$lineage"; /** Record keys a builder must never send. */ export declare const RESERVED_WRITE_KEYS: readonly string[]; /** An open write session: the bearer token plus what it was minted for. */ export interface WriteSession { /** Personal Server origin, without a trailing slash. */ personalServerUrl: string; /** Web3Signed audience every proof under this session is addressed to. */ audience: string; /** The write-grant the session is bound to. */ grantId: string; /** The bearer token (`vana_write_...`). */ accessToken: string; /** Unix milliseconds after which the token is no longer accepted. */ expiresAt: number; /** Write patterns (prefix stripped) the session may write into. */ writeScopes: readonly string[]; /** The builder key the session was opened with; signs every write proof. */ signer: WriteSigner; } export interface OpenWriteSessionParams extends ResolveWriteSignerOptions { /** Personal Server origin, e.g. `https://ps.example.com`. */ personalServerUrl: string; /** Builder key: a viem `LocalAccount`, `WalletClient`, or `{ signMessage }`. */ signer: WriteSignerSource; /** The write-grant issued to the builder. */ grantId: string; /** Web3Signed audience; defaults to `personalServerUrl`. */ audience?: string; /** `fetch` to use; defaults to `globalThis.fetch`. */ fetch?: typeof fetch; /** Extra request headers. */ headers?: HeadersInit; retry?: WriteTransportRetryOptions; } /** A lineage source: a data point id, or the pair it is derived from. */ export type LineageSource = Hex | { ownerAddress: Address; scope: string; }; /** Bytes to store as an unstructured (binary) record. */ export interface WriteBinaryPayload { bytes: Uint8Array; /** Media type, e.g. `application/pdf`. Parameters are dropped when stored. */ contentType: string; /** * Stored with the record. Sent as `X-Filename` when it is printable ASCII, * otherwise as `Content-Disposition: attachment; filename*=UTF-8''...`, * which the Personal Server decodes back to the same string. Must not have * leading or trailing whitespace (HTTP would strip it and the signed * representation would no longer match). */ filename?: string; } interface WriteDataBaseParams { session: WriteSession; /** * The scope to write into; must match one of the session's write patterns. * A derived scope must not share its first dot-segment with any source's * scope (the server rejects it with `LINEAGE_SCOPE_UNDER_SOURCE_PREFIX`): * put derivatives in the app's own namespace. */ scope: string; /** * The data points this record was derived from: ids * ({@link deriveDataPointId}) or `{ ownerAddress, scope }` pairs the SDK * derives the id from. Distinct, at most {@link MAX_LINEAGE_SOURCES}, all * belonging to the same owner as the target scope, never the record's own * id. Sent lowercased as the record's `lineage` field, inside the signed * bytes. `[]` is an explicit root statement and is sent; absent or `null` * makes no statement. Pairs also let the SDK apply the naming rule * ({@link assertDerivedScopeNaming}) before anything is signed. */ lineage?: readonly LineageSource[] | null; /** `fetch` to use; defaults to `globalThis.fetch`. */ fetch?: typeof fetch; /** Extra request headers. */ headers?: HeadersInit; retry?: WriteTransportRetryOptions; } /** * A JSON write: `data` is stored as the record (plus `lineage` when given). * Anything else to store goes inside `data`; `lineage` and reserved keys are * not accepted in it. */ export interface WriteJsonDataParams extends WriteDataBaseParams { data: Record; binary?: never; metadata?: never; } /** A binary write: the bytes are stored as a `$binary` record. */ export interface WriteBinaryDataParams extends WriteDataBaseParams { binary: WriteBinaryPayload; data?: never; /** * Caller metadata stored with the record (`X-Vana-Metadata`, part of the * signed `$binary` record). Must not contain `lineage` (use the option) or * a reserved key. */ metadata?: Record; } export type WriteDataParams = WriteJsonDataParams | WriteBinaryDataParams; /** The Personal Server's ingest answer, plus the accepted lineage if any. */ export type WriteDataResult = IngestResponse & { lineage?: { sources: Hex[]; }; }; /** * Open a write session against a Personal Server. * * @remarks * Sends `POST /v1/write/session` with a Web3Signed `Authorization` header * whose signed claims carry `grantId`. The handshake proof is single-use on * the server; a transport retry signs a new one. * * @returns The session to pass to {@link writeData}. * @throws {WriteSessionError} When the Personal Server refuses the handshake * (`errorCode` names why: `UNREGISTERED_BUILDER`, `GRANT_REQUIRED`, * `GRANT_REVOKED`, `SCOPE_MISMATCH` for a grant without write entries, * `INVALID_SIGNATURE` when the key is not the grantee, * `GRANT_OWNER_MISMATCH`, ...). * @throws {WriteTransportError} When `fetch` threw on every attempt. * @throws {WriteRequestError} When the signer is unusable. */ export declare function openWriteSession(params: OpenWriteSessionParams): Promise; /** `true` when one of the session's write patterns covers `scope`. */ export declare function sessionCoversScope(session: Pick, scope: string): boolean; /** * The media type the Personal Server stores for a binary write: the * `Content-Type` minus its parameters, `application/octet-stream` when blank. */ export declare function normalizeBinaryMimeType(contentType: string | null): string; /** * How the Personal Server reads an `X-Vana-Metadata` header: JSON when it * parses, the raw string otherwise, `undefined` when absent or blank. */ export declare function parseWriteMetadataHeader(value: string | null): unknown; /** * Encode a metadata object for the `X-Vana-Metadata` header. Non-ASCII * characters are `\uXXXX`-escaped so the value is header-safe everywhere; * it parses back to the same object. */ export declare function encodeWriteMetadataHeader(metadata: Record): string; export interface BinaryWriteSignedBytesInput { /** The raw body bytes the write sends. */ bytes: Uint8Array; /** The `Content-Type` header the write sends (parameters are ignored). */ contentType: string; /** The `X-Filename` header value the write sends, if any. */ filename?: string; /** The exact `X-Vana-Metadata` header value the write sends, if any. */ metadataHeader?: string; } /** * The bytes a builder signs for a binary write: the compact JSON of the * `$binary` record the Personal Server stores for these headers and bytes * (`personal-server-ts` `binaryWriteSignedBytes`, mirrored field for field). * * @returns UTF-8 bytes of the stored record's compact JSON. */ export declare function binaryWriteSignedBytes(input: BinaryWriteSignedBytesInput): Uint8Array; /** * Write one record into a scope under an open write session. * * @remarks * Sends `POST /v1/data/:scope` with `Authorization: Bearer ` * and `X-Vana-Write-Signature`, a Web3Signed proof over the stored * representation carrying the session's `grantId` as a signed claim. JSON * writes send `data` as compact JSON with `Content-Type: application/json`; * binary writes send the bytes with their `Content-Type`, `X-Filename`, and * sign {@link binaryWriteSignedBytes}. `lineage` is the record's top-level * `lineage` field for JSON and the `lineage` field of `X-Vana-Metadata` for * binary, so the proof covers it either way. * * @returns The ingest answer (`scope`, `collectedAt`, `status`, and * `lineage.sources` when the write carried lineage). * @throws {WriteRequestError} Before sending: no payload, a reserved key, a * malformed lineage id, or non-object data. * @throws {WriteSessionExpiredError} Before sending: the session token has * passed its lifetime. * @throws {WriteUnauthorizedError} 401 (proof or session rejected). * @throws {WriteForbiddenError} 403 (grant no longer authorises the write). * @throws {WriteConflictError} 409. * @throws {WriteLineageError} Any `LINEAGE_*` rejection: 422 * `LINEAGE_SOURCE_UNKNOWN` (`details.unknown`), 400 `LINEAGE_INVALID` / * `LINEAGE_SCOPE_UNDER_SOURCE_PREFIX`, 502 `LINEAGE_SOURCE_LOOKUP_FAILED`. * @throws {WriteRejectedError} Any other non-2xx. * @throws {WriteTransportError} `fetch` threw on every attempt. */ export declare function writeData(params: WriteDataParams): Promise; type DistributiveOmit = T extends unknown ? Omit : never; export type WritePersonalServerDataParams = Omit & DistributiveOmit; /** {@link writePersonalServerData}'s answer: the ingest result plus the session it opened. */ export interface WritePersonalServerDataResult extends WriteDataResult { /** Reuse for further writes until `expiresAt`. */ session: WriteSession; } /** * Open a write session and write one record in a single call. * * @remarks * Equivalent to {@link openWriteSession} followed by {@link writeData}. The * session is returned so further writes can reuse it; opening one per write * is correct but costs an extra signature and round-trip each time. * * @throws Everything {@link openWriteSession} and {@link writeData} throw. */ export declare function writePersonalServerData(params: WritePersonalServerDataParams): Promise;