@opendatalabs/vana-sdk 3.18.1 → 3.19.0

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.
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/protocol/personal-server-write.ts"],"sourcesContent":["/**\n * Builder-side client for the Personal Server Write API.\n *\n * @remarks\n * A builder holding a write-grant (a grant whose scope entries carry the\n * `write:` prefix, see {@link formatScopeEntry}) writes into a user's Personal\n * Server in two steps:\n *\n * 1. {@link openWriteSession}: `POST /v1/write/session` with a Web3Signed\n * handshake that carries the grant id as a signed claim. The Personal\n * Server verifies the builder key against the grant and mints a\n * short-lived bearer token bound to `{ builder, grantId }`.\n * 2. {@link writeData}: `POST /v1/data/:scope` with that bearer plus an\n * `X-Vana-Write-Signature` proof, a second Web3Signed signature over the\n * representation the Personal Server will store, again carrying the grant\n * id as a signed claim. The Personal Server stores the proof with the\n * record under the reserved `$writtenBy` key so anyone holding the record\n * can verify who wrote it.\n *\n * What the proof covers: a JSON write signs the request body, which must be\n * the compact `JSON.stringify` form (the server rejects anything else with\n * `WRITE_BODY_NOT_CANONICAL`); a binary write signs the `$binary` record the\n * server stores for the bytes and their representation headers\n * ({@link binaryWriteSignedBytes}), not the raw bytes. Every proof is\n * single-use: a retry after a lost response signs a fresh proof.\n *\n * Derivatives name their sources through `lineage` (see\n * {@link deriveDataPointId}): for a JSON write the ids are the top-level\n * `lineage` field of the body (inside the signed bytes); for a binary write\n * they are the `lineage` field of the `X-Vana-Metadata` JSON (inside the\n * signed `$binary` record). The server validates them and mirrors them under\n * the reserved `$lineage` key. Callers never send `$writtenBy` or `$lineage`.\n *\n * @category Protocol\n */\n\nimport { sha256 } from \"@noble/hashes/sha2\";\nimport { bytesToHex, isAddress, type Address, type Hex } from \"viem\";\nimport { z } from \"zod\";\nimport { buildWeb3SignedHeader } from \"../auth/web3-signed-builder\";\nimport {\n type PersonalServerWriteError,\n WriteConflictError,\n WriteForbiddenError,\n WriteLineageError,\n WriteRejectedError,\n WriteRequestError,\n WriteSessionError,\n WriteSessionExpiredError,\n WriteTransportError,\n WriteUnauthorizedError,\n} from \"../errors\";\nimport { toBase64 } from \"../utils/encoding\";\nimport { IngestResponseSchema, type IngestResponse } from \"./data-file\";\nimport {\n assertDerivedScopeNaming,\n deriveDataPointId,\n isDataPointId,\n} from \"./lineage\";\nimport {\n isRecord,\n readPersonalServerErrorBody,\n} from \"./personal-server-error-body\";\nimport { scopeMatchesPattern } from \"./scopes\";\nimport {\n resolveWriteSigner,\n type ResolveWriteSignerOptions,\n type WriteSigner,\n type WriteSignerSource,\n} from \"./write-signer\";\n\n/** Path of the write-session handshake. */\nexport const WRITE_SESSION_PATH = \"/v1/write/session\";\n/** Header carrying the builder's per-write payload proof. */\nexport const WRITE_SIGNATURE_HEADER = \"X-Vana-Write-Signature\";\n/** Header carrying caller metadata (and `lineage`) for a binary write. */\nexport const WRITE_METADATA_HEADER = \"X-Vana-Metadata\";\n/** Field the lineage source ids travel in (body for JSON, metadata for binary). */\nexport const LINEAGE_FIELD = \"lineage\";\n/** The most sources one record may cite. */\nexport const MAX_LINEAGE_SOURCES = 256;\n/** Header carrying the filename of a binary write (printable ASCII names). */\nexport const WRITE_FILENAME_HEADER = \"X-Filename\";\n/**\n * Header carrying a filename the `X-Filename` header cannot: the Personal\n * Server percent-decodes `filename*=UTF-8''...` (RFC 5987).\n */\nexport const WRITE_CONTENT_DISPOSITION_HEADER = \"Content-Disposition\";\n/** Reserved record key the Personal Server stamps builder attribution into. */\nexport const WRITER_ATTRIBUTION_KEY = \"$writtenBy\";\n/** Reserved record key the Personal Server stamps lineage sources into. */\nexport const LINEAGE_KEY = \"$lineage\";\n/** Record keys a builder must never send. */\nexport const RESERVED_WRITE_KEYS: readonly string[] = [\n WRITER_ATTRIBUTION_KEY,\n LINEAGE_KEY,\n];\n\n/**\n * Transport-level retry knobs shared by {@link openWriteSession} and\n * {@link writeData}.\n *\n * @remarks\n * Applies only when `fetch` **throws** (connection reset, DNS, a relay drop).\n * Every attempt signs a fresh proof, because the Personal Server consumes a\n * proof the moment it accepts it. A received HTTP response is never retried:\n * a 4xx/5xx is surfaced as a typed error.\n */\nexport interface WriteTransportRetryOptions {\n /** Total attempts including the first (default 3). `1` disables retries. */\n attempts?: number;\n /** Delay before the first retry (ms); doubles per retry (default 1_000). */\n initialDelayMs?: number;\n}\n\n/** An open write session: the bearer token plus what it was minted for. */\nexport interface WriteSession {\n /** Personal Server origin, without a trailing slash. */\n personalServerUrl: string;\n /** Web3Signed audience every proof under this session is addressed to. */\n audience: string;\n /** The write-grant the session is bound to. */\n grantId: string;\n /** The bearer token (`vana_write_...`). */\n accessToken: string;\n /** Unix milliseconds after which the token is no longer accepted. */\n expiresAt: number;\n /** Write patterns (prefix stripped) the session may write into. */\n writeScopes: readonly string[];\n /** The builder key the session was opened with; signs every write proof. */\n signer: WriteSigner;\n}\n\nexport interface OpenWriteSessionParams extends ResolveWriteSignerOptions {\n /** Personal Server origin, e.g. `https://ps.example.com`. */\n personalServerUrl: string;\n /** Builder key: a viem `LocalAccount`, `WalletClient`, or `{ signMessage }`. */\n signer: WriteSignerSource;\n /** The write-grant issued to the builder. */\n grantId: string;\n /** Web3Signed audience; defaults to `personalServerUrl`. */\n audience?: string;\n /** `fetch` to use; defaults to `globalThis.fetch`. */\n fetch?: typeof fetch;\n /** Extra request headers. */\n headers?: HeadersInit;\n retry?: WriteTransportRetryOptions;\n}\n\n/** A lineage source: a data point id, or the pair it is derived from. */\nexport type LineageSource = Hex | { ownerAddress: Address; scope: string };\n\n/** Bytes to store as an unstructured (binary) record. */\nexport interface WriteBinaryPayload {\n bytes: Uint8Array;\n /** Media type, e.g. `application/pdf`. Parameters are dropped when stored. */\n contentType: string;\n /**\n * Stored with the record. Sent as `X-Filename` when it is printable ASCII,\n * otherwise as `Content-Disposition: attachment; filename*=UTF-8''...`,\n * which the Personal Server decodes back to the same string. Must not have\n * leading or trailing whitespace (HTTP would strip it and the signed\n * representation would no longer match).\n */\n filename?: string;\n}\n\ninterface WriteDataBaseParams {\n session: WriteSession;\n /**\n * The scope to write into; must match one of the session's write patterns.\n * A derived scope must not share its first dot-segment with any source's\n * scope (the server rejects it with `LINEAGE_SCOPE_UNDER_SOURCE_PREFIX`):\n * put derivatives in the app's own namespace.\n */\n scope: string;\n /**\n * The data points this record was derived from: ids\n * ({@link deriveDataPointId}) or `{ ownerAddress, scope }` pairs the SDK\n * derives the id from. Distinct, at most {@link MAX_LINEAGE_SOURCES}, all\n * belonging to the same owner as the target scope, never the record's own\n * id. Sent lowercased as the record's `lineage` field, inside the signed\n * bytes. `[]` is an explicit root statement and is sent; absent or `null`\n * makes no statement. Pairs also let the SDK apply the naming rule\n * ({@link assertDerivedScopeNaming}) before anything is signed.\n */\n lineage?: readonly LineageSource[] | null;\n /** `fetch` to use; defaults to `globalThis.fetch`. */\n fetch?: typeof fetch;\n /** Extra request headers. */\n headers?: HeadersInit;\n retry?: WriteTransportRetryOptions;\n}\n\n/**\n * A JSON write: `data` is stored as the record (plus `lineage` when given).\n * Anything else to store goes inside `data`; `lineage` and reserved keys are\n * not accepted in it.\n */\nexport interface WriteJsonDataParams extends WriteDataBaseParams {\n data: Record<string, unknown>;\n binary?: never;\n metadata?: never;\n}\n\n/** A binary write: the bytes are stored as a `$binary` record. */\nexport interface WriteBinaryDataParams extends WriteDataBaseParams {\n binary: WriteBinaryPayload;\n data?: never;\n /**\n * Caller metadata stored with the record (`X-Vana-Metadata`, part of the\n * signed `$binary` record). Must not contain `lineage` (use the option) or\n * a reserved key.\n */\n metadata?: Record<string, unknown>;\n}\n\nexport type WriteDataParams = WriteJsonDataParams | WriteBinaryDataParams;\n\nconst WriteDataResultSchema = IngestResponseSchema.extend({\n // Present when the write carried lineage: the validated, lowercased ids.\n lineage: z.object({ sources: z.array(z.string()) }).optional(),\n});\n\n/** The Personal Server's ingest answer, plus the accepted lineage if any. */\nexport type WriteDataResult = IngestResponse & {\n lineage?: { sources: Hex[] };\n};\n\nconst WriteSessionResponseSchema = z.object({\n access_token: z.string().min(1),\n token_type: z.string(),\n expires_in: z.number().nonnegative(),\n scope: z.string(),\n});\n\nfunction normalizeBaseUrl(url: string): string {\n return url.replace(/\\/+$/, \"\");\n}\n\nfunction resolveFetch(fetchFn: typeof fetch | undefined): typeof fetch {\n const resolved = fetchFn ?? globalThis.fetch;\n if (resolved === undefined) {\n throw new WriteRequestError(\"No fetch implementation available\");\n }\n return resolved;\n}\n\nfunction dataPath(scope: string): string {\n return `/v1/data/${encodeURIComponent(scope)}`;\n}\n\nfunction errorMessage(err: unknown): string {\n return err instanceof Error ? err.message : String(err);\n}\n\nfunction finiteOr(value: number | undefined, fallback: number): number {\n return typeof value === \"number\" && Number.isFinite(value) ? value : fallback;\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\n/**\n * The Personal Server consumes every proof it accepts, and a Web3Signed\n * payload is fully determined by `{ aud, method, uri, bodyHash, grantId, iat,\n * exp }`, so two proofs for the same request signed within one second would\n * be byte-identical and the second rejected as a replay. Remember the\n * highest `iat` issued per request identity in this process and bump past\n * it when a second proof for the same identity falls inside the same second.\n *\n * A mark is kept for as long as the server can still remember the proof it\n * guards (its lifetime plus the verifier's clock skew), so a proof is never\n * re-issued while it could still be rejected as a replay: not by a burst,\n * and not by a wall clock stepping backwards (an identical request after a\n * step back waits for the clock instead of reusing the mark). Marks are\n * bucketed by their `iat` second, so pruning (once per second) only touches\n * the buckets that fell out of the retention window: the work per proof is\n * amortised constant and the map is bounded by the distinct requests signed\n * in the window.\n */\nconst issuedProofIats = new Map<string, number>();\n/** `iat` second -> identities whose current mark is that second. */\nconst issuedProofBuckets = new Map<number, Set<string>>();\nlet issuedProofIatsPrunedAtSec = 0;\n/** `buildWeb3SignedHeader`'s default `exp - iat`. */\nconst WEB3_SIGNED_PROOF_LIFETIME_SECONDS = 300;\n/** The verifier's tolerated clock skew (`verifyWeb3Signed`). */\nconst WEB3_SIGNED_CLOCK_SKEW_SECONDS = 60;\nconst PROOF_IAT_RETENTION_SECONDS =\n WEB3_SIGNED_PROOF_LIFETIME_SECONDS + WEB3_SIGNED_CLOCK_SKEW_SECONDS;\n/**\n * How far ahead of the clock a bumped `iat` may run when the proof is sent.\n * The verifier tolerates 60 s of skew. A burst of identical requests that\n * would need to run further ahead waits for the clock instead, so a proof is\n * never repeated; sustained identical requests are throttled to one per\n * second after a burst of this many.\n */\nconst PROOF_IAT_MAX_AHEAD_SECONDS = 30;\n\nfunction pruneIssuedProofIats(nowSec: number): void {\n if (issuedProofIatsPrunedAtSec === nowSec) return;\n issuedProofIatsPrunedAtSec = nowSec;\n const cutoff = nowSec - PROOF_IAT_RETENTION_SECONDS;\n // There is at most one bucket per second in the window, so this walk is\n // bounded by the window length, not by the number of marks.\n for (const [sec, keys] of issuedProofBuckets) {\n if (sec >= cutoff) continue;\n for (const key of keys) issuedProofIats.delete(key);\n issuedProofBuckets.delete(sec);\n }\n}\n\nfunction setIssuedProofIat(key: string, iat: number, previous?: number): void {\n if (previous !== undefined) {\n const bucket = issuedProofBuckets.get(previous);\n bucket?.delete(key);\n if (bucket?.size === 0) issuedProofBuckets.delete(previous);\n }\n issuedProofIats.set(key, iat);\n let bucket = issuedProofBuckets.get(iat);\n if (bucket === undefined) {\n bucket = new Set();\n issuedProofBuckets.set(iat, bucket);\n }\n bucket.add(key);\n}\n\n/**\n * Reserve the next `iat` for a request identity. The reservation is made\n * synchronously so concurrent callers never share a value; the returned\n * promise only waits when the reserved `iat` is further ahead of the clock\n * than {@link PROOF_IAT_MAX_AHEAD_SECONDS}.\n */\nfunction nextProofIat(proofKey: string): Promise<number> {\n const nowSec = Math.floor(Date.now() / 1000);\n pruneIssuedProofIats(nowSec);\n const last = issuedProofIats.get(proofKey);\n const iat = last === undefined ? nowSec : Math.max(nowSec, last + 1);\n setIssuedProofIat(proofKey, iat, last);\n const waitSec = iat - nowSec - PROOF_IAT_MAX_AHEAD_SECONDS;\n if (waitSec <= 0) return Promise.resolve(iat);\n return sleep(waitSec * 1000).then(() => iat);\n}\n\nfunction proofKeyFor(parts: {\n aud: string;\n method: string;\n uri: string;\n grantId: string;\n signedBytes?: Uint8Array;\n}): string {\n // A digest, so a retained mark costs a fixed amount of memory whatever the\n // request looked like.\n return bytesToHex(\n sha256(\n new TextEncoder().encode(\n JSON.stringify([\n parts.aud,\n parts.method,\n parts.uri,\n parts.grantId,\n parts.signedBytes ? bytesToHex(sha256(parts.signedBytes)) : \"\",\n ]),\n ),\n ),\n );\n}\n\n/**\n * Send a request, re-signing it on every attempt. Only a thrown `fetch` is\n * retried; the proof builder and any received response are never retried.\n */\nasync function sendWithFreshProof(\n label: string,\n fetchFn: typeof fetch,\n options: WriteTransportRetryOptions | undefined,\n proofKey: string,\n build: (iat: number) => Promise<{ url: string; init: RequestInit }>,\n): Promise<Response> {\n const attempts = Math.max(1, Math.floor(finiteOr(options?.attempts, 3)));\n let delayMs = Math.max(0, finiteOr(options?.initialDelayMs, 1_000));\n let lastError: unknown;\n for (let attempt = 0; attempt < attempts; attempt++) {\n const { url, init } = await build(await nextProofIat(proofKey));\n try {\n return await fetchFn(url, init);\n } catch (err) {\n lastError = err;\n }\n if (attempt < attempts - 1) {\n await sleep(delayMs);\n delayMs *= 2;\n }\n }\n throw new WriteTransportError(\n `${label} failed after ${attempts} attempt(s): ${errorMessage(lastError)}`,\n attempts,\n lastError,\n );\n}\n\n/**\n * Open a write session against a Personal Server.\n *\n * @remarks\n * Sends `POST /v1/write/session` with a Web3Signed `Authorization` header\n * whose signed claims carry `grantId`. The handshake proof is single-use on\n * the server; a transport retry signs a new one.\n *\n * @returns The session to pass to {@link writeData}.\n * @throws {WriteSessionError} When the Personal Server refuses the handshake\n * (`errorCode` names why: `UNREGISTERED_BUILDER`, `GRANT_REQUIRED`,\n * `GRANT_REVOKED`, `SCOPE_MISMATCH` for a grant without write entries,\n * `INVALID_SIGNATURE` when the key is not the grantee,\n * `GRANT_OWNER_MISMATCH`, ...).\n * @throws {WriteTransportError} When `fetch` threw on every attempt.\n * @throws {WriteRequestError} When the signer is unusable.\n */\nexport async function openWriteSession(\n params: OpenWriteSessionParams,\n): Promise<WriteSession> {\n const fetchFn = resolveFetch(params.fetch);\n const personalServerUrl = normalizeBaseUrl(params.personalServerUrl);\n const audience = params.audience ?? personalServerUrl;\n const signer = resolveWriteSigner(params.signer, { account: params.account });\n\n const response = await sendWithFreshProof(\n \"Write session handshake\",\n fetchFn,\n params.retry,\n proofKeyFor({\n aud: audience,\n method: \"POST\",\n uri: WRITE_SESSION_PATH,\n grantId: params.grantId,\n }),\n async (iat) => {\n const headers = new Headers(params.headers);\n headers.set(\n \"Authorization\",\n await buildWeb3SignedHeader({\n signMessage: signer.signMessage,\n aud: audience,\n method: \"POST\",\n uri: WRITE_SESSION_PATH,\n grantId: params.grantId,\n iat,\n }),\n );\n return {\n url: `${personalServerUrl}${WRITE_SESSION_PATH}`,\n init: { method: \"POST\", headers },\n };\n },\n );\n // Read the clock before parsing so `expiresAt` never overstates the\n // remaining lifetime.\n const mintedAt = Date.now();\n\n if (!response.ok) {\n const { errorCode, message, details } =\n await readPersonalServerErrorBody(response);\n throw new WriteSessionError(\n message ??\n `Write session handshake failed: ${response.status} ${response.statusText}`,\n response.status,\n errorCode,\n details,\n );\n }\n\n let body: unknown;\n try {\n body = await response.json();\n } catch (err) {\n throw new WriteSessionError(\n \"Write session response is not JSON\",\n response.status,\n null,\n { cause: errorMessage(err) },\n );\n }\n const parsed = WriteSessionResponseSchema.safeParse(body);\n if (!parsed.success) {\n throw new WriteSessionError(\n \"Write session response is not a session\",\n response.status,\n null,\n { issues: parsed.error.issues },\n );\n }\n if (parsed.data.token_type.toLowerCase() !== \"bearer\") {\n throw new WriteSessionError(\n `Write session token type is not Bearer: ${parsed.data.token_type}`,\n response.status,\n );\n }\n\n return {\n personalServerUrl,\n audience,\n grantId: params.grantId,\n accessToken: parsed.data.access_token,\n expiresAt: mintedAt + parsed.data.expires_in * 1000,\n writeScopes: parsed.data.scope.split(\" \").filter((s) => s.length > 0),\n signer,\n };\n}\n\n/** Every field {@link writeData} relies on, checked before any is used. */\nfunction isWriteSession(value: unknown): value is WriteSession {\n if (!isRecord(value)) return false;\n const signer: unknown = value.signer;\n return (\n typeof value.personalServerUrl === \"string\" &&\n typeof value.audience === \"string\" &&\n typeof value.grantId === \"string\" &&\n typeof value.accessToken === \"string\" &&\n value.accessToken.length > 0 &&\n typeof value.expiresAt === \"number\" &&\n Number.isFinite(value.expiresAt) &&\n Array.isArray(value.writeScopes) &&\n value.writeScopes.every((scope) => typeof scope === \"string\") &&\n isRecord(signer) &&\n typeof signer.signMessage === \"function\"\n );\n}\n\n/** `true` when one of the session's write patterns covers `scope`. */\nexport function sessionCoversScope(\n session: Pick<WriteSession, \"writeScopes\">,\n scope: string,\n): boolean {\n if (\n !isRecord(session) ||\n !Array.isArray(session.writeScopes) ||\n !session.writeScopes.every((pattern) => typeof pattern === \"string\")\n ) {\n throw new WriteRequestError(\"session must come from openWriteSession\");\n }\n return session.writeScopes.some((pattern) =>\n scopeMatchesPattern(scope, pattern),\n );\n}\n\n/**\n * The media type the Personal Server stores for a binary write: the\n * `Content-Type` minus its parameters, `application/octet-stream` when blank.\n */\nexport function normalizeBinaryMimeType(contentType: string | null): string {\n if (!contentType) return \"application/octet-stream\";\n return contentType.split(\";\")[0].trim() || \"application/octet-stream\";\n}\n\n/**\n * How the Personal Server reads an `X-Vana-Metadata` header: JSON when it\n * parses, the raw string otherwise, `undefined` when absent or blank.\n */\nexport function parseWriteMetadataHeader(value: string | null): unknown {\n if (value === null) return undefined;\n const trimmed = value.trim();\n if (trimmed === \"\") return undefined;\n try {\n return JSON.parse(trimmed);\n } catch {\n return value;\n }\n}\n\n/**\n * Encode a metadata object for the `X-Vana-Metadata` header. Non-ASCII\n * characters are `\\uXXXX`-escaped so the value is header-safe everywhere;\n * it parses back to the same object.\n */\nexport function encodeWriteMetadataHeader(\n metadata: Record<string, unknown>,\n): string {\n let json: string;\n try {\n json = JSON.stringify(metadata);\n } catch (err) {\n throw new WriteRequestError(\n `metadata is not JSON-serialisable: ${errorMessage(err)}`,\n );\n }\n if (typeof json !== \"string\") {\n throw new WriteRequestError(\"metadata must serialise to JSON\");\n }\n return json.replace(\n /[\\u007f-\\uffff]/g,\n (c) => `\\\\u${c.charCodeAt(0).toString(16).padStart(4, \"0\")}`,\n );\n}\n\nexport interface BinaryWriteSignedBytesInput {\n /** The raw body bytes the write sends. */\n bytes: Uint8Array;\n /** The `Content-Type` header the write sends (parameters are ignored). */\n contentType: string;\n /** The `X-Filename` header value the write sends, if any. */\n filename?: string;\n /** The exact `X-Vana-Metadata` header value the write sends, if any. */\n metadataHeader?: string;\n}\n\n/**\n * The bytes a builder signs for a binary write: the compact JSON of the\n * `$binary` record the Personal Server stores for these headers and bytes\n * (`personal-server-ts` `binaryWriteSignedBytes`, mirrored field for field).\n *\n * @returns UTF-8 bytes of the stored record's compact JSON.\n */\nexport function binaryWriteSignedBytes(\n input: BinaryWriteSignedBytesInput,\n): Uint8Array {\n const metadata = parseWriteMetadataHeader(input.metadataHeader ?? null);\n const record: Record<string, unknown> = {\n $binary: true,\n mimeType: normalizeBinaryMimeType(input.contentType),\n ...(input.filename ? { filename: input.filename } : {}),\n sizeBytes: input.bytes.length,\n contentHash: bytesToHex(sha256(input.bytes)),\n encoding: \"base64\",\n content: toBase64(input.bytes),\n ...(metadata !== undefined ? { metadata } : {}),\n };\n return new TextEncoder().encode(JSON.stringify(record));\n}\n\nconst PRINTABLE_ASCII = /^[\\x20-\\x7e]*$/;\n\n/**\n * Carry a filename the way the Personal Server reads it back verbatim:\n * `X-Filename` for printable ASCII, RFC 5987 `filename*` otherwise (a raw\n * non-ASCII header value is rejected by `fetch` or mangled in transit).\n */\nfunction setFilenameHeader(headers: Headers, filename: string): void {\n if (PRINTABLE_ASCII.test(filename)) {\n headers.set(WRITE_FILENAME_HEADER, filename);\n return;\n }\n headers.set(\n WRITE_CONTENT_DISPOSITION_HEADER,\n `attachment; filename*=UTF-8''${encodeURIComponent(filename)}`,\n );\n}\n\nfunction assertNoReservedKeys(\n value: Record<string, unknown>,\n where: string,\n): void {\n for (const key of RESERVED_WRITE_KEYS) {\n if (Object.prototype.hasOwnProperty.call(value, key)) {\n throw new WriteRequestError(\n `${where} must not contain the reserved ${key} key; the Personal Server stamps it`,\n { key },\n );\n }\n }\n}\n\nfunction isLineagePair(\n value: unknown,\n): value is { ownerAddress: Address; scope: string } {\n return (\n isRecord(value) &&\n typeof value.ownerAddress === \"string\" &&\n typeof value.scope === \"string\"\n );\n}\n\n/**\n * Validate and lowercase the sources the way the Personal Server will\n * (distinct, at most {@link MAX_LINEAGE_SOURCES}, never the record's own id)\n * and apply the naming rule to every source given as a pair. `[]` is kept:\n * it is an explicit root statement.\n */\nfunction normalizeLineage(lineage: unknown, derivedScope: string): Hex[] {\n if (!Array.isArray(lineage)) {\n throw new WriteRequestError(\"lineage must be an array of data point ids\");\n }\n if (lineage.length > MAX_LINEAGE_SOURCES) {\n throw new WriteRequestError(\n `lineage lists ${lineage.length} sources; the maximum is ${MAX_LINEAGE_SOURCES}`,\n { max: MAX_LINEAGE_SOURCES, count: lineage.length },\n );\n }\n const seen = new Set<string>();\n const sources: Hex[] = [];\n const sourceScopes: string[] = [];\n const ownIds = new Set<string>();\n for (const entry of lineage as unknown[]) {\n let id: Hex;\n if (isLineagePair(entry)) {\n if (!isAddress(entry.ownerAddress, { strict: false })) {\n throw new WriteRequestError(\n \"lineage source ownerAddress must be an EVM address\",\n { ownerAddress: entry.ownerAddress },\n );\n }\n if (entry.scope.length === 0) {\n throw new WriteRequestError(\"lineage source scope is required\");\n }\n id = deriveDataPointId(entry.ownerAddress, entry.scope);\n sourceScopes.push(entry.scope);\n ownIds.add(deriveDataPointId(entry.ownerAddress, derivedScope));\n } else if (isDataPointId(entry)) {\n id = entry;\n } else {\n throw new WriteRequestError(\n \"lineage entries must be 32-byte hex data point ids or { ownerAddress, scope } pairs\",\n { entry },\n );\n }\n const normalized = id.toLowerCase() as Hex;\n if (seen.has(normalized)) {\n throw new WriteRequestError(\"lineage must not repeat a data point id\", {\n dataPointId: normalized,\n });\n }\n seen.add(normalized);\n sources.push(normalized);\n }\n for (const own of ownIds) {\n if (seen.has(own)) {\n throw new WriteRequestError(\n \"lineage must not cite the record's own data point\",\n { dataPointId: own, scope: derivedScope },\n );\n }\n }\n assertDerivedScopeNaming(derivedScope, sourceScopes);\n return sources;\n}\n\nfunction assertNoLineageField(\n value: Record<string, unknown>,\n where: string,\n): void {\n if (Object.prototype.hasOwnProperty.call(value, LINEAGE_FIELD)) {\n throw new WriteRequestError(\n `${where}.${LINEAGE_FIELD} is reserved; pass sources through the lineage option`,\n );\n }\n}\n\n/** The `X-Vana-Metadata` header for a binary write, or `undefined`. */\nfunction buildMetadataHeader(\n metadata: Record<string, unknown> | undefined,\n sources: Hex[] | undefined,\n): string | undefined {\n if (metadata !== undefined) {\n if (!isRecord(metadata)) {\n throw new WriteRequestError(\"metadata must be a plain object\");\n }\n assertNoReservedKeys(metadata, \"metadata\");\n assertNoLineageField(metadata, \"metadata\");\n }\n if (metadata === undefined && sources === undefined) return undefined;\n return encodeWriteMetadataHeader({\n ...(metadata ?? {}),\n ...(sources !== undefined ? { [LINEAGE_FIELD]: sources } : {}),\n });\n}\n\ninterface PreparedWrite {\n body: Uint8Array;\n /** What the proof's `bodyHash` commits to. */\n signedBytes: Uint8Array;\n contentType: string;\n filename?: string;\n metadataHeader?: string;\n}\n\nfunction prepareWrite(params: WriteDataParams): PreparedWrite {\n if (params.binary !== undefined && params.data !== undefined) {\n throw new WriteRequestError(\"Pass either data or binary, not both\");\n }\n // Absent or null lineage makes no statement: send nothing. Anything else\n // (`[]` included: an explicit root) is validated before it is touched,\n // since callers may be untyped.\n const rawLineage: unknown = params.lineage;\n const sources =\n rawLineage === undefined || rawLineage === null\n ? undefined\n : normalizeLineage(rawLineage, params.scope);\n\n if (params.binary !== undefined) {\n const { bytes, contentType, filename } = params.binary;\n if (!(bytes instanceof Uint8Array)) {\n throw new WriteRequestError(\"binary.bytes must be a Uint8Array\");\n }\n if (typeof contentType !== \"string\" || contentType.trim() === \"\") {\n throw new WriteRequestError(\"binary.contentType is required\");\n }\n if (filename !== undefined) {\n if (typeof filename !== \"string\") {\n throw new WriteRequestError(\"binary.filename must be a string\");\n }\n if (filename !== filename.trim()) {\n throw new WriteRequestError(\n \"binary.filename must not have leading or trailing whitespace\",\n );\n }\n }\n const metadataHeader = buildMetadataHeader(params.metadata, sources);\n return {\n body: bytes,\n signedBytes: binaryWriteSignedBytes({\n bytes,\n contentType,\n filename,\n metadataHeader,\n }),\n contentType,\n filename,\n metadataHeader,\n };\n }\n\n if (params.data === undefined) {\n throw new WriteRequestError(\"Pass data (a JSON object) or binary\");\n }\n if (!isRecord(params.data)) {\n throw new WriteRequestError(\"data must be a plain JSON object\");\n }\n if (params.metadata !== undefined) {\n throw new WriteRequestError(\n \"metadata applies to binary writes; put fields to store inside data\",\n );\n }\n assertNoReservedKeys(params.data, \"data\");\n assertNoLineageField(params.data, \"data\");\n const record =\n sources === undefined\n ? params.data\n : { ...params.data, [LINEAGE_FIELD]: sources };\n let text: string;\n try {\n // Compact JSON is the contract: the server re-serialises the parsed\n // record and requires it to match the signed bytes.\n text = JSON.stringify(record);\n } catch (err) {\n throw new WriteRequestError(\n `data is not JSON-serialisable: ${errorMessage(err)}`,\n );\n }\n if (typeof text !== \"string\" || !text.startsWith(\"{\")) {\n throw new WriteRequestError(\"data must serialise to a JSON object\");\n }\n const body = new TextEncoder().encode(text);\n return {\n body,\n signedBytes: body,\n contentType: \"application/json\",\n };\n}\n\nasync function writeErrorFromResponse(\n response: Response,\n): Promise<PersonalServerWriteError> {\n const { errorCode, message, details } =\n await readPersonalServerErrorBody(response);\n const text =\n message ??\n `Personal Server write failed: ${response.status} ${response.statusText}`;\n // Lineage rejections span 400 / 422 / 502; the code is the discriminator.\n if (response.status === 422 || errorCode?.startsWith(\"LINEAGE_\")) {\n return new WriteLineageError(text, response.status, errorCode, details);\n }\n switch (response.status) {\n case 401:\n return new WriteUnauthorizedError(text, errorCode, details);\n case 403:\n return new WriteForbiddenError(text, errorCode, details);\n case 409:\n return new WriteConflictError(text, errorCode, details);\n default:\n return new WriteRejectedError(text, response.status, errorCode, details);\n }\n}\n\n/**\n * Write one record into a scope under an open write session.\n *\n * @remarks\n * Sends `POST /v1/data/:scope` with `Authorization: Bearer <session token>`\n * and `X-Vana-Write-Signature`, a Web3Signed proof over the stored\n * representation carrying the session's `grantId` as a signed claim. JSON\n * writes send `data` as compact JSON with `Content-Type: application/json`;\n * binary writes send the bytes with their `Content-Type`, `X-Filename`, and\n * sign {@link binaryWriteSignedBytes}. `lineage` is the record's top-level\n * `lineage` field for JSON and the `lineage` field of `X-Vana-Metadata` for\n * binary, so the proof covers it either way.\n *\n * @returns The ingest answer (`scope`, `collectedAt`, `status`, and\n * `lineage.sources` when the write carried lineage).\n * @throws {WriteRequestError} Before sending: no payload, a reserved key, a\n * malformed lineage id, or non-object data.\n * @throws {WriteSessionExpiredError} Before sending: the session token has\n * passed its lifetime.\n * @throws {WriteUnauthorizedError} 401 (proof or session rejected).\n * @throws {WriteForbiddenError} 403 (grant no longer authorises the write).\n * @throws {WriteConflictError} 409.\n * @throws {WriteLineageError} Any `LINEAGE_*` rejection: 422\n * `LINEAGE_SOURCE_UNKNOWN` (`details.unknown`), 400 `LINEAGE_INVALID` /\n * `LINEAGE_SCOPE_UNDER_SOURCE_PREFIX`, 502 `LINEAGE_SOURCE_LOOKUP_FAILED`.\n * @throws {WriteRejectedError} Any other non-2xx.\n * @throws {WriteTransportError} `fetch` threw on every attempt.\n */\nexport async function writeData(\n params: WriteDataParams,\n): Promise<WriteDataResult> {\n const { session } = params;\n if (!isWriteSession(session)) {\n throw new WriteRequestError(\"session must come from openWriteSession\");\n }\n const fetchFn = resolveFetch(params.fetch);\n if (typeof params.scope !== \"string\" || params.scope.length === 0) {\n throw new WriteRequestError(\"scope is required\");\n }\n const prepared = prepareWrite(params);\n if (Date.now() >= session.expiresAt) {\n throw new WriteSessionExpiredError(\n \"Write session has expired; open a new session\",\n { grantId: session.grantId, expiresAt: session.expiresAt },\n );\n }\n\n const path = dataPath(params.scope);\n const response = await sendWithFreshProof(\n `Write to ${params.scope}`,\n fetchFn,\n params.retry,\n proofKeyFor({\n aud: session.audience,\n method: \"POST\",\n uri: path,\n grantId: session.grantId,\n signedBytes: prepared.signedBytes,\n }),\n async (iat) => {\n const headers = new Headers(params.headers);\n headers.set(\"Content-Type\", prepared.contentType);\n headers.set(\"Authorization\", `Bearer ${session.accessToken}`);\n if (prepared.filename) {\n setFilenameHeader(headers, prepared.filename);\n }\n if (prepared.metadataHeader !== undefined) {\n headers.set(WRITE_METADATA_HEADER, prepared.metadataHeader);\n }\n headers.set(\n WRITE_SIGNATURE_HEADER,\n await buildWeb3SignedHeader({\n signMessage: session.signer.signMessage,\n aud: session.audience,\n method: \"POST\",\n uri: path,\n body: prepared.signedBytes,\n grantId: session.grantId,\n iat,\n }),\n );\n return {\n url: `${session.personalServerUrl}${path}`,\n init: {\n method: \"POST\",\n headers,\n body: prepared.body as unknown as BodyInit,\n },\n };\n },\n );\n\n if (!response.ok) {\n throw await writeErrorFromResponse(response);\n }\n let body: unknown;\n try {\n body = await response.json();\n } catch (err) {\n throw new WriteRejectedError(\n \"Personal Server write response is not JSON\",\n response.status,\n null,\n { cause: errorMessage(err) },\n );\n }\n const parsed = WriteDataResultSchema.safeParse(body);\n if (!parsed.success) {\n throw new WriteRejectedError(\n \"Personal Server write response is not an ingest result\",\n response.status,\n null,\n { issues: parsed.error.issues },\n );\n }\n return parsed.data as WriteDataResult;\n}\n\ntype DistributiveOmit<T, K extends PropertyKey> = T extends unknown\n ? Omit<T, K>\n : never;\n\nexport type WritePersonalServerDataParams = Omit<\n OpenWriteSessionParams,\n \"fetch\" | \"headers\" | \"retry\"\n> &\n DistributiveOmit<WriteDataParams, \"session\">;\n\n/** {@link writePersonalServerData}'s answer: the ingest result plus the session it opened. */\nexport interface WritePersonalServerDataResult extends WriteDataResult {\n /** Reuse for further writes until `expiresAt`. */\n session: WriteSession;\n}\n\n/**\n * Open a write session and write one record in a single call.\n *\n * @remarks\n * Equivalent to {@link openWriteSession} followed by {@link writeData}. The\n * session is returned so further writes can reuse it; opening one per write\n * is correct but costs an extra signature and round-trip each time.\n *\n * @throws Everything {@link openWriteSession} and {@link writeData} throw.\n */\nexport async function writePersonalServerData(\n params: WritePersonalServerDataParams,\n): Promise<WritePersonalServerDataResult> {\n const session = await openWriteSession({\n personalServerUrl: params.personalServerUrl,\n signer: params.signer,\n grantId: params.grantId,\n account: params.account,\n audience: params.audience,\n fetch: params.fetch,\n headers: params.headers,\n retry: params.retry,\n });\n const writeParams = { ...params, session } as WriteDataParams;\n const result = await writeData(writeParams);\n return { ...result, session };\n}\n"],"mappings":"AAoCA,SAAS,cAAc;AACvB,SAAS,YAAY,iBAAyC;AAC9D,SAAS,SAAS;AAClB,SAAS,6BAA6B;AACtC;AAAA,EAEE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,gBAAgB;AACzB,SAAS,4BAAiD;AAC1D;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP;AAAA,EACE;AAAA,EACA;AAAA,OACK;AACP,SAAS,2BAA2B;AACpC;AAAA,EACE;AAAA,OAIK;AAGA,MAAM,qBAAqB;AAE3B,MAAM,yBAAyB;AAE/B,MAAM,wBAAwB;AAE9B,MAAM,gBAAgB;AAEtB,MAAM,sBAAsB;AAE5B,MAAM,wBAAwB;AAK9B,MAAM,mCAAmC;AAEzC,MAAM,yBAAyB;AAE/B,MAAM,cAAc;AAEpB,MAAM,sBAAyC;AAAA,EACpD;AAAA,EACA;AACF;AA2HA,MAAM,wBAAwB,qBAAqB,OAAO;AAAA;AAAA,EAExD,SAAS,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC,EAAE,SAAS;AAC/D,CAAC;AAOD,MAAM,6BAA6B,EAAE,OAAO;AAAA,EAC1C,cAAc,EAAE,OAAO,EAAE,IAAI,CAAC;AAAA,EAC9B,YAAY,EAAE,OAAO;AAAA,EACrB,YAAY,EAAE,OAAO,EAAE,YAAY;AAAA,EACnC,OAAO,EAAE,OAAO;AAClB,CAAC;AAED,SAAS,iBAAiB,KAAqB;AAC7C,SAAO,IAAI,QAAQ,QAAQ,EAAE;AAC/B;AAEA,SAAS,aAAa,SAAiD;AACrE,QAAM,WAAW,WAAW,WAAW;AACvC,MAAI,aAAa,QAAW;AAC1B,UAAM,IAAI,kBAAkB,mCAAmC;AAAA,EACjE;AACA,SAAO;AACT;AAEA,SAAS,SAAS,OAAuB;AACvC,SAAO,YAAY,mBAAmB,KAAK,CAAC;AAC9C;AAEA,SAAS,aAAa,KAAsB;AAC1C,SAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AACxD;AAEA,SAAS,SAAS,OAA2B,UAA0B;AACrE,SAAO,OAAO,UAAU,YAAY,OAAO,SAAS,KAAK,IAAI,QAAQ;AACvE;AAEA,SAAS,MAAM,IAA2B;AACxC,SAAO,IAAI,QAAQ,CAAC,YAAY,WAAW,SAAS,EAAE,CAAC;AACzD;AAoBA,MAAM,kBAAkB,oBAAI,IAAoB;AAEhD,MAAM,qBAAqB,oBAAI,IAAyB;AACxD,IAAI,6BAA6B;AAEjC,MAAM,qCAAqC;AAE3C,MAAM,iCAAiC;AACvC,MAAM,8BACJ,qCAAqC;AAQvC,MAAM,8BAA8B;AAEpC,SAAS,qBAAqB,QAAsB;AAClD,MAAI,+BAA+B,OAAQ;AAC3C,+BAA6B;AAC7B,QAAM,SAAS,SAAS;AAGxB,aAAW,CAAC,KAAK,IAAI,KAAK,oBAAoB;AAC5C,QAAI,OAAO,OAAQ;AACnB,eAAW,OAAO,KAAM,iBAAgB,OAAO,GAAG;AAClD,uBAAmB,OAAO,GAAG;AAAA,EAC/B;AACF;AAEA,SAAS,kBAAkB,KAAa,KAAa,UAAyB;AAC5E,MAAI,aAAa,QAAW;AAC1B,UAAMA,UAAS,mBAAmB,IAAI,QAAQ;AAC9C,IAAAA,SAAQ,OAAO,GAAG;AAClB,QAAIA,SAAQ,SAAS,EAAG,oBAAmB,OAAO,QAAQ;AAAA,EAC5D;AACA,kBAAgB,IAAI,KAAK,GAAG;AAC5B,MAAI,SAAS,mBAAmB,IAAI,GAAG;AACvC,MAAI,WAAW,QAAW;AACxB,aAAS,oBAAI,IAAI;AACjB,uBAAmB,IAAI,KAAK,MAAM;AAAA,EACpC;AACA,SAAO,IAAI,GAAG;AAChB;AAQA,SAAS,aAAa,UAAmC;AACvD,QAAM,SAAS,KAAK,MAAM,KAAK,IAAI,IAAI,GAAI;AAC3C,uBAAqB,MAAM;AAC3B,QAAM,OAAO,gBAAgB,IAAI,QAAQ;AACzC,QAAM,MAAM,SAAS,SAAY,SAAS,KAAK,IAAI,QAAQ,OAAO,CAAC;AACnE,oBAAkB,UAAU,KAAK,IAAI;AACrC,QAAM,UAAU,MAAM,SAAS;AAC/B,MAAI,WAAW,EAAG,QAAO,QAAQ,QAAQ,GAAG;AAC5C,SAAO,MAAM,UAAU,GAAI,EAAE,KAAK,MAAM,GAAG;AAC7C;AAEA,SAAS,YAAY,OAMV;AAGT,SAAO;AAAA,IACL;AAAA,MACE,IAAI,YAAY,EAAE;AAAA,QAChB,KAAK,UAAU;AAAA,UACb,MAAM;AAAA,UACN,MAAM;AAAA,UACN,MAAM;AAAA,UACN,MAAM;AAAA,UACN,MAAM,cAAc,WAAW,OAAO,MAAM,WAAW,CAAC,IAAI;AAAA,QAC9D,CAAC;AAAA,MACH;AAAA,IACF;AAAA,EACF;AACF;AAMA,eAAe,mBACb,OACA,SACA,SACA,UACA,OACmB;AACnB,QAAM,WAAW,KAAK,IAAI,GAAG,KAAK,MAAM,SAAS,SAAS,UAAU,CAAC,CAAC,CAAC;AACvE,MAAI,UAAU,KAAK,IAAI,GAAG,SAAS,SAAS,gBAAgB,GAAK,CAAC;AAClE,MAAI;AACJ,WAAS,UAAU,GAAG,UAAU,UAAU,WAAW;AACnD,UAAM,EAAE,KAAK,KAAK,IAAI,MAAM,MAAM,MAAM,aAAa,QAAQ,CAAC;AAC9D,QAAI;AACF,aAAO,MAAM,QAAQ,KAAK,IAAI;AAAA,IAChC,SAAS,KAAK;AACZ,kBAAY;AAAA,IACd;AACA,QAAI,UAAU,WAAW,GAAG;AAC1B,YAAM,MAAM,OAAO;AACnB,iBAAW;AAAA,IACb;AAAA,EACF;AACA,QAAM,IAAI;AAAA,IACR,GAAG,KAAK,iBAAiB,QAAQ,gBAAgB,aAAa,SAAS,CAAC;AAAA,IACxE;AAAA,IACA;AAAA,EACF;AACF;AAmBA,eAAsB,iBACpB,QACuB;AACvB,QAAM,UAAU,aAAa,OAAO,KAAK;AACzC,QAAM,oBAAoB,iBAAiB,OAAO,iBAAiB;AACnE,QAAM,WAAW,OAAO,YAAY;AACpC,QAAM,SAAS,mBAAmB,OAAO,QAAQ,EAAE,SAAS,OAAO,QAAQ,CAAC;AAE5E,QAAM,WAAW,MAAM;AAAA,IACrB;AAAA,IACA;AAAA,IACA,OAAO;AAAA,IACP,YAAY;AAAA,MACV,KAAK;AAAA,MACL,QAAQ;AAAA,MACR,KAAK;AAAA,MACL,SAAS,OAAO;AAAA,IAClB,CAAC;AAAA,IACD,OAAO,QAAQ;AACb,YAAM,UAAU,IAAI,QAAQ,OAAO,OAAO;AAC1C,cAAQ;AAAA,QACN;AAAA,QACA,MAAM,sBAAsB;AAAA,UAC1B,aAAa,OAAO;AAAA,UACpB,KAAK;AAAA,UACL,QAAQ;AAAA,UACR,KAAK;AAAA,UACL,SAAS,OAAO;AAAA,UAChB;AAAA,QACF,CAAC;AAAA,MACH;AACA,aAAO;AAAA,QACL,KAAK,GAAG,iBAAiB,GAAG,kBAAkB;AAAA,QAC9C,MAAM,EAAE,QAAQ,QAAQ,QAAQ;AAAA,MAClC;AAAA,IACF;AAAA,EACF;AAGA,QAAM,WAAW,KAAK,IAAI;AAE1B,MAAI,CAAC,SAAS,IAAI;AAChB,UAAM,EAAE,WAAW,SAAS,QAAQ,IAClC,MAAM,4BAA4B,QAAQ;AAC5C,UAAM,IAAI;AAAA,MACR,WACE,mCAAmC,SAAS,MAAM,IAAI,SAAS,UAAU;AAAA,MAC3E,SAAS;AAAA,MACT;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAEA,MAAI;AACJ,MAAI;AACF,WAAO,MAAM,SAAS,KAAK;AAAA,EAC7B,SAAS,KAAK;AACZ,UAAM,IAAI;AAAA,MACR;AAAA,MACA,SAAS;AAAA,MACT;AAAA,MACA,EAAE,OAAO,aAAa,GAAG,EAAE;AAAA,IAC7B;AAAA,EACF;AACA,QAAM,SAAS,2BAA2B,UAAU,IAAI;AACxD,MAAI,CAAC,OAAO,SAAS;AACnB,UAAM,IAAI;AAAA,MACR;AAAA,MACA,SAAS;AAAA,MACT;AAAA,MACA,EAAE,QAAQ,OAAO,MAAM,OAAO;AAAA,IAChC;AAAA,EACF;AACA,MAAI,OAAO,KAAK,WAAW,YAAY,MAAM,UAAU;AACrD,UAAM,IAAI;AAAA,MACR,2CAA2C,OAAO,KAAK,UAAU;AAAA,MACjE,SAAS;AAAA,IACX;AAAA,EACF;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,SAAS,OAAO;AAAA,IAChB,aAAa,OAAO,KAAK;AAAA,IACzB,WAAW,WAAW,OAAO,KAAK,aAAa;AAAA,IAC/C,aAAa,OAAO,KAAK,MAAM,MAAM,GAAG,EAAE,OAAO,CAAC,MAAM,EAAE,SAAS,CAAC;AAAA,IACpE;AAAA,EACF;AACF;AAGA,SAAS,eAAe,OAAuC;AAC7D,MAAI,CAAC,SAAS,KAAK,EAAG,QAAO;AAC7B,QAAM,SAAkB,MAAM;AAC9B,SACE,OAAO,MAAM,sBAAsB,YACnC,OAAO,MAAM,aAAa,YAC1B,OAAO,MAAM,YAAY,YACzB,OAAO,MAAM,gBAAgB,YAC7B,MAAM,YAAY,SAAS,KAC3B,OAAO,MAAM,cAAc,YAC3B,OAAO,SAAS,MAAM,SAAS,KAC/B,MAAM,QAAQ,MAAM,WAAW,KAC/B,MAAM,YAAY,MAAM,CAAC,UAAU,OAAO,UAAU,QAAQ,KAC5D,SAAS,MAAM,KACf,OAAO,OAAO,gBAAgB;AAElC;AAGO,SAAS,mBACd,SACA,OACS;AACT,MACE,CAAC,SAAS,OAAO,KACjB,CAAC,MAAM,QAAQ,QAAQ,WAAW,KAClC,CAAC,QAAQ,YAAY,MAAM,CAAC,YAAY,OAAO,YAAY,QAAQ,GACnE;AACA,UAAM,IAAI,kBAAkB,yCAAyC;AAAA,EACvE;AACA,SAAO,QAAQ,YAAY;AAAA,IAAK,CAAC,YAC/B,oBAAoB,OAAO,OAAO;AAAA,EACpC;AACF;AAMO,SAAS,wBAAwB,aAAoC;AAC1E,MAAI,CAAC,YAAa,QAAO;AACzB,SAAO,YAAY,MAAM,GAAG,EAAE,CAAC,EAAE,KAAK,KAAK;AAC7C;AAMO,SAAS,yBAAyB,OAA+B;AACtE,MAAI,UAAU,KAAM,QAAO;AAC3B,QAAM,UAAU,MAAM,KAAK;AAC3B,MAAI,YAAY,GAAI,QAAO;AAC3B,MAAI;AACF,WAAO,KAAK,MAAM,OAAO;AAAA,EAC3B,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAOO,SAAS,0BACd,UACQ;AACR,MAAI;AACJ,MAAI;AACF,WAAO,KAAK,UAAU,QAAQ;AAAA,EAChC,SAAS,KAAK;AACZ,UAAM,IAAI;AAAA,MACR,sCAAsC,aAAa,GAAG,CAAC;AAAA,IACzD;AAAA,EACF;AACA,MAAI,OAAO,SAAS,UAAU;AAC5B,UAAM,IAAI,kBAAkB,iCAAiC;AAAA,EAC/D;AACA,SAAO,KAAK;AAAA,IACV;AAAA,IACA,CAAC,MAAM,MAAM,EAAE,WAAW,CAAC,EAAE,SAAS,EAAE,EAAE,SAAS,GAAG,GAAG,CAAC;AAAA,EAC5D;AACF;AAoBO,SAAS,uBACd,OACY;AACZ,QAAM,WAAW,yBAAyB,MAAM,kBAAkB,IAAI;AACtE,QAAM,SAAkC;AAAA,IACtC,SAAS;AAAA,IACT,UAAU,wBAAwB,MAAM,WAAW;AAAA,IACnD,GAAI,MAAM,WAAW,EAAE,UAAU,MAAM,SAAS,IAAI,CAAC;AAAA,IACrD,WAAW,MAAM,MAAM;AAAA,IACvB,aAAa,WAAW,OAAO,MAAM,KAAK,CAAC;AAAA,IAC3C,UAAU;AAAA,IACV,SAAS,SAAS,MAAM,KAAK;AAAA,IAC7B,GAAI,aAAa,SAAY,EAAE,SAAS,IAAI,CAAC;AAAA,EAC/C;AACA,SAAO,IAAI,YAAY,EAAE,OAAO,KAAK,UAAU,MAAM,CAAC;AACxD;AAEA,MAAM,kBAAkB;AAOxB,SAAS,kBAAkB,SAAkB,UAAwB;AACnE,MAAI,gBAAgB,KAAK,QAAQ,GAAG;AAClC,YAAQ,IAAI,uBAAuB,QAAQ;AAC3C;AAAA,EACF;AACA,UAAQ;AAAA,IACN;AAAA,IACA,gCAAgC,mBAAmB,QAAQ,CAAC;AAAA,EAC9D;AACF;AAEA,SAAS,qBACP,OACA,OACM;AACN,aAAW,OAAO,qBAAqB;AACrC,QAAI,OAAO,UAAU,eAAe,KAAK,OAAO,GAAG,GAAG;AACpD,YAAM,IAAI;AAAA,QACR,GAAG,KAAK,kCAAkC,GAAG;AAAA,QAC7C,EAAE,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AACF;AAEA,SAAS,cACP,OACmD;AACnD,SACE,SAAS,KAAK,KACd,OAAO,MAAM,iBAAiB,YAC9B,OAAO,MAAM,UAAU;AAE3B;AAQA,SAAS,iBAAiB,SAAkB,cAA6B;AACvE,MAAI,CAAC,MAAM,QAAQ,OAAO,GAAG;AAC3B,UAAM,IAAI,kBAAkB,4CAA4C;AAAA,EAC1E;AACA,MAAI,QAAQ,SAAS,qBAAqB;AACxC,UAAM,IAAI;AAAA,MACR,iBAAiB,QAAQ,MAAM,4BAA4B,mBAAmB;AAAA,MAC9E,EAAE,KAAK,qBAAqB,OAAO,QAAQ,OAAO;AAAA,IACpD;AAAA,EACF;AACA,QAAM,OAAO,oBAAI,IAAY;AAC7B,QAAM,UAAiB,CAAC;AACxB,QAAM,eAAyB,CAAC;AAChC,QAAM,SAAS,oBAAI,IAAY;AAC/B,aAAW,SAAS,SAAsB;AACxC,QAAI;AACJ,QAAI,cAAc,KAAK,GAAG;AACxB,UAAI,CAAC,UAAU,MAAM,cAAc,EAAE,QAAQ,MAAM,CAAC,GAAG;AACrD,cAAM,IAAI;AAAA,UACR;AAAA,UACA,EAAE,cAAc,MAAM,aAAa;AAAA,QACrC;AAAA,MACF;AACA,UAAI,MAAM,MAAM,WAAW,GAAG;AAC5B,cAAM,IAAI,kBAAkB,kCAAkC;AAAA,MAChE;AACA,WAAK,kBAAkB,MAAM,cAAc,MAAM,KAAK;AACtD,mBAAa,KAAK,MAAM,KAAK;AAC7B,aAAO,IAAI,kBAAkB,MAAM,cAAc,YAAY,CAAC;AAAA,IAChE,WAAW,cAAc,KAAK,GAAG;AAC/B,WAAK;AAAA,IACP,OAAO;AACL,YAAM,IAAI;AAAA,QACR;AAAA,QACA,EAAE,MAAM;AAAA,MACV;AAAA,IACF;AACA,UAAM,aAAa,GAAG,YAAY;AAClC,QAAI,KAAK,IAAI,UAAU,GAAG;AACxB,YAAM,IAAI,kBAAkB,2CAA2C;AAAA,QACrE,aAAa;AAAA,MACf,CAAC;AAAA,IACH;AACA,SAAK,IAAI,UAAU;AACnB,YAAQ,KAAK,UAAU;AAAA,EACzB;AACA,aAAW,OAAO,QAAQ;AACxB,QAAI,KAAK,IAAI,GAAG,GAAG;AACjB,YAAM,IAAI;AAAA,QACR;AAAA,QACA,EAAE,aAAa,KAAK,OAAO,aAAa;AAAA,MAC1C;AAAA,IACF;AAAA,EACF;AACA,2BAAyB,cAAc,YAAY;AACnD,SAAO;AACT;AAEA,SAAS,qBACP,OACA,OACM;AACN,MAAI,OAAO,UAAU,eAAe,KAAK,OAAO,aAAa,GAAG;AAC9D,UAAM,IAAI;AAAA,MACR,GAAG,KAAK,IAAI,aAAa;AAAA,IAC3B;AAAA,EACF;AACF;AAGA,SAAS,oBACP,UACA,SACoB;AACpB,MAAI,aAAa,QAAW;AAC1B,QAAI,CAAC,SAAS,QAAQ,GAAG;AACvB,YAAM,IAAI,kBAAkB,iCAAiC;AAAA,IAC/D;AACA,yBAAqB,UAAU,UAAU;AACzC,yBAAqB,UAAU,UAAU;AAAA,EAC3C;AACA,MAAI,aAAa,UAAa,YAAY,OAAW,QAAO;AAC5D,SAAO,0BAA0B;AAAA,IAC/B,GAAI,YAAY,CAAC;AAAA,IACjB,GAAI,YAAY,SAAY,EAAE,CAAC,aAAa,GAAG,QAAQ,IAAI,CAAC;AAAA,EAC9D,CAAC;AACH;AAWA,SAAS,aAAa,QAAwC;AAC5D,MAAI,OAAO,WAAW,UAAa,OAAO,SAAS,QAAW;AAC5D,UAAM,IAAI,kBAAkB,sCAAsC;AAAA,EACpE;AAIA,QAAM,aAAsB,OAAO;AACnC,QAAM,UACJ,eAAe,UAAa,eAAe,OACvC,SACA,iBAAiB,YAAY,OAAO,KAAK;AAE/C,MAAI,OAAO,WAAW,QAAW;AAC/B,UAAM,EAAE,OAAO,aAAa,SAAS,IAAI,OAAO;AAChD,QAAI,EAAE,iBAAiB,aAAa;AAClC,YAAM,IAAI,kBAAkB,mCAAmC;AAAA,IACjE;AACA,QAAI,OAAO,gBAAgB,YAAY,YAAY,KAAK,MAAM,IAAI;AAChE,YAAM,IAAI,kBAAkB,gCAAgC;AAAA,IAC9D;AACA,QAAI,aAAa,QAAW;AAC1B,UAAI,OAAO,aAAa,UAAU;AAChC,cAAM,IAAI,kBAAkB,kCAAkC;AAAA,MAChE;AACA,UAAI,aAAa,SAAS,KAAK,GAAG;AAChC,cAAM,IAAI;AAAA,UACR;AAAA,QACF;AAAA,MACF;AAAA,IACF;AACA,UAAM,iBAAiB,oBAAoB,OAAO,UAAU,OAAO;AACnE,WAAO;AAAA,MACL,MAAM;AAAA,MACN,aAAa,uBAAuB;AAAA,QAClC;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACF,CAAC;AAAA,MACD;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAEA,MAAI,OAAO,SAAS,QAAW;AAC7B,UAAM,IAAI,kBAAkB,qCAAqC;AAAA,EACnE;AACA,MAAI,CAAC,SAAS,OAAO,IAAI,GAAG;AAC1B,UAAM,IAAI,kBAAkB,kCAAkC;AAAA,EAChE;AACA,MAAI,OAAO,aAAa,QAAW;AACjC,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AACA,uBAAqB,OAAO,MAAM,MAAM;AACxC,uBAAqB,OAAO,MAAM,MAAM;AACxC,QAAM,SACJ,YAAY,SACR,OAAO,OACP,EAAE,GAAG,OAAO,MAAM,CAAC,aAAa,GAAG,QAAQ;AACjD,MAAI;AACJ,MAAI;AAGF,WAAO,KAAK,UAAU,MAAM;AAAA,EAC9B,SAAS,KAAK;AACZ,UAAM,IAAI;AAAA,MACR,kCAAkC,aAAa,GAAG,CAAC;AAAA,IACrD;AAAA,EACF;AACA,MAAI,OAAO,SAAS,YAAY,CAAC,KAAK,WAAW,GAAG,GAAG;AACrD,UAAM,IAAI,kBAAkB,sCAAsC;AAAA,EACpE;AACA,QAAM,OAAO,IAAI,YAAY,EAAE,OAAO,IAAI;AAC1C,SAAO;AAAA,IACL;AAAA,IACA,aAAa;AAAA,IACb,aAAa;AAAA,EACf;AACF;AAEA,eAAe,uBACb,UACmC;AACnC,QAAM,EAAE,WAAW,SAAS,QAAQ,IAClC,MAAM,4BAA4B,QAAQ;AAC5C,QAAM,OACJ,WACA,iCAAiC,SAAS,MAAM,IAAI,SAAS,UAAU;AAEzE,MAAI,SAAS,WAAW,OAAO,WAAW,WAAW,UAAU,GAAG;AAChE,WAAO,IAAI,kBAAkB,MAAM,SAAS,QAAQ,WAAW,OAAO;AAAA,EACxE;AACA,UAAQ,SAAS,QAAQ;AAAA,IACvB,KAAK;AACH,aAAO,IAAI,uBAAuB,MAAM,WAAW,OAAO;AAAA,IAC5D,KAAK;AACH,aAAO,IAAI,oBAAoB,MAAM,WAAW,OAAO;AAAA,IACzD,KAAK;AACH,aAAO,IAAI,mBAAmB,MAAM,WAAW,OAAO;AAAA,IACxD;AACE,aAAO,IAAI,mBAAmB,MAAM,SAAS,QAAQ,WAAW,OAAO;AAAA,EAC3E;AACF;AA8BA,eAAsB,UACpB,QAC0B;AAC1B,QAAM,EAAE,QAAQ,IAAI;AACpB,MAAI,CAAC,eAAe,OAAO,GAAG;AAC5B,UAAM,IAAI,kBAAkB,yCAAyC;AAAA,EACvE;AACA,QAAM,UAAU,aAAa,OAAO,KAAK;AACzC,MAAI,OAAO,OAAO,UAAU,YAAY,OAAO,MAAM,WAAW,GAAG;AACjE,UAAM,IAAI,kBAAkB,mBAAmB;AAAA,EACjD;AACA,QAAM,WAAW,aAAa,MAAM;AACpC,MAAI,KAAK,IAAI,KAAK,QAAQ,WAAW;AACnC,UAAM,IAAI;AAAA,MACR;AAAA,MACA,EAAE,SAAS,QAAQ,SAAS,WAAW,QAAQ,UAAU;AAAA,IAC3D;AAAA,EACF;AAEA,QAAM,OAAO,SAAS,OAAO,KAAK;AAClC,QAAM,WAAW,MAAM;AAAA,IACrB,YAAY,OAAO,KAAK;AAAA,IACxB;AAAA,IACA,OAAO;AAAA,IACP,YAAY;AAAA,MACV,KAAK,QAAQ;AAAA,MACb,QAAQ;AAAA,MACR,KAAK;AAAA,MACL,SAAS,QAAQ;AAAA,MACjB,aAAa,SAAS;AAAA,IACxB,CAAC;AAAA,IACD,OAAO,QAAQ;AACb,YAAM,UAAU,IAAI,QAAQ,OAAO,OAAO;AAC1C,cAAQ,IAAI,gBAAgB,SAAS,WAAW;AAChD,cAAQ,IAAI,iBAAiB,UAAU,QAAQ,WAAW,EAAE;AAC5D,UAAI,SAAS,UAAU;AACrB,0BAAkB,SAAS,SAAS,QAAQ;AAAA,MAC9C;AACA,UAAI,SAAS,mBAAmB,QAAW;AACzC,gBAAQ,IAAI,uBAAuB,SAAS,cAAc;AAAA,MAC5D;AACA,cAAQ;AAAA,QACN;AAAA,QACA,MAAM,sBAAsB;AAAA,UAC1B,aAAa,QAAQ,OAAO;AAAA,UAC5B,KAAK,QAAQ;AAAA,UACb,QAAQ;AAAA,UACR,KAAK;AAAA,UACL,MAAM,SAAS;AAAA,UACf,SAAS,QAAQ;AAAA,UACjB;AAAA,QACF,CAAC;AAAA,MACH;AACA,aAAO;AAAA,QACL,KAAK,GAAG,QAAQ,iBAAiB,GAAG,IAAI;AAAA,QACxC,MAAM;AAAA,UACJ,QAAQ;AAAA,UACR;AAAA,UACA,MAAM,SAAS;AAAA,QACjB;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,MAAI,CAAC,SAAS,IAAI;AAChB,UAAM,MAAM,uBAAuB,QAAQ;AAAA,EAC7C;AACA,MAAI;AACJ,MAAI;AACF,WAAO,MAAM,SAAS,KAAK;AAAA,EAC7B,SAAS,KAAK;AACZ,UAAM,IAAI;AAAA,MACR;AAAA,MACA,SAAS;AAAA,MACT;AAAA,MACA,EAAE,OAAO,aAAa,GAAG,EAAE;AAAA,IAC7B;AAAA,EACF;AACA,QAAM,SAAS,sBAAsB,UAAU,IAAI;AACnD,MAAI,CAAC,OAAO,SAAS;AACnB,UAAM,IAAI;AAAA,MACR;AAAA,MACA,SAAS;AAAA,MACT;AAAA,MACA,EAAE,QAAQ,OAAO,MAAM,OAAO;AAAA,IAChC;AAAA,EACF;AACA,SAAO,OAAO;AAChB;AA4BA,eAAsB,wBACpB,QACwC;AACxC,QAAM,UAAU,MAAM,iBAAiB;AAAA,IACrC,mBAAmB,OAAO;AAAA,IAC1B,QAAQ,OAAO;AAAA,IACf,SAAS,OAAO;AAAA,IAChB,SAAS,OAAO;AAAA,IAChB,UAAU,OAAO;AAAA,IACjB,OAAO,OAAO;AAAA,IACd,SAAS,OAAO;AAAA,IAChB,OAAO,OAAO;AAAA,EAChB,CAAC;AACD,QAAM,cAAc,EAAE,GAAG,QAAQ,QAAQ;AACzC,QAAM,SAAS,MAAM,UAAU,WAAW;AAC1C,SAAO,EAAE,GAAG,QAAQ,QAAQ;AAC9B;","names":["bucket"]}
1
+ {"version":3,"sources":["../../src/protocol/personal-server-write.ts"],"sourcesContent":["/**\n * Builder-side client for the Personal Server Write API.\n *\n * @remarks\n * A builder holding a write-grant (a grant whose scope entries carry the\n * `write:` prefix, see {@link formatScopeEntry}) writes into a user's Personal\n * Server in two steps:\n *\n * 1. {@link openWriteSession}: `POST /v1/write/session` with a Web3Signed\n * handshake that carries the grant id as a signed claim. The Personal\n * Server verifies the builder key against the grant and mints a\n * short-lived bearer token bound to `{ builder, grantId }`.\n * 2. {@link writeData}: `POST /v1/data/:scope` with that bearer plus an\n * `X-Vana-Write-Signature` proof, a second Web3Signed signature over the\n * representation the Personal Server will store, again carrying the grant\n * id as a signed claim. The Personal Server stores the proof with the\n * record under the reserved `$writtenBy` key so anyone holding the record\n * can verify who wrote it.\n *\n * What the proof covers: a JSON write signs the request body, which must be\n * the compact `JSON.stringify` form (the server rejects anything else with\n * `WRITE_BODY_NOT_CANONICAL`); a binary write signs the `$binary` record the\n * server stores for the bytes and their representation headers\n * ({@link binaryWriteSignedBytes}), not the raw bytes. Every proof is\n * single-use: a retry after a lost response signs a fresh proof.\n *\n * Derivatives name their sources through `lineage` (see\n * {@link deriveDataPointId}): for a JSON write the ids are the top-level\n * `lineage` field of the body (inside the signed bytes); for a binary write\n * they are the `lineage` field of the `X-Vana-Metadata` JSON (inside the\n * signed `$binary` record). The server validates them and mirrors them under\n * the reserved `$lineage` key. Callers never send `$writtenBy` or `$lineage`.\n *\n * @category Protocol\n */\n\nimport { sha256 } from \"@noble/hashes/sha2\";\nimport { bytesToHex, isAddress, type Address, type Hex } from \"viem\";\nimport { z } from \"zod\";\nimport { buildWeb3SignedHeader } from \"../auth/web3-signed-builder\";\nimport {\n type PersonalServerWriteError,\n WriteConflictError,\n WriteForbiddenError,\n WriteLineageError,\n WriteRejectedError,\n WriteRequestError,\n WriteSessionError,\n WriteSessionExpiredError,\n WriteUnauthorizedError,\n} from \"../errors\";\nimport { toBase64 } from \"../utils/encoding\";\nimport { IngestResponseSchema, type IngestResponse } from \"./data-file\";\nimport {\n assertDerivedScopeNaming,\n deriveDataPointId,\n isDataPointId,\n} from \"./lineage\";\nimport {\n isRecord,\n readPersonalServerErrorBody,\n} from \"./personal-server-error-body\";\nimport { scopeMatchesPattern } from \"./scopes\";\nimport {\n errorMessage,\n normalizeBaseUrl,\n proofKeyFor,\n resolveFetch,\n sendWithFreshProof,\n type WriteTransportRetryOptions,\n} from \"./write-request\";\nimport {\n resolveWriteSigner,\n type ResolveWriteSignerOptions,\n type WriteSigner,\n type WriteSignerSource,\n} from \"./write-signer\";\n\nexport type { WriteTransportRetryOptions };\n\n/** Path of the write-session handshake. */\nexport const WRITE_SESSION_PATH = \"/v1/write/session\";\n/** Header carrying the builder's per-write payload proof. */\nexport const WRITE_SIGNATURE_HEADER = \"X-Vana-Write-Signature\";\n/** Header carrying caller metadata (and `lineage`) for a binary write. */\nexport const WRITE_METADATA_HEADER = \"X-Vana-Metadata\";\n/** Field the lineage source ids travel in (body for JSON, metadata for binary). */\nexport const LINEAGE_FIELD = \"lineage\";\n/** The most sources one record may cite. */\nexport const MAX_LINEAGE_SOURCES = 256;\n/** Header carrying the filename of a binary write (printable ASCII names). */\nexport const WRITE_FILENAME_HEADER = \"X-Filename\";\n/**\n * Header carrying a filename the `X-Filename` header cannot: the Personal\n * Server percent-decodes `filename*=UTF-8''...` (RFC 5987).\n */\nexport const WRITE_CONTENT_DISPOSITION_HEADER = \"Content-Disposition\";\n/** Reserved record key the Personal Server stamps builder attribution into. */\nexport const WRITER_ATTRIBUTION_KEY = \"$writtenBy\";\n/** Reserved record key the Personal Server stamps lineage sources into. */\nexport const LINEAGE_KEY = \"$lineage\";\n/** Record keys a builder must never send. */\nexport const RESERVED_WRITE_KEYS: readonly string[] = [\n WRITER_ATTRIBUTION_KEY,\n LINEAGE_KEY,\n];\n\n/** An open write session: the bearer token plus what it was minted for. */\nexport interface WriteSession {\n /** Personal Server origin, without a trailing slash. */\n personalServerUrl: string;\n /** Web3Signed audience every proof under this session is addressed to. */\n audience: string;\n /** The write-grant the session is bound to. */\n grantId: string;\n /** The bearer token (`vana_write_...`). */\n accessToken: string;\n /** Unix milliseconds after which the token is no longer accepted. */\n expiresAt: number;\n /** Write patterns (prefix stripped) the session may write into. */\n writeScopes: readonly string[];\n /** The builder key the session was opened with; signs every write proof. */\n signer: WriteSigner;\n}\n\nexport interface OpenWriteSessionParams extends ResolveWriteSignerOptions {\n /** Personal Server origin, e.g. `https://ps.example.com`. */\n personalServerUrl: string;\n /** Builder key: a viem `LocalAccount`, `WalletClient`, or `{ signMessage }`. */\n signer: WriteSignerSource;\n /** The write-grant issued to the builder. */\n grantId: string;\n /** Web3Signed audience; defaults to `personalServerUrl`. */\n audience?: string;\n /** `fetch` to use; defaults to `globalThis.fetch`. */\n fetch?: typeof fetch;\n /** Extra request headers. */\n headers?: HeadersInit;\n retry?: WriteTransportRetryOptions;\n}\n\n/** A lineage source: a data point id, or the pair it is derived from. */\nexport type LineageSource = Hex | { ownerAddress: Address; scope: string };\n\n/** Bytes to store as an unstructured (binary) record. */\nexport interface WriteBinaryPayload {\n bytes: Uint8Array;\n /** Media type, e.g. `application/pdf`. Parameters are dropped when stored. */\n contentType: string;\n /**\n * Stored with the record. Sent as `X-Filename` when it is printable ASCII,\n * otherwise as `Content-Disposition: attachment; filename*=UTF-8''...`,\n * which the Personal Server decodes back to the same string. Must not have\n * leading or trailing whitespace (HTTP would strip it and the signed\n * representation would no longer match).\n */\n filename?: string;\n}\n\ninterface WriteDataBaseParams {\n session: WriteSession;\n /**\n * The scope to write into; must match one of the session's write patterns.\n * A derived scope must not share its first dot-segment with any source's\n * scope (the server rejects it with `LINEAGE_SCOPE_UNDER_SOURCE_PREFIX`):\n * put derivatives in the app's own namespace.\n */\n scope: string;\n /**\n * The data points this record was derived from: ids\n * ({@link deriveDataPointId}) or `{ ownerAddress, scope }` pairs the SDK\n * derives the id from. Distinct, at most {@link MAX_LINEAGE_SOURCES}, all\n * belonging to the same owner as the target scope, never the record's own\n * id. Sent lowercased as the record's `lineage` field, inside the signed\n * bytes. `[]` is an explicit root statement and is sent; absent or `null`\n * makes no statement. Pairs also let the SDK apply the naming rule\n * ({@link assertDerivedScopeNaming}) before anything is signed.\n */\n lineage?: readonly LineageSource[] | null;\n /** `fetch` to use; defaults to `globalThis.fetch`. */\n fetch?: typeof fetch;\n /** Extra request headers. */\n headers?: HeadersInit;\n retry?: WriteTransportRetryOptions;\n}\n\n/**\n * A JSON write: `data` is stored as the record (plus `lineage` when given).\n * Anything else to store goes inside `data`; `lineage` and reserved keys are\n * not accepted in it.\n */\nexport interface WriteJsonDataParams extends WriteDataBaseParams {\n data: Record<string, unknown>;\n binary?: never;\n metadata?: never;\n}\n\n/** A binary write: the bytes are stored as a `$binary` record. */\nexport interface WriteBinaryDataParams extends WriteDataBaseParams {\n binary: WriteBinaryPayload;\n data?: never;\n /**\n * Caller metadata stored with the record (`X-Vana-Metadata`, part of the\n * signed `$binary` record). Must not contain `lineage` (use the option) or\n * a reserved key.\n */\n metadata?: Record<string, unknown>;\n}\n\nexport type WriteDataParams = WriteJsonDataParams | WriteBinaryDataParams;\n\nconst WriteDataResultSchema = IngestResponseSchema.extend({\n // Present when the write carried lineage: the validated, lowercased ids.\n lineage: z.object({ sources: z.array(z.string()) }).optional(),\n});\n\n/** The Personal Server's ingest answer, plus the accepted lineage if any. */\nexport type WriteDataResult = IngestResponse & {\n lineage?: { sources: Hex[] };\n};\n\nconst WriteSessionResponseSchema = z.object({\n access_token: z.string().min(1),\n token_type: z.string(),\n expires_in: z.number().nonnegative(),\n scope: z.string(),\n});\n\nfunction dataPath(scope: string): string {\n return `/v1/data/${encodeURIComponent(scope)}`;\n}\n\n/**\n * Open a write session against a Personal Server.\n *\n * @remarks\n * Sends `POST /v1/write/session` with a Web3Signed `Authorization` header\n * whose signed claims carry `grantId`. The handshake proof is single-use on\n * the server; a transport retry signs a new one.\n *\n * @returns The session to pass to {@link writeData}.\n * @throws {WriteSessionError} When the Personal Server refuses the handshake\n * (`errorCode` names why: `UNREGISTERED_BUILDER`, `GRANT_REQUIRED`,\n * `GRANT_REVOKED`, `SCOPE_MISMATCH` for a grant without write entries,\n * `INVALID_SIGNATURE` when the key is not the grantee,\n * `GRANT_OWNER_MISMATCH`, ...).\n * @throws {WriteTransportError} When `fetch` threw on every attempt.\n * @throws {WriteRequestError} When the signer is unusable.\n */\nexport async function openWriteSession(\n params: OpenWriteSessionParams,\n): Promise<WriteSession> {\n const fetchFn = resolveFetch(params.fetch);\n const personalServerUrl = normalizeBaseUrl(params.personalServerUrl);\n const audience = params.audience ?? personalServerUrl;\n const signer = resolveWriteSigner(params.signer, { account: params.account });\n\n const response = await sendWithFreshProof(\n \"Write session handshake\",\n fetchFn,\n params.retry,\n proofKeyFor({\n aud: audience,\n method: \"POST\",\n uri: WRITE_SESSION_PATH,\n grantId: params.grantId,\n }),\n async (iat) => {\n const headers = new Headers(params.headers);\n headers.set(\n \"Authorization\",\n await buildWeb3SignedHeader({\n signMessage: signer.signMessage,\n aud: audience,\n method: \"POST\",\n uri: WRITE_SESSION_PATH,\n grantId: params.grantId,\n iat,\n }),\n );\n return {\n url: `${personalServerUrl}${WRITE_SESSION_PATH}`,\n init: { method: \"POST\", headers },\n };\n },\n );\n // Read the clock before parsing so `expiresAt` never overstates the\n // remaining lifetime.\n const mintedAt = Date.now();\n\n if (!response.ok) {\n const { errorCode, message, details } =\n await readPersonalServerErrorBody(response);\n throw new WriteSessionError(\n message ??\n `Write session handshake failed: ${response.status} ${response.statusText}`,\n response.status,\n errorCode,\n details,\n );\n }\n\n let body: unknown;\n try {\n body = await response.json();\n } catch (err) {\n throw new WriteSessionError(\n \"Write session response is not JSON\",\n response.status,\n null,\n { cause: errorMessage(err) },\n );\n }\n const parsed = WriteSessionResponseSchema.safeParse(body);\n if (!parsed.success) {\n throw new WriteSessionError(\n \"Write session response is not a session\",\n response.status,\n null,\n { issues: parsed.error.issues },\n );\n }\n if (parsed.data.token_type.toLowerCase() !== \"bearer\") {\n throw new WriteSessionError(\n `Write session token type is not Bearer: ${parsed.data.token_type}`,\n response.status,\n );\n }\n\n return {\n personalServerUrl,\n audience,\n grantId: params.grantId,\n accessToken: parsed.data.access_token,\n expiresAt: mintedAt + parsed.data.expires_in * 1000,\n writeScopes: parsed.data.scope.split(\" \").filter((s) => s.length > 0),\n signer,\n };\n}\n\n/** Every field {@link writeData} relies on, checked before any is used. */\nfunction isWriteSession(value: unknown): value is WriteSession {\n if (!isRecord(value)) return false;\n const signer: unknown = value.signer;\n return (\n typeof value.personalServerUrl === \"string\" &&\n typeof value.audience === \"string\" &&\n typeof value.grantId === \"string\" &&\n typeof value.accessToken === \"string\" &&\n value.accessToken.length > 0 &&\n typeof value.expiresAt === \"number\" &&\n Number.isFinite(value.expiresAt) &&\n Array.isArray(value.writeScopes) &&\n value.writeScopes.every((scope) => typeof scope === \"string\") &&\n isRecord(signer) &&\n typeof signer.signMessage === \"function\"\n );\n}\n\n/** `true` when one of the session's write patterns covers `scope`. */\nexport function sessionCoversScope(\n session: Pick<WriteSession, \"writeScopes\">,\n scope: string,\n): boolean {\n if (\n !isRecord(session) ||\n !Array.isArray(session.writeScopes) ||\n !session.writeScopes.every((pattern) => typeof pattern === \"string\")\n ) {\n throw new WriteRequestError(\"session must come from openWriteSession\");\n }\n return session.writeScopes.some((pattern) =>\n scopeMatchesPattern(scope, pattern),\n );\n}\n\n/**\n * The media type the Personal Server stores for a binary write: the\n * `Content-Type` minus its parameters, `application/octet-stream` when blank.\n */\nexport function normalizeBinaryMimeType(contentType: string | null): string {\n if (!contentType) return \"application/octet-stream\";\n return contentType.split(\";\")[0].trim() || \"application/octet-stream\";\n}\n\n/**\n * How the Personal Server reads an `X-Vana-Metadata` header: JSON when it\n * parses, the raw string otherwise, `undefined` when absent or blank.\n */\nexport function parseWriteMetadataHeader(value: string | null): unknown {\n if (value === null) return undefined;\n const trimmed = value.trim();\n if (trimmed === \"\") return undefined;\n try {\n return JSON.parse(trimmed);\n } catch {\n return value;\n }\n}\n\n/**\n * Encode a metadata object for the `X-Vana-Metadata` header. Non-ASCII\n * characters are `\\uXXXX`-escaped so the value is header-safe everywhere;\n * it parses back to the same object.\n */\nexport function encodeWriteMetadataHeader(\n metadata: Record<string, unknown>,\n): string {\n let json: string;\n try {\n json = JSON.stringify(metadata);\n } catch (err) {\n throw new WriteRequestError(\n `metadata is not JSON-serialisable: ${errorMessage(err)}`,\n );\n }\n if (typeof json !== \"string\") {\n throw new WriteRequestError(\"metadata must serialise to JSON\");\n }\n return json.replace(\n /[\\u007f-\\uffff]/g,\n (c) => `\\\\u${c.charCodeAt(0).toString(16).padStart(4, \"0\")}`,\n );\n}\n\nexport interface BinaryWriteSignedBytesInput {\n /** The raw body bytes the write sends. */\n bytes: Uint8Array;\n /** The `Content-Type` header the write sends (parameters are ignored). */\n contentType: string;\n /** The `X-Filename` header value the write sends, if any. */\n filename?: string;\n /** The exact `X-Vana-Metadata` header value the write sends, if any. */\n metadataHeader?: string;\n}\n\n/**\n * The bytes a builder signs for a binary write: the compact JSON of the\n * `$binary` record the Personal Server stores for these headers and bytes\n * (`personal-server-ts` `binaryWriteSignedBytes`, mirrored field for field).\n *\n * @returns UTF-8 bytes of the stored record's compact JSON.\n */\nexport function binaryWriteSignedBytes(\n input: BinaryWriteSignedBytesInput,\n): Uint8Array {\n const metadata = parseWriteMetadataHeader(input.metadataHeader ?? null);\n const record: Record<string, unknown> = {\n $binary: true,\n mimeType: normalizeBinaryMimeType(input.contentType),\n ...(input.filename ? { filename: input.filename } : {}),\n sizeBytes: input.bytes.length,\n contentHash: bytesToHex(sha256(input.bytes)),\n encoding: \"base64\",\n content: toBase64(input.bytes),\n ...(metadata !== undefined ? { metadata } : {}),\n };\n return new TextEncoder().encode(JSON.stringify(record));\n}\n\nconst PRINTABLE_ASCII = /^[\\x20-\\x7e]*$/;\n\n/**\n * Carry a filename the way the Personal Server reads it back verbatim:\n * `X-Filename` for printable ASCII, RFC 5987 `filename*` otherwise (a raw\n * non-ASCII header value is rejected by `fetch` or mangled in transit).\n */\nfunction setFilenameHeader(headers: Headers, filename: string): void {\n if (PRINTABLE_ASCII.test(filename)) {\n headers.set(WRITE_FILENAME_HEADER, filename);\n return;\n }\n headers.set(\n WRITE_CONTENT_DISPOSITION_HEADER,\n `attachment; filename*=UTF-8''${encodeURIComponent(filename)}`,\n );\n}\n\nfunction assertNoReservedKeys(\n value: Record<string, unknown>,\n where: string,\n): void {\n for (const key of RESERVED_WRITE_KEYS) {\n if (Object.prototype.hasOwnProperty.call(value, key)) {\n throw new WriteRequestError(\n `${where} must not contain the reserved ${key} key; the Personal Server stamps it`,\n { key },\n );\n }\n }\n}\n\nfunction isLineagePair(\n value: unknown,\n): value is { ownerAddress: Address; scope: string } {\n return (\n isRecord(value) &&\n typeof value.ownerAddress === \"string\" &&\n typeof value.scope === \"string\"\n );\n}\n\n/**\n * Validate and lowercase the sources the way the Personal Server will\n * (distinct, at most {@link MAX_LINEAGE_SOURCES}, never the record's own id)\n * and apply the naming rule to every source given as a pair. `[]` is kept:\n * it is an explicit root statement.\n */\nfunction normalizeLineage(lineage: unknown, derivedScope: string): Hex[] {\n if (!Array.isArray(lineage)) {\n throw new WriteRequestError(\"lineage must be an array of data point ids\");\n }\n if (lineage.length > MAX_LINEAGE_SOURCES) {\n throw new WriteRequestError(\n `lineage lists ${lineage.length} sources; the maximum is ${MAX_LINEAGE_SOURCES}`,\n { max: MAX_LINEAGE_SOURCES, count: lineage.length },\n );\n }\n const seen = new Set<string>();\n const sources: Hex[] = [];\n const sourceScopes: string[] = [];\n const ownIds = new Set<string>();\n for (const entry of lineage as unknown[]) {\n let id: Hex;\n if (isLineagePair(entry)) {\n if (!isAddress(entry.ownerAddress, { strict: false })) {\n throw new WriteRequestError(\n \"lineage source ownerAddress must be an EVM address\",\n { ownerAddress: entry.ownerAddress },\n );\n }\n if (entry.scope.length === 0) {\n throw new WriteRequestError(\"lineage source scope is required\");\n }\n id = deriveDataPointId(entry.ownerAddress, entry.scope);\n sourceScopes.push(entry.scope);\n ownIds.add(deriveDataPointId(entry.ownerAddress, derivedScope));\n } else if (isDataPointId(entry)) {\n id = entry;\n } else {\n throw new WriteRequestError(\n \"lineage entries must be 32-byte hex data point ids or { ownerAddress, scope } pairs\",\n { entry },\n );\n }\n const normalized = id.toLowerCase() as Hex;\n if (seen.has(normalized)) {\n throw new WriteRequestError(\"lineage must not repeat a data point id\", {\n dataPointId: normalized,\n });\n }\n seen.add(normalized);\n sources.push(normalized);\n }\n for (const own of ownIds) {\n if (seen.has(own)) {\n throw new WriteRequestError(\n \"lineage must not cite the record's own data point\",\n { dataPointId: own, scope: derivedScope },\n );\n }\n }\n assertDerivedScopeNaming(derivedScope, sourceScopes);\n return sources;\n}\n\nfunction assertNoLineageField(\n value: Record<string, unknown>,\n where: string,\n): void {\n if (Object.prototype.hasOwnProperty.call(value, LINEAGE_FIELD)) {\n throw new WriteRequestError(\n `${where}.${LINEAGE_FIELD} is reserved; pass sources through the lineage option`,\n );\n }\n}\n\n/** The `X-Vana-Metadata` header for a binary write, or `undefined`. */\nfunction buildMetadataHeader(\n metadata: Record<string, unknown> | undefined,\n sources: Hex[] | undefined,\n): string | undefined {\n if (metadata !== undefined) {\n if (!isRecord(metadata)) {\n throw new WriteRequestError(\"metadata must be a plain object\");\n }\n assertNoReservedKeys(metadata, \"metadata\");\n assertNoLineageField(metadata, \"metadata\");\n }\n if (metadata === undefined && sources === undefined) return undefined;\n return encodeWriteMetadataHeader({\n ...(metadata ?? {}),\n ...(sources !== undefined ? { [LINEAGE_FIELD]: sources } : {}),\n });\n}\n\ninterface PreparedWrite {\n body: Uint8Array;\n /** What the proof's `bodyHash` commits to. */\n signedBytes: Uint8Array;\n contentType: string;\n filename?: string;\n metadataHeader?: string;\n}\n\nfunction prepareWrite(params: WriteDataParams): PreparedWrite {\n if (params.binary !== undefined && params.data !== undefined) {\n throw new WriteRequestError(\"Pass either data or binary, not both\");\n }\n // Absent or null lineage makes no statement: send nothing. Anything else\n // (`[]` included: an explicit root) is validated before it is touched,\n // since callers may be untyped.\n const rawLineage: unknown = params.lineage;\n const sources =\n rawLineage === undefined || rawLineage === null\n ? undefined\n : normalizeLineage(rawLineage, params.scope);\n\n if (params.binary !== undefined) {\n const { bytes, contentType, filename } = params.binary;\n if (!(bytes instanceof Uint8Array)) {\n throw new WriteRequestError(\"binary.bytes must be a Uint8Array\");\n }\n if (typeof contentType !== \"string\" || contentType.trim() === \"\") {\n throw new WriteRequestError(\"binary.contentType is required\");\n }\n if (filename !== undefined) {\n if (typeof filename !== \"string\") {\n throw new WriteRequestError(\"binary.filename must be a string\");\n }\n if (filename !== filename.trim()) {\n throw new WriteRequestError(\n \"binary.filename must not have leading or trailing whitespace\",\n );\n }\n }\n const metadataHeader = buildMetadataHeader(params.metadata, sources);\n return {\n body: bytes,\n signedBytes: binaryWriteSignedBytes({\n bytes,\n contentType,\n filename,\n metadataHeader,\n }),\n contentType,\n filename,\n metadataHeader,\n };\n }\n\n if (params.data === undefined) {\n throw new WriteRequestError(\"Pass data (a JSON object) or binary\");\n }\n if (!isRecord(params.data)) {\n throw new WriteRequestError(\"data must be a plain JSON object\");\n }\n if (params.metadata !== undefined) {\n throw new WriteRequestError(\n \"metadata applies to binary writes; put fields to store inside data\",\n );\n }\n assertNoReservedKeys(params.data, \"data\");\n assertNoLineageField(params.data, \"data\");\n const record =\n sources === undefined\n ? params.data\n : { ...params.data, [LINEAGE_FIELD]: sources };\n let text: string;\n try {\n // Compact JSON is the contract: the server re-serialises the parsed\n // record and requires it to match the signed bytes.\n text = JSON.stringify(record);\n } catch (err) {\n throw new WriteRequestError(\n `data is not JSON-serialisable: ${errorMessage(err)}`,\n );\n }\n if (typeof text !== \"string\" || !text.startsWith(\"{\")) {\n throw new WriteRequestError(\"data must serialise to a JSON object\");\n }\n const body = new TextEncoder().encode(text);\n return {\n body,\n signedBytes: body,\n contentType: \"application/json\",\n };\n}\n\nasync function writeErrorFromResponse(\n response: Response,\n): Promise<PersonalServerWriteError> {\n const { errorCode, message, details } =\n await readPersonalServerErrorBody(response);\n const text =\n message ??\n `Personal Server write failed: ${response.status} ${response.statusText}`;\n // Lineage rejections span 400 / 422 / 502; the code is the discriminator.\n if (response.status === 422 || errorCode?.startsWith(\"LINEAGE_\")) {\n return new WriteLineageError(text, response.status, errorCode, details);\n }\n switch (response.status) {\n case 401:\n return new WriteUnauthorizedError(text, errorCode, details);\n case 403:\n return new WriteForbiddenError(text, errorCode, details);\n case 409:\n return new WriteConflictError(text, errorCode, details);\n default:\n return new WriteRejectedError(text, response.status, errorCode, details);\n }\n}\n\n/**\n * Write one record into a scope under an open write session.\n *\n * @remarks\n * Sends `POST /v1/data/:scope` with `Authorization: Bearer <session token>`\n * and `X-Vana-Write-Signature`, a Web3Signed proof over the stored\n * representation carrying the session's `grantId` as a signed claim. JSON\n * writes send `data` as compact JSON with `Content-Type: application/json`;\n * binary writes send the bytes with their `Content-Type`, `X-Filename`, and\n * sign {@link binaryWriteSignedBytes}. `lineage` is the record's top-level\n * `lineage` field for JSON and the `lineage` field of `X-Vana-Metadata` for\n * binary, so the proof covers it either way.\n *\n * @returns The ingest answer (`scope`, `collectedAt`, `status`, and\n * `lineage.sources` when the write carried lineage).\n * @throws {WriteRequestError} Before sending: no payload, a reserved key, a\n * malformed lineage id, or non-object data.\n * @throws {WriteSessionExpiredError} Before sending: the session token has\n * passed its lifetime.\n * @throws {WriteUnauthorizedError} 401 (proof or session rejected).\n * @throws {WriteForbiddenError} 403 (grant no longer authorises the write).\n * @throws {WriteConflictError} 409.\n * @throws {WriteLineageError} Any `LINEAGE_*` rejection: 422\n * `LINEAGE_SOURCE_UNKNOWN` (`details.unknown`), 400 `LINEAGE_INVALID` /\n * `LINEAGE_SCOPE_UNDER_SOURCE_PREFIX`, 502 `LINEAGE_SOURCE_LOOKUP_FAILED`.\n * @throws {WriteRejectedError} Any other non-2xx.\n * @throws {WriteTransportError} `fetch` threw on every attempt.\n */\nexport async function writeData(\n params: WriteDataParams,\n): Promise<WriteDataResult> {\n const { session } = params;\n if (!isWriteSession(session)) {\n throw new WriteRequestError(\"session must come from openWriteSession\");\n }\n const fetchFn = resolveFetch(params.fetch);\n if (typeof params.scope !== \"string\" || params.scope.length === 0) {\n throw new WriteRequestError(\"scope is required\");\n }\n const prepared = prepareWrite(params);\n if (Date.now() >= session.expiresAt) {\n throw new WriteSessionExpiredError(\n \"Write session has expired; open a new session\",\n { grantId: session.grantId, expiresAt: session.expiresAt },\n );\n }\n\n const path = dataPath(params.scope);\n const response = await sendWithFreshProof(\n `Write to ${params.scope}`,\n fetchFn,\n params.retry,\n proofKeyFor({\n aud: session.audience,\n method: \"POST\",\n uri: path,\n grantId: session.grantId,\n signedBytes: prepared.signedBytes,\n }),\n async (iat) => {\n const headers = new Headers(params.headers);\n headers.set(\"Content-Type\", prepared.contentType);\n headers.set(\"Authorization\", `Bearer ${session.accessToken}`);\n if (prepared.filename) {\n setFilenameHeader(headers, prepared.filename);\n }\n if (prepared.metadataHeader !== undefined) {\n headers.set(WRITE_METADATA_HEADER, prepared.metadataHeader);\n }\n headers.set(\n WRITE_SIGNATURE_HEADER,\n await buildWeb3SignedHeader({\n signMessage: session.signer.signMessage,\n aud: session.audience,\n method: \"POST\",\n uri: path,\n body: prepared.signedBytes,\n grantId: session.grantId,\n iat,\n }),\n );\n return {\n url: `${session.personalServerUrl}${path}`,\n init: {\n method: \"POST\",\n headers,\n body: prepared.body as unknown as BodyInit,\n },\n };\n },\n );\n\n if (!response.ok) {\n throw await writeErrorFromResponse(response);\n }\n let body: unknown;\n try {\n body = await response.json();\n } catch (err) {\n throw new WriteRejectedError(\n \"Personal Server write response is not JSON\",\n response.status,\n null,\n { cause: errorMessage(err) },\n );\n }\n const parsed = WriteDataResultSchema.safeParse(body);\n if (!parsed.success) {\n throw new WriteRejectedError(\n \"Personal Server write response is not an ingest result\",\n response.status,\n null,\n { issues: parsed.error.issues },\n );\n }\n return parsed.data as WriteDataResult;\n}\n\ntype DistributiveOmit<T, K extends PropertyKey> = T extends unknown\n ? Omit<T, K>\n : never;\n\nexport type WritePersonalServerDataParams = Omit<\n OpenWriteSessionParams,\n \"fetch\" | \"headers\" | \"retry\"\n> &\n DistributiveOmit<WriteDataParams, \"session\">;\n\n/** {@link writePersonalServerData}'s answer: the ingest result plus the session it opened. */\nexport interface WritePersonalServerDataResult extends WriteDataResult {\n /** Reuse for further writes until `expiresAt`. */\n session: WriteSession;\n}\n\n/**\n * Open a write session and write one record in a single call.\n *\n * @remarks\n * Equivalent to {@link openWriteSession} followed by {@link writeData}. The\n * session is returned so further writes can reuse it; opening one per write\n * is correct but costs an extra signature and round-trip each time.\n *\n * @throws Everything {@link openWriteSession} and {@link writeData} throw.\n */\nexport async function writePersonalServerData(\n params: WritePersonalServerDataParams,\n): Promise<WritePersonalServerDataResult> {\n const session = await openWriteSession({\n personalServerUrl: params.personalServerUrl,\n signer: params.signer,\n grantId: params.grantId,\n account: params.account,\n audience: params.audience,\n fetch: params.fetch,\n headers: params.headers,\n retry: params.retry,\n });\n const writeParams = { ...params, session } as WriteDataParams;\n const result = await writeData(writeParams);\n return { ...result, session };\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,142 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+ var write_request_exports = {};
20
+ __export(write_request_exports, {
21
+ errorMessage: () => errorMessage,
22
+ finiteOr: () => finiteOr,
23
+ nextProofIat: () => nextProofIat,
24
+ normalizeBaseUrl: () => normalizeBaseUrl,
25
+ proofKeyFor: () => proofKeyFor,
26
+ resolveFetch: () => resolveFetch,
27
+ sendWithFreshProof: () => sendWithFreshProof,
28
+ sleep: () => sleep
29
+ });
30
+ module.exports = __toCommonJS(write_request_exports);
31
+ var import_sha2 = require("@noble/hashes/sha2");
32
+ var import_viem = require("viem");
33
+ var import_errors = require("../errors");
34
+ function normalizeBaseUrl(url) {
35
+ return url.replace(/\/+$/, "");
36
+ }
37
+ function resolveFetch(fetchFn) {
38
+ const resolved = fetchFn ?? globalThis.fetch;
39
+ if (resolved === void 0) {
40
+ throw new import_errors.WriteRequestError("No fetch implementation available");
41
+ }
42
+ return resolved;
43
+ }
44
+ function errorMessage(err) {
45
+ return err instanceof Error ? err.message : String(err);
46
+ }
47
+ function finiteOr(value, fallback) {
48
+ return typeof value === "number" && Number.isFinite(value) ? value : fallback;
49
+ }
50
+ function sleep(ms) {
51
+ return new Promise((resolve) => setTimeout(resolve, ms));
52
+ }
53
+ const issuedProofIats = /* @__PURE__ */ new Map();
54
+ const issuedProofBuckets = /* @__PURE__ */ new Map();
55
+ let issuedProofIatsPrunedAtSec = 0;
56
+ const WEB3_SIGNED_PROOF_LIFETIME_SECONDS = 300;
57
+ const WEB3_SIGNED_CLOCK_SKEW_SECONDS = 60;
58
+ const PROOF_IAT_RETENTION_SECONDS = WEB3_SIGNED_PROOF_LIFETIME_SECONDS + WEB3_SIGNED_CLOCK_SKEW_SECONDS;
59
+ const PROOF_IAT_MAX_AHEAD_SECONDS = 30;
60
+ function pruneIssuedProofIats(nowSec) {
61
+ if (issuedProofIatsPrunedAtSec === nowSec) return;
62
+ issuedProofIatsPrunedAtSec = nowSec;
63
+ const cutoff = nowSec - PROOF_IAT_RETENTION_SECONDS;
64
+ for (const [sec, keys] of issuedProofBuckets) {
65
+ if (sec >= cutoff) continue;
66
+ for (const key of keys) issuedProofIats.delete(key);
67
+ issuedProofBuckets.delete(sec);
68
+ }
69
+ }
70
+ function setIssuedProofIat(key, iat, previous) {
71
+ if (previous !== void 0) {
72
+ const bucket2 = issuedProofBuckets.get(previous);
73
+ bucket2?.delete(key);
74
+ if (bucket2?.size === 0) issuedProofBuckets.delete(previous);
75
+ }
76
+ issuedProofIats.set(key, iat);
77
+ let bucket = issuedProofBuckets.get(iat);
78
+ if (bucket === void 0) {
79
+ bucket = /* @__PURE__ */ new Set();
80
+ issuedProofBuckets.set(iat, bucket);
81
+ }
82
+ bucket.add(key);
83
+ }
84
+ function nextProofIat(proofKey) {
85
+ const nowSec = Math.floor(Date.now() / 1e3);
86
+ pruneIssuedProofIats(nowSec);
87
+ const last = issuedProofIats.get(proofKey);
88
+ const iat = last === void 0 ? nowSec : Math.max(nowSec, last + 1);
89
+ setIssuedProofIat(proofKey, iat, last);
90
+ const waitSec = iat - nowSec - PROOF_IAT_MAX_AHEAD_SECONDS;
91
+ if (waitSec <= 0) return Promise.resolve(iat);
92
+ return sleep(waitSec * 1e3).then(() => iat);
93
+ }
94
+ function proofKeyFor(parts) {
95
+ return (0, import_viem.bytesToHex)(
96
+ (0, import_sha2.sha256)(
97
+ new TextEncoder().encode(
98
+ JSON.stringify([
99
+ parts.aud,
100
+ parts.method,
101
+ parts.uri,
102
+ parts.grantId,
103
+ parts.signedBytes ? (0, import_viem.bytesToHex)((0, import_sha2.sha256)(parts.signedBytes)) : ""
104
+ ])
105
+ )
106
+ )
107
+ );
108
+ }
109
+ async function sendWithFreshProof(label, fetchFn, options, proofKey, build) {
110
+ const attempts = Math.max(1, Math.floor(finiteOr(options?.attempts, 3)));
111
+ let delayMs = Math.max(0, finiteOr(options?.initialDelayMs, 1e3));
112
+ let lastError;
113
+ for (let attempt = 0; attempt < attempts; attempt++) {
114
+ const { url, init } = await build(await nextProofIat(proofKey));
115
+ try {
116
+ return await fetchFn(url, init);
117
+ } catch (err) {
118
+ lastError = err;
119
+ }
120
+ if (attempt < attempts - 1) {
121
+ await sleep(delayMs);
122
+ delayMs *= 2;
123
+ }
124
+ }
125
+ throw new import_errors.WriteTransportError(
126
+ `${label} failed after ${attempts} attempt(s): ${errorMessage(lastError)}`,
127
+ attempts,
128
+ lastError
129
+ );
130
+ }
131
+ // Annotate the CommonJS export names for ESM import in node:
132
+ 0 && (module.exports = {
133
+ errorMessage,
134
+ finiteOr,
135
+ nextProofIat,
136
+ normalizeBaseUrl,
137
+ proofKeyFor,
138
+ resolveFetch,
139
+ sendWithFreshProof,
140
+ sleep
141
+ });
142
+ //# sourceMappingURL=write-request.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/protocol/write-request.ts"],"sourcesContent":["/**\n * Transport shared by every builder call that authenticates with the\n * Personal Server Write API: the data writes of\n * {@link ../protocol/personal-server-write} and the derivative question\n * routes of {@link ../protocol/derivative-questions}.\n *\n * @remarks\n * Both sign a single-use Web3Signed proof per request, so both need the same\n * two things: a `fetch` wrapper that re-signs on every transport attempt, and\n * one process-wide record of the `iat` seconds already issued, so two proofs\n * for the same request identity can never come out byte-identical (the server\n * would reject the second as a replay). The record must be shared, not\n * per-module: a builder that polls one question every few milliseconds signs\n * the same `{ aud, method, uri, bodyHash, grantId }` many times a second.\n *\n * @internal\n */\n\nimport { sha256 } from \"@noble/hashes/sha2\";\nimport { bytesToHex } from \"viem\";\nimport { WriteRequestError, WriteTransportError } from \"../errors\";\n\n/**\n * Transport-level retry knobs shared by every Write API call.\n *\n * @remarks\n * Applies only when `fetch` **throws** (connection reset, DNS, a relay drop).\n * Every attempt signs a fresh proof, because the Personal Server consumes a\n * proof the moment it accepts it. A received HTTP response is never retried:\n * a 4xx/5xx is surfaced as a typed error.\n * @category Protocol\n */\nexport interface WriteTransportRetryOptions {\n /** Total attempts including the first (default 3). `1` disables retries. */\n attempts?: number;\n /** Delay before the first retry (ms); doubles per retry (default 1_000). */\n initialDelayMs?: number;\n}\n\n/** Strip trailing slashes so a base URL concatenates with a path. */\nexport function normalizeBaseUrl(url: string): string {\n return url.replace(/\\/+$/, \"\");\n}\n\n/** The caller's `fetch`, else the global one. */\nexport function resolveFetch(fetchFn: typeof fetch | undefined): typeof fetch {\n const resolved = fetchFn ?? globalThis.fetch;\n if (resolved === undefined) {\n throw new WriteRequestError(\"No fetch implementation available\");\n }\n return resolved;\n}\n\nexport function errorMessage(err: unknown): string {\n return err instanceof Error ? err.message : String(err);\n}\n\nexport function finiteOr(value: number | undefined, fallback: number): number {\n return typeof value === \"number\" && Number.isFinite(value) ? value : fallback;\n}\n\nexport function sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\n/**\n * The Personal Server consumes every proof it accepts, and a Web3Signed\n * payload is fully determined by `{ aud, method, uri, bodyHash, grantId, iat,\n * exp }`, so two proofs for the same request signed within one second would\n * be byte-identical and the second rejected as a replay. Remember the\n * highest `iat` issued per request identity in this process and bump past\n * it when a second proof for the same identity falls inside the same second.\n *\n * A mark is kept for as long as the server can still remember the proof it\n * guards (its lifetime plus the verifier's clock skew), so a proof is never\n * re-issued while it could still be rejected as a replay: not by a burst,\n * and not by a wall clock stepping backwards (an identical request after a\n * step back waits for the clock instead of reusing the mark). Marks are\n * bucketed by their `iat` second, so pruning (once per second) only touches\n * the buckets that fell out of the retention window: the work per proof is\n * amortised constant and the map is bounded by the distinct requests signed\n * in the window.\n */\nconst issuedProofIats = new Map<string, number>();\n/** `iat` second -> identities whose current mark is that second. */\nconst issuedProofBuckets = new Map<number, Set<string>>();\nlet issuedProofIatsPrunedAtSec = 0;\n/** `buildWeb3SignedHeader`'s default `exp - iat`. */\nconst WEB3_SIGNED_PROOF_LIFETIME_SECONDS = 300;\n/** The verifier's tolerated clock skew (`verifyWeb3Signed`). */\nconst WEB3_SIGNED_CLOCK_SKEW_SECONDS = 60;\nconst PROOF_IAT_RETENTION_SECONDS =\n WEB3_SIGNED_PROOF_LIFETIME_SECONDS + WEB3_SIGNED_CLOCK_SKEW_SECONDS;\n/**\n * How far ahead of the clock a bumped `iat` may run when the proof is sent.\n * The verifier tolerates 60 s of skew. A burst of identical requests that\n * would need to run further ahead waits for the clock instead, so a proof is\n * never repeated; sustained identical requests are throttled to one per\n * second after a burst of this many.\n */\nconst PROOF_IAT_MAX_AHEAD_SECONDS = 30;\n\nfunction pruneIssuedProofIats(nowSec: number): void {\n if (issuedProofIatsPrunedAtSec === nowSec) return;\n issuedProofIatsPrunedAtSec = nowSec;\n const cutoff = nowSec - PROOF_IAT_RETENTION_SECONDS;\n // There is at most one bucket per second in the window, so this walk is\n // bounded by the window length, not by the number of marks.\n for (const [sec, keys] of issuedProofBuckets) {\n if (sec >= cutoff) continue;\n for (const key of keys) issuedProofIats.delete(key);\n issuedProofBuckets.delete(sec);\n }\n}\n\nfunction setIssuedProofIat(key: string, iat: number, previous?: number): void {\n if (previous !== undefined) {\n const bucket = issuedProofBuckets.get(previous);\n bucket?.delete(key);\n if (bucket?.size === 0) issuedProofBuckets.delete(previous);\n }\n issuedProofIats.set(key, iat);\n let bucket = issuedProofBuckets.get(iat);\n if (bucket === undefined) {\n bucket = new Set();\n issuedProofBuckets.set(iat, bucket);\n }\n bucket.add(key);\n}\n\n/**\n * Reserve the next `iat` for a request identity. The reservation is made\n * synchronously so concurrent callers never share a value; the returned\n * promise only waits when the reserved `iat` is further ahead of the clock\n * than {@link PROOF_IAT_MAX_AHEAD_SECONDS}.\n */\nexport function nextProofIat(proofKey: string): Promise<number> {\n const nowSec = Math.floor(Date.now() / 1000);\n pruneIssuedProofIats(nowSec);\n const last = issuedProofIats.get(proofKey);\n const iat = last === undefined ? nowSec : Math.max(nowSec, last + 1);\n setIssuedProofIat(proofKey, iat, last);\n const waitSec = iat - nowSec - PROOF_IAT_MAX_AHEAD_SECONDS;\n if (waitSec <= 0) return Promise.resolve(iat);\n return sleep(waitSec * 1000).then(() => iat);\n}\n\n/** The identity a proof is deduplicated by. */\nexport function proofKeyFor(parts: {\n aud: string;\n method: string;\n uri: string;\n grantId: string;\n signedBytes?: Uint8Array;\n}): string {\n // A digest, so a retained mark costs a fixed amount of memory whatever the\n // request looked like.\n return bytesToHex(\n sha256(\n new TextEncoder().encode(\n JSON.stringify([\n parts.aud,\n parts.method,\n parts.uri,\n parts.grantId,\n parts.signedBytes ? bytesToHex(sha256(parts.signedBytes)) : \"\",\n ]),\n ),\n ),\n );\n}\n\n/**\n * Send a request, re-signing it on every attempt. Only a thrown `fetch` is\n * retried; the proof builder and any received response are never retried.\n */\nexport async function sendWithFreshProof(\n label: string,\n fetchFn: typeof fetch,\n options: WriteTransportRetryOptions | undefined,\n proofKey: string,\n build: (iat: number) => Promise<{ url: string; init: RequestInit }>,\n): Promise<Response> {\n const attempts = Math.max(1, Math.floor(finiteOr(options?.attempts, 3)));\n let delayMs = Math.max(0, finiteOr(options?.initialDelayMs, 1_000));\n let lastError: unknown;\n for (let attempt = 0; attempt < attempts; attempt++) {\n const { url, init } = await build(await nextProofIat(proofKey));\n try {\n return await fetchFn(url, init);\n } catch (err) {\n lastError = err;\n }\n if (attempt < attempts - 1) {\n await sleep(delayMs);\n delayMs *= 2;\n }\n }\n throw new WriteTransportError(\n `${label} failed after ${attempts} attempt(s): ${errorMessage(lastError)}`,\n attempts,\n lastError,\n );\n}\n"],"mappings":"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","names":["bucket"]}
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Transport shared by every builder call that authenticates with the
3
+ * Personal Server Write API: the data writes of
4
+ * {@link ../protocol/personal-server-write} and the derivative question
5
+ * routes of {@link ../protocol/derivative-questions}.
6
+ *
7
+ * @remarks
8
+ * Both sign a single-use Web3Signed proof per request, so both need the same
9
+ * two things: a `fetch` wrapper that re-signs on every transport attempt, and
10
+ * one process-wide record of the `iat` seconds already issued, so two proofs
11
+ * for the same request identity can never come out byte-identical (the server
12
+ * would reject the second as a replay). The record must be shared, not
13
+ * per-module: a builder that polls one question every few milliseconds signs
14
+ * the same `{ aud, method, uri, bodyHash, grantId }` many times a second.
15
+ *
16
+ * @internal
17
+ */
18
+ /**
19
+ * Transport-level retry knobs shared by every Write API call.
20
+ *
21
+ * @remarks
22
+ * Applies only when `fetch` **throws** (connection reset, DNS, a relay drop).
23
+ * Every attempt signs a fresh proof, because the Personal Server consumes a
24
+ * proof the moment it accepts it. A received HTTP response is never retried:
25
+ * a 4xx/5xx is surfaced as a typed error.
26
+ * @category Protocol
27
+ */
28
+ export interface WriteTransportRetryOptions {
29
+ /** Total attempts including the first (default 3). `1` disables retries. */
30
+ attempts?: number;
31
+ /** Delay before the first retry (ms); doubles per retry (default 1_000). */
32
+ initialDelayMs?: number;
33
+ }
34
+ /** Strip trailing slashes so a base URL concatenates with a path. */
35
+ export declare function normalizeBaseUrl(url: string): string;
36
+ /** The caller's `fetch`, else the global one. */
37
+ export declare function resolveFetch(fetchFn: typeof fetch | undefined): typeof fetch;
38
+ export declare function errorMessage(err: unknown): string;
39
+ export declare function finiteOr(value: number | undefined, fallback: number): number;
40
+ export declare function sleep(ms: number): Promise<void>;
41
+ /**
42
+ * Reserve the next `iat` for a request identity. The reservation is made
43
+ * synchronously so concurrent callers never share a value; the returned
44
+ * promise only waits when the reserved `iat` is further ahead of the clock
45
+ * than {@link PROOF_IAT_MAX_AHEAD_SECONDS}.
46
+ */
47
+ export declare function nextProofIat(proofKey: string): Promise<number>;
48
+ /** The identity a proof is deduplicated by. */
49
+ export declare function proofKeyFor(parts: {
50
+ aud: string;
51
+ method: string;
52
+ uri: string;
53
+ grantId: string;
54
+ signedBytes?: Uint8Array;
55
+ }): string;
56
+ /**
57
+ * Send a request, re-signing it on every attempt. Only a thrown `fetch` is
58
+ * retried; the proof builder and any received response are never retried.
59
+ */
60
+ export declare function sendWithFreshProof(label: string, fetchFn: typeof fetch, options: WriteTransportRetryOptions | undefined, proofKey: string, build: (iat: number) => Promise<{
61
+ url: string;
62
+ init: RequestInit;
63
+ }>): Promise<Response>;
@@ -0,0 +1,111 @@
1
+ import { sha256 } from "@noble/hashes/sha2";
2
+ import { bytesToHex } from "viem";
3
+ import { WriteRequestError, WriteTransportError } from "../errors.js";
4
+ function normalizeBaseUrl(url) {
5
+ return url.replace(/\/+$/, "");
6
+ }
7
+ function resolveFetch(fetchFn) {
8
+ const resolved = fetchFn ?? globalThis.fetch;
9
+ if (resolved === void 0) {
10
+ throw new WriteRequestError("No fetch implementation available");
11
+ }
12
+ return resolved;
13
+ }
14
+ function errorMessage(err) {
15
+ return err instanceof Error ? err.message : String(err);
16
+ }
17
+ function finiteOr(value, fallback) {
18
+ return typeof value === "number" && Number.isFinite(value) ? value : fallback;
19
+ }
20
+ function sleep(ms) {
21
+ return new Promise((resolve) => setTimeout(resolve, ms));
22
+ }
23
+ const issuedProofIats = /* @__PURE__ */ new Map();
24
+ const issuedProofBuckets = /* @__PURE__ */ new Map();
25
+ let issuedProofIatsPrunedAtSec = 0;
26
+ const WEB3_SIGNED_PROOF_LIFETIME_SECONDS = 300;
27
+ const WEB3_SIGNED_CLOCK_SKEW_SECONDS = 60;
28
+ const PROOF_IAT_RETENTION_SECONDS = WEB3_SIGNED_PROOF_LIFETIME_SECONDS + WEB3_SIGNED_CLOCK_SKEW_SECONDS;
29
+ const PROOF_IAT_MAX_AHEAD_SECONDS = 30;
30
+ function pruneIssuedProofIats(nowSec) {
31
+ if (issuedProofIatsPrunedAtSec === nowSec) return;
32
+ issuedProofIatsPrunedAtSec = nowSec;
33
+ const cutoff = nowSec - PROOF_IAT_RETENTION_SECONDS;
34
+ for (const [sec, keys] of issuedProofBuckets) {
35
+ if (sec >= cutoff) continue;
36
+ for (const key of keys) issuedProofIats.delete(key);
37
+ issuedProofBuckets.delete(sec);
38
+ }
39
+ }
40
+ function setIssuedProofIat(key, iat, previous) {
41
+ if (previous !== void 0) {
42
+ const bucket2 = issuedProofBuckets.get(previous);
43
+ bucket2?.delete(key);
44
+ if (bucket2?.size === 0) issuedProofBuckets.delete(previous);
45
+ }
46
+ issuedProofIats.set(key, iat);
47
+ let bucket = issuedProofBuckets.get(iat);
48
+ if (bucket === void 0) {
49
+ bucket = /* @__PURE__ */ new Set();
50
+ issuedProofBuckets.set(iat, bucket);
51
+ }
52
+ bucket.add(key);
53
+ }
54
+ function nextProofIat(proofKey) {
55
+ const nowSec = Math.floor(Date.now() / 1e3);
56
+ pruneIssuedProofIats(nowSec);
57
+ const last = issuedProofIats.get(proofKey);
58
+ const iat = last === void 0 ? nowSec : Math.max(nowSec, last + 1);
59
+ setIssuedProofIat(proofKey, iat, last);
60
+ const waitSec = iat - nowSec - PROOF_IAT_MAX_AHEAD_SECONDS;
61
+ if (waitSec <= 0) return Promise.resolve(iat);
62
+ return sleep(waitSec * 1e3).then(() => iat);
63
+ }
64
+ function proofKeyFor(parts) {
65
+ return bytesToHex(
66
+ sha256(
67
+ new TextEncoder().encode(
68
+ JSON.stringify([
69
+ parts.aud,
70
+ parts.method,
71
+ parts.uri,
72
+ parts.grantId,
73
+ parts.signedBytes ? bytesToHex(sha256(parts.signedBytes)) : ""
74
+ ])
75
+ )
76
+ )
77
+ );
78
+ }
79
+ async function sendWithFreshProof(label, fetchFn, options, proofKey, build) {
80
+ const attempts = Math.max(1, Math.floor(finiteOr(options?.attempts, 3)));
81
+ let delayMs = Math.max(0, finiteOr(options?.initialDelayMs, 1e3));
82
+ let lastError;
83
+ for (let attempt = 0; attempt < attempts; attempt++) {
84
+ const { url, init } = await build(await nextProofIat(proofKey));
85
+ try {
86
+ return await fetchFn(url, init);
87
+ } catch (err) {
88
+ lastError = err;
89
+ }
90
+ if (attempt < attempts - 1) {
91
+ await sleep(delayMs);
92
+ delayMs *= 2;
93
+ }
94
+ }
95
+ throw new WriteTransportError(
96
+ `${label} failed after ${attempts} attempt(s): ${errorMessage(lastError)}`,
97
+ attempts,
98
+ lastError
99
+ );
100
+ }
101
+ export {
102
+ errorMessage,
103
+ finiteOr,
104
+ nextProofIat,
105
+ normalizeBaseUrl,
106
+ proofKeyFor,
107
+ resolveFetch,
108
+ sendWithFreshProof,
109
+ sleep
110
+ };
111
+ //# sourceMappingURL=write-request.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/protocol/write-request.ts"],"sourcesContent":["/**\n * Transport shared by every builder call that authenticates with the\n * Personal Server Write API: the data writes of\n * {@link ../protocol/personal-server-write} and the derivative question\n * routes of {@link ../protocol/derivative-questions}.\n *\n * @remarks\n * Both sign a single-use Web3Signed proof per request, so both need the same\n * two things: a `fetch` wrapper that re-signs on every transport attempt, and\n * one process-wide record of the `iat` seconds already issued, so two proofs\n * for the same request identity can never come out byte-identical (the server\n * would reject the second as a replay). The record must be shared, not\n * per-module: a builder that polls one question every few milliseconds signs\n * the same `{ aud, method, uri, bodyHash, grantId }` many times a second.\n *\n * @internal\n */\n\nimport { sha256 } from \"@noble/hashes/sha2\";\nimport { bytesToHex } from \"viem\";\nimport { WriteRequestError, WriteTransportError } from \"../errors\";\n\n/**\n * Transport-level retry knobs shared by every Write API call.\n *\n * @remarks\n * Applies only when `fetch` **throws** (connection reset, DNS, a relay drop).\n * Every attempt signs a fresh proof, because the Personal Server consumes a\n * proof the moment it accepts it. A received HTTP response is never retried:\n * a 4xx/5xx is surfaced as a typed error.\n * @category Protocol\n */\nexport interface WriteTransportRetryOptions {\n /** Total attempts including the first (default 3). `1` disables retries. */\n attempts?: number;\n /** Delay before the first retry (ms); doubles per retry (default 1_000). */\n initialDelayMs?: number;\n}\n\n/** Strip trailing slashes so a base URL concatenates with a path. */\nexport function normalizeBaseUrl(url: string): string {\n return url.replace(/\\/+$/, \"\");\n}\n\n/** The caller's `fetch`, else the global one. */\nexport function resolveFetch(fetchFn: typeof fetch | undefined): typeof fetch {\n const resolved = fetchFn ?? globalThis.fetch;\n if (resolved === undefined) {\n throw new WriteRequestError(\"No fetch implementation available\");\n }\n return resolved;\n}\n\nexport function errorMessage(err: unknown): string {\n return err instanceof Error ? err.message : String(err);\n}\n\nexport function finiteOr(value: number | undefined, fallback: number): number {\n return typeof value === \"number\" && Number.isFinite(value) ? value : fallback;\n}\n\nexport function sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\n/**\n * The Personal Server consumes every proof it accepts, and a Web3Signed\n * payload is fully determined by `{ aud, method, uri, bodyHash, grantId, iat,\n * exp }`, so two proofs for the same request signed within one second would\n * be byte-identical and the second rejected as a replay. Remember the\n * highest `iat` issued per request identity in this process and bump past\n * it when a second proof for the same identity falls inside the same second.\n *\n * A mark is kept for as long as the server can still remember the proof it\n * guards (its lifetime plus the verifier's clock skew), so a proof is never\n * re-issued while it could still be rejected as a replay: not by a burst,\n * and not by a wall clock stepping backwards (an identical request after a\n * step back waits for the clock instead of reusing the mark). Marks are\n * bucketed by their `iat` second, so pruning (once per second) only touches\n * the buckets that fell out of the retention window: the work per proof is\n * amortised constant and the map is bounded by the distinct requests signed\n * in the window.\n */\nconst issuedProofIats = new Map<string, number>();\n/** `iat` second -> identities whose current mark is that second. */\nconst issuedProofBuckets = new Map<number, Set<string>>();\nlet issuedProofIatsPrunedAtSec = 0;\n/** `buildWeb3SignedHeader`'s default `exp - iat`. */\nconst WEB3_SIGNED_PROOF_LIFETIME_SECONDS = 300;\n/** The verifier's tolerated clock skew (`verifyWeb3Signed`). */\nconst WEB3_SIGNED_CLOCK_SKEW_SECONDS = 60;\nconst PROOF_IAT_RETENTION_SECONDS =\n WEB3_SIGNED_PROOF_LIFETIME_SECONDS + WEB3_SIGNED_CLOCK_SKEW_SECONDS;\n/**\n * How far ahead of the clock a bumped `iat` may run when the proof is sent.\n * The verifier tolerates 60 s of skew. A burst of identical requests that\n * would need to run further ahead waits for the clock instead, so a proof is\n * never repeated; sustained identical requests are throttled to one per\n * second after a burst of this many.\n */\nconst PROOF_IAT_MAX_AHEAD_SECONDS = 30;\n\nfunction pruneIssuedProofIats(nowSec: number): void {\n if (issuedProofIatsPrunedAtSec === nowSec) return;\n issuedProofIatsPrunedAtSec = nowSec;\n const cutoff = nowSec - PROOF_IAT_RETENTION_SECONDS;\n // There is at most one bucket per second in the window, so this walk is\n // bounded by the window length, not by the number of marks.\n for (const [sec, keys] of issuedProofBuckets) {\n if (sec >= cutoff) continue;\n for (const key of keys) issuedProofIats.delete(key);\n issuedProofBuckets.delete(sec);\n }\n}\n\nfunction setIssuedProofIat(key: string, iat: number, previous?: number): void {\n if (previous !== undefined) {\n const bucket = issuedProofBuckets.get(previous);\n bucket?.delete(key);\n if (bucket?.size === 0) issuedProofBuckets.delete(previous);\n }\n issuedProofIats.set(key, iat);\n let bucket = issuedProofBuckets.get(iat);\n if (bucket === undefined) {\n bucket = new Set();\n issuedProofBuckets.set(iat, bucket);\n }\n bucket.add(key);\n}\n\n/**\n * Reserve the next `iat` for a request identity. The reservation is made\n * synchronously so concurrent callers never share a value; the returned\n * promise only waits when the reserved `iat` is further ahead of the clock\n * than {@link PROOF_IAT_MAX_AHEAD_SECONDS}.\n */\nexport function nextProofIat(proofKey: string): Promise<number> {\n const nowSec = Math.floor(Date.now() / 1000);\n pruneIssuedProofIats(nowSec);\n const last = issuedProofIats.get(proofKey);\n const iat = last === undefined ? nowSec : Math.max(nowSec, last + 1);\n setIssuedProofIat(proofKey, iat, last);\n const waitSec = iat - nowSec - PROOF_IAT_MAX_AHEAD_SECONDS;\n if (waitSec <= 0) return Promise.resolve(iat);\n return sleep(waitSec * 1000).then(() => iat);\n}\n\n/** The identity a proof is deduplicated by. */\nexport function proofKeyFor(parts: {\n aud: string;\n method: string;\n uri: string;\n grantId: string;\n signedBytes?: Uint8Array;\n}): string {\n // A digest, so a retained mark costs a fixed amount of memory whatever the\n // request looked like.\n return bytesToHex(\n sha256(\n new TextEncoder().encode(\n JSON.stringify([\n parts.aud,\n parts.method,\n parts.uri,\n parts.grantId,\n parts.signedBytes ? bytesToHex(sha256(parts.signedBytes)) : \"\",\n ]),\n ),\n ),\n );\n}\n\n/**\n * Send a request, re-signing it on every attempt. Only a thrown `fetch` is\n * retried; the proof builder and any received response are never retried.\n */\nexport async function sendWithFreshProof(\n label: string,\n fetchFn: typeof fetch,\n options: WriteTransportRetryOptions | undefined,\n proofKey: string,\n build: (iat: number) => Promise<{ url: string; init: RequestInit }>,\n): Promise<Response> {\n const attempts = Math.max(1, Math.floor(finiteOr(options?.attempts, 3)));\n let delayMs = Math.max(0, finiteOr(options?.initialDelayMs, 1_000));\n let lastError: unknown;\n for (let attempt = 0; attempt < attempts; attempt++) {\n const { url, init } = await build(await nextProofIat(proofKey));\n try {\n return await fetchFn(url, init);\n } catch (err) {\n lastError = err;\n }\n if (attempt < attempts - 1) {\n await sleep(delayMs);\n delayMs *= 2;\n }\n }\n throw new WriteTransportError(\n `${label} failed after ${attempts} attempt(s): ${errorMessage(lastError)}`,\n attempts,\n lastError,\n );\n}\n"],"mappings":"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","names":["bucket"]}