@opendatalabs/vana-sdk 3.20.1 → 3.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/README.md +64 -0
  2. package/dist/direct/access-request-client.cjs +100 -2
  3. package/dist/direct/access-request-client.cjs.map +1 -1
  4. package/dist/direct/access-request-client.d.ts +23 -1
  5. package/dist/direct/access-request-client.js +98 -1
  6. package/dist/direct/access-request-client.js.map +1 -1
  7. package/dist/direct/controller.cjs +4 -0
  8. package/dist/direct/controller.cjs.map +1 -1
  9. package/dist/direct/controller.d.ts +10 -1
  10. package/dist/direct/controller.js +6 -1
  11. package/dist/direct/controller.js.map +1 -1
  12. package/dist/direct/types.cjs.map +1 -1
  13. package/dist/direct/types.d.ts +45 -0
  14. package/dist/direct/types.js.map +1 -1
  15. package/dist/index.browser.d.ts +2 -1
  16. package/dist/index.browser.js +186 -0
  17. package/dist/index.browser.js.map +4 -4
  18. package/dist/index.node.cjs +196 -0
  19. package/dist/index.node.cjs.map +4 -4
  20. package/dist/index.node.d.ts +2 -1
  21. package/dist/index.node.js +186 -0
  22. package/dist/index.node.js.map +4 -4
  23. package/dist/protocol/derivative-questions.cjs +22 -0
  24. package/dist/protocol/derivative-questions.cjs.map +1 -1
  25. package/dist/protocol/derivative-questions.d.ts +53 -0
  26. package/dist/protocol/derivative-questions.js +19 -0
  27. package/dist/protocol/derivative-questions.js.map +1 -1
  28. package/dist/protocol/derivative-status.cjs +209 -0
  29. package/dist/protocol/derivative-status.cjs.map +1 -0
  30. package/dist/protocol/derivative-status.d.ts +196 -0
  31. package/dist/protocol/derivative-status.js +190 -0
  32. package/dist/protocol/derivative-status.js.map +1 -0
  33. package/dist/protocol/derivative-status.test.d.ts +1 -0
  34. package/dist/server.cjs +4 -2
  35. package/dist/server.cjs.map +1 -1
  36. package/dist/server.d.ts +2 -2
  37. package/dist/server.js +4 -2
  38. package/dist/server.js.map +1 -1
  39. package/dist/tests/mock-personal-server.d.ts +9 -0
  40. package/package.json +1 -1
