@opendatalabs/vana-sdk 3.17.0 → 3.18.1
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.
- package/README.md +6 -3
- package/dist/direct/personal-server-read.cjs +20 -1
- package/dist/direct/personal-server-read.cjs.map +1 -1
- package/dist/direct/personal-server-read.js +23 -1
- package/dist/direct/personal-server-read.js.map +1 -1
- package/dist/errors.cjs +27 -0
- package/dist/errors.cjs.map +1 -1
- package/dist/errors.d.ts +67 -0
- package/dist/errors.js +24 -0
- package/dist/errors.js.map +1 -1
- package/dist/index.browser.d.ts +2 -1
- package/dist/index.browser.js +731 -311
- package/dist/index.browser.js.map +4 -4
- package/dist/index.node.cjs +743 -313
- package/dist/index.node.cjs.map +4 -4
- package/dist/index.node.d.ts +2 -1
- package/dist/index.node.js +731 -311
- package/dist/index.node.js.map +4 -4
- package/dist/protocol/data-point-deletion.cjs +220 -0
- package/dist/protocol/data-point-deletion.cjs.map +1 -0
- package/dist/protocol/data-point-deletion.d.ts +180 -0
- package/dist/protocol/data-point-deletion.js +193 -0
- package/dist/protocol/data-point-deletion.js.map +1 -0
- package/dist/protocol/data-point-deletion.test.d.ts +1 -0
- package/dist/protocol/gateway.cjs +145 -32
- package/dist/protocol/gateway.cjs.map +1 -1
- package/dist/protocol/gateway.d.ts +42 -3
- package/dist/protocol/gateway.js +156 -32
- package/dist/protocol/gateway.js.map +1 -1
- package/dist/protocol/lineage.cjs +9 -4
- package/dist/protocol/lineage.cjs.map +1 -1
- package/dist/protocol/lineage.d.ts +21 -12
- package/dist/protocol/lineage.js +9 -4
- package/dist/protocol/lineage.js.map +1 -1
- package/dist/protocol/personal-server-data.cjs +20 -1
- package/dist/protocol/personal-server-data.cjs.map +1 -1
- package/dist/protocol/personal-server-data.js +23 -1
- package/dist/protocol/personal-server-data.js.map +1 -1
- package/dist/storage/index.cjs.map +1 -1
- package/dist/storage/index.d.ts +1 -1
- package/dist/storage/index.js.map +1 -1
- package/dist/storage/providers/vana-storage.cjs +66 -0
- package/dist/storage/providers/vana-storage.cjs.map +1 -1
- package/dist/storage/providers/vana-storage.d.ts +26 -0
- package/dist/storage/providers/vana-storage.js +66 -0
- package/dist/storage/providers/vana-storage.js.map +1 -1
- package/dist/utils/response-body.cjs +46 -0
- package/dist/utils/response-body.cjs.map +1 -0
- package/dist/utils/response-body.d.ts +26 -0
- package/dist/utils/response-body.js +20 -0
- package/dist/utils/response-body.js.map +1 -0
- package/dist/utils/response-body.test.d.ts +1 -0
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/protocol/lineage.ts"],"sourcesContent":["/**\n * Derivative lineage: which data points a record was derived from, and which\n * records were derived from it.\n *\n * @remarks\n * A data point is addressed by `keccak256(abi.encode(address owner, string\n * scope))` ({@link deriveDataPointId}). A builder writing a derivative names\n * its sources through the `lineage` option of {@link writeData}; the Personal\n * Server stores them under the reserved `$lineage` key and both the Personal\n * Server (`GET /v1/data/:scope/lineage[/:version]`) and the gateway\n * (`GET /v1/data/:dataPointId/lineage[/:version]`) answer the resulting view.\n * Nodes the caller holds no grant for come back as\n * `{ dataPointId, redacted: true }`; a source that no longer resolves comes\n * back with `version: \"0\"`.\n *\n * A derived scope must not share its first dot-segment with any source scope\n * (a grant on `chatgpt.*` must never read a derivative of\n * `chatgpt.conversations`): see {@link assertDerivedScopeNaming}.\n *\n * @category Protocol\n */\n\nimport {\n encodeAbiParameters,\n isAddress,\n keccak256,\n type Address,\n type Hex,\n} from \"viem\";\nimport { z } from \"zod\";\nimport { buildWeb3SignedHeader } from \"../auth/web3-signed-builder\";\nimport { LineageReadError, WriteRequestError } from \"../errors\";\nimport {\n isRecord,\n readPersonalServerErrorBody,\n} from \"./personal-server-error-body\";\nimport {\n resolveWriteSigner,\n type ResolveWriteSignerOptions,\n type WriteSignerSource,\n} from \"./write-signer\";\n\nconst DATA_POINT_ID_PATTERN = /^0x[0-9a-fA-F]{64}$/;\n\n/** `true` when `value` is a 32-byte hex data point id. */\nexport function isDataPointId(value: unknown): value is Hex {\n return typeof value === \"string\" && DATA_POINT_ID_PATTERN.test(value);\n}\n\n/**\n * Derive the DataRegistryV2 data point id for an owner and scope:\n * `keccak256(abi.encode(address ownerAddress, string scope))`.\n *\n * @param ownerAddress - The data owner (the Personal Server owner).\n * @param scope - The scope the data point is stored under.\n * @returns The 32-byte id, lowercase hex.\n * @throws Error when `ownerAddress` is not an EVM address.\n */\nexport function deriveDataPointId(ownerAddress: Address, scope: string): Hex {\n if (!isAddress(ownerAddress, { strict: false })) {\n throw new Error(\n `ownerAddress is not an EVM address: ${String(ownerAddress)}`,\n );\n }\n return keccak256(\n encodeAbiParameters(\n [\n { name: \"ownerAddress\", type: \"address\" },\n { name: \"scope\", type: \"string\" },\n ],\n [ownerAddress, scope],\n ),\n );\n}\n\nconst DataPointIdSchema = z\n .string()\n .regex(DATA_POINT_ID_PATTERN)\n .transform((value) => value.toLowerCase() as Hex);\n\nconst VERSION_PATTERN = /^[1-9]\\d*$/;\nconst NODE_VERSION_PATTERN = /^(0|[1-9]\\d*)$/;\n\n// Versions are decimal integers, strings on the wire; a numeric value is\n// normalised to the same representation. A node's version may be \"0\": a\n// source that no longer resolves to a registered data point.\nconst VersionSchema = z\n .union([z.string(), z.number()])\n .transform(String)\n .refine((value) => NODE_VERSION_PATTERN.test(value), {\n message: \"version must be a decimal integer\",\n });\n// The view's own version is a registered one: always positive.\nconst ViewVersionSchema = VersionSchema.refine(\n (value) => VERSION_PATTERN.test(value),\n { message: \"version must be a positive decimal integer\" },\n);\n\n/** First dot-segment of a scope (`chatgpt` for `chatgpt.conversations`). */\nexport function scopeNamespace(scope: string): string {\n const dot = scope.indexOf(\".\");\n return dot === -1 ? scope : scope.slice(0, dot);\n}\n\n/**\n * The naming rule: a derived scope and a source scope must not share their\n * first dot-segment, because a `prefix.*` grant would then cover both and\n * leak across the lineage edge. Mirrors the Personal Server's check\n * (`LINEAGE_SCOPE_UNDER_SOURCE_PREFIX`).\n */\nexport function derivedScopeViolatesNaming(\n derivedScope: string,\n sourceScope: string,\n): boolean {\n return scopeNamespace(derivedScope) === scopeNamespace(sourceScope);\n}\n\n/**\n * Throw when `derivedScope` shares its first dot-segment with any source\n * scope (see {@link derivedScopeViolatesNaming}).\n *\n * @throws {WriteRequestError} Naming the offending source scope in `details`.\n */\nexport function assertDerivedScopeNaming(\n derivedScope: string,\n sourceScopes: readonly string[],\n): void {\n for (const sourceScope of sourceScopes) {\n if (derivedScopeViolatesNaming(derivedScope, sourceScope)) {\n throw new WriteRequestError(\n `Derived scope ${derivedScope} must not share its first segment with source scope ${sourceScope}; put derivatives in the app's own namespace`,\n { scope: derivedScope, sourceScope },\n );\n }\n }\n}\n\nexport const LineageNodeSchema = z.object({\n dataPointId: DataPointIdSchema,\n scope: z.string(),\n /**\n * The node's current version, decimal string; `\"0\"` for a source that no\n * longer resolves to a registered data point.\n */\n version: VersionSchema,\n /** The node's tombstone time, or `null` when live. */\n deletedAt: z.string().nullable(),\n});\n\nexport const RedactedLineageNodeSchema = z.object({\n dataPointId: DataPointIdSchema,\n redacted: z.literal(true),\n});\n\nexport const LineageEntrySchema = z.union([\n RedactedLineageNodeSchema,\n LineageNodeSchema,\n]);\n\nexport const LineageGraphSchema = z.object({\n dataPointId: DataPointIdSchema,\n /** The data point owner; every node in the view belongs to it. */\n ownerAddress: z.string().optional(),\n scope: z.string(),\n /**\n * The derived record's version whose lineage is shown: the requested one,\n * else the current one, else (current is a tombstone) the last version\n * that carried lineage.\n */\n version: ViewVersionSchema,\n deletedAt: z.string().nullable(),\n sources: z.array(LineageEntrySchema),\n derivatives: z.array(LineageEntrySchema),\n /** `true` when `derivatives` was cut at the server's cap (1000). */\n derivativesTruncated: z.boolean().optional(),\n});\n\n/** A lineage node the caller is allowed to see. */\nexport type LineageNode = z.infer<typeof LineageNodeSchema>;\n\n/** A lineage node the caller holds no grant for: only its id is disclosed. */\nexport type RedactedLineageNode = z.infer<typeof RedactedLineageNodeSchema>;\n\n/** One entry of a lineage graph. Narrow with {@link isRedactedLineageNode}. */\nexport type LineageEntry = z.infer<typeof LineageEntrySchema>;\n\n/** The lineage view of one data point (the `data` of the response). */\nexport type LineageGraph = z.infer<typeof LineageGraphSchema>;\n\n/** A lineage read: the view plus the gateway's attestation over it. */\nexport interface LineageReadResult extends LineageGraph {\n /**\n * The gateway `proof` (`GatewayAttestation` over the served view, so a\n * redacted view verifies on its own). Passed through as received; absent\n * when the server sent none.\n */\n proof?: Record<string, unknown>;\n}\n\n/** `true` when the entry was redacted (the caller holds no grant for it). */\nexport function isRedactedLineageNode(\n entry: LineageEntry,\n): entry is RedactedLineageNode {\n return \"redacted\" in entry && entry.redacted === true;\n}\n\n/**\n * The Personal Server lineage path: `/v1/data/:scope/lineage[/:version]`.\n * The version is a path segment (a query string is refused by the server),\n * so the signed `uri` covers the whole request.\n */\nexport function personalServerLineagePath(\n scope: string,\n version?: string | number,\n): string {\n return `/v1/data/${encodeURIComponent(scope)}/lineage${version === undefined ? \"\" : `/${String(version)}`}`;\n}\n\n/**\n * The gateway lineage path: `/v1/data/<id lowercase>/lineage[/:version]`,\n * what the request is signed over and sent to. The grant view is the signed\n * `grantId` claim, never a query parameter.\n */\nexport function gatewayLineagePath(\n dataPointId: Hex,\n version?: string | number,\n): string {\n return `/v1/data/${dataPointId.toLowerCase()}/lineage${version === undefined ? \"\" : `/${String(version)}`}`;\n}\n\ninterface LineageRequestOptions {\n /**\n * Read the lineage as of this version (a positive decimal integer);\n * omitted = the current version, or the last version that carried lineage\n * when the current one is a tombstone.\n */\n version?: string | number;\n /** `fetch` to use; defaults to `globalThis.fetch`. */\n fetch?: typeof fetch;\n /** Extra request headers. */\n headers?: HeadersInit;\n}\n\n/** Lineage read against the Personal Server holding the record. */\nexport interface PersonalServerLineageParams extends LineageRequestOptions {\n /** Personal Server origin, e.g. `https://ps.example.com`. */\n personalServerUrl: string;\n /** The scope whose lineage to read. */\n scope: string;\n /** A grant covering the scope, sent as the signed `grantId` claim. */\n grantId: string;\n /** Builder key: a viem `LocalAccount`, `WalletClient`, or `{ signMessage }`. */\n signer: WriteSignerSource;\n /** Account for a viem wallet client without a hoisted account. */\n account?: ResolveWriteSignerOptions[\"account\"];\n /** Web3Signed audience; defaults to `personalServerUrl`. */\n audience?: string;\n}\n\n/** Lineage read against the gateway, by data point id. */\nexport interface GatewayLineageParams extends LineageRequestOptions {\n /** Gateway origin, e.g. `https://dp-rpc.vana.org`. */\n gatewayUrl: string;\n /** The data point whose lineage to read (see {@link deriveDataPointId}). */\n dataPointId: Hex;\n /**\n * The key the request is signed with (Web3Signed, audience = the gateway\n * origin). The signer decides the view: the owner or one of its servers\n * gets the full view; a registered builder holding a live grant covering\n * the data point's scope gets that grant's view; anyone else is refused.\n */\n signer: WriteSignerSource;\n /** Account for a viem wallet client without a hoisted account. */\n account?: ResolveWriteSignerOptions[\"account\"];\n /**\n * The grant whose view to read, sent lowercased as the signed `grantId`\n * claim (never as a query parameter). An owner or server uses it to fetch\n * the view a builder's grant sees; a builder needs it to see anything.\n */\n grantId?: string;\n}\n\nexport type GetLineageParams =\n | PersonalServerLineageParams\n | GatewayLineageParams;\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 LineageReadError(\"No fetch implementation available\");\n }\n return resolved;\n}\n\nfunction normalizeVersion(\n version: string | number | undefined,\n): string | undefined {\n if (version === undefined) return undefined;\n const text = String(version);\n if (!VERSION_PATTERN.test(text)) {\n throw new LineageReadError(\n \"version must be a positive decimal integer\",\n undefined,\n \"INVALID_VERSION\",\n { version },\n );\n }\n return text;\n}\n\nasync function lineageReadFailure(\n source: string,\n response: Response,\n): Promise<LineageReadError> {\n const { errorCode, message, details } =\n await readPersonalServerErrorBody(response);\n return new LineageReadError(\n message ??\n `${source} lineage read failed: ${response.status} ${response.statusText}`,\n response.status,\n errorCode,\n details,\n );\n}\n\nasync function parseLineageGraph(\n source: string,\n response: Response,\n): Promise<LineageReadResult> {\n let body: unknown;\n try {\n body = await response.json();\n } catch (err) {\n throw new LineageReadError(\n `${source} lineage response is not JSON`,\n response.status,\n null,\n { cause: err instanceof Error ? err.message : String(err) },\n );\n }\n // Both servers answer the gateway envelope `{ data, proof }`; a bare view\n // is accepted too.\n const envelope = isRecord(body) && isRecord(body.data) ? body : undefined;\n const parsed = LineageGraphSchema.safeParse(envelope?.data ?? body);\n if (!parsed.success) {\n throw new LineageReadError(\n `${source} lineage response is not a lineage view`,\n response.status,\n null,\n { issues: parsed.error.issues },\n );\n }\n const proof = isRecord(envelope?.proof) ? envelope.proof : undefined;\n return proof === undefined ? parsed.data : { ...parsed.data, proof };\n}\n\nasync function sendLineageRead(\n source: string,\n fetchFn: typeof fetch,\n url: string,\n headers: Headers,\n): Promise<LineageReadResult> {\n let response: Response;\n try {\n response = await fetchFn(url, { method: \"GET\", headers });\n } catch (err) {\n throw new LineageReadError(\n `${source} lineage read failed: ${err instanceof Error ? err.message : String(err)}`,\n undefined,\n null,\n { cause: err instanceof Error ? err.message : String(err) },\n );\n }\n if (!response.ok) {\n throw await lineageReadFailure(source, response);\n }\n return parseLineageGraph(source, response);\n}\n\n/**\n * Read a scope's lineage from the Personal Server that stores it.\n *\n * @remarks\n * Sends `GET /v1/data/:scope/lineage[/:version]` with a Web3Signed\n * `Authorization` header carrying `grantId`, the same authentication a data\n * read uses; the signed `uri` is the full path, version segment included.\n * The server resolves the data point id, fetches the view the grant sees\n * from the gateway and returns the gateway's `data` + `proof`.\n *\n * @returns The lineage view, with redacted entries for nodes the grant does\n * not cover, plus the gateway attestation.\n * @throws {LineageReadError} On a non-2xx answer (`errorCode`: read errors,\n * `INVALID_VERSION`, `NOT_FOUND` when the scope or version is not\n * registered at the gateway, `LINEAGE_FORBIDDEN`, `LINEAGE_GATEWAY_ERROR`,\n * `LINEAGE_UNAVAILABLE`), an unreadable body, a bad `version`, or a\n * transport failure.\n */\nexport async function getPersonalServerLineage(\n params: PersonalServerLineageParams,\n): Promise<LineageReadResult> {\n const fetchFn = resolveFetch(params.fetch);\n const baseUrl = normalizeBaseUrl(params.personalServerUrl);\n const audience = params.audience ?? baseUrl;\n const signer = resolveWriteSigner(params.signer, { account: params.account });\n const path = personalServerLineagePath(\n params.scope,\n normalizeVersion(params.version),\n );\n const headers = new Headers(params.headers);\n headers.set(\n \"Authorization\",\n await buildWeb3SignedHeader({\n signMessage: signer.signMessage,\n aud: audience,\n method: \"GET\",\n uri: path,\n grantId: params.grantId,\n }),\n );\n return sendLineageRead(\n \"Personal Server\",\n fetchFn,\n `${baseUrl}${path}`,\n headers,\n );\n}\n\n/**\n * Read a data point's lineage from the gateway.\n *\n * @remarks\n * Sends `GET /v1/data/:dataPointId/lineage[/:version]` with a Web3Signed\n * `Authorization` header: `aud` = the gateway origin, `uri` =\n * {@link gatewayLineagePath} (lowercase id, version segment included),\n * empty-body `bodyHash`, and the lowercased `grantId` claim when given. The\n * gateway answers a uniform 404 for an unknown data point and for a signer\n * it will not serve, so the two cannot be told apart from outside.\n *\n * @returns The lineage view, with redacted entries for nodes the caller's\n * grant does not cover, plus the gateway attestation.\n * @throws {LineageReadError} On a malformed `dataPointId` or `version`, a\n * non-2xx answer (400 malformed request, 401 `LINEAGE_SIGNATURE_REQUIRED`\n * / `LINEAGE_SIGNATURE_INVALID`, 404 unknown or not served), an unreadable\n * body, or a transport failure.\n */\nexport async function getGatewayLineage(\n params: GatewayLineageParams,\n): Promise<LineageReadResult> {\n if (!isDataPointId(params.dataPointId)) {\n throw new LineageReadError(\n \"dataPointId must be a 32-byte hex string (see deriveDataPointId)\",\n undefined,\n \"INVALID_DATA_POINT_ID\",\n { dataPointId: params.dataPointId },\n );\n }\n const fetchFn = resolveFetch(params.fetch);\n const baseUrl = normalizeBaseUrl(params.gatewayUrl);\n const signer = resolveWriteSigner(params.signer, { account: params.account });\n const uri = gatewayLineagePath(\n params.dataPointId,\n normalizeVersion(params.version),\n );\n const headers = new Headers(params.headers);\n headers.set(\n \"Authorization\",\n await buildWeb3SignedHeader({\n signMessage: signer.signMessage,\n aud: baseUrl,\n method: \"GET\",\n uri,\n grantId: params.grantId?.toLowerCase(),\n }),\n );\n return sendLineageRead(\"Gateway\", fetchFn, `${baseUrl}${uri}`, headers);\n}\n\n/**\n * Read a lineage view from either the Personal Server (by scope) or the\n * gateway (by data point id), chosen by the params shape.\n *\n * @example\n * ```typescript\n * const fromPs = await getLineage({ personalServerUrl, scope, grantId, signer });\n * const fromGateway = await getLineage({ gatewayUrl, dataPointId, grantId, signer });\n * ```\n */\nexport function getLineage(\n params: GetLineageParams,\n): Promise<LineageReadResult> {\n return \"personalServerUrl\" in params\n ? getPersonalServerLineage(params)\n : getGatewayLineage(params);\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/protocol/lineage.ts"],"sourcesContent":["/**\n * Derivative lineage: which data points a record was derived from, and which\n * records were derived from it.\n *\n * @remarks\n * A data point is addressed by `keccak256(abi.encode(address owner, string\n * scope))` ({@link deriveDataPointId}). A builder writing a derivative names\n * its sources through the `lineage` option of {@link writeData}; the Personal\n * Server stores them under the reserved `$lineage` key and both the Personal\n * Server (`GET /v1/data/:scope/lineage[/:version]`) and the gateway\n * (`GET /v1/data/:dataPointId/lineage[/:version]`) answer the resulting view.\n * Nodes the caller holds no grant for come back as exactly\n * `{ redacted: true }`: no id, scope or version, because the id is\n * `keccak256(owner, scope)` and a grantee who knows the owner could recover\n * the scope from it with a small dictionary. Order and count are preserved,\n * so a redacted node is still identified by its position. A source that no\n * longer resolves comes back with `version: \"0\"`.\n *\n * A derived scope must not share its first dot-segment with any source scope\n * (a grant on `chatgpt.*` must never read a derivative of\n * `chatgpt.conversations`): see {@link assertDerivedScopeNaming}.\n *\n * @category Protocol\n */\n\nimport {\n encodeAbiParameters,\n isAddress,\n keccak256,\n type Address,\n type Hex,\n} from \"viem\";\nimport { z } from \"zod\";\nimport { buildWeb3SignedHeader } from \"../auth/web3-signed-builder\";\nimport { LineageReadError, WriteRequestError } from \"../errors\";\nimport {\n isRecord,\n readPersonalServerErrorBody,\n} from \"./personal-server-error-body\";\nimport {\n resolveWriteSigner,\n type ResolveWriteSignerOptions,\n type WriteSignerSource,\n} from \"./write-signer\";\n\nconst DATA_POINT_ID_PATTERN = /^0x[0-9a-fA-F]{64}$/;\n\n/** `true` when `value` is a 32-byte hex data point id. */\nexport function isDataPointId(value: unknown): value is Hex {\n return typeof value === \"string\" && DATA_POINT_ID_PATTERN.test(value);\n}\n\n/**\n * Derive the DataRegistryV2 data point id for an owner and scope:\n * `keccak256(abi.encode(address ownerAddress, string scope))`.\n *\n * @param ownerAddress - The data owner (the Personal Server owner).\n * @param scope - The scope the data point is stored under.\n * @returns The 32-byte id, lowercase hex.\n * @throws Error when `ownerAddress` is not an EVM address.\n */\nexport function deriveDataPointId(ownerAddress: Address, scope: string): Hex {\n if (!isAddress(ownerAddress, { strict: false })) {\n throw new Error(\n `ownerAddress is not an EVM address: ${String(ownerAddress)}`,\n );\n }\n return keccak256(\n encodeAbiParameters(\n [\n { name: \"ownerAddress\", type: \"address\" },\n { name: \"scope\", type: \"string\" },\n ],\n [ownerAddress, scope],\n ),\n );\n}\n\nconst DataPointIdSchema = z\n .string()\n .regex(DATA_POINT_ID_PATTERN)\n .transform((value) => value.toLowerCase() as Hex);\n\nconst VERSION_PATTERN = /^[1-9]\\d*$/;\nconst NODE_VERSION_PATTERN = /^(0|[1-9]\\d*)$/;\n\n// Versions are decimal integers, strings on the wire; a numeric value is\n// normalised to the same representation. A node's version may be \"0\": a\n// source that no longer resolves to a registered data point.\nconst VersionSchema = z\n .union([z.string(), z.number()])\n .transform(String)\n .refine((value) => NODE_VERSION_PATTERN.test(value), {\n message: \"version must be a decimal integer\",\n });\n// The view's own version is a registered one: always positive.\nconst ViewVersionSchema = VersionSchema.refine(\n (value) => VERSION_PATTERN.test(value),\n { message: \"version must be a positive decimal integer\" },\n);\n\n/** First dot-segment of a scope (`chatgpt` for `chatgpt.conversations`). */\nexport function scopeNamespace(scope: string): string {\n const dot = scope.indexOf(\".\");\n return dot === -1 ? scope : scope.slice(0, dot);\n}\n\n/**\n * The naming rule: a derived scope and a source scope must not share their\n * first dot-segment, because a `prefix.*` grant would then cover both and\n * leak across the lineage edge. Mirrors the Personal Server's check\n * (`LINEAGE_SCOPE_UNDER_SOURCE_PREFIX`).\n */\nexport function derivedScopeViolatesNaming(\n derivedScope: string,\n sourceScope: string,\n): boolean {\n return scopeNamespace(derivedScope) === scopeNamespace(sourceScope);\n}\n\n/**\n * Throw when `derivedScope` shares its first dot-segment with any source\n * scope (see {@link derivedScopeViolatesNaming}).\n *\n * @throws {WriteRequestError} Naming the offending source scope in `details`.\n */\nexport function assertDerivedScopeNaming(\n derivedScope: string,\n sourceScopes: readonly string[],\n): void {\n for (const sourceScope of sourceScopes) {\n if (derivedScopeViolatesNaming(derivedScope, sourceScope)) {\n throw new WriteRequestError(\n `Derived scope ${derivedScope} must not share its first segment with source scope ${sourceScope}; put derivatives in the app's own namespace`,\n { scope: derivedScope, sourceScope },\n );\n }\n }\n}\n\nexport const LineageNodeSchema = z.object({\n dataPointId: DataPointIdSchema,\n scope: z.string(),\n /**\n * The node's current version, decimal string; `\"0\"` for a source that no\n * longer resolves to a registered data point.\n */\n version: VersionSchema,\n /** The node's tombstone time, or `null` when live. */\n deletedAt: z.string().nullable(),\n /**\n * Never present on a visible node. Declared so a node that carries\n * `redacted: true` next to an id, scope and version cannot slip through\n * this branch of {@link LineageEntrySchema} with the key stripped.\n */\n redacted: z.never().optional(),\n});\n\n/**\n * A redacted node is exactly `{ redacted: true }`. Any other key on it would\n * leak what the redaction hides, so the schema is strict: a view carrying a\n * redacted node with an id (or anything else) is refused rather than passed\n * through.\n */\nexport const RedactedLineageNodeSchema = z.strictObject({\n redacted: z.literal(true),\n});\n\nexport const LineageEntrySchema = z.union([\n RedactedLineageNodeSchema,\n LineageNodeSchema,\n]);\n\nexport const LineageGraphSchema = z.object({\n dataPointId: DataPointIdSchema,\n /** The data point owner; every node in the view belongs to it. */\n ownerAddress: z.string().optional(),\n scope: z.string(),\n /**\n * The derived record's version whose lineage is shown: the requested one,\n * else the current one, else (current is a tombstone) the last version\n * that carried lineage.\n */\n version: ViewVersionSchema,\n deletedAt: z.string().nullable(),\n sources: z.array(LineageEntrySchema),\n derivatives: z.array(LineageEntrySchema),\n /** `true` when `derivatives` was cut at the server's cap (1000). */\n derivativesTruncated: z.boolean().optional(),\n});\n\n/** A lineage node the caller is allowed to see. */\nexport type LineageNode = z.infer<typeof LineageNodeSchema>;\n\n/** A lineage node the caller holds no grant for: nothing but its position. */\nexport type RedactedLineageNode = z.infer<typeof RedactedLineageNodeSchema>;\n\n/** One entry of a lineage graph. Narrow with {@link isRedactedLineageNode}. */\nexport type LineageEntry = z.infer<typeof LineageEntrySchema>;\n\n/** The lineage view of one data point (the `data` of the response). */\nexport type LineageGraph = z.infer<typeof LineageGraphSchema>;\n\n/** A lineage read: the view plus the gateway's attestation over it. */\nexport interface LineageReadResult extends LineageGraph {\n /**\n * The gateway `proof` (`GatewayAttestation` over the served view, so a\n * redacted view verifies on its own). Passed through as received; absent\n * when the server sent none.\n */\n proof?: Record<string, unknown>;\n}\n\n/** `true` when the entry was redacted (the caller holds no grant for it). */\nexport function isRedactedLineageNode(\n entry: LineageEntry,\n): entry is RedactedLineageNode {\n return (\n \"redacted\" in entry &&\n entry.redacted === true &&\n Object.keys(entry).length === 1\n );\n}\n\n/**\n * The Personal Server lineage path: `/v1/data/:scope/lineage[/:version]`.\n * The version is a path segment (a query string is refused by the server),\n * so the signed `uri` covers the whole request.\n */\nexport function personalServerLineagePath(\n scope: string,\n version?: string | number,\n): string {\n return `/v1/data/${encodeURIComponent(scope)}/lineage${version === undefined ? \"\" : `/${String(version)}`}`;\n}\n\n/**\n * The gateway lineage path: `/v1/data/<id lowercase>/lineage[/:version]`,\n * what the request is signed over and sent to. The grant view is the signed\n * `grantId` claim, never a query parameter.\n */\nexport function gatewayLineagePath(\n dataPointId: Hex,\n version?: string | number,\n): string {\n return `/v1/data/${dataPointId.toLowerCase()}/lineage${version === undefined ? \"\" : `/${String(version)}`}`;\n}\n\ninterface LineageRequestOptions {\n /**\n * Read the lineage as of this version (a positive decimal integer);\n * omitted = the current version, or the last version that carried lineage\n * when the current one is a tombstone.\n */\n version?: string | number;\n /** `fetch` to use; defaults to `globalThis.fetch`. */\n fetch?: typeof fetch;\n /** Extra request headers. */\n headers?: HeadersInit;\n}\n\n/** Lineage read against the Personal Server holding the record. */\nexport interface PersonalServerLineageParams extends LineageRequestOptions {\n /** Personal Server origin, e.g. `https://ps.example.com`. */\n personalServerUrl: string;\n /** The scope whose lineage to read. */\n scope: string;\n /** A grant covering the scope, sent as the signed `grantId` claim. */\n grantId: string;\n /** Builder key: a viem `LocalAccount`, `WalletClient`, or `{ signMessage }`. */\n signer: WriteSignerSource;\n /** Account for a viem wallet client without a hoisted account. */\n account?: ResolveWriteSignerOptions[\"account\"];\n /** Web3Signed audience; defaults to `personalServerUrl`. */\n audience?: string;\n}\n\n/** Lineage read against the gateway, by data point id. */\nexport interface GatewayLineageParams extends LineageRequestOptions {\n /** Gateway origin, e.g. `https://dp-rpc.vana.org`. */\n gatewayUrl: string;\n /** The data point whose lineage to read (see {@link deriveDataPointId}). */\n dataPointId: Hex;\n /**\n * The key the request is signed with (Web3Signed, audience = the gateway\n * origin). The signer decides the view: the owner or one of its servers\n * gets the full view; a registered builder holding a live grant covering\n * the data point's scope gets that grant's view; anyone else is refused.\n */\n signer: WriteSignerSource;\n /** Account for a viem wallet client without a hoisted account. */\n account?: ResolveWriteSignerOptions[\"account\"];\n /**\n * The grant whose view to read, sent lowercased as the signed `grantId`\n * claim (never as a query parameter). An owner or server uses it to fetch\n * the view a builder's grant sees; a builder needs it to see anything.\n */\n grantId?: string;\n}\n\nexport type GetLineageParams =\n | PersonalServerLineageParams\n | GatewayLineageParams;\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 LineageReadError(\"No fetch implementation available\");\n }\n return resolved;\n}\n\nfunction normalizeVersion(\n version: string | number | undefined,\n): string | undefined {\n if (version === undefined) return undefined;\n const text = String(version);\n if (!VERSION_PATTERN.test(text)) {\n throw new LineageReadError(\n \"version must be a positive decimal integer\",\n undefined,\n \"INVALID_VERSION\",\n { version },\n );\n }\n return text;\n}\n\nasync function lineageReadFailure(\n source: string,\n response: Response,\n): Promise<LineageReadError> {\n const { errorCode, message, details } =\n await readPersonalServerErrorBody(response);\n return new LineageReadError(\n message ??\n `${source} lineage read failed: ${response.status} ${response.statusText}`,\n response.status,\n errorCode,\n details,\n );\n}\n\nasync function parseLineageGraph(\n source: string,\n response: Response,\n): Promise<LineageReadResult> {\n let body: unknown;\n try {\n body = await response.json();\n } catch (err) {\n throw new LineageReadError(\n `${source} lineage response is not JSON`,\n response.status,\n null,\n { cause: err instanceof Error ? err.message : String(err) },\n );\n }\n // Both servers answer the gateway envelope `{ data, proof }`; a bare view\n // is accepted too.\n const envelope = isRecord(body) && isRecord(body.data) ? body : undefined;\n const parsed = LineageGraphSchema.safeParse(envelope?.data ?? body);\n if (!parsed.success) {\n throw new LineageReadError(\n `${source} lineage response is not a lineage view`,\n response.status,\n null,\n { issues: parsed.error.issues },\n );\n }\n const proof = isRecord(envelope?.proof) ? envelope.proof : undefined;\n return proof === undefined ? parsed.data : { ...parsed.data, proof };\n}\n\nasync function sendLineageRead(\n source: string,\n fetchFn: typeof fetch,\n url: string,\n headers: Headers,\n): Promise<LineageReadResult> {\n let response: Response;\n try {\n response = await fetchFn(url, { method: \"GET\", headers });\n } catch (err) {\n throw new LineageReadError(\n `${source} lineage read failed: ${err instanceof Error ? err.message : String(err)}`,\n undefined,\n null,\n { cause: err instanceof Error ? err.message : String(err) },\n );\n }\n if (!response.ok) {\n throw await lineageReadFailure(source, response);\n }\n return parseLineageGraph(source, response);\n}\n\n/**\n * Read a scope's lineage from the Personal Server that stores it.\n *\n * @remarks\n * Sends `GET /v1/data/:scope/lineage[/:version]` with a Web3Signed\n * `Authorization` header carrying `grantId`, the same authentication a data\n * read uses; the signed `uri` is the full path, version segment included.\n * The server resolves the data point id, fetches the view the grant sees\n * from the gateway and returns the gateway's `data` + `proof`.\n *\n * @returns The lineage view, with redacted entries for nodes the grant does\n * not cover, plus the gateway attestation.\n * @throws {LineageReadError} On a non-2xx answer (`errorCode`: read errors,\n * `INVALID_VERSION`, `NOT_FOUND` when the scope or version is not\n * registered at the gateway, `LINEAGE_FORBIDDEN`, `LINEAGE_GATEWAY_ERROR`,\n * `LINEAGE_UNAVAILABLE`), an unreadable body, a bad `version`, or a\n * transport failure.\n */\nexport async function getPersonalServerLineage(\n params: PersonalServerLineageParams,\n): Promise<LineageReadResult> {\n const fetchFn = resolveFetch(params.fetch);\n const baseUrl = normalizeBaseUrl(params.personalServerUrl);\n const audience = params.audience ?? baseUrl;\n const signer = resolveWriteSigner(params.signer, { account: params.account });\n const path = personalServerLineagePath(\n params.scope,\n normalizeVersion(params.version),\n );\n const headers = new Headers(params.headers);\n headers.set(\n \"Authorization\",\n await buildWeb3SignedHeader({\n signMessage: signer.signMessage,\n aud: audience,\n method: \"GET\",\n uri: path,\n grantId: params.grantId,\n }),\n );\n return sendLineageRead(\n \"Personal Server\",\n fetchFn,\n `${baseUrl}${path}`,\n headers,\n );\n}\n\n/**\n * Read a data point's lineage from the gateway.\n *\n * @remarks\n * Sends `GET /v1/data/:dataPointId/lineage[/:version]` with a Web3Signed\n * `Authorization` header: `aud` = the gateway origin, `uri` =\n * {@link gatewayLineagePath} (lowercase id, version segment included),\n * empty-body `bodyHash`, and the lowercased `grantId` claim when given. The\n * gateway answers a uniform 404 for an unknown data point and for a signer\n * it will not serve, so the two cannot be told apart from outside.\n *\n * @returns The lineage view, with redacted entries for nodes the caller's\n * grant does not cover, plus the gateway attestation.\n * @throws {LineageReadError} On a malformed `dataPointId` or `version`, a\n * non-2xx answer (400 malformed request, 401 `LINEAGE_SIGNATURE_REQUIRED`\n * / `LINEAGE_SIGNATURE_INVALID`, 404 unknown or not served), an unreadable\n * body, or a transport failure.\n */\nexport async function getGatewayLineage(\n params: GatewayLineageParams,\n): Promise<LineageReadResult> {\n if (!isDataPointId(params.dataPointId)) {\n throw new LineageReadError(\n \"dataPointId must be a 32-byte hex string (see deriveDataPointId)\",\n undefined,\n \"INVALID_DATA_POINT_ID\",\n { dataPointId: params.dataPointId },\n );\n }\n const fetchFn = resolveFetch(params.fetch);\n const baseUrl = normalizeBaseUrl(params.gatewayUrl);\n const signer = resolveWriteSigner(params.signer, { account: params.account });\n const uri = gatewayLineagePath(\n params.dataPointId,\n normalizeVersion(params.version),\n );\n const headers = new Headers(params.headers);\n headers.set(\n \"Authorization\",\n await buildWeb3SignedHeader({\n signMessage: signer.signMessage,\n aud: baseUrl,\n method: \"GET\",\n uri,\n grantId: params.grantId?.toLowerCase(),\n }),\n );\n return sendLineageRead(\"Gateway\", fetchFn, `${baseUrl}${uri}`, headers);\n}\n\n/**\n * Read a lineage view from either the Personal Server (by scope) or the\n * gateway (by data point id), chosen by the params shape.\n *\n * @example\n * ```typescript\n * const fromPs = await getLineage({ personalServerUrl, scope, grantId, signer });\n * const fromGateway = await getLineage({ gatewayUrl, dataPointId, grantId, signer });\n * ```\n */\nexport function getLineage(\n params: GetLineageParams,\n): Promise<LineageReadResult> {\n return \"personalServerUrl\" in params\n ? getPersonalServerLineage(params)\n : getGatewayLineage(params);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAyBA,kBAMO;AACP,iBAAkB;AAClB,iCAAsC;AACtC,oBAAoD;AACpD,wCAGO;AACP,0BAIO;AAEP,MAAM,wBAAwB;AAGvB,SAAS,cAAc,OAA8B;AAC1D,SAAO,OAAO,UAAU,YAAY,sBAAsB,KAAK,KAAK;AACtE;AAWO,SAAS,kBAAkB,cAAuB,OAAoB;AAC3E,MAAI,KAAC,uBAAU,cAAc,EAAE,QAAQ,MAAM,CAAC,GAAG;AAC/C,UAAM,IAAI;AAAA,MACR,uCAAuC,OAAO,YAAY,CAAC;AAAA,IAC7D;AAAA,EACF;AACA,aAAO;AAAA,QACL;AAAA,MACE;AAAA,QACE,EAAE,MAAM,gBAAgB,MAAM,UAAU;AAAA,QACxC,EAAE,MAAM,SAAS,MAAM,SAAS;AAAA,MAClC;AAAA,MACA,CAAC,cAAc,KAAK;AAAA,IACtB;AAAA,EACF;AACF;AAEA,MAAM,oBAAoB,aACvB,OAAO,EACP,MAAM,qBAAqB,EAC3B,UAAU,CAAC,UAAU,MAAM,YAAY,CAAQ;AAElD,MAAM,kBAAkB;AACxB,MAAM,uBAAuB;AAK7B,MAAM,gBAAgB,aACnB,MAAM,CAAC,aAAE,OAAO,GAAG,aAAE,OAAO,CAAC,CAAC,EAC9B,UAAU,MAAM,EAChB,OAAO,CAAC,UAAU,qBAAqB,KAAK,KAAK,GAAG;AAAA,EACnD,SAAS;AACX,CAAC;AAEH,MAAM,oBAAoB,cAAc;AAAA,EACtC,CAAC,UAAU,gBAAgB,KAAK,KAAK;AAAA,EACrC,EAAE,SAAS,6CAA6C;AAC1D;AAGO,SAAS,eAAe,OAAuB;AACpD,QAAM,MAAM,MAAM,QAAQ,GAAG;AAC7B,SAAO,QAAQ,KAAK,QAAQ,MAAM,MAAM,GAAG,GAAG;AAChD;AAQO,SAAS,2BACd,cACA,aACS;AACT,SAAO,eAAe,YAAY,MAAM,eAAe,WAAW;AACpE;AAQO,SAAS,yBACd,cACA,cACM;AACN,aAAW,eAAe,cAAc;AACtC,QAAI,2BAA2B,cAAc,WAAW,GAAG;AACzD,YAAM,IAAI;AAAA,QACR,iBAAiB,YAAY,uDAAuD,WAAW;AAAA,QAC/F,EAAE,OAAO,cAAc,YAAY;AAAA,MACrC;AAAA,IACF;AAAA,EACF;AACF;AAEO,MAAM,oBAAoB,aAAE,OAAO;AAAA,EACxC,aAAa;AAAA,EACb,OAAO,aAAE,OAAO;AAAA;AAAA;AAAA;AAAA;AAAA,EAKhB,SAAS;AAAA;AAAA,EAET,WAAW,aAAE,OAAO,EAAE,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAM/B,UAAU,aAAE,MAAM,EAAE,SAAS;AAC/B,CAAC;AAQM,MAAM,4BAA4B,aAAE,aAAa;AAAA,EACtD,UAAU,aAAE,QAAQ,IAAI;AAC1B,CAAC;AAEM,MAAM,qBAAqB,aAAE,MAAM;AAAA,EACxC;AAAA,EACA;AACF,CAAC;AAEM,MAAM,qBAAqB,aAAE,OAAO;AAAA,EACzC,aAAa;AAAA;AAAA,EAEb,cAAc,aAAE,OAAO,EAAE,SAAS;AAAA,EAClC,OAAO,aAAE,OAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMhB,SAAS;AAAA,EACT,WAAW,aAAE,OAAO,EAAE,SAAS;AAAA,EAC/B,SAAS,aAAE,MAAM,kBAAkB;AAAA,EACnC,aAAa,aAAE,MAAM,kBAAkB;AAAA;AAAA,EAEvC,sBAAsB,aAAE,QAAQ,EAAE,SAAS;AAC7C,CAAC;AAyBM,SAAS,sBACd,OAC8B;AAC9B,SACE,cAAc,SACd,MAAM,aAAa,QACnB,OAAO,KAAK,KAAK,EAAE,WAAW;AAElC;AAOO,SAAS,0BACd,OACA,SACQ;AACR,SAAO,YAAY,mBAAmB,KAAK,CAAC,WAAW,YAAY,SAAY,KAAK,IAAI,OAAO,OAAO,CAAC,EAAE;AAC3G;AAOO,SAAS,mBACd,aACA,SACQ;AACR,SAAO,YAAY,YAAY,YAAY,CAAC,WAAW,YAAY,SAAY,KAAK,IAAI,OAAO,OAAO,CAAC,EAAE;AAC3G;AA0DA,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,+BAAiB,mCAAmC;AAAA,EAChE;AACA,SAAO;AACT;AAEA,SAAS,iBACP,SACoB;AACpB,MAAI,YAAY,OAAW,QAAO;AAClC,QAAM,OAAO,OAAO,OAAO;AAC3B,MAAI,CAAC,gBAAgB,KAAK,IAAI,GAAG;AAC/B,UAAM,IAAI;AAAA,MACR;AAAA,MACA;AAAA,MACA;AAAA,MACA,EAAE,QAAQ;AAAA,IACZ;AAAA,EACF;AACA,SAAO;AACT;AAEA,eAAe,mBACb,QACA,UAC2B;AAC3B,QAAM,EAAE,WAAW,SAAS,QAAQ,IAClC,UAAM,+DAA4B,QAAQ;AAC5C,SAAO,IAAI;AAAA,IACT,WACE,GAAG,MAAM,yBAAyB,SAAS,MAAM,IAAI,SAAS,UAAU;AAAA,IAC1E,SAAS;AAAA,IACT;AAAA,IACA;AAAA,EACF;AACF;AAEA,eAAe,kBACb,QACA,UAC4B;AAC5B,MAAI;AACJ,MAAI;AACF,WAAO,MAAM,SAAS,KAAK;AAAA,EAC7B,SAAS,KAAK;AACZ,UAAM,IAAI;AAAA,MACR,GAAG,MAAM;AAAA,MACT,SAAS;AAAA,MACT;AAAA,MACA,EAAE,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,EAAE;AAAA,IAC5D;AAAA,EACF;AAGA,QAAM,eAAW,4CAAS,IAAI,SAAK,4CAAS,KAAK,IAAI,IAAI,OAAO;AAChE,QAAM,SAAS,mBAAmB,UAAU,UAAU,QAAQ,IAAI;AAClE,MAAI,CAAC,OAAO,SAAS;AACnB,UAAM,IAAI;AAAA,MACR,GAAG,MAAM;AAAA,MACT,SAAS;AAAA,MACT;AAAA,MACA,EAAE,QAAQ,OAAO,MAAM,OAAO;AAAA,IAChC;AAAA,EACF;AACA,QAAM,YAAQ,4CAAS,UAAU,KAAK,IAAI,SAAS,QAAQ;AAC3D,SAAO,UAAU,SAAY,OAAO,OAAO,EAAE,GAAG,OAAO,MAAM,MAAM;AACrE;AAEA,eAAe,gBACb,QACA,SACA,KACA,SAC4B;AAC5B,MAAI;AACJ,MAAI;AACF,eAAW,MAAM,QAAQ,KAAK,EAAE,QAAQ,OAAO,QAAQ,CAAC;AAAA,EAC1D,SAAS,KAAK;AACZ,UAAM,IAAI;AAAA,MACR,GAAG,MAAM,yBAAyB,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC;AAAA,MAClF;AAAA,MACA;AAAA,MACA,EAAE,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,EAAE;AAAA,IAC5D;AAAA,EACF;AACA,MAAI,CAAC,SAAS,IAAI;AAChB,UAAM,MAAM,mBAAmB,QAAQ,QAAQ;AAAA,EACjD;AACA,SAAO,kBAAkB,QAAQ,QAAQ;AAC3C;AAoBA,eAAsB,yBACpB,QAC4B;AAC5B,QAAM,UAAU,aAAa,OAAO,KAAK;AACzC,QAAM,UAAU,iBAAiB,OAAO,iBAAiB;AACzD,QAAM,WAAW,OAAO,YAAY;AACpC,QAAM,aAAS,wCAAmB,OAAO,QAAQ,EAAE,SAAS,OAAO,QAAQ,CAAC;AAC5E,QAAM,OAAO;AAAA,IACX,OAAO;AAAA,IACP,iBAAiB,OAAO,OAAO;AAAA,EACjC;AACA,QAAM,UAAU,IAAI,QAAQ,OAAO,OAAO;AAC1C,UAAQ;AAAA,IACN;AAAA,IACA,UAAM,kDAAsB;AAAA,MAC1B,aAAa,OAAO;AAAA,MACpB,KAAK;AAAA,MACL,QAAQ;AAAA,MACR,KAAK;AAAA,MACL,SAAS,OAAO;AAAA,IAClB,CAAC;AAAA,EACH;AACA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,GAAG,OAAO,GAAG,IAAI;AAAA,IACjB;AAAA,EACF;AACF;AAoBA,eAAsB,kBACpB,QAC4B;AAC5B,MAAI,CAAC,cAAc,OAAO,WAAW,GAAG;AACtC,UAAM,IAAI;AAAA,MACR;AAAA,MACA;AAAA,MACA;AAAA,MACA,EAAE,aAAa,OAAO,YAAY;AAAA,IACpC;AAAA,EACF;AACA,QAAM,UAAU,aAAa,OAAO,KAAK;AACzC,QAAM,UAAU,iBAAiB,OAAO,UAAU;AAClD,QAAM,aAAS,wCAAmB,OAAO,QAAQ,EAAE,SAAS,OAAO,QAAQ,CAAC;AAC5E,QAAM,MAAM;AAAA,IACV,OAAO;AAAA,IACP,iBAAiB,OAAO,OAAO;AAAA,EACjC;AACA,QAAM,UAAU,IAAI,QAAQ,OAAO,OAAO;AAC1C,UAAQ;AAAA,IACN;AAAA,IACA,UAAM,kDAAsB;AAAA,MAC1B,aAAa,OAAO;AAAA,MACpB,KAAK;AAAA,MACL,QAAQ;AAAA,MACR;AAAA,MACA,SAAS,OAAO,SAAS,YAAY;AAAA,IACvC,CAAC;AAAA,EACH;AACA,SAAO,gBAAgB,WAAW,SAAS,GAAG,OAAO,GAAG,GAAG,IAAI,OAAO;AACxE;AAYO,SAAS,WACd,QAC4B;AAC5B,SAAO,uBAAuB,SAC1B,yBAAyB,MAAM,IAC/B,kBAAkB,MAAM;AAC9B;","names":[]}
|
|
@@ -9,9 +9,12 @@
|
|
|
9
9
|
* Server stores them under the reserved `$lineage` key and both the Personal
|
|
10
10
|
* Server (`GET /v1/data/:scope/lineage[/:version]`) and the gateway
|
|
11
11
|
* (`GET /v1/data/:dataPointId/lineage[/:version]`) answer the resulting view.
|
|
12
|
-
* Nodes the caller holds no grant for come back as
|
|
13
|
-
* `{
|
|
14
|
-
*
|
|
12
|
+
* Nodes the caller holds no grant for come back as exactly
|
|
13
|
+
* `{ redacted: true }`: no id, scope or version, because the id is
|
|
14
|
+
* `keccak256(owner, scope)` and a grantee who knows the owner could recover
|
|
15
|
+
* the scope from it with a small dictionary. Order and count are preserved,
|
|
16
|
+
* so a redacted node is still identified by its position. A source that no
|
|
17
|
+
* longer resolves comes back with `version: "0"`.
|
|
15
18
|
*
|
|
16
19
|
* A derived scope must not share its first dot-segment with any source scope
|
|
17
20
|
* (a grant on `chatgpt.*` must never read a derivative of
|
|
@@ -55,19 +58,25 @@ export declare const LineageNodeSchema: z.ZodObject<{
|
|
|
55
58
|
scope: z.ZodString;
|
|
56
59
|
version: z.ZodPipe<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>, z.ZodTransform<string, string | number>>;
|
|
57
60
|
deletedAt: z.ZodNullable<z.ZodString>;
|
|
61
|
+
redacted: z.ZodOptional<z.ZodNever>;
|
|
58
62
|
}, z.core.$strip>;
|
|
63
|
+
/**
|
|
64
|
+
* A redacted node is exactly `{ redacted: true }`. Any other key on it would
|
|
65
|
+
* leak what the redaction hides, so the schema is strict: a view carrying a
|
|
66
|
+
* redacted node with an id (or anything else) is refused rather than passed
|
|
67
|
+
* through.
|
|
68
|
+
*/
|
|
59
69
|
export declare const RedactedLineageNodeSchema: z.ZodObject<{
|
|
60
|
-
dataPointId: z.ZodPipe<z.ZodString, z.ZodTransform<`0x${string}`, string>>;
|
|
61
70
|
redacted: z.ZodLiteral<true>;
|
|
62
|
-
}, z.core.$
|
|
71
|
+
}, z.core.$strict>;
|
|
63
72
|
export declare const LineageEntrySchema: z.ZodUnion<readonly [z.ZodObject<{
|
|
64
|
-
dataPointId: z.ZodPipe<z.ZodString, z.ZodTransform<`0x${string}`, string>>;
|
|
65
73
|
redacted: z.ZodLiteral<true>;
|
|
66
|
-
}, z.core.$
|
|
74
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
67
75
|
dataPointId: z.ZodPipe<z.ZodString, z.ZodTransform<`0x${string}`, string>>;
|
|
68
76
|
scope: z.ZodString;
|
|
69
77
|
version: z.ZodPipe<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>, z.ZodTransform<string, string | number>>;
|
|
70
78
|
deletedAt: z.ZodNullable<z.ZodString>;
|
|
79
|
+
redacted: z.ZodOptional<z.ZodNever>;
|
|
71
80
|
}, z.core.$strip>]>;
|
|
72
81
|
export declare const LineageGraphSchema: z.ZodObject<{
|
|
73
82
|
dataPointId: z.ZodPipe<z.ZodString, z.ZodTransform<`0x${string}`, string>>;
|
|
@@ -76,28 +85,28 @@ export declare const LineageGraphSchema: z.ZodObject<{
|
|
|
76
85
|
version: z.ZodPipe<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>, z.ZodTransform<string, string | number>>;
|
|
77
86
|
deletedAt: z.ZodNullable<z.ZodString>;
|
|
78
87
|
sources: z.ZodArray<z.ZodUnion<readonly [z.ZodObject<{
|
|
79
|
-
dataPointId: z.ZodPipe<z.ZodString, z.ZodTransform<`0x${string}`, string>>;
|
|
80
88
|
redacted: z.ZodLiteral<true>;
|
|
81
|
-
}, z.core.$
|
|
89
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
82
90
|
dataPointId: z.ZodPipe<z.ZodString, z.ZodTransform<`0x${string}`, string>>;
|
|
83
91
|
scope: z.ZodString;
|
|
84
92
|
version: z.ZodPipe<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>, z.ZodTransform<string, string | number>>;
|
|
85
93
|
deletedAt: z.ZodNullable<z.ZodString>;
|
|
94
|
+
redacted: z.ZodOptional<z.ZodNever>;
|
|
86
95
|
}, z.core.$strip>]>>;
|
|
87
96
|
derivatives: z.ZodArray<z.ZodUnion<readonly [z.ZodObject<{
|
|
88
|
-
dataPointId: z.ZodPipe<z.ZodString, z.ZodTransform<`0x${string}`, string>>;
|
|
89
97
|
redacted: z.ZodLiteral<true>;
|
|
90
|
-
}, z.core.$
|
|
98
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
91
99
|
dataPointId: z.ZodPipe<z.ZodString, z.ZodTransform<`0x${string}`, string>>;
|
|
92
100
|
scope: z.ZodString;
|
|
93
101
|
version: z.ZodPipe<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>, z.ZodTransform<string, string | number>>;
|
|
94
102
|
deletedAt: z.ZodNullable<z.ZodString>;
|
|
103
|
+
redacted: z.ZodOptional<z.ZodNever>;
|
|
95
104
|
}, z.core.$strip>]>>;
|
|
96
105
|
derivativesTruncated: z.ZodOptional<z.ZodBoolean>;
|
|
97
106
|
}, z.core.$strip>;
|
|
98
107
|
/** A lineage node the caller is allowed to see. */
|
|
99
108
|
export type LineageNode = z.infer<typeof LineageNodeSchema>;
|
|
100
|
-
/** A lineage node the caller holds no grant for:
|
|
109
|
+
/** A lineage node the caller holds no grant for: nothing but its position. */
|
|
101
110
|
export type RedactedLineageNode = z.infer<typeof RedactedLineageNodeSchema>;
|
|
102
111
|
/** One entry of a lineage graph. Narrow with {@link isRedactedLineageNode}. */
|
|
103
112
|
export type LineageEntry = z.infer<typeof LineageEntrySchema>;
|
package/dist/protocol/lineage.js
CHANGED
|
@@ -69,10 +69,15 @@ const LineageNodeSchema = z.object({
|
|
|
69
69
|
*/
|
|
70
70
|
version: VersionSchema,
|
|
71
71
|
/** The node's tombstone time, or `null` when live. */
|
|
72
|
-
deletedAt: z.string().nullable()
|
|
72
|
+
deletedAt: z.string().nullable(),
|
|
73
|
+
/**
|
|
74
|
+
* Never present on a visible node. Declared so a node that carries
|
|
75
|
+
* `redacted: true` next to an id, scope and version cannot slip through
|
|
76
|
+
* this branch of {@link LineageEntrySchema} with the key stripped.
|
|
77
|
+
*/
|
|
78
|
+
redacted: z.never().optional()
|
|
73
79
|
});
|
|
74
|
-
const RedactedLineageNodeSchema = z.
|
|
75
|
-
dataPointId: DataPointIdSchema,
|
|
80
|
+
const RedactedLineageNodeSchema = z.strictObject({
|
|
76
81
|
redacted: z.literal(true)
|
|
77
82
|
});
|
|
78
83
|
const LineageEntrySchema = z.union([
|
|
@@ -97,7 +102,7 @@ const LineageGraphSchema = z.object({
|
|
|
97
102
|
derivativesTruncated: z.boolean().optional()
|
|
98
103
|
});
|
|
99
104
|
function isRedactedLineageNode(entry) {
|
|
100
|
-
return "redacted" in entry && entry.redacted === true;
|
|
105
|
+
return "redacted" in entry && entry.redacted === true && Object.keys(entry).length === 1;
|
|
101
106
|
}
|
|
102
107
|
function personalServerLineagePath(scope, version) {
|
|
103
108
|
return `/v1/data/${encodeURIComponent(scope)}/lineage${version === void 0 ? "" : `/${String(version)}`}`;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/protocol/lineage.ts"],"sourcesContent":["/**\n * Derivative lineage: which data points a record was derived from, and which\n * records were derived from it.\n *\n * @remarks\n * A data point is addressed by `keccak256(abi.encode(address owner, string\n * scope))` ({@link deriveDataPointId}). A builder writing a derivative names\n * its sources through the `lineage` option of {@link writeData}; the Personal\n * Server stores them under the reserved `$lineage` key and both the Personal\n * Server (`GET /v1/data/:scope/lineage[/:version]`) and the gateway\n * (`GET /v1/data/:dataPointId/lineage[/:version]`) answer the resulting view.\n * Nodes the caller holds no grant for come back as\n * `{ dataPointId, redacted: true }`; a source that no longer resolves comes\n * back with `version: \"0\"`.\n *\n * A derived scope must not share its first dot-segment with any source scope\n * (a grant on `chatgpt.*` must never read a derivative of\n * `chatgpt.conversations`): see {@link assertDerivedScopeNaming}.\n *\n * @category Protocol\n */\n\nimport {\n encodeAbiParameters,\n isAddress,\n keccak256,\n type Address,\n type Hex,\n} from \"viem\";\nimport { z } from \"zod\";\nimport { buildWeb3SignedHeader } from \"../auth/web3-signed-builder\";\nimport { LineageReadError, WriteRequestError } from \"../errors\";\nimport {\n isRecord,\n readPersonalServerErrorBody,\n} from \"./personal-server-error-body\";\nimport {\n resolveWriteSigner,\n type ResolveWriteSignerOptions,\n type WriteSignerSource,\n} from \"./write-signer\";\n\nconst DATA_POINT_ID_PATTERN = /^0x[0-9a-fA-F]{64}$/;\n\n/** `true` when `value` is a 32-byte hex data point id. */\nexport function isDataPointId(value: unknown): value is Hex {\n return typeof value === \"string\" && DATA_POINT_ID_PATTERN.test(value);\n}\n\n/**\n * Derive the DataRegistryV2 data point id for an owner and scope:\n * `keccak256(abi.encode(address ownerAddress, string scope))`.\n *\n * @param ownerAddress - The data owner (the Personal Server owner).\n * @param scope - The scope the data point is stored under.\n * @returns The 32-byte id, lowercase hex.\n * @throws Error when `ownerAddress` is not an EVM address.\n */\nexport function deriveDataPointId(ownerAddress: Address, scope: string): Hex {\n if (!isAddress(ownerAddress, { strict: false })) {\n throw new Error(\n `ownerAddress is not an EVM address: ${String(ownerAddress)}`,\n );\n }\n return keccak256(\n encodeAbiParameters(\n [\n { name: \"ownerAddress\", type: \"address\" },\n { name: \"scope\", type: \"string\" },\n ],\n [ownerAddress, scope],\n ),\n );\n}\n\nconst DataPointIdSchema = z\n .string()\n .regex(DATA_POINT_ID_PATTERN)\n .transform((value) => value.toLowerCase() as Hex);\n\nconst VERSION_PATTERN = /^[1-9]\\d*$/;\nconst NODE_VERSION_PATTERN = /^(0|[1-9]\\d*)$/;\n\n// Versions are decimal integers, strings on the wire; a numeric value is\n// normalised to the same representation. A node's version may be \"0\": a\n// source that no longer resolves to a registered data point.\nconst VersionSchema = z\n .union([z.string(), z.number()])\n .transform(String)\n .refine((value) => NODE_VERSION_PATTERN.test(value), {\n message: \"version must be a decimal integer\",\n });\n// The view's own version is a registered one: always positive.\nconst ViewVersionSchema = VersionSchema.refine(\n (value) => VERSION_PATTERN.test(value),\n { message: \"version must be a positive decimal integer\" },\n);\n\n/** First dot-segment of a scope (`chatgpt` for `chatgpt.conversations`). */\nexport function scopeNamespace(scope: string): string {\n const dot = scope.indexOf(\".\");\n return dot === -1 ? scope : scope.slice(0, dot);\n}\n\n/**\n * The naming rule: a derived scope and a source scope must not share their\n * first dot-segment, because a `prefix.*` grant would then cover both and\n * leak across the lineage edge. Mirrors the Personal Server's check\n * (`LINEAGE_SCOPE_UNDER_SOURCE_PREFIX`).\n */\nexport function derivedScopeViolatesNaming(\n derivedScope: string,\n sourceScope: string,\n): boolean {\n return scopeNamespace(derivedScope) === scopeNamespace(sourceScope);\n}\n\n/**\n * Throw when `derivedScope` shares its first dot-segment with any source\n * scope (see {@link derivedScopeViolatesNaming}).\n *\n * @throws {WriteRequestError} Naming the offending source scope in `details`.\n */\nexport function assertDerivedScopeNaming(\n derivedScope: string,\n sourceScopes: readonly string[],\n): void {\n for (const sourceScope of sourceScopes) {\n if (derivedScopeViolatesNaming(derivedScope, sourceScope)) {\n throw new WriteRequestError(\n `Derived scope ${derivedScope} must not share its first segment with source scope ${sourceScope}; put derivatives in the app's own namespace`,\n { scope: derivedScope, sourceScope },\n );\n }\n }\n}\n\nexport const LineageNodeSchema = z.object({\n dataPointId: DataPointIdSchema,\n scope: z.string(),\n /**\n * The node's current version, decimal string; `\"0\"` for a source that no\n * longer resolves to a registered data point.\n */\n version: VersionSchema,\n /** The node's tombstone time, or `null` when live. */\n deletedAt: z.string().nullable(),\n});\n\nexport const RedactedLineageNodeSchema = z.object({\n dataPointId: DataPointIdSchema,\n redacted: z.literal(true),\n});\n\nexport const LineageEntrySchema = z.union([\n RedactedLineageNodeSchema,\n LineageNodeSchema,\n]);\n\nexport const LineageGraphSchema = z.object({\n dataPointId: DataPointIdSchema,\n /** The data point owner; every node in the view belongs to it. */\n ownerAddress: z.string().optional(),\n scope: z.string(),\n /**\n * The derived record's version whose lineage is shown: the requested one,\n * else the current one, else (current is a tombstone) the last version\n * that carried lineage.\n */\n version: ViewVersionSchema,\n deletedAt: z.string().nullable(),\n sources: z.array(LineageEntrySchema),\n derivatives: z.array(LineageEntrySchema),\n /** `true` when `derivatives` was cut at the server's cap (1000). */\n derivativesTruncated: z.boolean().optional(),\n});\n\n/** A lineage node the caller is allowed to see. */\nexport type LineageNode = z.infer<typeof LineageNodeSchema>;\n\n/** A lineage node the caller holds no grant for: only its id is disclosed. */\nexport type RedactedLineageNode = z.infer<typeof RedactedLineageNodeSchema>;\n\n/** One entry of a lineage graph. Narrow with {@link isRedactedLineageNode}. */\nexport type LineageEntry = z.infer<typeof LineageEntrySchema>;\n\n/** The lineage view of one data point (the `data` of the response). */\nexport type LineageGraph = z.infer<typeof LineageGraphSchema>;\n\n/** A lineage read: the view plus the gateway's attestation over it. */\nexport interface LineageReadResult extends LineageGraph {\n /**\n * The gateway `proof` (`GatewayAttestation` over the served view, so a\n * redacted view verifies on its own). Passed through as received; absent\n * when the server sent none.\n */\n proof?: Record<string, unknown>;\n}\n\n/** `true` when the entry was redacted (the caller holds no grant for it). */\nexport function isRedactedLineageNode(\n entry: LineageEntry,\n): entry is RedactedLineageNode {\n return \"redacted\" in entry && entry.redacted === true;\n}\n\n/**\n * The Personal Server lineage path: `/v1/data/:scope/lineage[/:version]`.\n * The version is a path segment (a query string is refused by the server),\n * so the signed `uri` covers the whole request.\n */\nexport function personalServerLineagePath(\n scope: string,\n version?: string | number,\n): string {\n return `/v1/data/${encodeURIComponent(scope)}/lineage${version === undefined ? \"\" : `/${String(version)}`}`;\n}\n\n/**\n * The gateway lineage path: `/v1/data/<id lowercase>/lineage[/:version]`,\n * what the request is signed over and sent to. The grant view is the signed\n * `grantId` claim, never a query parameter.\n */\nexport function gatewayLineagePath(\n dataPointId: Hex,\n version?: string | number,\n): string {\n return `/v1/data/${dataPointId.toLowerCase()}/lineage${version === undefined ? \"\" : `/${String(version)}`}`;\n}\n\ninterface LineageRequestOptions {\n /**\n * Read the lineage as of this version (a positive decimal integer);\n * omitted = the current version, or the last version that carried lineage\n * when the current one is a tombstone.\n */\n version?: string | number;\n /** `fetch` to use; defaults to `globalThis.fetch`. */\n fetch?: typeof fetch;\n /** Extra request headers. */\n headers?: HeadersInit;\n}\n\n/** Lineage read against the Personal Server holding the record. */\nexport interface PersonalServerLineageParams extends LineageRequestOptions {\n /** Personal Server origin, e.g. `https://ps.example.com`. */\n personalServerUrl: string;\n /** The scope whose lineage to read. */\n scope: string;\n /** A grant covering the scope, sent as the signed `grantId` claim. */\n grantId: string;\n /** Builder key: a viem `LocalAccount`, `WalletClient`, or `{ signMessage }`. */\n signer: WriteSignerSource;\n /** Account for a viem wallet client without a hoisted account. */\n account?: ResolveWriteSignerOptions[\"account\"];\n /** Web3Signed audience; defaults to `personalServerUrl`. */\n audience?: string;\n}\n\n/** Lineage read against the gateway, by data point id. */\nexport interface GatewayLineageParams extends LineageRequestOptions {\n /** Gateway origin, e.g. `https://dp-rpc.vana.org`. */\n gatewayUrl: string;\n /** The data point whose lineage to read (see {@link deriveDataPointId}). */\n dataPointId: Hex;\n /**\n * The key the request is signed with (Web3Signed, audience = the gateway\n * origin). The signer decides the view: the owner or one of its servers\n * gets the full view; a registered builder holding a live grant covering\n * the data point's scope gets that grant's view; anyone else is refused.\n */\n signer: WriteSignerSource;\n /** Account for a viem wallet client without a hoisted account. */\n account?: ResolveWriteSignerOptions[\"account\"];\n /**\n * The grant whose view to read, sent lowercased as the signed `grantId`\n * claim (never as a query parameter). An owner or server uses it to fetch\n * the view a builder's grant sees; a builder needs it to see anything.\n */\n grantId?: string;\n}\n\nexport type GetLineageParams =\n | PersonalServerLineageParams\n | GatewayLineageParams;\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 LineageReadError(\"No fetch implementation available\");\n }\n return resolved;\n}\n\nfunction normalizeVersion(\n version: string | number | undefined,\n): string | undefined {\n if (version === undefined) return undefined;\n const text = String(version);\n if (!VERSION_PATTERN.test(text)) {\n throw new LineageReadError(\n \"version must be a positive decimal integer\",\n undefined,\n \"INVALID_VERSION\",\n { version },\n );\n }\n return text;\n}\n\nasync function lineageReadFailure(\n source: string,\n response: Response,\n): Promise<LineageReadError> {\n const { errorCode, message, details } =\n await readPersonalServerErrorBody(response);\n return new LineageReadError(\n message ??\n `${source} lineage read failed: ${response.status} ${response.statusText}`,\n response.status,\n errorCode,\n details,\n );\n}\n\nasync function parseLineageGraph(\n source: string,\n response: Response,\n): Promise<LineageReadResult> {\n let body: unknown;\n try {\n body = await response.json();\n } catch (err) {\n throw new LineageReadError(\n `${source} lineage response is not JSON`,\n response.status,\n null,\n { cause: err instanceof Error ? err.message : String(err) },\n );\n }\n // Both servers answer the gateway envelope `{ data, proof }`; a bare view\n // is accepted too.\n const envelope = isRecord(body) && isRecord(body.data) ? body : undefined;\n const parsed = LineageGraphSchema.safeParse(envelope?.data ?? body);\n if (!parsed.success) {\n throw new LineageReadError(\n `${source} lineage response is not a lineage view`,\n response.status,\n null,\n { issues: parsed.error.issues },\n );\n }\n const proof = isRecord(envelope?.proof) ? envelope.proof : undefined;\n return proof === undefined ? parsed.data : { ...parsed.data, proof };\n}\n\nasync function sendLineageRead(\n source: string,\n fetchFn: typeof fetch,\n url: string,\n headers: Headers,\n): Promise<LineageReadResult> {\n let response: Response;\n try {\n response = await fetchFn(url, { method: \"GET\", headers });\n } catch (err) {\n throw new LineageReadError(\n `${source} lineage read failed: ${err instanceof Error ? err.message : String(err)}`,\n undefined,\n null,\n { cause: err instanceof Error ? err.message : String(err) },\n );\n }\n if (!response.ok) {\n throw await lineageReadFailure(source, response);\n }\n return parseLineageGraph(source, response);\n}\n\n/**\n * Read a scope's lineage from the Personal Server that stores it.\n *\n * @remarks\n * Sends `GET /v1/data/:scope/lineage[/:version]` with a Web3Signed\n * `Authorization` header carrying `grantId`, the same authentication a data\n * read uses; the signed `uri` is the full path, version segment included.\n * The server resolves the data point id, fetches the view the grant sees\n * from the gateway and returns the gateway's `data` + `proof`.\n *\n * @returns The lineage view, with redacted entries for nodes the grant does\n * not cover, plus the gateway attestation.\n * @throws {LineageReadError} On a non-2xx answer (`errorCode`: read errors,\n * `INVALID_VERSION`, `NOT_FOUND` when the scope or version is not\n * registered at the gateway, `LINEAGE_FORBIDDEN`, `LINEAGE_GATEWAY_ERROR`,\n * `LINEAGE_UNAVAILABLE`), an unreadable body, a bad `version`, or a\n * transport failure.\n */\nexport async function getPersonalServerLineage(\n params: PersonalServerLineageParams,\n): Promise<LineageReadResult> {\n const fetchFn = resolveFetch(params.fetch);\n const baseUrl = normalizeBaseUrl(params.personalServerUrl);\n const audience = params.audience ?? baseUrl;\n const signer = resolveWriteSigner(params.signer, { account: params.account });\n const path = personalServerLineagePath(\n params.scope,\n normalizeVersion(params.version),\n );\n const headers = new Headers(params.headers);\n headers.set(\n \"Authorization\",\n await buildWeb3SignedHeader({\n signMessage: signer.signMessage,\n aud: audience,\n method: \"GET\",\n uri: path,\n grantId: params.grantId,\n }),\n );\n return sendLineageRead(\n \"Personal Server\",\n fetchFn,\n `${baseUrl}${path}`,\n headers,\n );\n}\n\n/**\n * Read a data point's lineage from the gateway.\n *\n * @remarks\n * Sends `GET /v1/data/:dataPointId/lineage[/:version]` with a Web3Signed\n * `Authorization` header: `aud` = the gateway origin, `uri` =\n * {@link gatewayLineagePath} (lowercase id, version segment included),\n * empty-body `bodyHash`, and the lowercased `grantId` claim when given. The\n * gateway answers a uniform 404 for an unknown data point and for a signer\n * it will not serve, so the two cannot be told apart from outside.\n *\n * @returns The lineage view, with redacted entries for nodes the caller's\n * grant does not cover, plus the gateway attestation.\n * @throws {LineageReadError} On a malformed `dataPointId` or `version`, a\n * non-2xx answer (400 malformed request, 401 `LINEAGE_SIGNATURE_REQUIRED`\n * / `LINEAGE_SIGNATURE_INVALID`, 404 unknown or not served), an unreadable\n * body, or a transport failure.\n */\nexport async function getGatewayLineage(\n params: GatewayLineageParams,\n): Promise<LineageReadResult> {\n if (!isDataPointId(params.dataPointId)) {\n throw new LineageReadError(\n \"dataPointId must be a 32-byte hex string (see deriveDataPointId)\",\n undefined,\n \"INVALID_DATA_POINT_ID\",\n { dataPointId: params.dataPointId },\n );\n }\n const fetchFn = resolveFetch(params.fetch);\n const baseUrl = normalizeBaseUrl(params.gatewayUrl);\n const signer = resolveWriteSigner(params.signer, { account: params.account });\n const uri = gatewayLineagePath(\n params.dataPointId,\n normalizeVersion(params.version),\n );\n const headers = new Headers(params.headers);\n headers.set(\n \"Authorization\",\n await buildWeb3SignedHeader({\n signMessage: signer.signMessage,\n aud: baseUrl,\n method: \"GET\",\n uri,\n grantId: params.grantId?.toLowerCase(),\n }),\n );\n return sendLineageRead(\"Gateway\", fetchFn, `${baseUrl}${uri}`, headers);\n}\n\n/**\n * Read a lineage view from either the Personal Server (by scope) or the\n * gateway (by data point id), chosen by the params shape.\n *\n * @example\n * ```typescript\n * const fromPs = await getLineage({ personalServerUrl, scope, grantId, signer });\n * const fromGateway = await getLineage({ gatewayUrl, dataPointId, grantId, signer });\n * ```\n */\nexport function getLineage(\n params: GetLineageParams,\n): Promise<LineageReadResult> {\n return \"personalServerUrl\" in params\n ? getPersonalServerLineage(params)\n : getGatewayLineage(params);\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/protocol/lineage.ts"],"sourcesContent":["/**\n * Derivative lineage: which data points a record was derived from, and which\n * records were derived from it.\n *\n * @remarks\n * A data point is addressed by `keccak256(abi.encode(address owner, string\n * scope))` ({@link deriveDataPointId}). A builder writing a derivative names\n * its sources through the `lineage` option of {@link writeData}; the Personal\n * Server stores them under the reserved `$lineage` key and both the Personal\n * Server (`GET /v1/data/:scope/lineage[/:version]`) and the gateway\n * (`GET /v1/data/:dataPointId/lineage[/:version]`) answer the resulting view.\n * Nodes the caller holds no grant for come back as exactly\n * `{ redacted: true }`: no id, scope or version, because the id is\n * `keccak256(owner, scope)` and a grantee who knows the owner could recover\n * the scope from it with a small dictionary. Order and count are preserved,\n * so a redacted node is still identified by its position. A source that no\n * longer resolves comes back with `version: \"0\"`.\n *\n * A derived scope must not share its first dot-segment with any source scope\n * (a grant on `chatgpt.*` must never read a derivative of\n * `chatgpt.conversations`): see {@link assertDerivedScopeNaming}.\n *\n * @category Protocol\n */\n\nimport {\n encodeAbiParameters,\n isAddress,\n keccak256,\n type Address,\n type Hex,\n} from \"viem\";\nimport { z } from \"zod\";\nimport { buildWeb3SignedHeader } from \"../auth/web3-signed-builder\";\nimport { LineageReadError, WriteRequestError } from \"../errors\";\nimport {\n isRecord,\n readPersonalServerErrorBody,\n} from \"./personal-server-error-body\";\nimport {\n resolveWriteSigner,\n type ResolveWriteSignerOptions,\n type WriteSignerSource,\n} from \"./write-signer\";\n\nconst DATA_POINT_ID_PATTERN = /^0x[0-9a-fA-F]{64}$/;\n\n/** `true` when `value` is a 32-byte hex data point id. */\nexport function isDataPointId(value: unknown): value is Hex {\n return typeof value === \"string\" && DATA_POINT_ID_PATTERN.test(value);\n}\n\n/**\n * Derive the DataRegistryV2 data point id for an owner and scope:\n * `keccak256(abi.encode(address ownerAddress, string scope))`.\n *\n * @param ownerAddress - The data owner (the Personal Server owner).\n * @param scope - The scope the data point is stored under.\n * @returns The 32-byte id, lowercase hex.\n * @throws Error when `ownerAddress` is not an EVM address.\n */\nexport function deriveDataPointId(ownerAddress: Address, scope: string): Hex {\n if (!isAddress(ownerAddress, { strict: false })) {\n throw new Error(\n `ownerAddress is not an EVM address: ${String(ownerAddress)}`,\n );\n }\n return keccak256(\n encodeAbiParameters(\n [\n { name: \"ownerAddress\", type: \"address\" },\n { name: \"scope\", type: \"string\" },\n ],\n [ownerAddress, scope],\n ),\n );\n}\n\nconst DataPointIdSchema = z\n .string()\n .regex(DATA_POINT_ID_PATTERN)\n .transform((value) => value.toLowerCase() as Hex);\n\nconst VERSION_PATTERN = /^[1-9]\\d*$/;\nconst NODE_VERSION_PATTERN = /^(0|[1-9]\\d*)$/;\n\n// Versions are decimal integers, strings on the wire; a numeric value is\n// normalised to the same representation. A node's version may be \"0\": a\n// source that no longer resolves to a registered data point.\nconst VersionSchema = z\n .union([z.string(), z.number()])\n .transform(String)\n .refine((value) => NODE_VERSION_PATTERN.test(value), {\n message: \"version must be a decimal integer\",\n });\n// The view's own version is a registered one: always positive.\nconst ViewVersionSchema = VersionSchema.refine(\n (value) => VERSION_PATTERN.test(value),\n { message: \"version must be a positive decimal integer\" },\n);\n\n/** First dot-segment of a scope (`chatgpt` for `chatgpt.conversations`). */\nexport function scopeNamespace(scope: string): string {\n const dot = scope.indexOf(\".\");\n return dot === -1 ? scope : scope.slice(0, dot);\n}\n\n/**\n * The naming rule: a derived scope and a source scope must not share their\n * first dot-segment, because a `prefix.*` grant would then cover both and\n * leak across the lineage edge. Mirrors the Personal Server's check\n * (`LINEAGE_SCOPE_UNDER_SOURCE_PREFIX`).\n */\nexport function derivedScopeViolatesNaming(\n derivedScope: string,\n sourceScope: string,\n): boolean {\n return scopeNamespace(derivedScope) === scopeNamespace(sourceScope);\n}\n\n/**\n * Throw when `derivedScope` shares its first dot-segment with any source\n * scope (see {@link derivedScopeViolatesNaming}).\n *\n * @throws {WriteRequestError} Naming the offending source scope in `details`.\n */\nexport function assertDerivedScopeNaming(\n derivedScope: string,\n sourceScopes: readonly string[],\n): void {\n for (const sourceScope of sourceScopes) {\n if (derivedScopeViolatesNaming(derivedScope, sourceScope)) {\n throw new WriteRequestError(\n `Derived scope ${derivedScope} must not share its first segment with source scope ${sourceScope}; put derivatives in the app's own namespace`,\n { scope: derivedScope, sourceScope },\n );\n }\n }\n}\n\nexport const LineageNodeSchema = z.object({\n dataPointId: DataPointIdSchema,\n scope: z.string(),\n /**\n * The node's current version, decimal string; `\"0\"` for a source that no\n * longer resolves to a registered data point.\n */\n version: VersionSchema,\n /** The node's tombstone time, or `null` when live. */\n deletedAt: z.string().nullable(),\n /**\n * Never present on a visible node. Declared so a node that carries\n * `redacted: true` next to an id, scope and version cannot slip through\n * this branch of {@link LineageEntrySchema} with the key stripped.\n */\n redacted: z.never().optional(),\n});\n\n/**\n * A redacted node is exactly `{ redacted: true }`. Any other key on it would\n * leak what the redaction hides, so the schema is strict: a view carrying a\n * redacted node with an id (or anything else) is refused rather than passed\n * through.\n */\nexport const RedactedLineageNodeSchema = z.strictObject({\n redacted: z.literal(true),\n});\n\nexport const LineageEntrySchema = z.union([\n RedactedLineageNodeSchema,\n LineageNodeSchema,\n]);\n\nexport const LineageGraphSchema = z.object({\n dataPointId: DataPointIdSchema,\n /** The data point owner; every node in the view belongs to it. */\n ownerAddress: z.string().optional(),\n scope: z.string(),\n /**\n * The derived record's version whose lineage is shown: the requested one,\n * else the current one, else (current is a tombstone) the last version\n * that carried lineage.\n */\n version: ViewVersionSchema,\n deletedAt: z.string().nullable(),\n sources: z.array(LineageEntrySchema),\n derivatives: z.array(LineageEntrySchema),\n /** `true` when `derivatives` was cut at the server's cap (1000). */\n derivativesTruncated: z.boolean().optional(),\n});\n\n/** A lineage node the caller is allowed to see. */\nexport type LineageNode = z.infer<typeof LineageNodeSchema>;\n\n/** A lineage node the caller holds no grant for: nothing but its position. */\nexport type RedactedLineageNode = z.infer<typeof RedactedLineageNodeSchema>;\n\n/** One entry of a lineage graph. Narrow with {@link isRedactedLineageNode}. */\nexport type LineageEntry = z.infer<typeof LineageEntrySchema>;\n\n/** The lineage view of one data point (the `data` of the response). */\nexport type LineageGraph = z.infer<typeof LineageGraphSchema>;\n\n/** A lineage read: the view plus the gateway's attestation over it. */\nexport interface LineageReadResult extends LineageGraph {\n /**\n * The gateway `proof` (`GatewayAttestation` over the served view, so a\n * redacted view verifies on its own). Passed through as received; absent\n * when the server sent none.\n */\n proof?: Record<string, unknown>;\n}\n\n/** `true` when the entry was redacted (the caller holds no grant for it). */\nexport function isRedactedLineageNode(\n entry: LineageEntry,\n): entry is RedactedLineageNode {\n return (\n \"redacted\" in entry &&\n entry.redacted === true &&\n Object.keys(entry).length === 1\n );\n}\n\n/**\n * The Personal Server lineage path: `/v1/data/:scope/lineage[/:version]`.\n * The version is a path segment (a query string is refused by the server),\n * so the signed `uri` covers the whole request.\n */\nexport function personalServerLineagePath(\n scope: string,\n version?: string | number,\n): string {\n return `/v1/data/${encodeURIComponent(scope)}/lineage${version === undefined ? \"\" : `/${String(version)}`}`;\n}\n\n/**\n * The gateway lineage path: `/v1/data/<id lowercase>/lineage[/:version]`,\n * what the request is signed over and sent to. The grant view is the signed\n * `grantId` claim, never a query parameter.\n */\nexport function gatewayLineagePath(\n dataPointId: Hex,\n version?: string | number,\n): string {\n return `/v1/data/${dataPointId.toLowerCase()}/lineage${version === undefined ? \"\" : `/${String(version)}`}`;\n}\n\ninterface LineageRequestOptions {\n /**\n * Read the lineage as of this version (a positive decimal integer);\n * omitted = the current version, or the last version that carried lineage\n * when the current one is a tombstone.\n */\n version?: string | number;\n /** `fetch` to use; defaults to `globalThis.fetch`. */\n fetch?: typeof fetch;\n /** Extra request headers. */\n headers?: HeadersInit;\n}\n\n/** Lineage read against the Personal Server holding the record. */\nexport interface PersonalServerLineageParams extends LineageRequestOptions {\n /** Personal Server origin, e.g. `https://ps.example.com`. */\n personalServerUrl: string;\n /** The scope whose lineage to read. */\n scope: string;\n /** A grant covering the scope, sent as the signed `grantId` claim. */\n grantId: string;\n /** Builder key: a viem `LocalAccount`, `WalletClient`, or `{ signMessage }`. */\n signer: WriteSignerSource;\n /** Account for a viem wallet client without a hoisted account. */\n account?: ResolveWriteSignerOptions[\"account\"];\n /** Web3Signed audience; defaults to `personalServerUrl`. */\n audience?: string;\n}\n\n/** Lineage read against the gateway, by data point id. */\nexport interface GatewayLineageParams extends LineageRequestOptions {\n /** Gateway origin, e.g. `https://dp-rpc.vana.org`. */\n gatewayUrl: string;\n /** The data point whose lineage to read (see {@link deriveDataPointId}). */\n dataPointId: Hex;\n /**\n * The key the request is signed with (Web3Signed, audience = the gateway\n * origin). The signer decides the view: the owner or one of its servers\n * gets the full view; a registered builder holding a live grant covering\n * the data point's scope gets that grant's view; anyone else is refused.\n */\n signer: WriteSignerSource;\n /** Account for a viem wallet client without a hoisted account. */\n account?: ResolveWriteSignerOptions[\"account\"];\n /**\n * The grant whose view to read, sent lowercased as the signed `grantId`\n * claim (never as a query parameter). An owner or server uses it to fetch\n * the view a builder's grant sees; a builder needs it to see anything.\n */\n grantId?: string;\n}\n\nexport type GetLineageParams =\n | PersonalServerLineageParams\n | GatewayLineageParams;\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 LineageReadError(\"No fetch implementation available\");\n }\n return resolved;\n}\n\nfunction normalizeVersion(\n version: string | number | undefined,\n): string | undefined {\n if (version === undefined) return undefined;\n const text = String(version);\n if (!VERSION_PATTERN.test(text)) {\n throw new LineageReadError(\n \"version must be a positive decimal integer\",\n undefined,\n \"INVALID_VERSION\",\n { version },\n );\n }\n return text;\n}\n\nasync function lineageReadFailure(\n source: string,\n response: Response,\n): Promise<LineageReadError> {\n const { errorCode, message, details } =\n await readPersonalServerErrorBody(response);\n return new LineageReadError(\n message ??\n `${source} lineage read failed: ${response.status} ${response.statusText}`,\n response.status,\n errorCode,\n details,\n );\n}\n\nasync function parseLineageGraph(\n source: string,\n response: Response,\n): Promise<LineageReadResult> {\n let body: unknown;\n try {\n body = await response.json();\n } catch (err) {\n throw new LineageReadError(\n `${source} lineage response is not JSON`,\n response.status,\n null,\n { cause: err instanceof Error ? err.message : String(err) },\n );\n }\n // Both servers answer the gateway envelope `{ data, proof }`; a bare view\n // is accepted too.\n const envelope = isRecord(body) && isRecord(body.data) ? body : undefined;\n const parsed = LineageGraphSchema.safeParse(envelope?.data ?? body);\n if (!parsed.success) {\n throw new LineageReadError(\n `${source} lineage response is not a lineage view`,\n response.status,\n null,\n { issues: parsed.error.issues },\n );\n }\n const proof = isRecord(envelope?.proof) ? envelope.proof : undefined;\n return proof === undefined ? parsed.data : { ...parsed.data, proof };\n}\n\nasync function sendLineageRead(\n source: string,\n fetchFn: typeof fetch,\n url: string,\n headers: Headers,\n): Promise<LineageReadResult> {\n let response: Response;\n try {\n response = await fetchFn(url, { method: \"GET\", headers });\n } catch (err) {\n throw new LineageReadError(\n `${source} lineage read failed: ${err instanceof Error ? err.message : String(err)}`,\n undefined,\n null,\n { cause: err instanceof Error ? err.message : String(err) },\n );\n }\n if (!response.ok) {\n throw await lineageReadFailure(source, response);\n }\n return parseLineageGraph(source, response);\n}\n\n/**\n * Read a scope's lineage from the Personal Server that stores it.\n *\n * @remarks\n * Sends `GET /v1/data/:scope/lineage[/:version]` with a Web3Signed\n * `Authorization` header carrying `grantId`, the same authentication a data\n * read uses; the signed `uri` is the full path, version segment included.\n * The server resolves the data point id, fetches the view the grant sees\n * from the gateway and returns the gateway's `data` + `proof`.\n *\n * @returns The lineage view, with redacted entries for nodes the grant does\n * not cover, plus the gateway attestation.\n * @throws {LineageReadError} On a non-2xx answer (`errorCode`: read errors,\n * `INVALID_VERSION`, `NOT_FOUND` when the scope or version is not\n * registered at the gateway, `LINEAGE_FORBIDDEN`, `LINEAGE_GATEWAY_ERROR`,\n * `LINEAGE_UNAVAILABLE`), an unreadable body, a bad `version`, or a\n * transport failure.\n */\nexport async function getPersonalServerLineage(\n params: PersonalServerLineageParams,\n): Promise<LineageReadResult> {\n const fetchFn = resolveFetch(params.fetch);\n const baseUrl = normalizeBaseUrl(params.personalServerUrl);\n const audience = params.audience ?? baseUrl;\n const signer = resolveWriteSigner(params.signer, { account: params.account });\n const path = personalServerLineagePath(\n params.scope,\n normalizeVersion(params.version),\n );\n const headers = new Headers(params.headers);\n headers.set(\n \"Authorization\",\n await buildWeb3SignedHeader({\n signMessage: signer.signMessage,\n aud: audience,\n method: \"GET\",\n uri: path,\n grantId: params.grantId,\n }),\n );\n return sendLineageRead(\n \"Personal Server\",\n fetchFn,\n `${baseUrl}${path}`,\n headers,\n );\n}\n\n/**\n * Read a data point's lineage from the gateway.\n *\n * @remarks\n * Sends `GET /v1/data/:dataPointId/lineage[/:version]` with a Web3Signed\n * `Authorization` header: `aud` = the gateway origin, `uri` =\n * {@link gatewayLineagePath} (lowercase id, version segment included),\n * empty-body `bodyHash`, and the lowercased `grantId` claim when given. The\n * gateway answers a uniform 404 for an unknown data point and for a signer\n * it will not serve, so the two cannot be told apart from outside.\n *\n * @returns The lineage view, with redacted entries for nodes the caller's\n * grant does not cover, plus the gateway attestation.\n * @throws {LineageReadError} On a malformed `dataPointId` or `version`, a\n * non-2xx answer (400 malformed request, 401 `LINEAGE_SIGNATURE_REQUIRED`\n * / `LINEAGE_SIGNATURE_INVALID`, 404 unknown or not served), an unreadable\n * body, or a transport failure.\n */\nexport async function getGatewayLineage(\n params: GatewayLineageParams,\n): Promise<LineageReadResult> {\n if (!isDataPointId(params.dataPointId)) {\n throw new LineageReadError(\n \"dataPointId must be a 32-byte hex string (see deriveDataPointId)\",\n undefined,\n \"INVALID_DATA_POINT_ID\",\n { dataPointId: params.dataPointId },\n );\n }\n const fetchFn = resolveFetch(params.fetch);\n const baseUrl = normalizeBaseUrl(params.gatewayUrl);\n const signer = resolveWriteSigner(params.signer, { account: params.account });\n const uri = gatewayLineagePath(\n params.dataPointId,\n normalizeVersion(params.version),\n );\n const headers = new Headers(params.headers);\n headers.set(\n \"Authorization\",\n await buildWeb3SignedHeader({\n signMessage: signer.signMessage,\n aud: baseUrl,\n method: \"GET\",\n uri,\n grantId: params.grantId?.toLowerCase(),\n }),\n );\n return sendLineageRead(\"Gateway\", fetchFn, `${baseUrl}${uri}`, headers);\n}\n\n/**\n * Read a lineage view from either the Personal Server (by scope) or the\n * gateway (by data point id), chosen by the params shape.\n *\n * @example\n * ```typescript\n * const fromPs = await getLineage({ personalServerUrl, scope, grantId, signer });\n * const fromGateway = await getLineage({ gatewayUrl, dataPointId, grantId, signer });\n * ```\n */\nexport function getLineage(\n params: GetLineageParams,\n): Promise<LineageReadResult> {\n return \"personalServerUrl\" in params\n ? getPersonalServerLineage(params)\n : getGatewayLineage(params);\n}\n"],"mappings":"AAyBA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OAGK;AACP,SAAS,SAAS;AAClB,SAAS,6BAA6B;AACtC,SAAS,kBAAkB,yBAAyB;AACpD;AAAA,EACE;AAAA,EACA;AAAA,OACK;AACP;AAAA,EACE;AAAA,OAGK;AAEP,MAAM,wBAAwB;AAGvB,SAAS,cAAc,OAA8B;AAC1D,SAAO,OAAO,UAAU,YAAY,sBAAsB,KAAK,KAAK;AACtE;AAWO,SAAS,kBAAkB,cAAuB,OAAoB;AAC3E,MAAI,CAAC,UAAU,cAAc,EAAE,QAAQ,MAAM,CAAC,GAAG;AAC/C,UAAM,IAAI;AAAA,MACR,uCAAuC,OAAO,YAAY,CAAC;AAAA,IAC7D;AAAA,EACF;AACA,SAAO;AAAA,IACL;AAAA,MACE;AAAA,QACE,EAAE,MAAM,gBAAgB,MAAM,UAAU;AAAA,QACxC,EAAE,MAAM,SAAS,MAAM,SAAS;AAAA,MAClC;AAAA,MACA,CAAC,cAAc,KAAK;AAAA,IACtB;AAAA,EACF;AACF;AAEA,MAAM,oBAAoB,EACvB,OAAO,EACP,MAAM,qBAAqB,EAC3B,UAAU,CAAC,UAAU,MAAM,YAAY,CAAQ;AAElD,MAAM,kBAAkB;AACxB,MAAM,uBAAuB;AAK7B,MAAM,gBAAgB,EACnB,MAAM,CAAC,EAAE,OAAO,GAAG,EAAE,OAAO,CAAC,CAAC,EAC9B,UAAU,MAAM,EAChB,OAAO,CAAC,UAAU,qBAAqB,KAAK,KAAK,GAAG;AAAA,EACnD,SAAS;AACX,CAAC;AAEH,MAAM,oBAAoB,cAAc;AAAA,EACtC,CAAC,UAAU,gBAAgB,KAAK,KAAK;AAAA,EACrC,EAAE,SAAS,6CAA6C;AAC1D;AAGO,SAAS,eAAe,OAAuB;AACpD,QAAM,MAAM,MAAM,QAAQ,GAAG;AAC7B,SAAO,QAAQ,KAAK,QAAQ,MAAM,MAAM,GAAG,GAAG;AAChD;AAQO,SAAS,2BACd,cACA,aACS;AACT,SAAO,eAAe,YAAY,MAAM,eAAe,WAAW;AACpE;AAQO,SAAS,yBACd,cACA,cACM;AACN,aAAW,eAAe,cAAc;AACtC,QAAI,2BAA2B,cAAc,WAAW,GAAG;AACzD,YAAM,IAAI;AAAA,QACR,iBAAiB,YAAY,uDAAuD,WAAW;AAAA,QAC/F,EAAE,OAAO,cAAc,YAAY;AAAA,MACrC;AAAA,IACF;AAAA,EACF;AACF;AAEO,MAAM,oBAAoB,EAAE,OAAO;AAAA,EACxC,aAAa;AAAA,EACb,OAAO,EAAE,OAAO;AAAA;AAAA;AAAA;AAAA;AAAA,EAKhB,SAAS;AAAA;AAAA,EAET,WAAW,EAAE,OAAO,EAAE,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAM/B,UAAU,EAAE,MAAM,EAAE,SAAS;AAC/B,CAAC;AAQM,MAAM,4BAA4B,EAAE,aAAa;AAAA,EACtD,UAAU,EAAE,QAAQ,IAAI;AAC1B,CAAC;AAEM,MAAM,qBAAqB,EAAE,MAAM;AAAA,EACxC;AAAA,EACA;AACF,CAAC;AAEM,MAAM,qBAAqB,EAAE,OAAO;AAAA,EACzC,aAAa;AAAA;AAAA,EAEb,cAAc,EAAE,OAAO,EAAE,SAAS;AAAA,EAClC,OAAO,EAAE,OAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMhB,SAAS;AAAA,EACT,WAAW,EAAE,OAAO,EAAE,SAAS;AAAA,EAC/B,SAAS,EAAE,MAAM,kBAAkB;AAAA,EACnC,aAAa,EAAE,MAAM,kBAAkB;AAAA;AAAA,EAEvC,sBAAsB,EAAE,QAAQ,EAAE,SAAS;AAC7C,CAAC;AAyBM,SAAS,sBACd,OAC8B;AAC9B,SACE,cAAc,SACd,MAAM,aAAa,QACnB,OAAO,KAAK,KAAK,EAAE,WAAW;AAElC;AAOO,SAAS,0BACd,OACA,SACQ;AACR,SAAO,YAAY,mBAAmB,KAAK,CAAC,WAAW,YAAY,SAAY,KAAK,IAAI,OAAO,OAAO,CAAC,EAAE;AAC3G;AAOO,SAAS,mBACd,aACA,SACQ;AACR,SAAO,YAAY,YAAY,YAAY,CAAC,WAAW,YAAY,SAAY,KAAK,IAAI,OAAO,OAAO,CAAC,EAAE;AAC3G;AA0DA,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,iBAAiB,mCAAmC;AAAA,EAChE;AACA,SAAO;AACT;AAEA,SAAS,iBACP,SACoB;AACpB,MAAI,YAAY,OAAW,QAAO;AAClC,QAAM,OAAO,OAAO,OAAO;AAC3B,MAAI,CAAC,gBAAgB,KAAK,IAAI,GAAG;AAC/B,UAAM,IAAI;AAAA,MACR;AAAA,MACA;AAAA,MACA;AAAA,MACA,EAAE,QAAQ;AAAA,IACZ;AAAA,EACF;AACA,SAAO;AACT;AAEA,eAAe,mBACb,QACA,UAC2B;AAC3B,QAAM,EAAE,WAAW,SAAS,QAAQ,IAClC,MAAM,4BAA4B,QAAQ;AAC5C,SAAO,IAAI;AAAA,IACT,WACE,GAAG,MAAM,yBAAyB,SAAS,MAAM,IAAI,SAAS,UAAU;AAAA,IAC1E,SAAS;AAAA,IACT;AAAA,IACA;AAAA,EACF;AACF;AAEA,eAAe,kBACb,QACA,UAC4B;AAC5B,MAAI;AACJ,MAAI;AACF,WAAO,MAAM,SAAS,KAAK;AAAA,EAC7B,SAAS,KAAK;AACZ,UAAM,IAAI;AAAA,MACR,GAAG,MAAM;AAAA,MACT,SAAS;AAAA,MACT;AAAA,MACA,EAAE,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,EAAE;AAAA,IAC5D;AAAA,EACF;AAGA,QAAM,WAAW,SAAS,IAAI,KAAK,SAAS,KAAK,IAAI,IAAI,OAAO;AAChE,QAAM,SAAS,mBAAmB,UAAU,UAAU,QAAQ,IAAI;AAClE,MAAI,CAAC,OAAO,SAAS;AACnB,UAAM,IAAI;AAAA,MACR,GAAG,MAAM;AAAA,MACT,SAAS;AAAA,MACT;AAAA,MACA,EAAE,QAAQ,OAAO,MAAM,OAAO;AAAA,IAChC;AAAA,EACF;AACA,QAAM,QAAQ,SAAS,UAAU,KAAK,IAAI,SAAS,QAAQ;AAC3D,SAAO,UAAU,SAAY,OAAO,OAAO,EAAE,GAAG,OAAO,MAAM,MAAM;AACrE;AAEA,eAAe,gBACb,QACA,SACA,KACA,SAC4B;AAC5B,MAAI;AACJ,MAAI;AACF,eAAW,MAAM,QAAQ,KAAK,EAAE,QAAQ,OAAO,QAAQ,CAAC;AAAA,EAC1D,SAAS,KAAK;AACZ,UAAM,IAAI;AAAA,MACR,GAAG,MAAM,yBAAyB,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC;AAAA,MAClF;AAAA,MACA;AAAA,MACA,EAAE,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,EAAE;AAAA,IAC5D;AAAA,EACF;AACA,MAAI,CAAC,SAAS,IAAI;AAChB,UAAM,MAAM,mBAAmB,QAAQ,QAAQ;AAAA,EACjD;AACA,SAAO,kBAAkB,QAAQ,QAAQ;AAC3C;AAoBA,eAAsB,yBACpB,QAC4B;AAC5B,QAAM,UAAU,aAAa,OAAO,KAAK;AACzC,QAAM,UAAU,iBAAiB,OAAO,iBAAiB;AACzD,QAAM,WAAW,OAAO,YAAY;AACpC,QAAM,SAAS,mBAAmB,OAAO,QAAQ,EAAE,SAAS,OAAO,QAAQ,CAAC;AAC5E,QAAM,OAAO;AAAA,IACX,OAAO;AAAA,IACP,iBAAiB,OAAO,OAAO;AAAA,EACjC;AACA,QAAM,UAAU,IAAI,QAAQ,OAAO,OAAO;AAC1C,UAAQ;AAAA,IACN;AAAA,IACA,MAAM,sBAAsB;AAAA,MAC1B,aAAa,OAAO;AAAA,MACpB,KAAK;AAAA,MACL,QAAQ;AAAA,MACR,KAAK;AAAA,MACL,SAAS,OAAO;AAAA,IAClB,CAAC;AAAA,EACH;AACA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,GAAG,OAAO,GAAG,IAAI;AAAA,IACjB;AAAA,EACF;AACF;AAoBA,eAAsB,kBACpB,QAC4B;AAC5B,MAAI,CAAC,cAAc,OAAO,WAAW,GAAG;AACtC,UAAM,IAAI;AAAA,MACR;AAAA,MACA;AAAA,MACA;AAAA,MACA,EAAE,aAAa,OAAO,YAAY;AAAA,IACpC;AAAA,EACF;AACA,QAAM,UAAU,aAAa,OAAO,KAAK;AACzC,QAAM,UAAU,iBAAiB,OAAO,UAAU;AAClD,QAAM,SAAS,mBAAmB,OAAO,QAAQ,EAAE,SAAS,OAAO,QAAQ,CAAC;AAC5E,QAAM,MAAM;AAAA,IACV,OAAO;AAAA,IACP,iBAAiB,OAAO,OAAO;AAAA,EACjC;AACA,QAAM,UAAU,IAAI,QAAQ,OAAO,OAAO;AAC1C,UAAQ;AAAA,IACN;AAAA,IACA,MAAM,sBAAsB;AAAA,MAC1B,aAAa,OAAO;AAAA,MACpB,KAAK;AAAA,MACL,QAAQ;AAAA,MACR;AAAA,MACA,SAAS,OAAO,SAAS,YAAY;AAAA,IACvC,CAAC;AAAA,EACH;AACA,SAAO,gBAAgB,WAAW,SAAS,GAAG,OAAO,GAAG,GAAG,IAAI,OAAO;AACxE;AAYO,SAAS,WACd,QAC4B;AAC5B,SAAO,uBAAuB,SAC1B,yBAAyB,MAAM,IAC/B,kBAAkB,MAAM;AAC9B;","names":[]}
|
|
@@ -24,7 +24,10 @@ __export(personal_server_data_exports, {
|
|
|
24
24
|
});
|
|
25
25
|
module.exports = __toCommonJS(personal_server_data_exports);
|
|
26
26
|
var import_web3_signed_builder = require("../auth/web3-signed-builder");
|
|
27
|
+
var import_errors = require("../errors");
|
|
27
28
|
var import_data_file = require("./data-file");
|
|
29
|
+
var import_data_point_deletion = require("./data-point-deletion");
|
|
30
|
+
var import_response_body = require("../utils/response-body");
|
|
28
31
|
function personalServerDataReadPath(scope) {
|
|
29
32
|
return `/v1/data/${encodeURIComponent(scope)}`;
|
|
30
33
|
}
|
|
@@ -55,12 +58,28 @@ async function readPersonalServerData(params) {
|
|
|
55
58
|
}
|
|
56
59
|
const request = await buildPersonalServerDataReadRequest(params);
|
|
57
60
|
const response = await fetchFn(request);
|
|
61
|
+
if (response.status === 410) {
|
|
62
|
+
throw new import_errors.DataPointDeletedError(
|
|
63
|
+
`Personal Server scope '${params.scope}' has been deleted`,
|
|
64
|
+
{
|
|
65
|
+
scope: params.scope,
|
|
66
|
+
deletedAt: (0, import_data_point_deletion.tombstoneDeletedAt)(await (0, import_response_body.readJsonValue)(response))
|
|
67
|
+
}
|
|
68
|
+
);
|
|
69
|
+
}
|
|
58
70
|
if (!response.ok) {
|
|
59
71
|
throw new Error(
|
|
60
72
|
`Personal Server data read failed: ${response.status} ${response.statusText}`
|
|
61
73
|
);
|
|
62
74
|
}
|
|
63
|
-
|
|
75
|
+
const body = await response.json();
|
|
76
|
+
if ((0, import_data_point_deletion.isDataPointTombstone)(body)) {
|
|
77
|
+
throw new import_errors.DataPointDeletedError(
|
|
78
|
+
`Personal Server scope '${params.scope}' has been deleted`,
|
|
79
|
+
{ scope: params.scope, deletedAt: (0, import_data_point_deletion.tombstoneDeletedAt)(body) }
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
return import_data_file.DataFileEnvelopeSchema.parse(body);
|
|
64
83
|
}
|
|
65
84
|
// Annotate the CommonJS export names for ESM import in node:
|
|
66
85
|
0 && (module.exports = {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/protocol/personal-server-data.ts"],"sourcesContent":["import {\n buildWeb3SignedHeader,\n type Web3SignedSignFn,\n} from \"../auth/web3-signed-builder\";\nimport { DataFileEnvelopeSchema, type DataFileEnvelope } from \"./data-file\";\n\nexport interface BuildPersonalServerDataReadRequestParams {\n personalServerUrl: string;\n scope: string;\n grantId: string;\n signMessage: Web3SignedSignFn;\n audience?: string;\n headers?: HeadersInit;\n}\n\nexport interface ReadPersonalServerDataParams extends BuildPersonalServerDataReadRequestParams {\n fetch?: typeof fetch;\n}\n\nexport function personalServerDataReadPath(scope: string): string {\n return `/v1/data/${encodeURIComponent(scope)}`;\n}\n\nexport async function buildPersonalServerDataReadRequest(\n params: BuildPersonalServerDataReadRequestParams,\n): Promise<Request> {\n const path = personalServerDataReadPath(params.scope);\n const baseUrl = params.personalServerUrl.replace(/\\/+$/, \"\");\n const audience = params.audience ?? baseUrl;\n const headers = new Headers(params.headers);\n\n headers.set(\n \"Authorization\",\n await buildWeb3SignedHeader({\n aud: audience,\n grantId: params.grantId,\n method: \"GET\",\n signMessage: params.signMessage,\n uri: path,\n }),\n );\n\n return new Request(`${baseUrl}${path}`, {\n headers,\n method: \"GET\",\n });\n}\n\nexport async function readPersonalServerData(\n params: ReadPersonalServerDataParams,\n): Promise<DataFileEnvelope> {\n const fetchFn = params.fetch ?? globalThis.fetch;\n if (fetchFn === undefined) {\n throw new Error(\"No fetch implementation available\");\n }\n\n const request = await buildPersonalServerDataReadRequest(params);\n const response = await fetchFn(request);\n\n if (!response.ok) {\n throw new Error(\n `Personal Server data read failed: ${response.status} ${response.statusText}`,\n );\n }\n\n
|
|
1
|
+
{"version":3,"sources":["../../src/protocol/personal-server-data.ts"],"sourcesContent":["import {\n buildWeb3SignedHeader,\n type Web3SignedSignFn,\n} from \"../auth/web3-signed-builder\";\nimport { DataPointDeletedError } from \"../errors\";\nimport { DataFileEnvelopeSchema, type DataFileEnvelope } from \"./data-file\";\nimport {\n isDataPointTombstone,\n tombstoneDeletedAt,\n} from \"./data-point-deletion\";\nimport { readJsonValue } from \"../utils/response-body\";\n\nexport interface BuildPersonalServerDataReadRequestParams {\n personalServerUrl: string;\n scope: string;\n grantId: string;\n signMessage: Web3SignedSignFn;\n audience?: string;\n headers?: HeadersInit;\n}\n\nexport interface ReadPersonalServerDataParams extends BuildPersonalServerDataReadRequestParams {\n fetch?: typeof fetch;\n}\n\nexport function personalServerDataReadPath(scope: string): string {\n return `/v1/data/${encodeURIComponent(scope)}`;\n}\n\nexport async function buildPersonalServerDataReadRequest(\n params: BuildPersonalServerDataReadRequestParams,\n): Promise<Request> {\n const path = personalServerDataReadPath(params.scope);\n const baseUrl = params.personalServerUrl.replace(/\\/+$/, \"\");\n const audience = params.audience ?? baseUrl;\n const headers = new Headers(params.headers);\n\n headers.set(\n \"Authorization\",\n await buildWeb3SignedHeader({\n aud: audience,\n grantId: params.grantId,\n method: \"GET\",\n signMessage: params.signMessage,\n uri: path,\n }),\n );\n\n return new Request(`${baseUrl}${path}`, {\n headers,\n method: \"GET\",\n });\n}\n\nexport async function readPersonalServerData(\n params: ReadPersonalServerDataParams,\n): Promise<DataFileEnvelope> {\n const fetchFn = params.fetch ?? globalThis.fetch;\n if (fetchFn === undefined) {\n throw new Error(\"No fetch implementation available\");\n }\n\n const request = await buildPersonalServerDataReadRequest(params);\n const response = await fetchFn(request);\n\n // 410 = the scope was tombstoned. Typed so callers can branch on it\n // instead of pattern-matching a generic read failure.\n if (response.status === 410) {\n throw new DataPointDeletedError(\n `Personal Server scope '${params.scope}' has been deleted`,\n {\n scope: params.scope,\n deletedAt: tombstoneDeletedAt(await readJsonValue(response)),\n },\n );\n }\n\n if (!response.ok) {\n throw new Error(\n `Personal Server data read failed: ${response.status} ${response.statusText}`,\n );\n }\n\n const body: unknown = await response.json();\n // A tombstone must never be returned as data, even if the server answered\n // 200 with the deleted row (deletedAt / tombstone hash pair).\n if (isDataPointTombstone(body)) {\n throw new DataPointDeletedError(\n `Personal Server scope '${params.scope}' has been deleted`,\n { scope: params.scope, deletedAt: tombstoneDeletedAt(body) },\n );\n }\n\n return DataFileEnvelopeSchema.parse(body);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,iCAGO;AACP,oBAAsC;AACtC,uBAA8D;AAC9D,iCAGO;AACP,2BAA8B;AAevB,SAAS,2BAA2B,OAAuB;AAChE,SAAO,YAAY,mBAAmB,KAAK,CAAC;AAC9C;AAEA,eAAsB,mCACpB,QACkB;AAClB,QAAM,OAAO,2BAA2B,OAAO,KAAK;AACpD,QAAM,UAAU,OAAO,kBAAkB,QAAQ,QAAQ,EAAE;AAC3D,QAAM,WAAW,OAAO,YAAY;AACpC,QAAM,UAAU,IAAI,QAAQ,OAAO,OAAO;AAE1C,UAAQ;AAAA,IACN;AAAA,IACA,UAAM,kDAAsB;AAAA,MAC1B,KAAK;AAAA,MACL,SAAS,OAAO;AAAA,MAChB,QAAQ;AAAA,MACR,aAAa,OAAO;AAAA,MACpB,KAAK;AAAA,IACP,CAAC;AAAA,EACH;AAEA,SAAO,IAAI,QAAQ,GAAG,OAAO,GAAG,IAAI,IAAI;AAAA,IACtC;AAAA,IACA,QAAQ;AAAA,EACV,CAAC;AACH;AAEA,eAAsB,uBACpB,QAC2B;AAC3B,QAAM,UAAU,OAAO,SAAS,WAAW;AAC3C,MAAI,YAAY,QAAW;AACzB,UAAM,IAAI,MAAM,mCAAmC;AAAA,EACrD;AAEA,QAAM,UAAU,MAAM,mCAAmC,MAAM;AAC/D,QAAM,WAAW,MAAM,QAAQ,OAAO;AAItC,MAAI,SAAS,WAAW,KAAK;AAC3B,UAAM,IAAI;AAAA,MACR,0BAA0B,OAAO,KAAK;AAAA,MACtC;AAAA,QACE,OAAO,OAAO;AAAA,QACd,eAAW,+CAAmB,UAAM,oCAAc,QAAQ,CAAC;AAAA,MAC7D;AAAA,IACF;AAAA,EACF;AAEA,MAAI,CAAC,SAAS,IAAI;AAChB,UAAM,IAAI;AAAA,MACR,qCAAqC,SAAS,MAAM,IAAI,SAAS,UAAU;AAAA,IAC7E;AAAA,EACF;AAEA,QAAM,OAAgB,MAAM,SAAS,KAAK;AAG1C,UAAI,iDAAqB,IAAI,GAAG;AAC9B,UAAM,IAAI;AAAA,MACR,0BAA0B,OAAO,KAAK;AAAA,MACtC,EAAE,OAAO,OAAO,OAAO,eAAW,+CAAmB,IAAI,EAAE;AAAA,IAC7D;AAAA,EACF;AAEA,SAAO,wCAAuB,MAAM,IAAI;AAC1C;","names":[]}
|
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
import {
|
|
2
2
|
buildWeb3SignedHeader
|
|
3
3
|
} from "../auth/web3-signed-builder.js";
|
|
4
|
+
import { DataPointDeletedError } from "../errors.js";
|
|
4
5
|
import { DataFileEnvelopeSchema } from "./data-file.js";
|
|
6
|
+
import {
|
|
7
|
+
isDataPointTombstone,
|
|
8
|
+
tombstoneDeletedAt
|
|
9
|
+
} from "./data-point-deletion.js";
|
|
10
|
+
import { readJsonValue } from "../utils/response-body.js";
|
|
5
11
|
function personalServerDataReadPath(scope) {
|
|
6
12
|
return `/v1/data/${encodeURIComponent(scope)}`;
|
|
7
13
|
}
|
|
@@ -32,12 +38,28 @@ async function readPersonalServerData(params) {
|
|
|
32
38
|
}
|
|
33
39
|
const request = await buildPersonalServerDataReadRequest(params);
|
|
34
40
|
const response = await fetchFn(request);
|
|
41
|
+
if (response.status === 410) {
|
|
42
|
+
throw new DataPointDeletedError(
|
|
43
|
+
`Personal Server scope '${params.scope}' has been deleted`,
|
|
44
|
+
{
|
|
45
|
+
scope: params.scope,
|
|
46
|
+
deletedAt: tombstoneDeletedAt(await readJsonValue(response))
|
|
47
|
+
}
|
|
48
|
+
);
|
|
49
|
+
}
|
|
35
50
|
if (!response.ok) {
|
|
36
51
|
throw new Error(
|
|
37
52
|
`Personal Server data read failed: ${response.status} ${response.statusText}`
|
|
38
53
|
);
|
|
39
54
|
}
|
|
40
|
-
|
|
55
|
+
const body = await response.json();
|
|
56
|
+
if (isDataPointTombstone(body)) {
|
|
57
|
+
throw new DataPointDeletedError(
|
|
58
|
+
`Personal Server scope '${params.scope}' has been deleted`,
|
|
59
|
+
{ scope: params.scope, deletedAt: tombstoneDeletedAt(body) }
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
return DataFileEnvelopeSchema.parse(body);
|
|
41
63
|
}
|
|
42
64
|
export {
|
|
43
65
|
buildPersonalServerDataReadRequest,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/protocol/personal-server-data.ts"],"sourcesContent":["import {\n buildWeb3SignedHeader,\n type Web3SignedSignFn,\n} from \"../auth/web3-signed-builder\";\nimport { DataFileEnvelopeSchema, type DataFileEnvelope } from \"./data-file\";\n\nexport interface BuildPersonalServerDataReadRequestParams {\n personalServerUrl: string;\n scope: string;\n grantId: string;\n signMessage: Web3SignedSignFn;\n audience?: string;\n headers?: HeadersInit;\n}\n\nexport interface ReadPersonalServerDataParams extends BuildPersonalServerDataReadRequestParams {\n fetch?: typeof fetch;\n}\n\nexport function personalServerDataReadPath(scope: string): string {\n return `/v1/data/${encodeURIComponent(scope)}`;\n}\n\nexport async function buildPersonalServerDataReadRequest(\n params: BuildPersonalServerDataReadRequestParams,\n): Promise<Request> {\n const path = personalServerDataReadPath(params.scope);\n const baseUrl = params.personalServerUrl.replace(/\\/+$/, \"\");\n const audience = params.audience ?? baseUrl;\n const headers = new Headers(params.headers);\n\n headers.set(\n \"Authorization\",\n await buildWeb3SignedHeader({\n aud: audience,\n grantId: params.grantId,\n method: \"GET\",\n signMessage: params.signMessage,\n uri: path,\n }),\n );\n\n return new Request(`${baseUrl}${path}`, {\n headers,\n method: \"GET\",\n });\n}\n\nexport async function readPersonalServerData(\n params: ReadPersonalServerDataParams,\n): Promise<DataFileEnvelope> {\n const fetchFn = params.fetch ?? globalThis.fetch;\n if (fetchFn === undefined) {\n throw new Error(\"No fetch implementation available\");\n }\n\n const request = await buildPersonalServerDataReadRequest(params);\n const response = await fetchFn(request);\n\n if (!response.ok) {\n throw new Error(\n `Personal Server data read failed: ${response.status} ${response.statusText}`,\n );\n }\n\n
|
|
1
|
+
{"version":3,"sources":["../../src/protocol/personal-server-data.ts"],"sourcesContent":["import {\n buildWeb3SignedHeader,\n type Web3SignedSignFn,\n} from \"../auth/web3-signed-builder\";\nimport { DataPointDeletedError } from \"../errors\";\nimport { DataFileEnvelopeSchema, type DataFileEnvelope } from \"./data-file\";\nimport {\n isDataPointTombstone,\n tombstoneDeletedAt,\n} from \"./data-point-deletion\";\nimport { readJsonValue } from \"../utils/response-body\";\n\nexport interface BuildPersonalServerDataReadRequestParams {\n personalServerUrl: string;\n scope: string;\n grantId: string;\n signMessage: Web3SignedSignFn;\n audience?: string;\n headers?: HeadersInit;\n}\n\nexport interface ReadPersonalServerDataParams extends BuildPersonalServerDataReadRequestParams {\n fetch?: typeof fetch;\n}\n\nexport function personalServerDataReadPath(scope: string): string {\n return `/v1/data/${encodeURIComponent(scope)}`;\n}\n\nexport async function buildPersonalServerDataReadRequest(\n params: BuildPersonalServerDataReadRequestParams,\n): Promise<Request> {\n const path = personalServerDataReadPath(params.scope);\n const baseUrl = params.personalServerUrl.replace(/\\/+$/, \"\");\n const audience = params.audience ?? baseUrl;\n const headers = new Headers(params.headers);\n\n headers.set(\n \"Authorization\",\n await buildWeb3SignedHeader({\n aud: audience,\n grantId: params.grantId,\n method: \"GET\",\n signMessage: params.signMessage,\n uri: path,\n }),\n );\n\n return new Request(`${baseUrl}${path}`, {\n headers,\n method: \"GET\",\n });\n}\n\nexport async function readPersonalServerData(\n params: ReadPersonalServerDataParams,\n): Promise<DataFileEnvelope> {\n const fetchFn = params.fetch ?? globalThis.fetch;\n if (fetchFn === undefined) {\n throw new Error(\"No fetch implementation available\");\n }\n\n const request = await buildPersonalServerDataReadRequest(params);\n const response = await fetchFn(request);\n\n // 410 = the scope was tombstoned. Typed so callers can branch on it\n // instead of pattern-matching a generic read failure.\n if (response.status === 410) {\n throw new DataPointDeletedError(\n `Personal Server scope '${params.scope}' has been deleted`,\n {\n scope: params.scope,\n deletedAt: tombstoneDeletedAt(await readJsonValue(response)),\n },\n );\n }\n\n if (!response.ok) {\n throw new Error(\n `Personal Server data read failed: ${response.status} ${response.statusText}`,\n );\n }\n\n const body: unknown = await response.json();\n // A tombstone must never be returned as data, even if the server answered\n // 200 with the deleted row (deletedAt / tombstone hash pair).\n if (isDataPointTombstone(body)) {\n throw new DataPointDeletedError(\n `Personal Server scope '${params.scope}' has been deleted`,\n { scope: params.scope, deletedAt: tombstoneDeletedAt(body) },\n );\n }\n\n return DataFileEnvelopeSchema.parse(body);\n}\n"],"mappings":"AAAA;AAAA,EACE;AAAA,OAEK;AACP,SAAS,6BAA6B;AACtC,SAAS,8BAAqD;AAC9D;AAAA,EACE;AAAA,EACA;AAAA,OACK;AACP,SAAS,qBAAqB;AAevB,SAAS,2BAA2B,OAAuB;AAChE,SAAO,YAAY,mBAAmB,KAAK,CAAC;AAC9C;AAEA,eAAsB,mCACpB,QACkB;AAClB,QAAM,OAAO,2BAA2B,OAAO,KAAK;AACpD,QAAM,UAAU,OAAO,kBAAkB,QAAQ,QAAQ,EAAE;AAC3D,QAAM,WAAW,OAAO,YAAY;AACpC,QAAM,UAAU,IAAI,QAAQ,OAAO,OAAO;AAE1C,UAAQ;AAAA,IACN;AAAA,IACA,MAAM,sBAAsB;AAAA,MAC1B,KAAK;AAAA,MACL,SAAS,OAAO;AAAA,MAChB,QAAQ;AAAA,MACR,aAAa,OAAO;AAAA,MACpB,KAAK;AAAA,IACP,CAAC;AAAA,EACH;AAEA,SAAO,IAAI,QAAQ,GAAG,OAAO,GAAG,IAAI,IAAI;AAAA,IACtC;AAAA,IACA,QAAQ;AAAA,EACV,CAAC;AACH;AAEA,eAAsB,uBACpB,QAC2B;AAC3B,QAAM,UAAU,OAAO,SAAS,WAAW;AAC3C,MAAI,YAAY,QAAW;AACzB,UAAM,IAAI,MAAM,mCAAmC;AAAA,EACrD;AAEA,QAAM,UAAU,MAAM,mCAAmC,MAAM;AAC/D,QAAM,WAAW,MAAM,QAAQ,OAAO;AAItC,MAAI,SAAS,WAAW,KAAK;AAC3B,UAAM,IAAI;AAAA,MACR,0BAA0B,OAAO,KAAK;AAAA,MACtC;AAAA,QACE,OAAO,OAAO;AAAA,QACd,WAAW,mBAAmB,MAAM,cAAc,QAAQ,CAAC;AAAA,MAC7D;AAAA,IACF;AAAA,EACF;AAEA,MAAI,CAAC,SAAS,IAAI;AAChB,UAAM,IAAI;AAAA,MACR,qCAAqC,SAAS,MAAM,IAAI,SAAS,UAAU;AAAA,IAC7E;AAAA,EACF;AAEA,QAAM,OAAgB,MAAM,SAAS,KAAK;AAG1C,MAAI,qBAAqB,IAAI,GAAG;AAC9B,UAAM,IAAI;AAAA,MACR,0BAA0B,OAAO,KAAK;AAAA,MACtC,EAAE,OAAO,OAAO,OAAO,WAAW,mBAAmB,IAAI,EAAE;AAAA,IAC7D;AAAA,EACF;AAEA,SAAO,uBAAuB,MAAM,IAAI;AAC1C;","names":[]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/storage/index.ts"],"sourcesContent":["/**\n * Storage API for Vana SDK\n *\n * Provides unified interface for different storage providers\n * to upload, download, and manage user data files.\n *\n * ## Storage Provider Decision Tree\n *\n * Choose your storage provider based on your needs:\n *\n * **Default backend hosted by ODL?**\n * - ✅ Use `VanaStorage` - storage.vana.org with Web3Signed auth.\n *\n * **Need full CRUD operations and metadata?**\n * - ✅ Use `PinataStorage` - Managed IPFS with listing, deletion, and rich metadata\n *\n * **Want to use your own IPFS infrastructure?**\n * - ✅ Use `IpfsStorage.forInfura()` - Connect to Infura IPFS service\n * - ✅ Use `IpfsStorage.forLocalNode()` - Connect to local IPFS node\n * - ✅ Use `new IpfsStorage()` - Connect to any IPFS-compatible service\n *\n * **Want flexible callback-based storage?**\n * - ✅ Use `CallbackStorage` - Implement storage via custom callbacks (HTTP, WebSocket, etc.)\n *\n * **Need Google Drive integration?**\n * - ✅ Use `GoogleDriveStorage` - Direct Google Drive API with folder management\n *\n * @example\n * ```typescript\n * // Managed IPFS with full features\n * const pinata = new PinataStorage({ jwt: \"your-jwt\" });\n *\n * // Standard IPFS with Infura\n * const ipfs = IpfsStorage.forInfura({ projectId: \"...\", projectSecret: \"...\" });\n *\n * // Callback-based storage (flexible)\n * const storage = new CallbackStorage({\n * async upload(blob, filename) {\n * // Your custom upload logic\n * const response = await fetch('/api/upload', { method: 'POST', body: blob });\n * const data = await response.json();\n * return { url: data.url, size: blob.size, contentType: blob.type };\n * },\n * async download(identifier) {\n * // Your custom download logic\n * const response = await fetch(`/api/download/${identifier}`);\n * return response.blob();\n * }\n * });\n * ```\n */\n\n// Re-export storage types from types module to avoid circular dependencies\nexport type {\n StorageProvider,\n StorageUploadResult,\n StorageFile,\n StorageListOptions,\n StorageProviderConfig,\n} from \"../types/storage\";\n\nexport { StorageError } from \"../types/storage\";\n\n// Export default Vana storage factory\nexport { createVanaStorageProvider } from \"./default\";\nexport type { VanaStorageProviderOptions } from \"./default\";\n\n// Export storage providers\nexport { R2Storage } from \"./providers/r2\";\nexport type { R2Config } from \"./providers/r2\";\nexport { VanaStorage } from \"./providers/vana-storage\";\nexport type {\n VanaStorageConfig,\n VanaStorageSigner,\n} from \"./providers/vana-storage\";\nexport type { ProtocolNetwork } from \"../protocol/networks\";\nexport { GoogleDriveStorage } from \"./providers/google-drive\";\nexport { DropboxStorage } from \"./providers/dropbox\";\nexport { IpfsStorage } from \"./providers/ipfs\";\nexport { PinataStorage } from \"./providers/pinata\";\nexport { CallbackStorage } from \"./providers/callback-storage\";\n\n// Export storage manager\nexport { StorageManager } from \"./manager\";\n\n// Export storage callback types\nexport type {\n StorageCallbacks,\n StorageDownloadOptions,\n StorageListResult,\n} from \"../types/config\";\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA6DA,qBAA6B;AAG7B,qBAA0C;AAI1C,gBAA0B;AAE1B,0BAA4B;
|
|
1
|
+
{"version":3,"sources":["../../src/storage/index.ts"],"sourcesContent":["/**\n * Storage API for Vana SDK\n *\n * Provides unified interface for different storage providers\n * to upload, download, and manage user data files.\n *\n * ## Storage Provider Decision Tree\n *\n * Choose your storage provider based on your needs:\n *\n * **Default backend hosted by ODL?**\n * - ✅ Use `VanaStorage` - storage.vana.org with Web3Signed auth.\n *\n * **Need full CRUD operations and metadata?**\n * - ✅ Use `PinataStorage` - Managed IPFS with listing, deletion, and rich metadata\n *\n * **Want to use your own IPFS infrastructure?**\n * - ✅ Use `IpfsStorage.forInfura()` - Connect to Infura IPFS service\n * - ✅ Use `IpfsStorage.forLocalNode()` - Connect to local IPFS node\n * - ✅ Use `new IpfsStorage()` - Connect to any IPFS-compatible service\n *\n * **Want flexible callback-based storage?**\n * - ✅ Use `CallbackStorage` - Implement storage via custom callbacks (HTTP, WebSocket, etc.)\n *\n * **Need Google Drive integration?**\n * - ✅ Use `GoogleDriveStorage` - Direct Google Drive API with folder management\n *\n * @example\n * ```typescript\n * // Managed IPFS with full features\n * const pinata = new PinataStorage({ jwt: \"your-jwt\" });\n *\n * // Standard IPFS with Infura\n * const ipfs = IpfsStorage.forInfura({ projectId: \"...\", projectSecret: \"...\" });\n *\n * // Callback-based storage (flexible)\n * const storage = new CallbackStorage({\n * async upload(blob, filename) {\n * // Your custom upload logic\n * const response = await fetch('/api/upload', { method: 'POST', body: blob });\n * const data = await response.json();\n * return { url: data.url, size: blob.size, contentType: blob.type };\n * },\n * async download(identifier) {\n * // Your custom download logic\n * const response = await fetch(`/api/download/${identifier}`);\n * return response.blob();\n * }\n * });\n * ```\n */\n\n// Re-export storage types from types module to avoid circular dependencies\nexport type {\n StorageProvider,\n StorageUploadResult,\n StorageFile,\n StorageListOptions,\n StorageProviderConfig,\n} from \"../types/storage\";\n\nexport { StorageError } from \"../types/storage\";\n\n// Export default Vana storage factory\nexport { createVanaStorageProvider } from \"./default\";\nexport type { VanaStorageProviderOptions } from \"./default\";\n\n// Export storage providers\nexport { R2Storage } from \"./providers/r2\";\nexport type { R2Config } from \"./providers/r2\";\nexport { VanaStorage } from \"./providers/vana-storage\";\nexport type {\n VanaStorageConfig,\n VanaStorageSigner,\n VanaStorageScopeDeleteResult,\n} from \"./providers/vana-storage\";\nexport type { ProtocolNetwork } from \"../protocol/networks\";\nexport { GoogleDriveStorage } from \"./providers/google-drive\";\nexport { DropboxStorage } from \"./providers/dropbox\";\nexport { IpfsStorage } from \"./providers/ipfs\";\nexport { PinataStorage } from \"./providers/pinata\";\nexport { CallbackStorage } from \"./providers/callback-storage\";\n\n// Export storage manager\nexport { StorageManager } from \"./manager\";\n\n// Export storage callback types\nexport type {\n StorageCallbacks,\n StorageDownloadOptions,\n StorageListResult,\n} from \"../types/config\";\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA6DA,qBAA6B;AAG7B,qBAA0C;AAI1C,gBAA0B;AAE1B,0BAA4B;AAO5B,0BAAmC;AACnC,qBAA+B;AAC/B,kBAA4B;AAC5B,oBAA8B;AAC9B,8BAAgC;AAGhC,qBAA+B;","names":[]}
|
package/dist/storage/index.d.ts
CHANGED
|
@@ -56,7 +56,7 @@ export type { VanaStorageProviderOptions } from "./default.js";
|
|
|
56
56
|
export { R2Storage } from "./providers/r2.js";
|
|
57
57
|
export type { R2Config } from "./providers/r2.js";
|
|
58
58
|
export { VanaStorage } from "./providers/vana-storage.js";
|
|
59
|
-
export type { VanaStorageConfig, VanaStorageSigner, } from "./providers/vana-storage.js";
|
|
59
|
+
export type { VanaStorageConfig, VanaStorageSigner, VanaStorageScopeDeleteResult, } from "./providers/vana-storage.js";
|
|
60
60
|
export type { ProtocolNetwork } from "../protocol/networks.js";
|
|
61
61
|
export { GoogleDriveStorage } from "./providers/google-drive.js";
|
|
62
62
|
export { DropboxStorage } from "./providers/dropbox.js";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/storage/index.ts"],"sourcesContent":["/**\n * Storage API for Vana SDK\n *\n * Provides unified interface for different storage providers\n * to upload, download, and manage user data files.\n *\n * ## Storage Provider Decision Tree\n *\n * Choose your storage provider based on your needs:\n *\n * **Default backend hosted by ODL?**\n * - ✅ Use `VanaStorage` - storage.vana.org with Web3Signed auth.\n *\n * **Need full CRUD operations and metadata?**\n * - ✅ Use `PinataStorage` - Managed IPFS with listing, deletion, and rich metadata\n *\n * **Want to use your own IPFS infrastructure?**\n * - ✅ Use `IpfsStorage.forInfura()` - Connect to Infura IPFS service\n * - ✅ Use `IpfsStorage.forLocalNode()` - Connect to local IPFS node\n * - ✅ Use `new IpfsStorage()` - Connect to any IPFS-compatible service\n *\n * **Want flexible callback-based storage?**\n * - ✅ Use `CallbackStorage` - Implement storage via custom callbacks (HTTP, WebSocket, etc.)\n *\n * **Need Google Drive integration?**\n * - ✅ Use `GoogleDriveStorage` - Direct Google Drive API with folder management\n *\n * @example\n * ```typescript\n * // Managed IPFS with full features\n * const pinata = new PinataStorage({ jwt: \"your-jwt\" });\n *\n * // Standard IPFS with Infura\n * const ipfs = IpfsStorage.forInfura({ projectId: \"...\", projectSecret: \"...\" });\n *\n * // Callback-based storage (flexible)\n * const storage = new CallbackStorage({\n * async upload(blob, filename) {\n * // Your custom upload logic\n * const response = await fetch('/api/upload', { method: 'POST', body: blob });\n * const data = await response.json();\n * return { url: data.url, size: blob.size, contentType: blob.type };\n * },\n * async download(identifier) {\n * // Your custom download logic\n * const response = await fetch(`/api/download/${identifier}`);\n * return response.blob();\n * }\n * });\n * ```\n */\n\n// Re-export storage types from types module to avoid circular dependencies\nexport type {\n StorageProvider,\n StorageUploadResult,\n StorageFile,\n StorageListOptions,\n StorageProviderConfig,\n} from \"../types/storage\";\n\nexport { StorageError } from \"../types/storage\";\n\n// Export default Vana storage factory\nexport { createVanaStorageProvider } from \"./default\";\nexport type { VanaStorageProviderOptions } from \"./default\";\n\n// Export storage providers\nexport { R2Storage } from \"./providers/r2\";\nexport type { R2Config } from \"./providers/r2\";\nexport { VanaStorage } from \"./providers/vana-storage\";\nexport type {\n VanaStorageConfig,\n VanaStorageSigner,\n} from \"./providers/vana-storage\";\nexport type { ProtocolNetwork } from \"../protocol/networks\";\nexport { GoogleDriveStorage } from \"./providers/google-drive\";\nexport { DropboxStorage } from \"./providers/dropbox\";\nexport { IpfsStorage } from \"./providers/ipfs\";\nexport { PinataStorage } from \"./providers/pinata\";\nexport { CallbackStorage } from \"./providers/callback-storage\";\n\n// Export storage manager\nexport { StorageManager } from \"./manager\";\n\n// Export storage callback types\nexport type {\n StorageCallbacks,\n StorageDownloadOptions,\n StorageListResult,\n} from \"../types/config\";\n"],"mappings":"AA6DA,SAAS,oBAAoB;AAG7B,SAAS,iCAAiC;AAI1C,SAAS,iBAAiB;AAE1B,SAAS,mBAAmB;
|
|
1
|
+
{"version":3,"sources":["../../src/storage/index.ts"],"sourcesContent":["/**\n * Storage API for Vana SDK\n *\n * Provides unified interface for different storage providers\n * to upload, download, and manage user data files.\n *\n * ## Storage Provider Decision Tree\n *\n * Choose your storage provider based on your needs:\n *\n * **Default backend hosted by ODL?**\n * - ✅ Use `VanaStorage` - storage.vana.org with Web3Signed auth.\n *\n * **Need full CRUD operations and metadata?**\n * - ✅ Use `PinataStorage` - Managed IPFS with listing, deletion, and rich metadata\n *\n * **Want to use your own IPFS infrastructure?**\n * - ✅ Use `IpfsStorage.forInfura()` - Connect to Infura IPFS service\n * - ✅ Use `IpfsStorage.forLocalNode()` - Connect to local IPFS node\n * - ✅ Use `new IpfsStorage()` - Connect to any IPFS-compatible service\n *\n * **Want flexible callback-based storage?**\n * - ✅ Use `CallbackStorage` - Implement storage via custom callbacks (HTTP, WebSocket, etc.)\n *\n * **Need Google Drive integration?**\n * - ✅ Use `GoogleDriveStorage` - Direct Google Drive API with folder management\n *\n * @example\n * ```typescript\n * // Managed IPFS with full features\n * const pinata = new PinataStorage({ jwt: \"your-jwt\" });\n *\n * // Standard IPFS with Infura\n * const ipfs = IpfsStorage.forInfura({ projectId: \"...\", projectSecret: \"...\" });\n *\n * // Callback-based storage (flexible)\n * const storage = new CallbackStorage({\n * async upload(blob, filename) {\n * // Your custom upload logic\n * const response = await fetch('/api/upload', { method: 'POST', body: blob });\n * const data = await response.json();\n * return { url: data.url, size: blob.size, contentType: blob.type };\n * },\n * async download(identifier) {\n * // Your custom download logic\n * const response = await fetch(`/api/download/${identifier}`);\n * return response.blob();\n * }\n * });\n * ```\n */\n\n// Re-export storage types from types module to avoid circular dependencies\nexport type {\n StorageProvider,\n StorageUploadResult,\n StorageFile,\n StorageListOptions,\n StorageProviderConfig,\n} from \"../types/storage\";\n\nexport { StorageError } from \"../types/storage\";\n\n// Export default Vana storage factory\nexport { createVanaStorageProvider } from \"./default\";\nexport type { VanaStorageProviderOptions } from \"./default\";\n\n// Export storage providers\nexport { R2Storage } from \"./providers/r2\";\nexport type { R2Config } from \"./providers/r2\";\nexport { VanaStorage } from \"./providers/vana-storage\";\nexport type {\n VanaStorageConfig,\n VanaStorageSigner,\n VanaStorageScopeDeleteResult,\n} from \"./providers/vana-storage\";\nexport type { ProtocolNetwork } from \"../protocol/networks\";\nexport { GoogleDriveStorage } from \"./providers/google-drive\";\nexport { DropboxStorage } from \"./providers/dropbox\";\nexport { IpfsStorage } from \"./providers/ipfs\";\nexport { PinataStorage } from \"./providers/pinata\";\nexport { CallbackStorage } from \"./providers/callback-storage\";\n\n// Export storage manager\nexport { StorageManager } from \"./manager\";\n\n// Export storage callback types\nexport type {\n StorageCallbacks,\n StorageDownloadOptions,\n StorageListResult,\n} from \"../types/config\";\n"],"mappings":"AA6DA,SAAS,oBAAoB;AAG7B,SAAS,iCAAiC;AAI1C,SAAS,iBAAiB;AAE1B,SAAS,mBAAmB;AAO5B,SAAS,0BAA0B;AACnC,SAAS,sBAAsB;AAC/B,SAAS,mBAAmB;AAC5B,SAAS,qBAAqB;AAC9B,SAAS,uBAAuB;AAGhC,SAAS,sBAAsB;","names":[]}
|
|
@@ -24,6 +24,7 @@ module.exports = __toCommonJS(vana_storage_exports);
|
|
|
24
24
|
var import__ = require("../index");
|
|
25
25
|
var import_web3_signed_builder = require("../../auth/web3-signed-builder");
|
|
26
26
|
var import_networks = require("../../protocol/networks");
|
|
27
|
+
var import_response_body = require("../../utils/response-body");
|
|
27
28
|
const DEFAULT_ENDPOINT = "https://storage.vana.org";
|
|
28
29
|
const LEGACY_BLOB_PATH_PREFIX = "/v1/blobs";
|
|
29
30
|
const DEFAULT_TOKEN_TTL_SECONDS = 300;
|
|
@@ -194,6 +195,65 @@ class VanaStorage {
|
|
|
194
195
|
}
|
|
195
196
|
return true;
|
|
196
197
|
}
|
|
198
|
+
/**
|
|
199
|
+
* Delete every version's blob under `(owner, scope)` --
|
|
200
|
+
* `DELETE {prefix}/{owner}/{scope}` on vana-storage, signed with the same
|
|
201
|
+
* Web3Signed header as uploads (aud = endpoint origin, empty bodyHash).
|
|
202
|
+
* The worker accepts the owner's own signature or a personal server the
|
|
203
|
+
* owner registered with the gateway.
|
|
204
|
+
*
|
|
205
|
+
* @param ownerAddress - Must equal the provider's configured owner; a
|
|
206
|
+
* mismatch throws before anything is signed so this wallet can never be
|
|
207
|
+
* induced to sign a delete for another namespace.
|
|
208
|
+
* @param scope - The scope segment, e.g. `"instagram.profile"`.
|
|
209
|
+
*/
|
|
210
|
+
async deleteScope(ownerAddress, scope) {
|
|
211
|
+
if (ownerAddress.toLowerCase() !== this.ownerAddress) {
|
|
212
|
+
throw new import__.StorageError(
|
|
213
|
+
`deleteScope owner '${ownerAddress}' does not match the configured owner '${this.ownerAddress}'`,
|
|
214
|
+
"INVALID_OWNER",
|
|
215
|
+
"vana-storage"
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
if (scope.length === 0 || scope.includes("/") || isTraversalSegment(scope)) {
|
|
219
|
+
throw new import__.StorageError(
|
|
220
|
+
`scope must be a single non-empty path segment, got '${scope}'`,
|
|
221
|
+
"INVALID_SCOPE",
|
|
222
|
+
"vana-storage"
|
|
223
|
+
);
|
|
224
|
+
}
|
|
225
|
+
const path = `${this.blobPathPrefix}/${this.ownerAddress}/${encodeURIComponent(scope)}`;
|
|
226
|
+
const header = await this.signRequest("DELETE", path);
|
|
227
|
+
let response;
|
|
228
|
+
try {
|
|
229
|
+
response = await this.fetchImpl(`${this.endpoint}${path}`, {
|
|
230
|
+
method: "DELETE",
|
|
231
|
+
headers: { authorization: header }
|
|
232
|
+
});
|
|
233
|
+
} catch (cause) {
|
|
234
|
+
throw new import__.StorageError(
|
|
235
|
+
`vana-storage scope delete network error: ${describe(cause)}`,
|
|
236
|
+
"DELETE_ERROR",
|
|
237
|
+
"vana-storage",
|
|
238
|
+
{ cause: cause instanceof Error ? cause : void 0 }
|
|
239
|
+
);
|
|
240
|
+
}
|
|
241
|
+
if (!response.ok) {
|
|
242
|
+
const responseText = await safeText(response);
|
|
243
|
+
throw new import__.StorageError(
|
|
244
|
+
`vana-storage scope delete failed: ${response.status} ${response.statusText} - ${responseText}`,
|
|
245
|
+
"DELETE_FAILED",
|
|
246
|
+
"vana-storage"
|
|
247
|
+
);
|
|
248
|
+
}
|
|
249
|
+
const body = await (0, import_response_body.readJsonObject)(response);
|
|
250
|
+
return {
|
|
251
|
+
deleted: typeof body["deleted"] === "boolean" ? body["deleted"] : true,
|
|
252
|
+
scope: typeof body["scope"] === "string" ? body["scope"] : scope,
|
|
253
|
+
count: nonNegativeInteger(body["count"]) ?? 0,
|
|
254
|
+
totalBytes: nonNegativeInteger(body["totalBytes"]) ?? 0
|
|
255
|
+
};
|
|
256
|
+
}
|
|
197
257
|
getConfig() {
|
|
198
258
|
return {
|
|
199
259
|
name: "vana-storage",
|
|
@@ -322,6 +382,12 @@ function encodeRelativePath(filename) {
|
|
|
322
382
|
}
|
|
323
383
|
return parts.map((p) => encodeURIComponent(p)).join("/");
|
|
324
384
|
}
|
|
385
|
+
function isTraversalSegment(segment) {
|
|
386
|
+
return segment === "." || segment === "..";
|
|
387
|
+
}
|
|
388
|
+
function nonNegativeInteger(value) {
|
|
389
|
+
return typeof value === "number" && Number.isInteger(value) && value >= 0 ? value : void 0;
|
|
390
|
+
}
|
|
325
391
|
function describe(value) {
|
|
326
392
|
if (value instanceof Error) return value.message;
|
|
327
393
|
return String(value);
|