@opendatalabs/vana-sdk 3.16.0 → 3.18.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.
Files changed (72) hide show
  1. package/README.md +107 -0
  2. package/dist/direct/personal-server-read.cjs +20 -1
  3. package/dist/direct/personal-server-read.cjs.map +1 -1
  4. package/dist/direct/personal-server-read.js +23 -1
  5. package/dist/direct/personal-server-read.js.map +1 -1
  6. package/dist/errors.cjs +121 -2
  7. package/dist/errors.cjs.map +1 -1
  8. package/dist/errors.d.ts +190 -0
  9. package/dist/errors.js +106 -1
  10. package/dist/errors.js.map +1 -1
  11. package/dist/index.browser.d.ts +5 -1
  12. package/dist/index.browser.js +1449 -58
  13. package/dist/index.browser.js.map +4 -4
  14. package/dist/index.node.cjs +1501 -59
  15. package/dist/index.node.cjs.map +4 -4
  16. package/dist/index.node.d.ts +5 -1
  17. package/dist/index.node.js +1449 -58
  18. package/dist/index.node.js.map +4 -4
  19. package/dist/protocol/data-point-deletion.cjs +220 -0
  20. package/dist/protocol/data-point-deletion.cjs.map +1 -0
  21. package/dist/protocol/data-point-deletion.d.ts +180 -0
  22. package/dist/protocol/data-point-deletion.js +193 -0
  23. package/dist/protocol/data-point-deletion.js.map +1 -0
  24. package/dist/protocol/data-point-deletion.test.d.ts +1 -0
  25. package/dist/protocol/gateway.cjs +145 -32
  26. package/dist/protocol/gateway.cjs.map +1 -1
  27. package/dist/protocol/gateway.d.ts +42 -3
  28. package/dist/protocol/gateway.js +156 -32
  29. package/dist/protocol/gateway.js.map +1 -1
  30. package/dist/protocol/lineage.cjs +287 -0
  31. package/dist/protocol/lineage.cjs.map +1 -0
  32. package/dist/protocol/lineage.d.ts +228 -0
  33. package/dist/protocol/lineage.js +258 -0
  34. package/dist/protocol/lineage.js.map +1 -0
  35. package/dist/protocol/lineage.test.d.ts +1 -0
  36. package/dist/protocol/personal-server-data.cjs +20 -1
  37. package/dist/protocol/personal-server-data.cjs.map +1 -1
  38. package/dist/protocol/personal-server-data.js +23 -1
  39. package/dist/protocol/personal-server-data.js.map +1 -1
  40. package/dist/protocol/personal-server-error-body.cjs +57 -0
  41. package/dist/protocol/personal-server-error-body.cjs.map +1 -0
  42. package/dist/protocol/personal-server-error-body.d.ts +18 -0
  43. package/dist/protocol/personal-server-error-body.js +32 -0
  44. package/dist/protocol/personal-server-error-body.js.map +1 -0
  45. package/dist/protocol/personal-server-write.cjs +623 -0
  46. package/dist/protocol/personal-server-write.cjs.map +1 -0
  47. package/dist/protocol/personal-server-write.d.ts +284 -0
  48. package/dist/protocol/personal-server-write.js +601 -0
  49. package/dist/protocol/personal-server-write.js.map +1 -0
  50. package/dist/protocol/personal-server-write.test.d.ts +1 -0
  51. package/dist/protocol/write-signer.cjs +67 -0
  52. package/dist/protocol/write-signer.cjs.map +1 -0
  53. package/dist/protocol/write-signer.d.ts +59 -0
  54. package/dist/protocol/write-signer.js +43 -0
  55. package/dist/protocol/write-signer.js.map +1 -0
  56. package/dist/protocol/write-signer.test.d.ts +1 -0
  57. package/dist/storage/index.cjs.map +1 -1
  58. package/dist/storage/index.d.ts +1 -1
  59. package/dist/storage/index.js.map +1 -1
  60. package/dist/storage/providers/vana-storage.cjs +66 -0
  61. package/dist/storage/providers/vana-storage.cjs.map +1 -1
  62. package/dist/storage/providers/vana-storage.d.ts +26 -0
  63. package/dist/storage/providers/vana-storage.js +66 -0
  64. package/dist/storage/providers/vana-storage.js.map +1 -1
  65. package/dist/tests/mock-personal-server.d.ts +127 -0
  66. package/dist/utils/response-body.cjs +46 -0
  67. package/dist/utils/response-body.cjs.map +1 -0
  68. package/dist/utils/response-body.d.ts +26 -0
  69. package/dist/utils/response-body.js +20 -0
  70. package/dist/utils/response-body.js.map +1 -0
  71. package/dist/utils/response-body.test.d.ts +1 -0
  72. package/package.json +1 -1
@@ -66,6 +66,19 @@ export interface VanaStorageConfig {
66
66
  */
67
67
  fetchImpl?: typeof fetch;
68
68
  }
69
+ /**
70
+ * Response of {@link VanaStorage.deleteScope} -- the worker's
71
+ * `DELETE /:owner/:scope` body. `count` is the number of blobs removed (0 is
72
+ * a success: nothing was stored under that scope).
73
+ *
74
+ * @category Storage
75
+ */
76
+ export interface VanaStorageScopeDeleteResult {
77
+ deleted: boolean;
78
+ scope: string;
79
+ count: number;
80
+ totalBytes: number;
81
+ }
69
82
  /**
70
83
  * Storage provider that talks to the vana-storage Worker
71
84
  * (`https://storage.vana.org` by default). All requests are authenticated
@@ -132,6 +145,19 @@ export declare class VanaStorage implements StorageProvider {
132
145
  */
133
146
  list(_options?: StorageListOptions): Promise<StorageFile[]>;
134
147
  delete(url: string): Promise<boolean>;
148
+ /**
149
+ * Delete every version's blob under `(owner, scope)` --
150
+ * `DELETE {prefix}/{owner}/{scope}` on vana-storage, signed with the same
151
+ * Web3Signed header as uploads (aud = endpoint origin, empty bodyHash).
152
+ * The worker accepts the owner's own signature or a personal server the
153
+ * owner registered with the gateway.
154
+ *
155
+ * @param ownerAddress - Must equal the provider's configured owner; a
156
+ * mismatch throws before anything is signed so this wallet can never be
157
+ * induced to sign a delete for another namespace.
158
+ * @param scope - The scope segment, e.g. `"instagram.profile"`.
159
+ */
160
+ deleteScope(ownerAddress: `0x${string}`, scope: string): Promise<VanaStorageScopeDeleteResult>;
135
161
  getConfig(): StorageProviderConfig;
136
162
  private signRequest;
137
163
  private pathFromUrl;
@@ -8,6 +8,7 @@ import {
8
8
  getProtocolNetworkChainId,
9
9
  isProtocolNetwork
10
10
  } from "../../protocol/networks.js";
11
+ import { readJsonObject } from "../../utils/response-body.js";
11
12
  const DEFAULT_ENDPOINT = "https://storage.vana.org";
12
13
  const LEGACY_BLOB_PATH_PREFIX = "/v1/blobs";
13
14
  const DEFAULT_TOKEN_TTL_SECONDS = 300;
@@ -178,6 +179,65 @@ class VanaStorage {
178
179
  }
179
180
  return true;
180
181
  }