@@ -0,0 +1,196 @@
1
+ /**
2
+ * Derivative status: the lifecycle of the question behind a derived scope,
3
+ * as the party that READS the answer sees it.
4
+ *
5
+ * @remarks
6
+ * A builder registers a question with {@link registerQuestion} and follows it
7
+ * with {@link waitForQuestion}, both of which need a write session. The app
8
+ * that only consumes the answer holds no write entry at all — a consent flow
9
+ * grants it a bare read on the derived scope — so it cannot open one, and
10
+ * `GET /v1/data/<derivedScope>` answers 404 for all three of "computing right
11
+ * now", "failed but retrying" and "failed for good".
12
+ *
13
+ * `GET /v1/derivatives/status?derivedScope=<scope>` is that reader's view.
14
+ * Authorization is the data read's (a live grant covering the derived scope,
15
+ * or the owner), nothing is served and nothing is charged, and the view is
16
+ * deliberately narrow: lifecycle, a coarse {@link DerivativeErrorCode} and
17
+ * the next retry. The question text, the source scopes, the question id, the
18
+ * registrar and the server's raw error string stay owner-only.
19
+ *
20
+ * Requires `personal-server-ts` with the status route; an older Personal
21
+ * Server answers 404 for the route itself.
22
+ *
23
+ * @category Protocol
24
+ */
25
+ import { z } from "zod";
26
+ import { type QuestionStatus } from "./derivative-questions.js";
27
+ import { type ResolveWriteSignerOptions, type WriteSignerSource } from "./write-signer.js";
28
+ export { DERIVATIVE_ERROR_CODES, DerivativeErrorCodeSchema, type DerivativeErrorCode, } from "./derivative-questions.js";
29
+ /** The reader-facing status route. */
30
+ export declare const DERIVATIVE_STATUS_PATH = "/v1/derivatives/status";
31
+ /** How long {@link waitForDerivativeStatus} polls before giving up. */
32
+ export declare const DEFAULT_DERIVATIVE_STATUS_TIMEOUT_MS = 120000;
33
+ /**
34
+ * How long {@link waitForDerivativeStatus} waits between polls when the
35
+ * server names no retry time of its own.
36
+ */
37
+ export declare const DEFAULT_DERIVATIVE_STATUS_POLL_INTERVAL_MS = 2000;
38
+ /**
39
+ * The status of the derived scope, not of one registration: when several
40
+ * questions write the same scope, the server reports the most optimistic
41
+ * true state, because serving data is registration-agnostic.
42
+ */
43
+ export declare const DerivativeStatusSchema: z.ZodObject<{
44
+ derivedScope: z.ZodString;
45
+ status: z.ZodEnum<{
46
+ pending: "pending";
47
+ ready: "ready";
48
+ failed: "failed";
49
+ stale: "stale";
50
+ }>;
51
+ lastComputedAt: z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodString>>, z.ZodTransform<string | null, string | null | undefined>>;
52
+ derivedVersion: z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodNumber>>, z.ZodTransform<number | null, number | null | undefined>>;
53
+ derivedCollectedAt: z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodString>>, z.ZodTransform<string | null, string | null | undefined>>;
54
+ errorCode: z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodEnum<{
55
+ internal: "internal";
56
+ inference_unavailable: "inference_unavailable";
57
+ source_missing: "source_missing";
58
+ grant_invalid: "grant_invalid";
59
+ }>>>, z.ZodTransform<NonNullable<"internal" | "inference_unavailable" | "source_missing" | "grant_invalid"> | null, "internal" | "inference_unavailable" | "source_missing" | "grant_invalid" | null | undefined>>;
60
+ retryAfterSeconds: z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodNumber>>, z.ZodTransform<number | null, number | null | undefined>>;
61
+ }, z.core.$strip>;
62
+ /** @see {@link DerivativeStatusSchema} */
63
+ export type DerivativeStatus = z.infer<typeof DerivativeStatusSchema>;
64
+ /** What {@link getDerivativeStatus} needs to sign and send one read. */
65
+ export interface GetDerivativeStatusParams extends ResolveWriteSignerOptions {
66
+ /** Personal Server origin, e.g. `https://ps.example.com`. */
67
+ personalServerUrl: string;
68
+ /** The derived scope whose question to observe. */
69
+ derivedScope: string;
70
+ /**
71
+ * A grant covering the derived scope, sent as the signed `grantId` claim.
72
+ * Omit only when the signer is the Personal Server's owner, who is
73
+ * authorized without one.
74
+ */
75
+ grantId?: string;
76
+ /** Reader key: a viem `LocalAccount`, `WalletClient`, or `{ signMessage }`. */
77
+ signer: WriteSignerSource;
78
+ /** Web3Signed audience; defaults to `personalServerUrl`. */
79
+ audience?: string;
80
+ /** `fetch` to use; defaults to `globalThis.fetch`. */
81
+ fetch?: typeof fetch;
82
+ /** Extra request headers. */
83
+ headers?: HeadersInit;
84
+ /** Aborts the request in flight. */
85
+ signal?: AbortSignal;
86
+ }
87
+ /** What {@link waitForDerivativeStatus} polls with. */
88
+ export interface WaitForDerivativeStatusParams extends GetDerivativeStatusParams {
89
+ /** Give up after this long (default 120s). */
90
+ timeoutMs?: number;
91
+ /**
92
+ * Wait between polls when the server names no retry time (default 2s).
93
+ * A `retryAfterSeconds` from the server replaces this outright, longer or
94
+ * shorter: it is when the next compute actually happens.
95
+ */
96
+ pollIntervalMs?: number;
97
+ /**
98
+ * Aborts the wait, and the request in flight with it: the signal is passed
99
+ * to every poll, so an abort during a stalled request does not sit until
100
+ * the transport gives up.
101
+ */
102
+ signal?: AbortSignal;
103
+ }
104
+ /**
105
+ * The request target for a status read: the query carries the derived scope,
106
+ * the signed `uri` does not.
107
+ *
108
+ * @remarks
109
+ * Like every Web3Signed read (data, lineage), the Personal Server verifies
110
+ * the signature over the PATH; per-scope authorization is enforced live
111
+ * against the caller's grant on each request, so the query needs no
112
+ * signature to be safe. Only the write path signs path AND query, where a
113
+ * parameter decides what is written.
114
+ */
115
+ export declare function derivativeStatusTarget(derivedScope: string): string;
116
+ /**
117
+ * Is this a state the reader can act on?
118
+ *
119
+ * @remarks
120
+ * `ready` means the derived scope has an answer to read. A `failed` status
121
+ * is settled only when no retry is pending: with `retryAfterSeconds` set the
122
+ * Personal Server will compute again on its own, so the answer may still
123
+ * arrive. `pending` and `stale` are always in flight.
124
+ */
125
+ export declare function isDerivativeStatusSettled(status: DerivativeStatus): boolean;
126
+ /**
127
+ * Read the lifecycle of the question behind a derived scope.
128
+ *
129
+ * @remarks
130
+ * Sends `GET /v1/derivatives/status?derivedScope=<scope>` with a Web3Signed
131
+ * `Authorization` header carrying `grantId`, the same authentication a data
132
+ * read uses. Nothing is charged: the route authorizes, it does not serve
133
+ * data, so a priced grant raises no 402 here.
134
+ *
135
+ * @returns The status of the derived scope. When several registrations write
136
+ * it, the most optimistic true state answers — `ready`, then `stale`, then
137
+ * `pending`, then `failed` — because a duplicate that never wrote anything
138
+ * must not report away an answer the scope has.
139
+ * @throws {DerivativeQuestionNotFoundError} 404: the caller may read the
140
+ * scope but no question stands behind it (and, on an older Personal
141
+ * Server, the route itself is unknown).
142
+ * @throws {WriteForbiddenError} 403: the grant does not cover the derived
143
+ * scope. The check runs before any store lookup, so a caller cannot probe
144
+ * which scopes have questions.
145
+ * @throws {DerivativeQuestionRejectedError} On any other non-2xx answer or
146
+ * an unparseable body.
147
+ * @throws {WriteTransportError} When `fetch` itself failed.
148
+ * @throws {WriteRequestError} On a missing `derivedScope` or no `fetch`.
149
+ *
150
+ * @example
151
+ * ```typescript
152
+ * const status = await getDerivativeStatus({
153
+ * personalServerUrl: "https://ps.example.com",
154
+ * derivedScope: "coach.weekly",
155
+ * grantId,
156
+ * signer,
157
+ * });
158
+ * if (status.status === "ready") {
159
+ * const record = await readPersonalServerData({ ... });
160
+ * } else if (status.retryAfterSeconds !== null) {
161
+ * // Computing or retrying: come back then.
162
+ * }
163
+ * ```
164
+ */
165
+ export declare function getDerivativeStatus(params: GetDerivativeStatusParams): Promise<DerivativeStatus>;
166
+ /**
167
+ * Poll {@link getDerivativeStatus} until the derived scope has an answer or
168
+ * has stopped trying to get one.
169
+ *
170
+ * @remarks
171
+ * Returns as soon as {@link isDerivativeStatusSettled} holds: `ready`, or
172
+ * `failed` with no retry pending. A failure the server will retry is not a
173
+ * result, so the wait continues through it — on the server's own cadence,
174
+ * because `retryAfterSeconds` is when the next compute actually happens and
175
+ * polling faster only spends requests. A failed status is returned, not
176
+ * thrown: the reader branches on `errorCode`.
177
+ *
178
+ * @returns The settled status.
179
+ * @throws {DerivativeQuestionTimeoutError} When the budget ran out first.
180
+ * The question keeps computing on the server; call again.
181
+ *
182
+ * @example
183
+ * ```typescript
184
+ * const status = await waitForDerivativeStatus({
185
+ * personalServerUrl,
186
+ * derivedScope: "coach.weekly",
187
+ * grantId,
188
+ * signer,
189
+ * timeoutMs: 60_000,
190
+ * });
191
+ * if (status.status !== "ready") console.log(status.errorCode);
192
+ * ```
193
+ */
194
+ export declare function waitForDerivativeStatus(params: WaitForDerivativeStatusParams): Promise<DerivativeStatus>;
195
+ /** Statuses that mean a compute is in flight. @see {@link QuestionStatus} */
196
+ export type PendingDerivativeStatus = Extract<QuestionStatus, "pending" | "stale">;
@@ -0,0 +1,190 @@
1
+ import { z } from "zod";
2
+ import { buildWeb3SignedHeader } from "../auth/web3-signed-builder.js";
3
+ import {
4
+ DerivativeQuestionRejectedError,
5
+ DerivativeQuestionTimeoutError,
6
+ WriteRequestError,
7
+ WriteTransportError
8
+ } from "../errors.js";
9
+ import {
10
+ DerivativeErrorCodeSchema,
11
+ personalServerErrorFromQuestionResponse,
12
+ QuestionStatusSchema
13
+ } from "./derivative-questions.js";
14
+ import {
15
+ resolveWriteSigner
16
+ } from "./write-signer.js";
17
+ import {
18
+ DERIVATIVE_ERROR_CODES,
19
+ DerivativeErrorCodeSchema as DerivativeErrorCodeSchema2
20
+ } from "./derivative-questions.js";
21
+ const DERIVATIVE_STATUS_PATH = "/v1/derivatives/status";
22
+ const DEFAULT_DERIVATIVE_STATUS_TIMEOUT_MS = 12e4;
23
+ const DEFAULT_DERIVATIVE_STATUS_POLL_INTERVAL_MS = 2e3;
24
+ const nullable = (schema) => schema.nullish().transform((value) => value ?? null);
25
+ const DerivativeStatusSchema = z.object({
26
+ derivedScope: z.string().min(1),
27
+ status: QuestionStatusSchema,
28
+ /** When the last compute finished, or `null` if none ever has. */
29
+ lastComputedAt: nullable(z.string()),
30
+ /** Local version of the derived record the last compute wrote. */
31
+ derivedVersion: nullable(z.number()),
32
+ derivedCollectedAt: nullable(z.string()),
33
+ /** The failure class; `null` unless `status` is `failed`. */
34
+ errorCode: nullable(DerivativeErrorCodeSchema),
35
+ /**
36
+ * Seconds until the Personal Server's next automatic retry, or `null` when
37
+ * none is pending or running — the terminal signature. Poll on this cadence
38
+ * rather than guessing one.
39
+ */
40
+ retryAfterSeconds: nullable(z.number())
41
+ });
42
+ function derivativeStatusTarget(derivedScope) {
43
+ return `${DERIVATIVE_STATUS_PATH}?derivedScope=${encodeURIComponent(derivedScope)}`;
44
+ }
45
+ function normalizeBaseUrl(url) {
46
+ return url.replace(/\/+$/, "");
47
+ }
48
+ function resolveFetch(fetchFn) {
49
+ const resolved = fetchFn ?? globalThis.fetch;
50
+ if (resolved === void 0) {
51
+ throw new WriteRequestError("No fetch implementation available");
52
+ }
53
+ return resolved;
54
+ }
55
+ function requireDerivedScope(derivedScope) {
56
+ if (typeof derivedScope !== "string" || derivedScope.length === 0) {
57
+ throw new WriteRequestError("derivedScope is required");
58
+ }
59
+ return derivedScope;
60
+ }
61
+ function sleep(ms, signal) {
62
+ if (ms <= 0) return Promise.resolve();
63
+ return new Promise((resolve, reject) => {
64
+ const timer = setTimeout(() => {
65
+ signal?.removeEventListener("abort", onAbort);
66
+ resolve();
67
+ }, ms);
68
+ const onAbort = () => {
69
+ clearTimeout(timer);
70
+ reject(abortError(signal));
71
+ };
72
+ signal?.addEventListener("abort", onAbort, { once: true });
73
+ });
74
+ }
75
+ function timeoutError(latest, timeoutMs) {
76
+ return new DerivativeQuestionTimeoutError(
77
+ `Derived scope ${latest.derivedScope} was still ${latest.status} after ${timeoutMs}ms`,
78
+ {
79
+ derivedScope: latest.derivedScope,
80
+ status: latest.status,
81
+ errorCode: latest.errorCode,
82
+ retryAfterSeconds: latest.retryAfterSeconds,
83
+ timeoutMs
84
+ }
85
+ );
86
+ }
87
+ function abortError(signal) {
88
+ const reason = signal?.reason;
89
+ return reason instanceof Error ? reason : new WriteRequestError("Derivative status wait was aborted");
90
+ }
91
+ function isDerivativeStatusSettled(status) {
92
+ if (status.status === "ready") return true;
93
+ return status.status === "failed" && status.retryAfterSeconds === null;
94
+ }
95
+ async function getDerivativeStatus(params) {
96
+ const derivedScope = requireDerivedScope(params.derivedScope);
97
+ const fetchFn = resolveFetch(params.fetch);
98
+ const baseUrl = normalizeBaseUrl(params.personalServerUrl);
99
+ const signer = resolveWriteSigner(params.signer, { account: params.account });
100
+ const headers = new Headers(params.headers);
101
+ headers.set(
102
+ "Authorization",
103
+ await buildWeb3SignedHeader({
104
+ signMessage: signer.signMessage,
105
+ aud: params.audience ?? baseUrl,
106
+ method: "GET",
107
+ uri: DERIVATIVE_STATUS_PATH,
108
+ grantId: params.grantId
109
+ })
110
+ );
111
+ let response;
112
+ try {
113
+ response = await fetchFn(
114
+ `${baseUrl}${derivativeStatusTarget(derivedScope)}`,
115
+ {
116
+ method: "GET",
117
+ headers,
118
+ ...params.signal ? { signal: params.signal } : {}
119
+ }
120
+ );
121
+ } catch (err) {
122
+ throw new WriteTransportError(
123
+ `Derivative status read failed: ${err instanceof Error ? err.message : String(err)}`,
124
+ 1,
125
+ err
126
+ );
127
+ }
128
+ if (!response.ok) {
129
+ throw await personalServerErrorFromQuestionResponse(
130
+ response
131
+ );
132
+ }
133
+ let body;
134
+ try {
135
+ body = await response.json();
136
+ } catch (err) {
137
+ throw new DerivativeQuestionRejectedError(
138
+ "Derivative status response is not JSON",
139
+ response.status,
140
+ null,
141
+ { cause: err instanceof Error ? err.message : String(err) }
142
+ );
143
+ }
144
+ const parsed = DerivativeStatusSchema.safeParse(body);
145
+ if (!parsed.success) {
146
+ throw new DerivativeQuestionRejectedError(
147
+ "Derivative status response is not a status view",
148
+ response.status,
149
+ null,
150
+ { issues: parsed.error.issues }
151
+ );
152
+ }
153
+ return parsed.data;
154
+ }
155
+ async function waitForDerivativeStatus(params) {
156
+ const timeoutMs = Math.max(
157
+ 0,
158
+ params.timeoutMs ?? DEFAULT_DERIVATIVE_STATUS_TIMEOUT_MS
159
+ );
160
+ const pollIntervalMs = Math.max(
161
+ 0,
162
+ params.pollIntervalMs ?? DEFAULT_DERIVATIVE_STATUS_POLL_INTERVAL_MS
163
+ );
164
+ const deadline = Date.now() + timeoutMs;
165
+ for (; ; ) {
166
+ if (params.signal?.aborted) throw abortError(params.signal);
167
+ const latest = await getDerivativeStatus(params);
168
+ if (isDerivativeStatusSettled(latest)) return latest;
169
+ const remaining = deadline - Date.now();
170
+ if (remaining <= 0) throw timeoutError(latest, timeoutMs);
171
+ const waitMs = latest.retryAfterSeconds === null ? pollIntervalMs : latest.retryAfterSeconds * 1e3;
172
+ if (waitMs > remaining) {
173
+ throw timeoutError(latest, timeoutMs);
174
+ }
175
+ await sleep(waitMs, params.signal);
176
+ }
177
+ }
178
+ export {
179
+ DEFAULT_DERIVATIVE_STATUS_POLL_INTERVAL_MS,
180
+ DEFAULT_DERIVATIVE_STATUS_TIMEOUT_MS,
181
+ DERIVATIVE_ERROR_CODES,
182
+ DERIVATIVE_STATUS_PATH,
183
+ DerivativeErrorCodeSchema2 as DerivativeErrorCodeSchema,
184
+ DerivativeStatusSchema,
185
+ derivativeStatusTarget,
186
+ getDerivativeStatus,
187
+ isDerivativeStatusSettled,
188
+ waitForDerivativeStatus
189
+ };
190
+ //# sourceMappingURL=derivative-status.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/protocol/derivative-status.ts"],"sourcesContent":["/**\n * Derivative status: the lifecycle of the question behind a derived scope,\n * as the party that READS the answer sees it.\n *\n * @remarks\n * A builder registers a question with {@link registerQuestion} and follows it\n * with {@link waitForQuestion}, both of which need a write session. The app\n * that only consumes the answer holds no write entry at all — a consent flow\n * grants it a bare read on the derived scope — so it cannot open one, and\n * `GET /v1/data/<derivedScope>` answers 404 for all three of \"computing right\n * now\", \"failed but retrying\" and \"failed for good\".\n *\n * `GET /v1/derivatives/status?derivedScope=<scope>` is that reader's view.\n * Authorization is the data read's (a live grant covering the derived scope,\n * or the owner), nothing is served and nothing is charged, and the view is\n * deliberately narrow: lifecycle, a coarse {@link DerivativeErrorCode} and\n * the next retry. The question text, the source scopes, the question id, the\n * registrar and the server's raw error string stay owner-only.\n *\n * Requires `personal-server-ts` with the status route; an older Personal\n * Server answers 404 for the route itself.\n *\n * @category Protocol\n */\n\nimport { z } from \"zod\";\nimport { buildWeb3SignedHeader } from \"../auth/web3-signed-builder\";\nimport {\n DerivativeQuestionRejectedError,\n DerivativeQuestionTimeoutError,\n WriteRequestError,\n WriteTransportError,\n type PersonalServerWriteError,\n} from \"../errors\";\nimport {\n DerivativeErrorCodeSchema,\n personalServerErrorFromQuestionResponse,\n QuestionStatusSchema,\n type QuestionStatus,\n} from \"./derivative-questions\";\nimport {\n resolveWriteSigner,\n type ResolveWriteSignerOptions,\n type WriteSignerSource,\n} from \"./write-signer\";\n\nexport {\n DERIVATIVE_ERROR_CODES,\n DerivativeErrorCodeSchema,\n type DerivativeErrorCode,\n} from \"./derivative-questions\";\n\n/** The reader-facing status route. */\nexport const DERIVATIVE_STATUS_PATH = \"/v1/derivatives/status\";\n\n/** How long {@link waitForDerivativeStatus} polls before giving up. */\nexport const DEFAULT_DERIVATIVE_STATUS_TIMEOUT_MS = 120_000;\n\n/**\n * How long {@link waitForDerivativeStatus} waits between polls when the\n * server names no retry time of its own.\n */\nexport const DEFAULT_DERIVATIVE_STATUS_POLL_INTERVAL_MS = 2_000;\n\nconst nullable = <T extends z.ZodTypeAny>(schema: T) =>\n schema\n .nullish()\n .transform((value: z.infer<T> | null | undefined) => value ?? null);\n\n/**\n * The status of the derived scope, not of one registration: when several\n * questions write the same scope, the server reports the most optimistic\n * true state, because serving data is registration-agnostic.\n */\nexport const DerivativeStatusSchema = z.object({\n derivedScope: z.string().min(1),\n status: QuestionStatusSchema,\n /** When the last compute finished, or `null` if none ever has. */\n lastComputedAt: nullable(z.string()),\n /** Local version of the derived record the last compute wrote. */\n derivedVersion: nullable(z.number()),\n derivedCollectedAt: nullable(z.string()),\n /** The failure class; `null` unless `status` is `failed`. */\n errorCode: nullable(DerivativeErrorCodeSchema),\n /**\n * Seconds until the Personal Server's next automatic retry, or `null` when\n * none is pending or running — the terminal signature. Poll on this cadence\n * rather than guessing one.\n */\n retryAfterSeconds: nullable(z.number()),\n});\n\n/** @see {@link DerivativeStatusSchema} */\nexport type DerivativeStatus = z.infer<typeof DerivativeStatusSchema>;\n\n/** What {@link getDerivativeStatus} needs to sign and send one read. */\nexport interface GetDerivativeStatusParams extends ResolveWriteSignerOptions {\n /** Personal Server origin, e.g. `https://ps.example.com`. */\n personalServerUrl: string;\n /** The derived scope whose question to observe. */\n derivedScope: string;\n /**\n * A grant covering the derived scope, sent as the signed `grantId` claim.\n * Omit only when the signer is the Personal Server's owner, who is\n * authorized without one.\n */\n grantId?: string;\n /** Reader key: a viem `LocalAccount`, `WalletClient`, or `{ signMessage }`. */\n signer: WriteSignerSource;\n /** Web3Signed audience; defaults to `personalServerUrl`. */\n audience?: string;\n /** `fetch` to use; defaults to `globalThis.fetch`. */\n fetch?: typeof fetch;\n /** Extra request headers. */\n headers?: HeadersInit;\n /** Aborts the request in flight. */\n signal?: AbortSignal;\n}\n\n/** What {@link waitForDerivativeStatus} polls with. */\nexport interface WaitForDerivativeStatusParams extends GetDerivativeStatusParams {\n /** Give up after this long (default 120s). */\n timeoutMs?: number;\n /**\n * Wait between polls when the server names no retry time (default 2s).\n * A `retryAfterSeconds` from the server replaces this outright, longer or\n * shorter: it is when the next compute actually happens.\n */\n pollIntervalMs?: number;\n /**\n * Aborts the wait, and the request in flight with it: the signal is passed\n * to every poll, so an abort during a stalled request does not sit until\n * the transport gives up.\n */\n signal?: AbortSignal;\n}\n\n/**\n * The request target for a status read: the query carries the derived scope,\n * the signed `uri` does not.\n *\n * @remarks\n * Like every Web3Signed read (data, lineage), the Personal Server verifies\n * the signature over the PATH; per-scope authorization is enforced live\n * against the caller's grant on each request, so the query needs no\n * signature to be safe. Only the write path signs path AND query, where a\n * parameter decides what is written.\n */\nexport function derivativeStatusTarget(derivedScope: string): string {\n return `${DERIVATIVE_STATUS_PATH}?derivedScope=${encodeURIComponent(derivedScope)}`;\n}\n\nfunction normalizeBaseUrl(url: string): string {\n return url.replace(/\\/+$/, \"\");\n}\n\nfunction resolveFetch(fetchFn: typeof fetch | undefined): typeof fetch {\n const resolved = fetchFn ?? globalThis.fetch;\n if (resolved === undefined) {\n throw new WriteRequestError(\"No fetch implementation available\");\n }\n return resolved;\n}\n\nfunction requireDerivedScope(derivedScope: string): string {\n if (typeof derivedScope !== \"string\" || derivedScope.length === 0) {\n // The server answers 400 DERIVATIVE_DERIVED_SCOPE_REQUIRED; refuse before\n // signing rather than spend a signature on a request that cannot pass.\n throw new WriteRequestError(\"derivedScope is required\");\n }\n return derivedScope;\n}\n\nfunction sleep(ms: number, signal?: AbortSignal): Promise<void> {\n if (ms <= 0) return Promise.resolve();\n return new Promise((resolve, reject) => {\n const timer = setTimeout(() => {\n signal?.removeEventListener(\"abort\", onAbort);\n resolve();\n }, ms);\n const onAbort = () => {\n clearTimeout(timer);\n reject(abortError(signal));\n };\n signal?.addEventListener(\"abort\", onAbort, { once: true });\n });\n}\n\nfunction timeoutError(\n latest: DerivativeStatus,\n timeoutMs: number,\n): DerivativeQuestionTimeoutError {\n return new DerivativeQuestionTimeoutError(\n `Derived scope ${latest.derivedScope} was still ${latest.status} after ${timeoutMs}ms`,\n {\n derivedScope: latest.derivedScope,\n status: latest.status,\n errorCode: latest.errorCode,\n retryAfterSeconds: latest.retryAfterSeconds,\n timeoutMs,\n },\n );\n}\n\nfunction abortError(signal?: AbortSignal): Error {\n const reason = signal?.reason;\n return reason instanceof Error\n ? reason\n : new WriteRequestError(\"Derivative status wait was aborted\");\n}\n\n/**\n * Is this a state the reader can act on?\n *\n * @remarks\n * `ready` means the derived scope has an answer to read. A `failed` status\n * is settled only when no retry is pending: with `retryAfterSeconds` set the\n * Personal Server will compute again on its own, so the answer may still\n * arrive. `pending` and `stale` are always in flight.\n */\nexport function isDerivativeStatusSettled(status: DerivativeStatus): boolean {\n if (status.status === \"ready\") return true;\n return status.status === \"failed\" && status.retryAfterSeconds === null;\n}\n\n/**\n * Read the lifecycle of the question behind a derived scope.\n *\n * @remarks\n * Sends `GET /v1/derivatives/status?derivedScope=<scope>` with a Web3Signed\n * `Authorization` header carrying `grantId`, the same authentication a data\n * read uses. Nothing is charged: the route authorizes, it does not serve\n * data, so a priced grant raises no 402 here.\n *\n * @returns The status of the derived scope. When several registrations write\n * it, the most optimistic true state answers — `ready`, then `stale`, then\n * `pending`, then `failed` — because a duplicate that never wrote anything\n * must not report away an answer the scope has.\n * @throws {DerivativeQuestionNotFoundError} 404: the caller may read the\n * scope but no question stands behind it (and, on an older Personal\n * Server, the route itself is unknown).\n * @throws {WriteForbiddenError} 403: the grant does not cover the derived\n * scope. The check runs before any store lookup, so a caller cannot probe\n * which scopes have questions.\n * @throws {DerivativeQuestionRejectedError} On any other non-2xx answer or\n * an unparseable body.\n * @throws {WriteTransportError} When `fetch` itself failed.\n * @throws {WriteRequestError} On a missing `derivedScope` or no `fetch`.\n *\n * @example\n * ```typescript\n * const status = await getDerivativeStatus({\n * personalServerUrl: \"https://ps.example.com\",\n * derivedScope: \"coach.weekly\",\n * grantId,\n * signer,\n * });\n * if (status.status === \"ready\") {\n * const record = await readPersonalServerData({ ... });\n * } else if (status.retryAfterSeconds !== null) {\n * // Computing or retrying: come back then.\n * }\n * ```\n */\nexport async function getDerivativeStatus(\n params: GetDerivativeStatusParams,\n): Promise<DerivativeStatus> {\n const derivedScope = requireDerivedScope(params.derivedScope);\n const fetchFn = resolveFetch(params.fetch);\n const baseUrl = normalizeBaseUrl(params.personalServerUrl);\n const signer = resolveWriteSigner(params.signer, { account: params.account });\n const headers = new Headers(params.headers);\n headers.set(\n \"Authorization\",\n await buildWeb3SignedHeader({\n signMessage: signer.signMessage,\n aud: params.audience ?? baseUrl,\n method: \"GET\",\n uri: DERIVATIVE_STATUS_PATH,\n grantId: params.grantId,\n }),\n );\n\n let response: Response;\n try {\n response = await fetchFn(\n `${baseUrl}${derivativeStatusTarget(derivedScope)}`,\n {\n method: \"GET\",\n headers,\n ...(params.signal ? { signal: params.signal } : {}),\n },\n );\n } catch (err) {\n throw new WriteTransportError(\n `Derivative status read failed: ${err instanceof Error ? err.message : String(err)}`,\n 1,\n err,\n );\n }\n if (!response.ok) {\n throw (await personalServerErrorFromQuestionResponse(\n response,\n )) as PersonalServerWriteError;\n }\n let body: unknown;\n try {\n body = await response.json();\n } catch (err) {\n throw new DerivativeQuestionRejectedError(\n \"Derivative status response is not JSON\",\n response.status,\n null,\n { cause: err instanceof Error ? err.message : String(err) },\n );\n }\n const parsed = DerivativeStatusSchema.safeParse(body);\n if (!parsed.success) {\n throw new DerivativeQuestionRejectedError(\n \"Derivative status response is not a status view\",\n response.status,\n null,\n { issues: parsed.error.issues },\n );\n }\n return parsed.data;\n}\n\n/**\n * Poll {@link getDerivativeStatus} until the derived scope has an answer or\n * has stopped trying to get one.\n *\n * @remarks\n * Returns as soon as {@link isDerivativeStatusSettled} holds: `ready`, or\n * `failed` with no retry pending. A failure the server will retry is not a\n * result, so the wait continues through it — on the server's own cadence,\n * because `retryAfterSeconds` is when the next compute actually happens and\n * polling faster only spends requests. A failed status is returned, not\n * thrown: the reader branches on `errorCode`.\n *\n * @returns The settled status.\n * @throws {DerivativeQuestionTimeoutError} When the budget ran out first.\n * The question keeps computing on the server; call again.\n *\n * @example\n * ```typescript\n * const status = await waitForDerivativeStatus({\n * personalServerUrl,\n * derivedScope: \"coach.weekly\",\n * grantId,\n * signer,\n * timeoutMs: 60_000,\n * });\n * if (status.status !== \"ready\") console.log(status.errorCode);\n * ```\n */\nexport async function waitForDerivativeStatus(\n params: WaitForDerivativeStatusParams,\n): Promise<DerivativeStatus> {\n const timeoutMs = Math.max(\n 0,\n params.timeoutMs ?? DEFAULT_DERIVATIVE_STATUS_TIMEOUT_MS,\n );\n const pollIntervalMs = Math.max(\n 0,\n params.pollIntervalMs ?? DEFAULT_DERIVATIVE_STATUS_POLL_INTERVAL_MS,\n );\n const deadline = Date.now() + timeoutMs;\n for (;;) {\n if (params.signal?.aborted) throw abortError(params.signal);\n const latest = await getDerivativeStatus(params);\n if (isDerivativeStatusSettled(latest)) return latest;\n const remaining = deadline - Date.now();\n if (remaining <= 0) throw timeoutError(latest, timeoutMs);\n // The server's scheduled retry decides the cadence outright, in both\n // directions: a caller's longer pollIntervalMs would sit past a retry\n // that has already produced an answer, and a shorter one would ask\n // before anything can have changed. pollIntervalMs is the cadence for\n // the case where the server named none.\n const waitMs =\n latest.retryAfterSeconds === null\n ? pollIntervalMs\n : latest.retryAfterSeconds * 1000;\n if (waitMs > remaining) {\n // The budget cannot cover the next cadence, so the poll after this\n // sleep would land before anything could have changed. Give up now\n // instead of spending a request to say the same thing.\n throw timeoutError(latest, timeoutMs);\n }\n await sleep(waitMs, params.signal);\n }\n}\n\n/** Statuses that mean a compute is in flight. @see {@link QuestionStatus} */\nexport type PendingDerivativeStatus = Extract<\n QuestionStatus,\n \"pending\" | \"stale\"\n>;\n"],"mappings":"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","names":["DerivativeErrorCodeSchema"]}
@@ -0,0 +1 @@
1
+ export {};
package/dist/server.cjs CHANGED
@@ -55,7 +55,8 @@ __export(server_exports, {
55
55
  scopeMatchesPattern: () => import_scopes.scopeMatchesPattern,
56
56
  toDirectFeeBreakdown: () => import_escrow_payment.toDirectFeeBreakdown,
57
57
  toDirectPaymentReceipt: () => import_escrow_payment.toDirectPaymentReceipt,
58
- tryGrantPermissions: () => import_scope_actions.tryGrantPermissions
58
+ tryGrantPermissions: () => import_scope_actions.tryGrantPermissions,
59
+ validateAccessRequestQuestions: () => import_access_request_client.validateAccessRequestQuestions
59
60
  });