182
+ /**
183
+ * Delete every version's blob under `(owner, scope)` --
184
+ * `DELETE {prefix}/{owner}/{scope}` on vana-storage, signed with the same
185
+ * Web3Signed header as uploads (aud = endpoint origin, empty bodyHash).
186
+ * The worker accepts the owner's own signature or a personal server the
187
+ * owner registered with the gateway.
188
+ *
189
+ * @param ownerAddress - Must equal the provider's configured owner; a
190
+ * mismatch throws before anything is signed so this wallet can never be
191
+ * induced to sign a delete for another namespace.
192
+ * @param scope - The scope segment, e.g. `"instagram.profile"`.
193
+ */
194
+ async deleteScope(ownerAddress, scope) {
195
+ if (ownerAddress.toLowerCase() !== this.ownerAddress) {
196
+ throw new StorageError(
197
+ `deleteScope owner '${ownerAddress}' does not match the configured owner '${this.ownerAddress}'`,
198
+ "INVALID_OWNER",
199
+ "vana-storage"
200
+ );
201
+ }
202
+ if (scope.length === 0 || scope.includes("/") || isTraversalSegment(scope)) {
203
+ throw new StorageError(
204
+ `scope must be a single non-empty path segment, got '${scope}'`,
205
+ "INVALID_SCOPE",
206
+ "vana-storage"
207
+ );
208
+ }
209
+ const path = `${this.blobPathPrefix}/${this.ownerAddress}/${encodeURIComponent(scope)}`;
210
+ const header = await this.signRequest("DELETE", path);
211
+ let response;
212
+ try {
213
+ response = await this.fetchImpl(`${this.endpoint}${path}`, {
214
+ method: "DELETE",
215
+ headers: { authorization: header }
216
+ });
217
+ } catch (cause) {
218
+ throw new StorageError(
219
+ `vana-storage scope delete network error: ${describe(cause)}`,
220
+ "DELETE_ERROR",
221
+ "vana-storage",
222
+ { cause: cause instanceof Error ? cause : void 0 }
223
+ );
224
+ }
225
+ if (!response.ok) {
226
+ const responseText = await safeText(response);
227
+ throw new StorageError(
228
+ `vana-storage scope delete failed: ${response.status} ${response.statusText} - ${responseText}`,
229
+ "DELETE_FAILED",
230
+ "vana-storage"
231
+ );
232
+ }
233
+ const body = await readJsonObject(response);
234
+ return {
235
+ deleted: typeof body["deleted"] === "boolean" ? body["deleted"] : true,
236
+ scope: typeof body["scope"] === "string" ? body["scope"] : scope,
237
+ count: nonNegativeInteger(body["count"]) ?? 0,
238
+ totalBytes: nonNegativeInteger(body["totalBytes"]) ?? 0
239
+ };
240
+ }
181
241
  getConfig() {
182
242
  return {
183
243
  name: "vana-storage",
@@ -306,6 +366,12 @@ function encodeRelativePath(filename) {
306
366
  }
307
367
  return parts.map((p) => encodeURIComponent(p)).join("/");
308
368
  }
369
+ function isTraversalSegment(segment) {
370
+ return segment === "." || segment === "..";
371
+ }
372
+ function nonNegativeInteger(value) {
373
+ return typeof value === "number" && Number.isInteger(value) && value >= 0 ? value : void 0;
374
+ }
309
375
  function describe(value) {
310
376
  if (value instanceof Error) return value.message;
311
377
  return String(value);
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/storage/providers/vana-storage.ts"],"sourcesContent":["import {\n StorageError,\n type StorageProvider,\n type StorageUploadResult,\n type StorageFile,\n type StorageListOptions,\n type StorageProviderConfig,\n} from \"../index\";\nimport {\n buildWeb3SignedHeader,\n type Web3SignedSignFn,\n} from \"../../auth/web3-signed-builder\";\nimport {\n getProtocolNetworkChainId,\n isProtocolNetwork,\n type ProtocolNetwork,\n} from \"../../protocol/networks\";\n\nconst DEFAULT_ENDPOINT = \"https://storage.vana.org\";\nconst LEGACY_BLOB_PATH_PREFIX = \"/v1/blobs\";\nconst DEFAULT_TOKEN_TTL_SECONDS = 300;\nconst MAX_UPLOAD_ATTEMPTS = 4;\nconst MAX_RATE_LIMIT_DELAY_MS = 30_000;\n\n/**\n * Wallet-style signer used by {@link VanaStorage} to authenticate every\n * request. For Personal Server flows this can be a registered server wallet\n * signing requests for the owner's storage namespace.\n *\n * @category Storage\n */\nexport interface VanaStorageSigner {\n /** EIP-191 address (`0x...`). */\n address: `0x${string}`;\n /** EIP-191 personal_sign callback (e.g. viem `account.signMessage`). */\n signMessage: Web3SignedSignFn;\n}\n\n/**\n * Configuration for {@link VanaStorage}.\n *\n * @category Storage\n */\nexport interface VanaStorageConfig {\n /**\n * Base URL of the vana-storage Worker. Defaults to `https://storage.vana.org`.\n *\n * This selects the storage endpoint and is independent of {@link network} /\n * {@link chainId}.\n */\n endpoint?: string;\n /**\n * Named Vana protocol network that scopes blob paths. Prefer this for known\n * Vana networks because it keeps callers on the SDK's typed network registry.\n *\n * When set, uploads use chain-scoped routes derived from the network's chain\n * ID (`/v1/chains/1480/...` for mainnet, `/v1/chains/14800/...` for Moksha).\n */\n network?: ProtocolNetwork;\n /**\n * Explicit numeric chain ID for custom or future networks not yet represented\n * by {@link ProtocolNetwork}.\n *\n * When set, uploads use chain-scoped routes\n * (`/v1/chains/{chainId}/blobs/...`) so data for different chains under the\n * same owner/scope/timestamp never collides. Reads and deletes must use the\n * same chain-scoped namespace; legacy blobs should be migrated or re-collected\n * rather than read through an ambiguous fallback. When omitted, the provider\n * preserves the legacy `/v1/blobs/...` routes and behavior.\n *\n * `chainId` and {@link network} are mutually consistent ways to select the\n * namespace. If both are provided, they must resolve to the same chain ID.\n * `endpoint` remains orthogonal: it picks the storage host, not the protocol\n * namespace.\n */\n chainId?: number;\n /**\n * Wallet signer used to authenticate writes and reads.\n */\n signer: VanaStorageSigner;\n /**\n * Owner namespace under which blobs are stored. Defaults to the signer address.\n */\n ownerAddress?: `0x${string}`;\n /**\n * Optional `fetch` implementation. Defaults to the global `fetch`.\n * Useful for tests and for environments that need a custom HTTP client.\n */\n fetchImpl?: typeof fetch;\n}\n\ninterface VanaStorageUploadResponse {\n key: string;\n url: string;\n etag: string;\n size: number;\n}\n\n/**\n * Storage provider that talks to the vana-storage Worker\n * (`https://storage.vana.org` by default). All requests are authenticated\n * with Web3Signed headers signed by the configured wallet.\n *\n * @remarks\n * Filenames passed to {@link VanaStorage.upload} must be of the form\n * `\"{scope}/{collectedAt}\"` (e.g. `\"instagram.profile/2026-05-08T20:00:00.000Z\"`).\n * The owner address is prepended automatically to produce the canonical\n * blob path `/v1/blobs/{owner}/{scope}/{collectedAt}`.\n *\n * When {@link VanaStorageConfig.network} or {@link VanaStorageConfig.chainId}\n * is set, paths are chain-scoped as\n * `/v1/chains/{chainId}/blobs/{owner}/{scope}/{collectedAt}` so different chains\n * never collide on the same host. The Web3Signed audience remains the endpoint\n * origin regardless of network.\n *\n * @category Storage\n *\n * @example\n * ```typescript\n * import { privateKeyToAccount } from \"viem/accounts\";\n * import { VanaStorage } from \"@opendatalabs/vana-sdk/node\";\n *\n * const account = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`);\n * const storage = new VanaStorage({\n * signer: {\n * address: account.address,\n * signMessage: (msg) => account.signMessage({ message: msg }),\n * },\n * });\n *\n * const result = await storage.upload(\n * new Blob([ciphertext]),\n * \"instagram.profile/2026-05-08T20:00:00.000Z\",\n * );\n * ```\n */\nexport class VanaStorage implements StorageProvider {\n private readonly endpoint: string;\n private readonly network?: ProtocolNetwork;\n private readonly chainId?: number;\n private readonly blobPathPrefix: string;\n private readonly signer: VanaStorageSigner;\n private readonly ownerAddress: string;\n private readonly fetchImpl: typeof fetch;\n\n constructor(config: VanaStorageConfig) {\n if (!config?.signer?.address || !config?.signer?.signMessage) {\n throw new StorageError(\n \"VanaStorage requires a signer with address and signMessage\",\n \"MISSING_SIGNER\",\n \"vana-storage\",\n );\n }\n this.endpoint = (config.endpoint ?? DEFAULT_ENDPOINT).replace(/\\/+$/, \"\");\n const resolved = resolveStorageNamespace(config);\n this.network = resolved.network;\n this.chainId = resolved.chainId;\n this.blobPathPrefix =\n this.chainId !== undefined\n ? `/v1/chains/${this.chainId}/blobs`\n : LEGACY_BLOB_PATH_PREFIX;\n this.signer = config.signer;\n this.ownerAddress = (\n config.ownerAddress ?? config.signer.address\n ).toLowerCase();\n this.fetchImpl = config.fetchImpl ?? globalThis.fetch.bind(globalThis);\n }\n\n /**\n * Upload an encrypted blob to vana-storage.\n *\n * @param file - The blob to upload.\n * @param filename - Required relative key in the form `\"{scope}/{collectedAt}\"`.\n * The owner address is prepended automatically.\n */\n async upload(file: Blob, filename?: string): Promise<StorageUploadResult> {\n if (!filename) {\n throw new StorageError(\n \"VanaStorage.upload requires a filename of the form '{scope}/{collectedAt}'\",\n \"MISSING_FILENAME\",\n \"vana-storage\",\n );\n }\n\n const subpath = encodeRelativePath(filename);\n const path = `${this.blobPathPrefix}/${this.ownerAddress}/${subpath}`;\n const body = new Uint8Array(await file.arrayBuffer());\n const contentType =\n file.type !== \"\" ? file.type : \"application/octet-stream\";\n\n const header = await this.signRequest(\"PUT\", path, body);\n\n let response: Response | null = null;\n let responseText = \"\";\n for (let attempt = 1; attempt <= MAX_UPLOAD_ATTEMPTS; attempt++) {\n try {\n response = await this.fetchImpl(`${this.endpoint}${path}`, {\n method: \"PUT\",\n headers: {\n authorization: header,\n \"content-type\": contentType,\n },\n body,\n });\n } catch (cause) {\n throw new StorageError(\n `vana-storage upload network error: ${describe(cause)}`,\n \"UPLOAD_ERROR\",\n \"vana-storage\",\n { cause: cause instanceof Error ? cause : undefined },\n );\n }\n\n if (response.ok) {\n break;\n }\n\n responseText = await safeText(response);\n if (response.status === 429 && attempt < MAX_UPLOAD_ATTEMPTS) {\n const delayMs = retryDelayMs(response, responseText);\n if (delayMs > 0) {\n await sleep(delayMs);\n }\n continue;\n }\n\n throw new StorageError(\n `vana-storage upload failed: ${response.status} ${response.statusText} - ${responseText}`,\n \"UPLOAD_FAILED\",\n \"vana-storage\",\n );\n }\n\n if (!response?.ok) {\n throw new StorageError(\n `vana-storage upload failed after ${MAX_UPLOAD_ATTEMPTS} attempts - ${responseText}`,\n \"UPLOAD_FAILED\",\n \"vana-storage\",\n );\n }\n\n const result = (await response.json()) as VanaStorageUploadResponse;\n return {\n url: result.url,\n size: result.size,\n contentType,\n metadata: { key: result.key, etag: result.etag },\n };\n }\n\n /**\n * Download a blob by URL. The URL must point at a path under this\n * provider's endpoint.\n */\n async download(url: string): Promise<Blob> {\n const path = this.pathFromUrl(url);\n const header = await this.signRequest(\"GET\", path);\n\n let response: Response;\n try {\n response = await this.fetchImpl(`${this.endpoint}${path}`, {\n method: \"GET\",\n headers: { authorization: header },\n });\n } catch (cause) {\n throw new StorageError(\n `vana-storage download network error: ${describe(cause)}`,\n \"DOWNLOAD_ERROR\",\n \"vana-storage\",\n { cause: cause instanceof Error ? cause : undefined },\n );\n }\n\n if (!response.ok) {\n throw new StorageError(\n `vana-storage download failed: ${response.status} ${response.statusText}`,\n \"DOWNLOAD_FAILED\",\n \"vana-storage\",\n );\n }\n\n return await response.blob();\n }\n\n /**\n * Listing is not supported by vana-storage — file discovery is handled by\n * the Gateway DataRegistry, not the storage layer.\n */\n async list(_options?: StorageListOptions): Promise<StorageFile[]> {\n throw new StorageError(\n \"list is not supported by vana-storage; query the Gateway DataRegistry instead\",\n \"NOT_IMPLEMENTED\",\n \"vana-storage\",\n );\n }\n\n async delete(url: string): Promise<boolean> {\n const path = this.pathFromUrl(url);\n const header = await this.signRequest(\"DELETE\", path);\n\n let response: Response;\n try {\n response = await this.fetchImpl(`${this.endpoint}${path}`, {\n method: \"DELETE\",\n headers: { authorization: header },\n });\n } catch (cause) {\n throw new StorageError(\n `vana-storage delete network error: ${describe(cause)}`,\n \"DELETE_ERROR\",\n \"vana-storage\",\n { cause: cause instanceof Error ? cause : undefined },\n );\n }\n\n if (response.status === 404) return false;\n if (!response.ok) {\n throw new StorageError(\n `vana-storage delete failed: ${response.status} ${response.statusText}`,\n \"DELETE_FAILED\",\n \"vana-storage\",\n );\n }\n return true;\n }\n\n getConfig(): StorageProviderConfig {\n return {\n name: \"vana-storage\",\n type: \"vana-storage\",\n requiresAuth: true,\n features: {\n upload: true,\n download: true,\n list: false,\n delete: true,\n },\n };\n }\n\n private async signRequest(\n method: \"GET\" | \"PUT\" | \"DELETE\",\n path: string,\n body?: Uint8Array,\n ): Promise<string> {\n const now = Math.floor(Date.now() / 1000);\n return buildWeb3SignedHeader({\n signMessage: this.signer.signMessage,\n aud: this.endpoint,\n method,\n uri: path,\n iat: now,\n exp: now + DEFAULT_TOKEN_TTL_SECONDS,\n ...(body !== undefined && body.length > 0 && { body }),\n });\n }\n\n private pathFromUrl(url: string): string {\n let parsed: URL;\n try {\n parsed = new URL(url);\n } catch {\n throw new StorageError(\n `Invalid URL: ${url}`,\n \"INVALID_URL\",\n \"vana-storage\",\n );\n }\n const expectedHost = new URL(this.endpoint).host;\n if (parsed.host !== expectedHost) {\n throw new StorageError(\n `URL host '${parsed.host}' does not match storage endpoint '${expectedHost}'`,\n \"INVALID_URL\",\n \"vana-storage\",\n );\n }\n // Restrict to a blob path for this owner so a caller cannot induce this\n // wallet to sign arbitrary same-host paths.\n const route = parseBlobPath(parsed.pathname);\n if (!route || route.owner.toLowerCase() !== this.ownerAddress) {\n throw new StorageError(\n `URL path '${parsed.pathname}' must be ${this.blobPathPrefix}/${this.ownerAddress}/{scope}/{collectedAt}`,\n \"INVALID_URL\",\n \"vana-storage\",\n );\n }\n // A provider must only sign requests for its configured namespace. Chain\n // mode is strict: old legacy blobs should be migrated or re-collected\n // rather than read through a second ambiguous path.\n if (route.chainId !== this.chainId) {\n throw new StorageError(\n `URL chainId '${route.chainId ?? \"legacy\"}' does not match provider namespace '${this.namespaceDescription()}'`,\n \"INVALID_URL\",\n \"vana-storage\",\n );\n }\n return parsed.pathname;\n }\n\n private namespaceDescription(): string {\n if (this.network !== undefined) {\n return `${this.network} (${this.chainId})`;\n }\n return this.chainId === undefined ? \"legacy\" : String(this.chainId);\n }\n}\n\ninterface ParsedBlobRoute {\n chainId?: number;\n owner: string;\n scope: string;\n collectedAt: string;\n}\n\nfunction isValidChainId(value: unknown): value is number {\n return typeof value === \"number\" && Number.isInteger(value) && value > 0;\n}\n\nfunction resolveStorageNamespace(\n config: Pick<VanaStorageConfig, \"network\" | \"chainId\">,\n): {\n chainId?: number;\n network?: ProtocolNetwork;\n} {\n if (config.network !== undefined && !isProtocolNetwork(config.network)) {\n throw new StorageError(\n `Unsupported vana-storage network '${String(config.network)}'`,\n \"INVALID_NETWORK\",\n \"vana-storage\",\n );\n }\n\n if (config.chainId !== undefined && !isValidChainId(config.chainId)) {\n throw new StorageError(\n `Unsupported vana-storage chainId '${String(config.chainId)}'`,\n \"INVALID_CHAIN_ID\",\n \"vana-storage\",\n );\n }\n\n if (config.network === undefined) {\n return { chainId: config.chainId };\n }\n\n const networkChainId = getProtocolNetworkChainId(config.network);\n if (config.chainId !== undefined && config.chainId !== networkChainId) {\n throw new StorageError(\n `vana-storage network '${config.network}' resolves to chainId '${networkChainId}', not '${config.chainId}'`,\n \"INVALID_CHAIN_ID\",\n \"vana-storage\",\n );\n }\n\n return { chainId: networkChainId, network: config.network };\n}\n\n/**\n * Parse a storage blob pathname into structured route info, or `null` when the\n * path is neither the legacy nor the chain-scoped blob shape. Traversal\n * segments (`.` / `..`) are rejected as invalid.\n */\nfunction parseBlobPath(pathname: string): ParsedBlobRoute | null {\n const segments = pathname.split(\"/\").filter((s) => s.length > 0);\n const isTraversal = (s: string): boolean => s === \".\" || s === \"..\";\n\n // Legacy: /v1/blobs/{owner}/{scope}/{collectedAt}\n if (\n segments.length === 5 &&\n segments[0] === \"v1\" &&\n segments[1] === \"blobs\"\n ) {\n const [, , owner, scope, collectedAt] = segments as [\n string,\n string,\n string,\n string,\n string,\n ];\n if (isTraversal(scope) || isTraversal(collectedAt)) return null;\n return { owner, scope, collectedAt };\n }\n\n // Chain-scoped: /v1/chains/{chainId}/blobs/{owner}/{scope}/{collectedAt}\n if (\n segments.length === 7 &&\n segments[0] === \"v1\" &&\n segments[1] === \"chains\" &&\n segments[3] === \"blobs\"\n ) {\n const [, , chainIdSegment, , owner, scope, collectedAt] = segments as [\n string,\n string,\n string,\n string,\n string,\n string,\n string,\n ];\n if (!/^[0-9]+$/.test(chainIdSegment)) return null;\n const chainId = Number(chainIdSegment);\n if (!isValidChainId(chainId)) return null;\n if (isTraversal(scope) || isTraversal(collectedAt)) return null;\n return { chainId, owner, scope, collectedAt };\n }\n\n return null;\n}\n\nfunction encodeRelativePath(filename: string): string {\n const parts = filename.split(\"/\");\n if (\n parts.length !== 2 ||\n parts.some((p) => p.length === 0 || p === \".\" || p === \"..\")\n ) {\n throw new StorageError(\n `filename must be exactly '{scope}/{collectedAt}' with non-empty segments, got '${filename}'`,\n \"INVALID_FILENAME\",\n \"vana-storage\",\n );\n }\n return parts.map((p) => encodeURIComponent(p)).join(\"/\");\n}\n\nfunction describe(value: unknown): string {\n if (value instanceof Error) return value.message;\n return String(value);\n}\n\nfunction retryDelayMs(response: Response, responseText: string): number {\n const headerDelayMs = parseRetryAfterHeaderMs(\n response.headers.get(\"retry-after\"),\n );\n if (headerDelayMs !== null) {\n return clampRateLimitDelay(headerDelayMs);\n }\n\n return clampRateLimitDelay(parseRetryAfterBodyMs(responseText) ?? 0);\n}\n\nfunction parseRetryAfterHeaderMs(value: string | null): number | null {\n if (!value) return null;\n\n const seconds = Number(value);\n if (Number.isFinite(seconds)) {\n return seconds * 1000;\n }\n\n const dateMs = Date.parse(value);\n if (Number.isFinite(dateMs)) {\n return Math.max(0, dateMs - Date.now());\n }\n\n return null;\n}\n\nfunction parseRetryAfterBodyMs(responseText: string): number | null {\n if (!responseText) return null;\n\n try {\n const parsed = JSON.parse(responseText) as { retryAfter?: unknown };\n const seconds = Number(parsed.retryAfter);\n return Number.isFinite(seconds) ? seconds * 1000 : null;\n } catch {\n return null;\n }\n}\n\nfunction clampRateLimitDelay(delayMs: number): number {\n return Math.min(Math.max(0, delayMs), MAX_RATE_LIMIT_DELAY_MS);\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\nasync function safeText(response: Response): Promise<string> {\n try {\n return await response.text();\n } catch {\n return \"\";\n }\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../../src/storage/providers/vana-storage.ts"],"sourcesContent":["import {\n StorageError,\n type StorageProvider,\n type StorageUploadResult,\n type StorageFile,\n type StorageListOptions,\n type StorageProviderConfig,\n} from \"../index\";\nimport {\n buildWeb3SignedHeader,\n type Web3SignedSignFn,\n} from \"../../auth/web3-signed-builder\";\nimport {\n getProtocolNetworkChainId,\n isProtocolNetwork,\n type ProtocolNetwork,\n} from \"../../protocol/networks\";\nimport { readJsonObject } from \"../../utils/response-body\";\n\nconst DEFAULT_ENDPOINT = \"https://storage.vana.org\";\nconst LEGACY_BLOB_PATH_PREFIX = \"/v1/blobs\";\nconst DEFAULT_TOKEN_TTL_SECONDS = 300;\nconst MAX_UPLOAD_ATTEMPTS = 4;\nconst MAX_RATE_LIMIT_DELAY_MS = 30_000;\n\n/**\n * Wallet-style signer used by {@link VanaStorage} to authenticate every\n * request. For Personal Server flows this can be a registered server wallet\n * signing requests for the owner's storage namespace.\n *\n * @category Storage\n */\nexport interface VanaStorageSigner {\n /** EIP-191 address (`0x...`). */\n address: `0x${string}`;\n /** EIP-191 personal_sign callback (e.g. viem `account.signMessage`). */\n signMessage: Web3SignedSignFn;\n}\n\n/**\n * Configuration for {@link VanaStorage}.\n *\n * @category Storage\n */\nexport interface VanaStorageConfig {\n /**\n * Base URL of the vana-storage Worker. Defaults to `https://storage.vana.org`.\n *\n * This selects the storage endpoint and is independent of {@link network} /\n * {@link chainId}.\n */\n endpoint?: string;\n /**\n * Named Vana protocol network that scopes blob paths. Prefer this for known\n * Vana networks because it keeps callers on the SDK's typed network registry.\n *\n * When set, uploads use chain-scoped routes derived from the network's chain\n * ID (`/v1/chains/1480/...` for mainnet, `/v1/chains/14800/...` for Moksha).\n */\n network?: ProtocolNetwork;\n /**\n * Explicit numeric chain ID for custom or future networks not yet represented\n * by {@link ProtocolNetwork}.\n *\n * When set, uploads use chain-scoped routes\n * (`/v1/chains/{chainId}/blobs/...`) so data for different chains under the\n * same owner/scope/timestamp never collides. Reads and deletes must use the\n * same chain-scoped namespace; legacy blobs should be migrated or re-collected\n * rather than read through an ambiguous fallback. When omitted, the provider\n * preserves the legacy `/v1/blobs/...` routes and behavior.\n *\n * `chainId` and {@link network} are mutually consistent ways to select the\n * namespace. If both are provided, they must resolve to the same chain ID.\n * `endpoint` remains orthogonal: it picks the storage host, not the protocol\n * namespace.\n */\n chainId?: number;\n /**\n * Wallet signer used to authenticate writes and reads.\n */\n signer: VanaStorageSigner;\n /**\n * Owner namespace under which blobs are stored. Defaults to the signer address.\n */\n ownerAddress?: `0x${string}`;\n /**\n * Optional `fetch` implementation. Defaults to the global `fetch`.\n * Useful for tests and for environments that need a custom HTTP client.\n */\n fetchImpl?: typeof fetch;\n}\n\ninterface VanaStorageUploadResponse {\n key: string;\n url: string;\n etag: string;\n size: number;\n}\n\n/**\n * Response of {@link VanaStorage.deleteScope} -- the worker's\n * `DELETE /:owner/:scope` body. `count` is the number of blobs removed (0 is\n * a success: nothing was stored under that scope).\n *\n * @category Storage\n */\nexport interface VanaStorageScopeDeleteResult {\n deleted: boolean;\n scope: string;\n count: number;\n totalBytes: number;\n}\n\n/**\n * Storage provider that talks to the vana-storage Worker\n * (`https://storage.vana.org` by default). All requests are authenticated\n * with Web3Signed headers signed by the configured wallet.\n *\n * @remarks\n * Filenames passed to {@link VanaStorage.upload} must be of the form\n * `\"{scope}/{collectedAt}\"` (e.g. `\"instagram.profile/2026-05-08T20:00:00.000Z\"`).\n * The owner address is prepended automatically to produce the canonical\n * blob path `/v1/blobs/{owner}/{scope}/{collectedAt}`.\n *\n * When {@link VanaStorageConfig.network} or {@link VanaStorageConfig.chainId}\n * is set, paths are chain-scoped as\n * `/v1/chains/{chainId}/blobs/{owner}/{scope}/{collectedAt}` so different chains\n * never collide on the same host. The Web3Signed audience remains the endpoint\n * origin regardless of network.\n *\n * @category Storage\n *\n * @example\n * ```typescript\n * import { privateKeyToAccount } from \"viem/accounts\";\n * import { VanaStorage } from \"@opendatalabs/vana-sdk/node\";\n *\n * const account = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`);\n * const storage = new VanaStorage({\n * signer: {\n * address: account.address,\n * signMessage: (msg) => account.signMessage({ message: msg }),\n * },\n * });\n *\n * const result = await storage.upload(\n * new Blob([ciphertext]),\n * \"instagram.profile/2026-05-08T20:00:00.000Z\",\n * );\n * ```\n */\nexport class VanaStorage implements StorageProvider {\n private readonly endpoint: string;\n private readonly network?: ProtocolNetwork;\n private readonly chainId?: number;\n private readonly blobPathPrefix: string;\n private readonly signer: VanaStorageSigner;\n private readonly ownerAddress: string;\n private readonly fetchImpl: typeof fetch;\n\n constructor(config: VanaStorageConfig) {\n if (!config?.signer?.address || !config?.signer?.signMessage) {\n throw new StorageError(\n \"VanaStorage requires a signer with address and signMessage\",\n \"MISSING_SIGNER\",\n \"vana-storage\",\n );\n }\n this.endpoint = (config.endpoint ?? DEFAULT_ENDPOINT).replace(/\\/+$/, \"\");\n const resolved = resolveStorageNamespace(config);\n this.network = resolved.network;\n this.chainId = resolved.chainId;\n this.blobPathPrefix =\n this.chainId !== undefined\n ? `/v1/chains/${this.chainId}/blobs`\n : LEGACY_BLOB_PATH_PREFIX;\n this.signer = config.signer;\n this.ownerAddress = (\n config.ownerAddress ?? config.signer.address\n ).toLowerCase();\n this.fetchImpl = config.fetchImpl ?? globalThis.fetch.bind(globalThis);\n }\n\n /**\n * Upload an encrypted blob to vana-storage.\n *\n * @param file - The blob to upload.\n * @param filename - Required relative key in the form `\"{scope}/{collectedAt}\"`.\n * The owner address is prepended automatically.\n */\n async upload(file: Blob, filename?: string): Promise<StorageUploadResult> {\n if (!filename) {\n throw new StorageError(\n \"VanaStorage.upload requires a filename of the form '{scope}/{collectedAt}'\",\n \"MISSING_FILENAME\",\n \"vana-storage\",\n );\n }\n\n const subpath = encodeRelativePath(filename);\n const path = `${this.blobPathPrefix}/${this.ownerAddress}/${subpath}`;\n const body = new Uint8Array(await file.arrayBuffer());\n const contentType =\n file.type !== \"\" ? file.type : \"application/octet-stream\";\n\n const header = await this.signRequest(\"PUT\", path, body);\n\n let response: Response | null = null;\n let responseText = \"\";\n for (let attempt = 1; attempt <= MAX_UPLOAD_ATTEMPTS; attempt++) {\n try {\n response = await this.fetchImpl(`${this.endpoint}${path}`, {\n method: \"PUT\",\n headers: {\n authorization: header,\n \"content-type\": contentType,\n },\n body,\n });\n } catch (cause) {\n throw new StorageError(\n `vana-storage upload network error: ${describe(cause)}`,\n \"UPLOAD_ERROR\",\n \"vana-storage\",\n { cause: cause instanceof Error ? cause : undefined },\n );\n }\n\n if (response.ok) {\n break;\n }\n\n responseText = await safeText(response);\n if (response.status === 429 && attempt < MAX_UPLOAD_ATTEMPTS) {\n const delayMs = retryDelayMs(response, responseText);\n if (delayMs > 0) {\n await sleep(delayMs);\n }\n continue;\n }\n\n throw new StorageError(\n `vana-storage upload failed: ${response.status} ${response.statusText} - ${responseText}`,\n \"UPLOAD_FAILED\",\n \"vana-storage\",\n );\n }\n\n if (!response?.ok) {\n throw new StorageError(\n `vana-storage upload failed after ${MAX_UPLOAD_ATTEMPTS} attempts - ${responseText}`,\n \"UPLOAD_FAILED\",\n \"vana-storage\",\n );\n }\n\n const result = (await response.json()) as VanaStorageUploadResponse;\n return {\n url: result.url,\n size: result.size,\n contentType,\n metadata: { key: result.key, etag: result.etag },\n };\n }\n\n /**\n * Download a blob by URL. The URL must point at a path under this\n * provider's endpoint.\n */\n async download(url: string): Promise<Blob> {\n const path = this.pathFromUrl(url);\n const header = await this.signRequest(\"GET\", path);\n\n let response: Response;\n try {\n response = await this.fetchImpl(`${this.endpoint}${path}`, {\n method: \"GET\",\n headers: { authorization: header },\n });\n } catch (cause) {\n throw new StorageError(\n `vana-storage download network error: ${describe(cause)}`,\n \"DOWNLOAD_ERROR\",\n \"vana-storage\",\n { cause: cause instanceof Error ? cause : undefined },\n );\n }\n\n if (!response.ok) {\n throw new StorageError(\n `vana-storage download failed: ${response.status} ${response.statusText}`,\n \"DOWNLOAD_FAILED\",\n \"vana-storage\",\n );\n }\n\n return await response.blob();\n }\n\n /**\n * Listing is not supported by vana-storage — file discovery is handled by\n * the Gateway DataRegistry, not the storage layer.\n */\n async list(_options?: StorageListOptions): Promise<StorageFile[]> {\n throw new StorageError(\n \"list is not supported by vana-storage; query the Gateway DataRegistry instead\",\n \"NOT_IMPLEMENTED\",\n \"vana-storage\",\n );\n }\n\n async delete(url: string): Promise<boolean> {\n const path = this.pathFromUrl(url);\n const header = await this.signRequest(\"DELETE\", path);\n\n let response: Response;\n try {\n response = await this.fetchImpl(`${this.endpoint}${path}`, {\n method: \"DELETE\",\n headers: { authorization: header },\n });\n } catch (cause) {\n throw new StorageError(\n `vana-storage delete network error: ${describe(cause)}`,\n \"DELETE_ERROR\",\n \"vana-storage\",\n { cause: cause instanceof Error ? cause : undefined },\n );\n }\n\n if (response.status === 404) return false;\n if (!response.ok) {\n throw new StorageError(\n `vana-storage delete failed: ${response.status} ${response.statusText}`,\n \"DELETE_FAILED\",\n \"vana-storage\",\n );\n }\n return true;\n }\n\n /**\n * Delete every version's blob under `(owner, scope)` --\n * `DELETE {prefix}/{owner}/{scope}` on vana-storage, signed with the same\n * Web3Signed header as uploads (aud = endpoint origin, empty bodyHash).\n * The worker accepts the owner's own signature or a personal server the\n * owner registered with the gateway.\n *\n * @param ownerAddress - Must equal the provider's configured owner; a\n * mismatch throws before anything is signed so this wallet can never be\n * induced to sign a delete for another namespace.\n * @param scope - The scope segment, e.g. `\"instagram.profile\"`.\n */\n async deleteScope(\n ownerAddress: `0x${string}`,\n scope: string,\n ): Promise<VanaStorageScopeDeleteResult> {\n if (ownerAddress.toLowerCase() !== this.ownerAddress) {\n throw new StorageError(\n `deleteScope owner '${ownerAddress}' does not match the configured owner '${this.ownerAddress}'`,\n \"INVALID_OWNER\",\n \"vana-storage\",\n );\n }\n if (\n scope.length === 0 ||\n scope.includes(\"/\") ||\n isTraversalSegment(scope)\n ) {\n throw new StorageError(\n `scope must be a single non-empty path segment, got '${scope}'`,\n \"INVALID_SCOPE\",\n \"vana-storage\",\n );\n }\n\n const path = `${this.blobPathPrefix}/${this.ownerAddress}/${encodeURIComponent(scope)}`;\n const header = await this.signRequest(\"DELETE\", path);\n\n let response: Response;\n try {\n response = await this.fetchImpl(`${this.endpoint}${path}`, {\n method: \"DELETE\",\n headers: { authorization: header },\n });\n } catch (cause) {\n throw new StorageError(\n `vana-storage scope delete network error: ${describe(cause)}`,\n \"DELETE_ERROR\",\n \"vana-storage\",\n { cause: cause instanceof Error ? cause : undefined },\n );\n }\n\n if (!response.ok) {\n const responseText = await safeText(response);\n throw new StorageError(\n `vana-storage scope delete failed: ${response.status} ${response.statusText} - ${responseText}`,\n \"DELETE_FAILED\",\n \"vana-storage\",\n );\n }\n\n // A 2xx is the success signal; the body is informational. Tolerate an\n // empty 204, a non-JSON body, or a JSON `null`/array, and only trust\n // fields that carry the documented type.\n const body = await readJsonObject(response);\n return {\n deleted: typeof body[\"deleted\"] === \"boolean\" ? body[\"deleted\"] : true,\n scope: typeof body[\"scope\"] === \"string\" ? body[\"scope\"] : scope,\n count: nonNegativeInteger(body[\"count\"]) ?? 0,\n totalBytes: nonNegativeInteger(body[\"totalBytes\"]) ?? 0,\n };\n }\n\n getConfig(): StorageProviderConfig {\n return {\n name: \"vana-storage\",\n type: \"vana-storage\",\n requiresAuth: true,\n features: {\n upload: true,\n download: true,\n list: false,\n delete: true,\n },\n };\n }\n\n private async signRequest(\n method: \"GET\" | \"PUT\" | \"DELETE\",\n path: string,\n body?: Uint8Array,\n ): Promise<string> {\n const now = Math.floor(Date.now() / 1000);\n return buildWeb3SignedHeader({\n signMessage: this.signer.signMessage,\n aud: this.endpoint,\n method,\n uri: path,\n iat: now,\n exp: now + DEFAULT_TOKEN_TTL_SECONDS,\n ...(body !== undefined && body.length > 0 && { body }),\n });\n }\n\n private pathFromUrl(url: string): string {\n let parsed: URL;\n try {\n parsed = new URL(url);\n } catch {\n throw new StorageError(\n `Invalid URL: ${url}`,\n \"INVALID_URL\",\n \"vana-storage\",\n );\n }\n const expectedHost = new URL(this.endpoint).host;\n if (parsed.host !== expectedHost) {\n throw new StorageError(\n `URL host '${parsed.host}' does not match storage endpoint '${expectedHost}'`,\n \"INVALID_URL\",\n \"vana-storage\",\n );\n }\n // Restrict to a blob path for this owner so a caller cannot induce this\n // wallet to sign arbitrary same-host paths.\n const route = parseBlobPath(parsed.pathname);\n if (!route || route.owner.toLowerCase() !== this.ownerAddress) {\n throw new StorageError(\n `URL path '${parsed.pathname}' must be ${this.blobPathPrefix}/${this.ownerAddress}/{scope}/{collectedAt}`,\n \"INVALID_URL\",\n \"vana-storage\",\n );\n }\n // A provider must only sign requests for its configured namespace. Chain\n // mode is strict: old legacy blobs should be migrated or re-collected\n // rather than read through a second ambiguous path.\n if (route.chainId !== this.chainId) {\n throw new StorageError(\n `URL chainId '${route.chainId ?? \"legacy\"}' does not match provider namespace '${this.namespaceDescription()}'`,\n \"INVALID_URL\",\n \"vana-storage\",\n );\n }\n return parsed.pathname;\n }\n\n private namespaceDescription(): string {\n if (this.network !== undefined) {\n return `${this.network} (${this.chainId})`;\n }\n return this.chainId === undefined ? \"legacy\" : String(this.chainId);\n }\n}\n\ninterface ParsedBlobRoute {\n chainId?: number;\n owner: string;\n scope: string;\n collectedAt: string;\n}\n\nfunction isValidChainId(value: unknown): value is number {\n return typeof value === \"number\" && Number.isInteger(value) && value > 0;\n}\n\nfunction resolveStorageNamespace(\n config: Pick<VanaStorageConfig, \"network\" | \"chainId\">,\n): {\n chainId?: number;\n network?: ProtocolNetwork;\n} {\n if (config.network !== undefined && !isProtocolNetwork(config.network)) {\n throw new StorageError(\n `Unsupported vana-storage network '${String(config.network)}'`,\n \"INVALID_NETWORK\",\n \"vana-storage\",\n );\n }\n\n if (config.chainId !== undefined && !isValidChainId(config.chainId)) {\n throw new StorageError(\n `Unsupported vana-storage chainId '${String(config.chainId)}'`,\n \"INVALID_CHAIN_ID\",\n \"vana-storage\",\n );\n }\n\n if (config.network === undefined) {\n return { chainId: config.chainId };\n }\n\n const networkChainId = getProtocolNetworkChainId(config.network);\n if (config.chainId !== undefined && config.chainId !== networkChainId) {\n throw new StorageError(\n `vana-storage network '${config.network}' resolves to chainId '${networkChainId}', not '${config.chainId}'`,\n \"INVALID_CHAIN_ID\",\n \"vana-storage\",\n );\n }\n\n return { chainId: networkChainId, network: config.network };\n}\n\n/**\n * Parse a storage blob pathname into structured route info, or `null` when the\n * path is neither the legacy nor the chain-scoped blob shape. Traversal\n * segments (`.` / `..`) are rejected as invalid.\n */\nfunction parseBlobPath(pathname: string): ParsedBlobRoute | null {\n const segments = pathname.split(\"/\").filter((s) => s.length > 0);\n const isTraversal = (s: string): boolean => s === \".\" || s === \"..\";\n\n // Legacy: /v1/blobs/{owner}/{scope}/{collectedAt}\n if (\n segments.length === 5 &&\n segments[0] === \"v1\" &&\n segments[1] === \"blobs\"\n ) {\n const [, , owner, scope, collectedAt] = segments as [\n string,\n string,\n string,\n string,\n string,\n ];\n if (isTraversal(scope) || isTraversal(collectedAt)) return null;\n return { owner, scope, collectedAt };\n }\n\n // Chain-scoped: /v1/chains/{chainId}/blobs/{owner}/{scope}/{collectedAt}\n if (\n segments.length === 7 &&\n segments[0] === \"v1\" &&\n segments[1] === \"chains\" &&\n segments[3] === \"blobs\"\n ) {\n const [, , chainIdSegment, , owner, scope, collectedAt] = segments as [\n string,\n string,\n string,\n string,\n string,\n string,\n string,\n ];\n if (!/^[0-9]+$/.test(chainIdSegment)) return null;\n const chainId = Number(chainIdSegment);\n if (!isValidChainId(chainId)) return null;\n if (isTraversal(scope) || isTraversal(collectedAt)) return null;\n return { chainId, owner, scope, collectedAt };\n }\n\n return null;\n}\n\nfunction encodeRelativePath(filename: string): string {\n const parts = filename.split(\"/\");\n if (\n parts.length !== 2 ||\n parts.some((p) => p.length === 0 || p === \".\" || p === \"..\")\n ) {\n throw new StorageError(\n `filename must be exactly '{scope}/{collectedAt}' with non-empty segments, got '${filename}'`,\n \"INVALID_FILENAME\",\n \"vana-storage\",\n );\n }\n return parts.map((p) => encodeURIComponent(p)).join(\"/\");\n}\n\nfunction isTraversalSegment(segment: string): boolean {\n return segment === \".\" || segment === \"..\";\n}\n\nfunction nonNegativeInteger(value: unknown): number | undefined {\n return typeof value === \"number\" && Number.isInteger(value) && value >= 0\n ? value\n : undefined;\n}\n\nfunction describe(value: unknown): string {\n if (value instanceof Error) return value.message;\n return String(value);\n}\n\nfunction retryDelayMs(response: Response, responseText: string): number {\n const headerDelayMs = parseRetryAfterHeaderMs(\n response.headers.get(\"retry-after\"),\n );\n if (headerDelayMs !== null) {\n return clampRateLimitDelay(headerDelayMs);\n }\n\n return clampRateLimitDelay(parseRetryAfterBodyMs(responseText) ?? 0);\n}\n\nfunction parseRetryAfterHeaderMs(value: string | null): number | null {\n if (!value) return null;\n\n const seconds = Number(value);\n if (Number.isFinite(seconds)) {\n return seconds * 1000;\n }\n\n const dateMs = Date.parse(value);\n if (Number.isFinite(dateMs)) {\n return Math.max(0, dateMs - Date.now());\n }\n\n return null;\n}\n\nfunction parseRetryAfterBodyMs(responseText: string): number | null {\n if (!responseText) return null;\n\n try {\n const parsed = JSON.parse(responseText) as { retryAfter?: unknown };\n const seconds = Number(parsed.retryAfter);\n return Number.isFinite(seconds) ? seconds * 1000 : null;\n } catch {\n return null;\n }\n}\n\nfunction clampRateLimitDelay(delayMs: number): number {\n return Math.min(Math.max(0, delayMs), MAX_RATE_LIMIT_DELAY_MS);\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\nasync function safeText(response: Response): Promise<string> {\n try {\n return await response.text();\n } catch {\n return \"\";\n }\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,127 @@
1
+ /**
2
+ * In-memory Personal Server + gateway that enforce the Write API contract the
3
+ * way `personal-server-ts` does (routes/write-session.ts, api-auth.ts,
4
+ * write/attribution.ts, contracts/binary.ts), so the SDK client is tested
5
+ * against the real rules rather than a permissive stub:
6
+ *
7
+ * - handshake: Web3Signed proof, grantId claim required, grant must carry
8
+ * `write:` entries, signer must be the grantee, proof single-use
9
+ * - write: bearer must resolve to a live session, scope must be covered by
10
+ * the session's write patterns, X-Vana-Write-Signature must recover to
11
+ * the session builder over the STORED representation (body for JSON,
12
+ * the `$binary` record for anything else), carry the session grantId as
13
+ * a signed claim, JSON bodies must be compact, reserved keys rejected,
14
+ * proof single-use; `lineage` is the body's top-level field (JSON) or
15
+ * the metadata object's field (binary), validated per
16
+ * docs/derivative-data-api.md and mirrored to `$lineage`
17
+ * - lineage reads on both the Personal Server and the gateway: Web3Signed
18
+ * over the bare path (`/lineage[/:version]`, the version is a path
19
+ * segment, any query is 400), grant view from the signed `grantId` claim
20
+ * only, 401 for a missing / invalid gateway signature, a uniform 404 for
21
+ * an unknown id and for a signer the gateway will not serve; answering
22
+ * the `{ data, proof }` envelope with redaction for nodes the caller's
23
+ * grant does not cover
24
+ *
25
+ * The binary representation is a verbatim port of the Personal Server's
26
+ * `buildBinaryEnvelopeData` / `parseMetadataHeader` (Web Crypto + btoa), so
27
+ * it is an independent oracle for the SDK's `binaryWriteSignedBytes`.
28
+ */
29
+ import { type Address, type Hex } from "viem";
30
+ export interface MockGrant {
31
+ id: string;
32
+ grantorAddress: Address;
33
+ granteeId: Address;
34
+ scopes: string[];
35
+ revokedAt?: string | null;
36
+ }
37
+ export interface MockStoredRecord {
38
+ scope: string;
39
+ collectedAt: string;
40
+ data: Record<string, unknown>;
41
+ }
42
+ export interface MockLineageSource {
43
+ dataPointId: Hex;
44
+ scope: string;
45
+ version: string;
46
+ deletedAt: string | null;
47
+ }
48
+ export interface MockPersonalServerOptions {
49
+ origin: string;
50
+ owner: Address;
51
+ grants: MockGrant[];
52
+ /** Data points a lineage source may reference (id -> node). */
53
+ knownDataPoints?: MockLineageSource[];
54
+ /** Fixed ingest status. */
55
+ status?: "stored" | "syncing";
56
+ sessionTtlSeconds?: number;
57
+ now?: () => number;
58
+ }
59
+ export interface MockRequestLog {
60
+ method: string;
61
+ path: string;
62
+ headers: Record<string, string>;
63
+ body: Uint8Array;
64
+ }
65
+ export interface MockPersonalServer {
66
+ fetch: typeof fetch;
67
+ origin: string;
68
+ records: MockStoredRecord[];
69
+ requests: MockRequestLog[];
70
+ /** Handshake and write proofs consumed so far (sha-256 hex of the header). */
71
+ proofsSeen: Set<string>;
72
+ /** Make the next `n` fetches throw (transport failure) before answering. */
73
+ failNext(n: number, error?: Error): void;
74
+ /** Force the next response (any route). */
75
+ respondNextWith(status: number, body: unknown): void;
76
+ /** Sessions minted (token -> record). */
77
+ sessions: Map<string, MockSession>;
78
+ }
79
+ export interface MockSession {
80
+ token: string;
81
+ builderAddress: Address;
82
+ grantId: string;
83
+ writeScopes: string[];
84
+ expiresAtMs: number;
85
+ }
86
+ /** The Personal Server's `binaryWriteSignedBytes`, ported for the oracle. */
87
+ export declare function personalServerBinaryWriteSignedBytes(input: {
88
+ bytes: Uint8Array;
89
+ contentType: string;
90
+ filename?: string;
91
+ metadataHeader?: string;
92
+ }): Promise<Uint8Array>;
93
+ /** A Personal Server that enforces the Write API contract, as a `fetch`. */
94
+ export declare function createMockPersonalServer(options: MockPersonalServerOptions): MockPersonalServer;
95
+ export interface MockGatewayOptions {
96
+ origin: string;
97
+ /** Lineage views by data point id (lowercase). */
98
+ graphs: Record<string, MockGatewayView>;
99
+ /** Builder address -> grant ids it holds (lowercase), for the 404 rule. */
100
+ grants?: Record<string, string[]>;
101
+ /** Wrap answers in the gateway `{ data, proof }` envelope (default true). */
102
+ envelope?: boolean;
103
+ now?: () => number;
104
+ }
105
+ export interface MockGatewayView {
106
+ dataPointId: Hex;
107
+ ownerAddress?: Address;
108
+ scope: string;
109
+ version: string;
110
+ deletedAt: string | null;
111
+ sources: unknown[];
112
+ derivatives: unknown[];
113
+ derivativesTruncated?: boolean;
114
+ }
115
+ export interface MockGateway {
116
+ fetch: typeof fetch;
117
+ requests: MockRequestLog[];
118
+ }
119
+ /**
120
+ * A gateway answering `GET /v1/data/:id/lineage[/:version]`: the version is
121
+ * a path segment and any query string is 400; the request must carry a
122
+ * Web3Signed header whose `uri` is that bare path (401
123
+ * LINEAGE_SIGNATURE_REQUIRED / LINEAGE_SIGNATURE_INVALID otherwise); the
124
+ * grant view is the signed `grantId` claim; an unknown id and a signer that
125
+ * holds no such grant both answer a uniform 404.
126
+ */
127
+ export declare function createMockGateway(options: MockGatewayOptions): MockGateway;
@@ -0,0 +1,46 @@
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 response_body_exports = {};
20
+ __export(response_body_exports, {
21
+ isPlainObject: () => isPlainObject,
22
+ readJsonObject: () => readJsonObject,
23
+ readJsonValue: () => readJsonValue
24
+ });
25
+ module.exports = __toCommonJS(response_body_exports);
26
+ function isPlainObject(value) {
27
+ return typeof value === "object" && value !== null && !Array.isArray(value);
28
+ }
29
+ async function readJsonValue(res) {
30
+ try {
31
+ return await res.json();
32
+ } catch {
33
+ return null;
34
+ }
35
+ }
36
+ async function readJsonObject(res) {
37
+ const value = await readJsonValue(res);
38
+ return isPlainObject(value) ? value : {};
39
+ }
40
+ // Annotate the CommonJS export names for ESM import in node:
41
+ 0 && (module.exports = {
42
+ isPlainObject,
43
+ readJsonObject,
44
+ readJsonValue
45
+ });
46
+ //# sourceMappingURL=response-body.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/utils/response-body.ts"],"sourcesContent":["/**\n * Tolerant JSON body readers for `fetch` responses.\n *\n * A response body can legitimately be empty (204), non-JSON (an HTML error\n * page from a proxy), or JSON that is not an object (`null`, an array, a\n * string). Every SDK code path that indexes into a parsed body must go\n * through these helpers so those shapes degrade to \"no fields\" instead of a\n * `TypeError` that bypasses the typed error the caller expects.\n *\n * @internal\n */\n\n/** The one method the readers need, so custom fetch shims qualify too. */\nexport type JsonBodySource = Pick<Response, \"json\">;\n\n/** True for a non-null, non-array object -- the only shape safe to index. */\nexport function isPlainObject(\n value: unknown,\n): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\n/**\n * Parse the body as JSON. Resolves `null` when the body is empty or not\n * valid JSON. Never throws.\n */\nexport async function readJsonValue(res: JsonBodySource): Promise<unknown> {\n try {\n return (await res.json()) as unknown;\n } catch {\n return null;\n }\n}\n\n/**\n * Parse the body as JSON and return it only when it is a plain object;\n * every other shape (empty, non-JSON, `null`, array, primitive) becomes `{}`.\n * Never throws.\n */\nexport async function readJsonObject(\n res: JsonBodySource,\n): Promise<Record<string, unknown>> {\n const value = await readJsonValue(res);\n return isPlainObject(value) ? value : {};\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAgBO,SAAS,cACd,OACkC;AAClC,SAAO,OAAO,UAAU,YAAY,UAAU,QAAQ,CAAC,MAAM,QAAQ,KAAK;AAC5E;AAMA,eAAsB,cAAc,KAAuC;AACzE,MAAI;AACF,WAAQ,MAAM,IAAI,KAAK;AAAA,EACzB,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAOA,eAAsB,eACpB,KACkC;AAClC,QAAM,QAAQ,MAAM,cAAc,GAAG;AACrC,SAAO,cAAc,KAAK,IAAI,QAAQ,CAAC;AACzC;","names":[]}
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Tolerant JSON body readers for `fetch` responses.
3
+ *
4
+ * A response body can legitimately be empty (204), non-JSON (an HTML error
5
+ * page from a proxy), or JSON that is not an object (`null`, an array, a
6
+ * string). Every SDK code path that indexes into a parsed body must go
7
+ * through these helpers so those shapes degrade to "no fields" instead of a
8
+ * `TypeError` that bypasses the typed error the caller expects.
9
+ *
10
+ * @internal
11
+ */
12
+ /** The one method the readers need, so custom fetch shims qualify too. */
13
+ export type JsonBodySource = Pick<Response, "json">;
14
+ /** True for a non-null, non-array object -- the only shape safe to index. */
15
+ export declare function isPlainObject(value: unknown): value is Record<string, unknown>;
16
+ /**
17
+ * Parse the body as JSON. Resolves `null` when the body is empty or not
18
+ * valid JSON. Never throws.
19
+ */
20
+ export declare function readJsonValue(res: JsonBodySource): Promise<unknown>;
21
+ /**
22
+ * Parse the body as JSON and return it only when it is a plain object;
23
+ * every other shape (empty, non-JSON, `null`, array, primitive) becomes `{}`.
24
+ * Never throws.
25
+ */
26
+ export declare function readJsonObject(res: JsonBodySource): Promise<Record<string, unknown>>;
@@ -0,0 +1,20 @@
1
+ function isPlainObject(value) {
2
+ return typeof value === "object" && value !== null && !Array.isArray(value);
3
+ }
4
+ async function readJsonValue(res) {
5
+ try {
6
+ return await res.json();
7
+ } catch {
8
+ return null;
9
+ }
10
+ }
11
+ async function readJsonObject(res) {
12
+ const value = await readJsonValue(res);
13
+ return isPlainObject(value) ? value : {};
14
+ }
15
+ export {
16
+ isPlainObject,
17
+ readJsonObject,
18
+ readJsonValue
19
+ };
20
+ //# sourceMappingURL=response-body.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/utils/response-body.ts"],"sourcesContent":["/**\n * Tolerant JSON body readers for `fetch` responses.\n *\n * A response body can legitimately be empty (204), non-JSON (an HTML error\n * page from a proxy), or JSON that is not an object (`null`, an array, a\n * string). Every SDK code path that indexes into a parsed body must go\n * through these helpers so those shapes degrade to \"no fields\" instead of a\n * `TypeError` that bypasses the typed error the caller expects.\n *\n * @internal\n */\n\n/** The one method the readers need, so custom fetch shims qualify too. */\nexport type JsonBodySource = Pick<Response, \"json\">;\n\n/** True for a non-null, non-array object -- the only shape safe to index. */\nexport function isPlainObject(\n value: unknown,\n): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\n/**\n * Parse the body as JSON. Resolves `null` when the body is empty or not\n * valid JSON. Never throws.\n */\nexport async function readJsonValue(res: JsonBodySource): Promise<unknown> {\n try {\n return (await res.json()) as unknown;\n } catch {\n return null;\n }\n}\n\n/**\n * Parse the body as JSON and return it only when it is a plain object;\n * every other shape (empty, non-JSON, `null`, array, primitive) becomes `{}`.\n * Never throws.\n */\nexport async function readJsonObject(\n res: JsonBodySource,\n): Promise<Record<string, unknown>> {\n const value = await readJsonValue(res);\n return isPlainObject(value) ? value : {};\n}\n"],"mappings":"AAgBO,SAAS,cACd,OACkC;AAClC,SAAO,OAAO,UAAU,YAAY,UAAU,QAAQ,CAAC,MAAM,QAAQ,KAAK;AAC5E;AAMA,eAAsB,cAAc,KAAuC;AACzE,MAAI;AACF,WAAQ,MAAM,IAAI,KAAK;AAAA,EACzB,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAOA,eAAsB,eACpB,KACkC;AAClC,QAAM,QAAQ,MAAM,cAAc,GAAG;AACrC,SAAO,cAAc,KAAK,IAAI,QAAQ,CAAC;AACzC;","names":[]}
@@ -0,0 +1 @@
1
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opendatalabs/vana-sdk",
3
- "version": "3.16.0",
3
+ "version": "3.18.0",
4
4
  "description": "A TypeScript library for interacting with Vana Network smart contracts.",
5
5
  "publishConfig": {
6
6
  "access": "public"