60
61
  module.exports = __toCommonJS(server_exports);
61
62
  var import_controller = require("./direct/controller");
@@ -106,6 +107,7 @@ var import_types = require("./direct/types");
106
107
  scopeMatchesPattern,
107
108
  toDirectFeeBreakdown,
108
109
  toDirectPaymentReceipt,
109
- tryGrantPermissions
110
+ tryGrantPermissions,
111
+ validateAccessRequestQuestions
110
112
  });
111
113
  //# sourceMappingURL=server.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/server.ts"],"sourcesContent":["/**\n * Server entry point for the Vana SDK direct Data Portability flow.\n *\n * @remarks\n * Exposes {@link createDirectDataController} and its supporting types/errors.\n * This is a Node/server entry point — it owns the app private key and must never\n * be imported into browser code.\n *\n * @example\n * ```typescript\n * import { createDirectDataController } from \"@opendatalabs/vana-sdk/server\";\n *\n * export const vana = createDirectDataController({\n * env: process.env.VANA_ENV === \"dev\" ? \"dev\" : \"production\",\n * appPrivateKey: process.env.VANA_APP_PRIVATE_KEY!,\n * app: { id: \"notes-lens\", name: \"Notes Lens\", homepageUrl: process.env.VANA_APP_URL! },\n * source: \"icloud_notes\",\n * scopes: [\"icloud_notes.notes\"],\n * });\n * ```\n *\n * @category Direct\n * @module server\n */\n\nexport {\n createDirectDataController,\n type DirectDataController,\n type DirectDataControllerConfig,\n type DirectEscrowConfig,\n} from \"./direct/controller\";\n\n// Lower-level building blocks (advanced use / custom transports).\nexport {\n createDefaultAccessRequestClient,\n buildApprovalUrl,\n type DefaultAccessRequestClientOptions,\n type FetchLike,\n} from \"./direct/access-request-client\";\nexport {\n buildPersonalServerDataReadRequest,\n readPersonalServerData,\n parsePersonalServerPaymentRequired,\n dataPathForScope,\n type PersonalServerDataReadRequest,\n type PersonalServerReadResult,\n type PersonalServerFetch,\n type PersonalServerTransportRetryOptions,\n type FetchResponseLike,\n} from \"./direct/personal-server-read\";\n// Escrow-backed payment (built on protocol/escrow).\nexport {\n authorizeEscrowPayment,\n authorizeGrantPayment,\n buildEscrowPaymentHeader,\n buildGrantPaymentHeader,\n paymentResponseMetadataFromHeader,\n toDirectPaymentReceipt,\n toDirectFeeBreakdown,\n createDefaultNonceSource,\n DATA_ACCESS_OP_TYPE,\n GRANT_OP_TYPE,\n type EscrowPaymentConfig,\n type EscrowPaymentHeaderConfig,\n type SignTypedDataFn,\n type PaymentNonceSource,\n} from \"./direct/escrow-payment\";\nexport {\n getDirectEndpoints,\n PRODUCTION_ENDPOINTS,\n DEV_ENDPOINTS,\n} from \"./direct/endpoints\";\n// Grant scope entries: the `[operation:]scope` vocabulary the controller's\n// `scopes` are written in, so a backend can build and read them without\n// reaching for a platform entry point.\nexport {\n ScopeSchema,\n parseScope,\n scopeMatchesPattern,\n scopeCoveredByGrant,\n type Scope,\n type ParsedScope,\n} from \"./protocol/scopes\";\nexport {\n SCOPE_ACTIONS,\n InvalidScopeEntryError,\n parseScopeEntry,\n formatScopeEntry,\n grantPermissions,\n permissionsToScopes,\n tryGrantPermissions,\n hasAction,\n type ScopeAction,\n type ParsedScopeEntry,\n type GrantPermission,\n} from \"./protocol/scope-actions\";\n\n// Errors\nexport {\n DirectConfigError,\n AccessNotApprovedError,\n ScopeNotApprovedError,\n PersonalServerReadError,\n PaymentRequiredError,\n} from \"./direct/errors\";\n\n// Shared types\nexport type {\n DirectEnv,\n DirectNetwork,\n DirectAppConfig,\n ForegroundDelivery,\n AppIdentity,\n DirectServiceEndpoints,\n AccessRequest,\n AccessRequestStatus,\n AccessRequestStatusValue,\n ApprovedDataResult,\n MultiScopeDataResult,\n AccessRequestClient,\n DirectOpTypeValue,\n PersonalServerDataAccessPaymentOperation,\n PersonalServerGrantPaymentOperation,\n PersonalServerPaymentOperation,\n PersonalServerPaymentRequired,\n DirectPaymentReceipt,\n DirectPaymentResponseMetadata,\n DirectFeeBreakdown,\n} from \"./direct/types\";\n\n// Op-type vocabulary constant.\nexport { DirectOpType } from \"./direct/types\";\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAyBA,wBAKO;AAGP,mCAKO;AACP,kCAUO;AAEP,4BAeO;AACP,uBAIO;AAIP,oBAOO;AACP,2BAYO;AAGP,oBAMO;AA2BP,mBAA6B;","names":[]}
1
+ {"version":3,"sources":["../src/server.ts"],"sourcesContent":["/**\n * Server entry point for the Vana SDK direct Data Portability flow.\n *\n * @remarks\n * Exposes {@link createDirectDataController} and its supporting types/errors.\n * This is a Node/server entry point — it owns the app private key and must never\n * be imported into browser code.\n *\n * @example\n * ```typescript\n * import { createDirectDataController } from \"@opendatalabs/vana-sdk/server\";\n *\n * export const vana = createDirectDataController({\n * env: process.env.VANA_ENV === \"dev\" ? \"dev\" : \"production\",\n * appPrivateKey: process.env.VANA_APP_PRIVATE_KEY!,\n * app: { id: \"notes-lens\", name: \"Notes Lens\", homepageUrl: process.env.VANA_APP_URL! },\n * source: \"icloud_notes\",\n * scopes: [\"icloud_notes.notes\"],\n * });\n * ```\n *\n * @category Direct\n * @module server\n */\n\nexport {\n createDirectDataController,\n type DirectDataController,\n type DirectDataControllerConfig,\n type DirectEscrowConfig,\n} from \"./direct/controller\";\n\n// Lower-level building blocks (advanced use / custom transports).\nexport {\n createDefaultAccessRequestClient,\n buildApprovalUrl,\n validateAccessRequestQuestions,\n type DefaultAccessRequestClientOptions,\n type FetchLike,\n} from \"./direct/access-request-client\";\nexport {\n buildPersonalServerDataReadRequest,\n readPersonalServerData,\n parsePersonalServerPaymentRequired,\n dataPathForScope,\n type PersonalServerDataReadRequest,\n type PersonalServerReadResult,\n type PersonalServerFetch,\n type PersonalServerTransportRetryOptions,\n type FetchResponseLike,\n} from \"./direct/personal-server-read\";\n// Escrow-backed payment (built on protocol/escrow).\nexport {\n authorizeEscrowPayment,\n authorizeGrantPayment,\n buildEscrowPaymentHeader,\n buildGrantPaymentHeader,\n paymentResponseMetadataFromHeader,\n toDirectPaymentReceipt,\n toDirectFeeBreakdown,\n createDefaultNonceSource,\n DATA_ACCESS_OP_TYPE,\n GRANT_OP_TYPE,\n type EscrowPaymentConfig,\n type EscrowPaymentHeaderConfig,\n type SignTypedDataFn,\n type PaymentNonceSource,\n} from \"./direct/escrow-payment\";\nexport {\n getDirectEndpoints,\n PRODUCTION_ENDPOINTS,\n DEV_ENDPOINTS,\n} from \"./direct/endpoints\";\n// Grant scope entries: the `[operation:]scope` vocabulary the controller's\n// `scopes` are written in, so a backend can build and read them without\n// reaching for a platform entry point.\nexport {\n ScopeSchema,\n parseScope,\n scopeMatchesPattern,\n scopeCoveredByGrant,\n type Scope,\n type ParsedScope,\n} from \"./protocol/scopes\";\nexport {\n SCOPE_ACTIONS,\n InvalidScopeEntryError,\n parseScopeEntry,\n formatScopeEntry,\n grantPermissions,\n permissionsToScopes,\n tryGrantPermissions,\n hasAction,\n type ScopeAction,\n type ParsedScopeEntry,\n type GrantPermission,\n} from \"./protocol/scope-actions\";\n\n// Errors\nexport {\n DirectConfigError,\n AccessNotApprovedError,\n ScopeNotApprovedError,\n PersonalServerReadError,\n PaymentRequiredError,\n} from \"./direct/errors\";\n\n// Shared types\nexport type {\n DirectEnv,\n DirectNetwork,\n DirectAppConfig,\n ForegroundDelivery,\n AppIdentity,\n DirectServiceEndpoints,\n AccessRequest,\n AccessRequestQuestion,\n AccessRequestStatus,\n AccessRequestStatusValue,\n ApprovedDataResult,\n MultiScopeDataResult,\n AccessRequestClient,\n DirectOpTypeValue,\n PersonalServerDataAccessPaymentOperation,\n PersonalServerGrantPaymentOperation,\n PersonalServerPaymentOperation,\n PersonalServerPaymentRequired,\n DirectPaymentReceipt,\n DirectPaymentResponseMetadata,\n DirectFeeBreakdown,\n} from \"./direct/types\";\n\n// Op-type vocabulary constant.\nexport { DirectOpType } from \"./direct/types\";\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAyBA,wBAKO;AAGP,mCAMO;AACP,kCAUO;AAEP,4BAeO;AACP,uBAIO;AAIP,oBAOO;AACP,2BAYO;AAGP,oBAMO;AA4BP,mBAA6B;","names":[]}
package/dist/server.d.ts CHANGED
@@ -23,12 +23,12 @@
23
23
  * @module server
24
24
  */
25
25
  export { createDirectDataController, type DirectDataController, type DirectDataControllerConfig, type DirectEscrowConfig, } from "./direct/controller.js";
26
- export { createDefaultAccessRequestClient, buildApprovalUrl, type DefaultAccessRequestClientOptions, type FetchLike, } from "./direct/access-request-client.js";
26
+ export { createDefaultAccessRequestClient, buildApprovalUrl, validateAccessRequestQuestions, type DefaultAccessRequestClientOptions, type FetchLike, } from "./direct/access-request-client.js";
27
27
  export { buildPersonalServerDataReadRequest, readPersonalServerData, parsePersonalServerPaymentRequired, dataPathForScope, type PersonalServerDataReadRequest, type PersonalServerReadResult, type PersonalServerFetch, type PersonalServerTransportRetryOptions, type FetchResponseLike, } from "./direct/personal-server-read.js";
28
28
  export { authorizeEscrowPayment, authorizeGrantPayment, buildEscrowPaymentHeader, buildGrantPaymentHeader, paymentResponseMetadataFromHeader, toDirectPaymentReceipt, toDirectFeeBreakdown, createDefaultNonceSource, DATA_ACCESS_OP_TYPE, GRANT_OP_TYPE, type EscrowPaymentConfig, type EscrowPaymentHeaderConfig, type SignTypedDataFn, type PaymentNonceSource, } from "./direct/escrow-payment.js";
29
29
  export { getDirectEndpoints, PRODUCTION_ENDPOINTS, DEV_ENDPOINTS, } from "./direct/endpoints.js";
30
30
  export { ScopeSchema, parseScope, scopeMatchesPattern, scopeCoveredByGrant, type Scope, type ParsedScope, } from "./protocol/scopes.js";
31
31
  export { SCOPE_ACTIONS, InvalidScopeEntryError, parseScopeEntry, formatScopeEntry, grantPermissions, permissionsToScopes, tryGrantPermissions, hasAction, type ScopeAction, type ParsedScopeEntry, type GrantPermission, } from "./protocol/scope-actions.js";
32
32
  export { DirectConfigError, AccessNotApprovedError, ScopeNotApprovedError, PersonalServerReadError, PaymentRequiredError, } from "./direct/errors.js";
33
- export type { DirectEnv, DirectNetwork, DirectAppConfig, ForegroundDelivery, AppIdentity, DirectServiceEndpoints, AccessRequest, AccessRequestStatus, AccessRequestStatusValue, ApprovedDataResult, MultiScopeDataResult, AccessRequestClient, DirectOpTypeValue, PersonalServerDataAccessPaymentOperation, PersonalServerGrantPaymentOperation, PersonalServerPaymentOperation, PersonalServerPaymentRequired, DirectPaymentReceipt, DirectPaymentResponseMetadata, DirectFeeBreakdown, } from "./direct/types.js";
33
+ export type { DirectEnv, DirectNetwork, DirectAppConfig, ForegroundDelivery, AppIdentity, DirectServiceEndpoints, AccessRequest, AccessRequestQuestion, AccessRequestStatus, AccessRequestStatusValue, ApprovedDataResult, MultiScopeDataResult, AccessRequestClient, DirectOpTypeValue, PersonalServerDataAccessPaymentOperation, PersonalServerGrantPaymentOperation, PersonalServerPaymentOperation, PersonalServerPaymentRequired, DirectPaymentReceipt, DirectPaymentResponseMetadata, DirectFeeBreakdown, } from "./direct/types.js";
34
34
  export { DirectOpType } from "./direct/types.js";
package/dist/server.js CHANGED
@@ -3,7 +3,8 @@ import {
3
3
  } from "./direct/controller.js";
4
4
  import {
5
5
  createDefaultAccessRequestClient,
6
- buildApprovalUrl
6
+ buildApprovalUrl,
7
+ validateAccessRequestQuestions
7
8
  } from "./direct/access-request-client.js";
8
9
  import {
9
10
  buildPersonalServerDataReadRequest,
@@ -90,6 +91,7 @@ export {
90
91
  scopeMatchesPattern,
91
92
  toDirectFeeBreakdown,
92
93
  toDirectPaymentReceipt,
93
- tryGrantPermissions
94
+ tryGrantPermissions,
95
+ validateAccessRequestQuestions
94
96
  };
95
97
  //# sourceMappingURL=server.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/server.ts"],"sourcesContent":["/**\n * Server entry point for the Vana SDK direct Data Portability flow.\n *\n * @remarks\n * Exposes {@link createDirectDataController} and its supporting types/errors.\n * This is a Node/server entry point — it owns the app private key and must never\n * be imported into browser code.\n *\n * @example\n * ```typescript\n * import { createDirectDataController } from \"@opendatalabs/vana-sdk/server\";\n *\n * export const vana = createDirectDataController({\n * env: process.env.VANA_ENV === \"dev\" ? \"dev\" : \"production\",\n * appPrivateKey: process.env.VANA_APP_PRIVATE_KEY!,\n * app: { id: \"notes-lens\", name: \"Notes Lens\", homepageUrl: process.env.VANA_APP_URL! },\n * source: \"icloud_notes\",\n * scopes: [\"icloud_notes.notes\"],\n * });\n * ```\n *\n * @category Direct\n * @module server\n */\n\nexport {\n createDirectDataController,\n type DirectDataController,\n type DirectDataControllerConfig,\n type DirectEscrowConfig,\n} from \"./direct/controller\";\n\n// Lower-level building blocks (advanced use / custom transports).\nexport {\n createDefaultAccessRequestClient,\n buildApprovalUrl,\n type DefaultAccessRequestClientOptions,\n type FetchLike,\n} from \"./direct/access-request-client\";\nexport {\n buildPersonalServerDataReadRequest,\n readPersonalServerData,\n parsePersonalServerPaymentRequired,\n dataPathForScope,\n type PersonalServerDataReadRequest,\n type PersonalServerReadResult,\n type PersonalServerFetch,\n type PersonalServerTransportRetryOptions,\n type FetchResponseLike,\n} from \"./direct/personal-server-read\";\n// Escrow-backed payment (built on protocol/escrow).\nexport {\n authorizeEscrowPayment,\n authorizeGrantPayment,\n buildEscrowPaymentHeader,\n buildGrantPaymentHeader,\n paymentResponseMetadataFromHeader,\n toDirectPaymentReceipt,\n toDirectFeeBreakdown,\n createDefaultNonceSource,\n DATA_ACCESS_OP_TYPE,\n GRANT_OP_TYPE,\n type EscrowPaymentConfig,\n type EscrowPaymentHeaderConfig,\n type SignTypedDataFn,\n type PaymentNonceSource,\n} from \"./direct/escrow-payment\";\nexport {\n getDirectEndpoints,\n PRODUCTION_ENDPOINTS,\n DEV_ENDPOINTS,\n} from \"./direct/endpoints\";\n// Grant scope entries: the `[operation:]scope` vocabulary the controller's\n// `scopes` are written in, so a backend can build and read them without\n// reaching for a platform entry point.\nexport {\n ScopeSchema,\n parseScope,\n scopeMatchesPattern,\n scopeCoveredByGrant,\n type Scope,\n type ParsedScope,\n} from \"./protocol/scopes\";\nexport {\n SCOPE_ACTIONS,\n InvalidScopeEntryError,\n parseScopeEntry,\n formatScopeEntry,\n grantPermissions,\n permissionsToScopes,\n tryGrantPermissions,\n hasAction,\n type ScopeAction,\n type ParsedScopeEntry,\n type GrantPermission,\n} from \"./protocol/scope-actions\";\n\n// Errors\nexport {\n DirectConfigError,\n AccessNotApprovedError,\n ScopeNotApprovedError,\n PersonalServerReadError,\n PaymentRequiredError,\n} from \"./direct/errors\";\n\n// Shared types\nexport type {\n DirectEnv,\n DirectNetwork,\n DirectAppConfig,\n ForegroundDelivery,\n AppIdentity,\n DirectServiceEndpoints,\n AccessRequest,\n AccessRequestStatus,\n AccessRequestStatusValue,\n ApprovedDataResult,\n MultiScopeDataResult,\n AccessRequestClient,\n DirectOpTypeValue,\n PersonalServerDataAccessPaymentOperation,\n PersonalServerGrantPaymentOperation,\n PersonalServerPaymentOperation,\n PersonalServerPaymentRequired,\n DirectPaymentReceipt,\n DirectPaymentResponseMetadata,\n DirectFeeBreakdown,\n} from \"./direct/types\";\n\n// Op-type vocabulary constant.\nexport { DirectOpType } from \"./direct/types\";\n"],"mappings":"AAyBA;AAAA,EACE;AAAA,OAIK;AAGP;AAAA,EACE;AAAA,EACA;AAAA,OAGK;AACP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAMK;AAEP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAKK;AACP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAIP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAGK;AACP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAIK;AAGP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AA2BP,SAAS,oBAAoB;","names":[]}
1
+ {"version":3,"sources":["../src/server.ts"],"sourcesContent":["/**\n * Server entry point for the Vana SDK direct Data Portability flow.\n *\n * @remarks\n * Exposes {@link createDirectDataController} and its supporting types/errors.\n * This is a Node/server entry point — it owns the app private key and must never\n * be imported into browser code.\n *\n * @example\n * ```typescript\n * import { createDirectDataController } from \"@opendatalabs/vana-sdk/server\";\n *\n * export const vana = createDirectDataController({\n * env: process.env.VANA_ENV === \"dev\" ? \"dev\" : \"production\",\n * appPrivateKey: process.env.VANA_APP_PRIVATE_KEY!,\n * app: { id: \"notes-lens\", name: \"Notes Lens\", homepageUrl: process.env.VANA_APP_URL! },\n * source: \"icloud_notes\",\n * scopes: [\"icloud_notes.notes\"],\n * });\n * ```\n *\n * @category Direct\n * @module server\n */\n\nexport {\n createDirectDataController,\n type DirectDataController,\n type DirectDataControllerConfig,\n type DirectEscrowConfig,\n} from \"./direct/controller\";\n\n// Lower-level building blocks (advanced use / custom transports).\nexport {\n createDefaultAccessRequestClient,\n buildApprovalUrl,\n validateAccessRequestQuestions,\n type DefaultAccessRequestClientOptions,\n type FetchLike,\n} from \"./direct/access-request-client\";\nexport {\n buildPersonalServerDataReadRequest,\n readPersonalServerData,\n parsePersonalServerPaymentRequired,\n dataPathForScope,\n type PersonalServerDataReadRequest,\n type PersonalServerReadResult,\n type PersonalServerFetch,\n type PersonalServerTransportRetryOptions,\n type FetchResponseLike,\n} from \"./direct/personal-server-read\";\n// Escrow-backed payment (built on protocol/escrow).\nexport {\n authorizeEscrowPayment,\n authorizeGrantPayment,\n buildEscrowPaymentHeader,\n buildGrantPaymentHeader,\n paymentResponseMetadataFromHeader,\n toDirectPaymentReceipt,\n toDirectFeeBreakdown,\n createDefaultNonceSource,\n DATA_ACCESS_OP_TYPE,\n GRANT_OP_TYPE,\n type EscrowPaymentConfig,\n type EscrowPaymentHeaderConfig,\n type SignTypedDataFn,\n type PaymentNonceSource,\n} from \"./direct/escrow-payment\";\nexport {\n getDirectEndpoints,\n PRODUCTION_ENDPOINTS,\n DEV_ENDPOINTS,\n} from \"./direct/endpoints\";\n// Grant scope entries: the `[operation:]scope` vocabulary the controller's\n// `scopes` are written in, so a backend can build and read them without\n// reaching for a platform entry point.\nexport {\n ScopeSchema,\n parseScope,\n scopeMatchesPattern,\n scopeCoveredByGrant,\n type Scope,\n type ParsedScope,\n} from \"./protocol/scopes\";\nexport {\n SCOPE_ACTIONS,\n InvalidScopeEntryError,\n parseScopeEntry,\n formatScopeEntry,\n grantPermissions,\n permissionsToScopes,\n tryGrantPermissions,\n hasAction,\n type ScopeAction,\n type ParsedScopeEntry,\n type GrantPermission,\n} from \"./protocol/scope-actions\";\n\n// Errors\nexport {\n DirectConfigError,\n AccessNotApprovedError,\n ScopeNotApprovedError,\n PersonalServerReadError,\n PaymentRequiredError,\n} from \"./direct/errors\";\n\n// Shared types\nexport type {\n DirectEnv,\n DirectNetwork,\n DirectAppConfig,\n ForegroundDelivery,\n AppIdentity,\n DirectServiceEndpoints,\n AccessRequest,\n AccessRequestQuestion,\n AccessRequestStatus,\n AccessRequestStatusValue,\n ApprovedDataResult,\n MultiScopeDataResult,\n AccessRequestClient,\n DirectOpTypeValue,\n PersonalServerDataAccessPaymentOperation,\n PersonalServerGrantPaymentOperation,\n PersonalServerPaymentOperation,\n PersonalServerPaymentRequired,\n DirectPaymentReceipt,\n DirectPaymentResponseMetadata,\n DirectFeeBreakdown,\n} from \"./direct/types\";\n\n// Op-type vocabulary constant.\nexport { DirectOpType } from \"./direct/types\";\n"],"mappings":"AAyBA;AAAA,EACE;AAAA,OAIK;AAGP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OAGK;AACP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAMK;AAEP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAKK;AACP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAIP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAGK;AACP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAIK;AAGP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AA4BP,SAAS,oBAAoB;","names":[]}
@@ -84,6 +84,13 @@ export interface MockQuestion {
84
84
  };
85
85
  status: "pending" | "ready" | "failed" | "stale";
86
86
  error: string | null;
87
+ /** The coarse failure class the status route serves; null unless failed. */
88
+ errorCode: "inference_unavailable" | "source_missing" | "grant_invalid" | "internal" | null;
89
+ /**
90
+ * What the status route reports as the next automatic retry. The mock has
91
+ * no scheduler, so a test sets it through `settleQuestion`.
92
+ */
93
+ retryAfterSeconds: number | null;
87
94
  createdAt: string;
88
95
  updatedAt: string;
89
96
  lastComputedAt: string | null;
@@ -126,6 +133,8 @@ export interface MockPersonalServer {
126
133
  } | {
127
134
  status: "failed";
128
135
  error: string;
136
+ errorCode?: MockQuestion["errorCode"];
137
+ retryAfterSeconds?: number | null;
129
138
  } | {
130
139
  status: "stale" | "pending";
131
140
  }): void;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opendatalabs/vana-sdk",
3
- "version": "3.20.1",
3
+ "version": "3.22.0",
4
4
  "description": "A TypeScript library for interacting with Vana Network smart contracts.",
5
5
  "publishConfig": {
6
6
  "access": "public"