@tanstack/ai-client 0.25.2 → 0.28.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 (51) hide show
  1. package/README.md +2 -1
  2. package/dist/esm/byok/client.d.ts +52 -0
  3. package/dist/esm/byok/client.js +300 -0
  4. package/dist/esm/byok/client.js.map +1 -0
  5. package/dist/esm/byok/passkey.d.ts +43 -0
  6. package/dist/esm/byok/passkey.js +251 -0
  7. package/dist/esm/byok/passkey.js.map +1 -0
  8. package/dist/esm/byok/resolve.d.ts +8 -0
  9. package/dist/esm/byok/resolve.js +20 -0
  10. package/dist/esm/byok/resolve.js.map +1 -0
  11. package/dist/esm/byok/storage.d.ts +15 -0
  12. package/dist/esm/byok/storage.js +16 -0
  13. package/dist/esm/byok/storage.js.map +1 -0
  14. package/dist/esm/byok.d.ts +8 -0
  15. package/dist/esm/byok.js +5 -0
  16. package/dist/esm/chat-client.d.ts +18 -5
  17. package/dist/esm/chat-client.js +96 -59
  18. package/dist/esm/chat-client.js.map +1 -1
  19. package/dist/esm/connection-adapters.d.ts +2 -0
  20. package/dist/esm/connection-adapters.js +70 -30
  21. package/dist/esm/connection-adapters.js.map +1 -1
  22. package/dist/esm/generation-client.d.ts +3 -1
  23. package/dist/esm/generation-client.js +27 -6
  24. package/dist/esm/generation-client.js.map +1 -1
  25. package/dist/esm/generation-types.d.ts +14 -0
  26. package/dist/esm/generation-types.js +4 -2
  27. package/dist/esm/generation-types.js.map +1 -1
  28. package/dist/esm/interrupt-manager.d.ts +1 -0
  29. package/dist/esm/interrupt-manager.js +15 -6
  30. package/dist/esm/interrupt-manager.js.map +1 -1
  31. package/dist/esm/sse-parser.js +8 -1
  32. package/dist/esm/sse-parser.js.map +1 -1
  33. package/dist/esm/types.d.ts +46 -3
  34. package/dist/esm/types.js.map +1 -1
  35. package/dist/esm/video-generation-client.d.ts +3 -1
  36. package/dist/esm/video-generation-client.js +29 -8
  37. package/dist/esm/video-generation-client.js.map +1 -1
  38. package/package.json +7 -3
  39. package/src/byok/client.ts +360 -0
  40. package/src/byok/passkey.ts +399 -0
  41. package/src/byok/resolve.ts +30 -0
  42. package/src/byok/storage.ts +29 -0
  43. package/src/byok.ts +20 -0
  44. package/src/chat-client.ts +138 -77
  45. package/src/connection-adapters.ts +108 -53
  46. package/src/generation-client.ts +53 -9
  47. package/src/generation-types.ts +24 -2
  48. package/src/interrupt-manager.ts +20 -6
  49. package/src/sse-parser.ts +10 -0
  50. package/src/types.ts +48 -3
  51. package/src/video-generation-client.ts +50 -9
package/README.md CHANGED
@@ -210,8 +210,9 @@ Official adapters include:
210
210
  | ------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------ |
211
211
  | [`@tanstack/ai-openrouter`](https://tanstack.com/ai/latest/docs/adapters/openrouter) | 300+ models through one OpenRouter API, with per-request cost tracking |
212
212
  | [`@tanstack/ai-openai`](https://tanstack.com/ai/latest/docs/adapters/openai) | OpenAI chat, image, video, speech, transcription, realtime, and provider tools |
213
- | [`@tanstack/ai-anthropic`](https://tanstack.com/ai/latest/docs/adapters/anthropic) | Anthropic Claude chat, thinking, tools, and structured outputs |
213
+ | [`@tanstack/ai-anthropic`](https://tanstack.com/ai/latest/docs/adapters/anthropic) | Anthropic Claude chat, thinking, tools, structured outputs, and Vertex Claude |
214
214
  | [`@tanstack/ai-gemini`](https://tanstack.com/ai/latest/docs/adapters/gemini) | Google Gemini chat, image, speech, and audio generation |
215
+ | [`@tanstack/ai-vertex`](https://tanstack.com/ai/latest/docs/adapters/vertex) | Gemini on Vertex AI with regional endpoints and Google Cloud credentials |
215
216
  | [`@tanstack/ai-ollama`](https://tanstack.com/ai/latest/docs/adapters/ollama) | Local Ollama models |
216
217
  | [`@tanstack/ai-grok`](https://tanstack.com/ai/latest/docs/adapters/grok) | xAI Grok chat, images, and realtime |
217
218
  | [`@tanstack/ai-groq`](https://tanstack.com/ai/latest/docs/adapters/groq) | Groq low-latency inference |
@@ -0,0 +1,52 @@
1
+ import { ProviderId } from '@tanstack/ai/byok';
2
+ import { Keyring, KeyringStorage } from './storage.js';
3
+ export type KeyStatus = {
4
+ state: 'empty';
5
+ } | {
6
+ state: 'set';
7
+ masked: string;
8
+ } | {
9
+ state: 'locked';
10
+ masked: string;
11
+ } | {
12
+ state: 'error';
13
+ masked: string;
14
+ message: string;
15
+ };
16
+ export type ByokPrompt = {
17
+ provider: ProviderId;
18
+ reason: 'missing' | 'locked';
19
+ };
20
+ export type ByokSnapshot = {
21
+ status: Partial<Record<string, KeyStatus>>;
22
+ locked: boolean;
23
+ prompt: ByokPrompt | null;
24
+ storageError: string | null;
25
+ };
26
+ export interface DefineByokOptions {
27
+ storage?: KeyringStorage;
28
+ }
29
+ export declare const EMPTY_BYOK_SNAPSHOT: ByokSnapshot;
30
+ export declare class ByokClient {
31
+ #private;
32
+ readonly storage: KeyringStorage;
33
+ constructor(options?: DefineByokOptions);
34
+ /** Resolves when constructor hydration (peek/load) finishes. */
35
+ ready: () => Promise<void>;
36
+ subscribe: (listener: () => void) => (() => void);
37
+ getSnapshot: () => ByokSnapshot;
38
+ keys(): Keyring;
39
+ request(provider: ProviderId, reason: ByokPrompt['reason']): void;
40
+ /**
41
+ * `true` — the server can fill any slug from env.
42
+ * `false` — no env coverage.
43
+ * A record merges per-id flags and clears the all/none boolean.
44
+ */
45
+ setServerCoverage(flags: boolean | Readonly<Record<string, boolean>>): void;
46
+ headers(provider?: ProviderId): Record<string, string>;
47
+ prepare(provider?: ProviderId): Promise<void>;
48
+ update(providerOrKey: ProviderId | string, key?: string): Promise<void>;
49
+ clear(provider?: ProviderId): Promise<void>;
50
+ unlock(): Promise<void>;
51
+ }
52
+ export declare function defineByok(options?: DefineByokOptions): ByokClient;
@@ -0,0 +1,300 @@
1
+ import { memoryStorage } from "./storage.js";
2
+ import { ByokBlockedError, byokHeaderName, isProviderId, maskKey } from "@tanstack/ai/byok";
3
+ //#region src/byok/client.ts
4
+ var EMPTY_BYOK_SNAPSHOT = {
5
+ status: {},
6
+ locked: false,
7
+ prompt: null,
8
+ storageError: null
9
+ };
10
+ function requireProviderId(value) {
11
+ if (!isProviderId(value)) throw new Error(`Invalid BYOK provider id: ${value}`);
12
+ return value;
13
+ }
14
+ function errorMessage(error, fallback) {
15
+ return error instanceof Error && error.message.length > 0 ? error.message : fallback;
16
+ }
17
+ function isByokCeremonyCancel(error) {
18
+ if (typeof DOMException !== "undefined" && error instanceof DOMException && error.name === "NotAllowedError") return true;
19
+ if (error instanceof Error) {
20
+ if (error.name === "NotAllowedError") return true;
21
+ const message = error.message.toLowerCase();
22
+ return message.includes("cancelled") || message.includes("not allowed");
23
+ }
24
+ return false;
25
+ }
26
+ function sanitizeKeyring(value) {
27
+ if (typeof value !== "object" || value === null) return {};
28
+ const keys = {};
29
+ for (const [provider, key] of Object.entries(value)) if (isProviderId(provider) && typeof key === "string" && key.length > 0) keys[provider] = key;
30
+ return keys;
31
+ }
32
+ var ByokClient = class {
33
+ storage;
34
+ #keys = {};
35
+ #statuses = {};
36
+ #locked;
37
+ #prompt = null;
38
+ #coverageAll = false;
39
+ #coverage = {};
40
+ #listeners = /* @__PURE__ */ new Set();
41
+ #snapshot;
42
+ #storageError = null;
43
+ #ready;
44
+ constructor(options = {}) {
45
+ this.storage = options.storage ?? memoryStorage();
46
+ this.#locked = Boolean(this.storage.unlockable);
47
+ this.#snapshot = this.#buildSnapshot();
48
+ this.#ready = this.#hydrate();
49
+ }
50
+ /** Resolves when constructor hydration (peek/load) finishes. */
51
+ ready = () => this.#ready;
52
+ subscribe = (listener) => {
53
+ this.#listeners.add(listener);
54
+ return () => {
55
+ this.#listeners.delete(listener);
56
+ };
57
+ };
58
+ getSnapshot = () => this.#snapshot;
59
+ #buildSnapshot() {
60
+ return {
61
+ status: { ...this.#statuses },
62
+ locked: this.#locked,
63
+ prompt: this.#prompt,
64
+ storageError: this.#storageError
65
+ };
66
+ }
67
+ keys() {
68
+ return { ...this.#keys };
69
+ }
70
+ request(provider, reason) {
71
+ this.#prompt = {
72
+ provider: requireProviderId(provider),
73
+ reason
74
+ };
75
+ this.#emit();
76
+ }
77
+ /**
78
+ * `true` — the server can fill any slug from env.
79
+ * `false` — no env coverage.
80
+ * A record merges per-id flags and clears the all/none boolean.
81
+ */
82
+ setServerCoverage(flags) {
83
+ if (typeof flags === "boolean") {
84
+ this.#coverageAll = flags;
85
+ this.#coverage = {};
86
+ return;
87
+ }
88
+ this.#coverageAll = false;
89
+ this.#coverage = {
90
+ ...this.#coverage,
91
+ ...flags
92
+ };
93
+ }
94
+ #hasCoverage(provider) {
95
+ return this.#coverageAll || this.#coverage[provider] === true;
96
+ }
97
+ headers(provider) {
98
+ const headers = {};
99
+ if (provider) {
100
+ requireProviderId(provider);
101
+ const key = this.#keys[provider];
102
+ if (key) headers[byokHeaderName(provider)] = key;
103
+ return headers;
104
+ }
105
+ for (const [id, key] of Object.entries(this.#keys)) if (key) headers[byokHeaderName(id)] = key;
106
+ return headers;
107
+ }
108
+ async prepare(provider) {
109
+ await this.#ready;
110
+ if (this.storage.unlockable && this.#locked) await this.unlock();
111
+ if (!provider) return;
112
+ requireProviderId(provider);
113
+ if (this.#keys[provider]) return;
114
+ if (this.#hasCoverage(provider)) return;
115
+ this.request(provider, "missing");
116
+ throw new ByokBlockedError(provider, "missing");
117
+ }
118
+ async update(providerOrKey, key) {
119
+ await this.#ready;
120
+ let provider;
121
+ let nextKey;
122
+ if (key === void 0) {
123
+ if (!this.#prompt) throw new Error("byok.update(key) cannot run when prompt is null");
124
+ provider = this.#prompt.provider;
125
+ nextKey = providerOrKey;
126
+ } else {
127
+ provider = requireProviderId(providerOrKey);
128
+ nextKey = key;
129
+ }
130
+ nextKey = nextKey.trim();
131
+ if (nextKey.length === 0) throw new Error("BYOK key must be non-empty");
132
+ if (this.storage.unlockable && this.#locked) await this.unlock();
133
+ const previousKeys = this.#keys;
134
+ const previousStatuses = { ...this.#statuses };
135
+ const previousLocked = this.#locked;
136
+ const previousPrompt = this.#prompt;
137
+ const previousStorageError = this.#storageError;
138
+ const next = {
139
+ ...this.#keys,
140
+ [provider]: nextKey
141
+ };
142
+ try {
143
+ await this.storage.save(next);
144
+ } catch (error) {
145
+ this.#keys = previousKeys;
146
+ this.#statuses = previousStatuses;
147
+ this.#locked = previousLocked;
148
+ this.#prompt = previousPrompt;
149
+ this.#storageError = previousStorageError;
150
+ this.#statuses[provider] = {
151
+ state: "error",
152
+ masked: maskKey(nextKey),
153
+ message: errorMessage(error, `Failed to persist ${provider} key`)
154
+ };
155
+ this.#emit();
156
+ throw error;
157
+ }
158
+ this.#keys = next;
159
+ this.#statuses[provider] = {
160
+ state: "set",
161
+ masked: maskKey(nextKey)
162
+ };
163
+ this.#locked = false;
164
+ this.#prompt = null;
165
+ this.#storageError = null;
166
+ this.#emit();
167
+ }
168
+ async clear(provider) {
169
+ await this.#ready;
170
+ if (provider) {
171
+ requireProviderId(provider);
172
+ if (this.storage.unlockable && this.#locked) await this.unlock();
173
+ const previousKeys = this.#keys;
174
+ const previousStatuses = { ...this.#statuses };
175
+ const next = { ...this.#keys };
176
+ delete next[provider];
177
+ try {
178
+ await this.storage.save(next);
179
+ } catch (error) {
180
+ this.#keys = previousKeys;
181
+ this.#statuses = previousStatuses;
182
+ this.#emit();
183
+ throw error;
184
+ }
185
+ this.#keys = next;
186
+ delete this.#statuses[provider];
187
+ this.#emit();
188
+ return;
189
+ }
190
+ const previousKeys = this.#keys;
191
+ const previousStatuses = this.#statuses;
192
+ const previousLocked = this.#locked;
193
+ const previousPrompt = this.#prompt;
194
+ try {
195
+ await this.storage.clear();
196
+ } catch (error) {
197
+ this.#keys = previousKeys;
198
+ this.#statuses = previousStatuses;
199
+ this.#locked = previousLocked;
200
+ this.#prompt = previousPrompt;
201
+ this.#emit();
202
+ throw error;
203
+ }
204
+ this.#keys = {};
205
+ this.#statuses = {};
206
+ this.#locked = false;
207
+ this.#prompt = null;
208
+ this.#storageError = null;
209
+ this.#emit();
210
+ }
211
+ async unlock() {
212
+ await this.#ready;
213
+ if (!this.storage.unlockable) return;
214
+ try {
215
+ const loaded = sanitizeKeyring(await this.storage.load());
216
+ this.#keys = {
217
+ ...loaded,
218
+ ...this.#keys
219
+ };
220
+ for (const [id, value] of Object.entries(loaded)) {
221
+ if (!isProviderId(id) || !value) continue;
222
+ const existing = this.#statuses[id];
223
+ if (!existing || existing.state === "locked") this.#statuses[id] = {
224
+ state: "set",
225
+ masked: maskKey(value)
226
+ };
227
+ }
228
+ this.#locked = false;
229
+ this.#storageError = null;
230
+ this.#emit();
231
+ } catch (error) {
232
+ if (isByokCeremonyCancel(error)) {
233
+ const provider = this.#lockedProvider();
234
+ if (provider) {
235
+ this.request(provider, "locked");
236
+ throw new ByokBlockedError(provider, "locked");
237
+ }
238
+ }
239
+ this.#storageError = errorMessage(error, "Failed to unlock keyring");
240
+ this.#emit();
241
+ throw error instanceof Error ? error : new Error(String(error));
242
+ }
243
+ }
244
+ #lockedProvider() {
245
+ if (this.#prompt && isProviderId(this.#prompt.provider)) return this.#prompt.provider;
246
+ for (const [id, status] of Object.entries(this.#statuses)) if (status?.state === "locked" && isProviderId(id)) return id;
247
+ }
248
+ async #hydrate() {
249
+ if (this.storage.unlockable) {
250
+ if (!this.storage.peek) return;
251
+ try {
252
+ const preview = await this.storage.peek();
253
+ for (const [id, last4] of Object.entries(preview)) {
254
+ if (!isProviderId(id) || this.#statuses[id]) continue;
255
+ this.#statuses[id] = {
256
+ state: "locked",
257
+ masked: last4 ? last4 : "••"
258
+ };
259
+ }
260
+ this.#locked = Object.keys(preview).length > 0;
261
+ this.#storageError = null;
262
+ this.#emit();
263
+ } catch (error) {
264
+ this.#storageError = errorMessage(error, "Failed to read keyring");
265
+ this.#emit();
266
+ }
267
+ return;
268
+ }
269
+ try {
270
+ const loaded = sanitizeKeyring(await this.storage.load());
271
+ this.#keys = {
272
+ ...loaded,
273
+ ...this.#keys
274
+ };
275
+ for (const [id, value] of Object.entries(loaded)) {
276
+ if (!isProviderId(id) || !value) continue;
277
+ this.#statuses[id] = {
278
+ state: "set",
279
+ masked: maskKey(value)
280
+ };
281
+ }
282
+ this.#storageError = null;
283
+ this.#emit();
284
+ } catch (error) {
285
+ this.#storageError = errorMessage(error, "Failed to read keyring");
286
+ this.#emit();
287
+ }
288
+ }
289
+ #emit() {
290
+ this.#snapshot = this.#buildSnapshot();
291
+ for (const listener of this.#listeners) listener();
292
+ }
293
+ };
294
+ function defineByok(options = {}) {
295
+ return new ByokClient(options);
296
+ }
297
+ //#endregion
298
+ export { ByokClient, EMPTY_BYOK_SNAPSHOT, defineByok };
299
+
300
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","names":["#listeners","#ready","#locked","#snapshot","#buildSnapshot","#hydrate","#statuses","#prompt","#storageError","#keys","#emit","#coverageAll","#coverage","#hasCoverage","#lockedProvider"],"sources":["../../../src/byok/client.ts"],"sourcesContent":["import {\n ByokBlockedError,\n byokHeaderName,\n isProviderId,\n maskKey,\n} from '@tanstack/ai/byok'\nimport { memoryStorage } from './storage'\nimport type { ProviderId } from '@tanstack/ai/byok'\nimport type { Keyring, KeyringStorage } from './storage'\n\nexport type KeyStatus =\n | { state: 'empty' }\n | { state: 'set'; masked: string }\n | { state: 'locked'; masked: string }\n | { state: 'error'; masked: string; message: string }\n\nexport type ByokPrompt = {\n provider: ProviderId\n reason: 'missing' | 'locked'\n}\n\nexport type ByokSnapshot = {\n status: Partial<Record<string, KeyStatus>>\n locked: boolean\n prompt: ByokPrompt | null\n storageError: string | null\n}\n\nexport interface DefineByokOptions {\n storage?: KeyringStorage\n}\n\nexport const EMPTY_BYOK_SNAPSHOT: ByokSnapshot = {\n status: {},\n locked: false,\n prompt: null,\n storageError: null,\n}\n\nfunction requireProviderId(value: string): ProviderId {\n if (!isProviderId(value)) {\n throw new Error(`Invalid BYOK provider id: ${value}`)\n }\n return value\n}\n\nfunction errorMessage(error: unknown, fallback: string): string {\n return error instanceof Error && error.message.length > 0\n ? error.message\n : fallback\n}\n\nfunction isByokCeremonyCancel(error: unknown): boolean {\n if (\n typeof DOMException !== 'undefined' &&\n error instanceof DOMException &&\n error.name === 'NotAllowedError'\n ) {\n return true\n }\n if (error instanceof Error) {\n if (error.name === 'NotAllowedError') return true\n const message = error.message.toLowerCase()\n return message.includes('cancelled') || message.includes('not allowed')\n }\n return false\n}\n\nfunction sanitizeKeyring(value: unknown): Keyring {\n if (typeof value !== 'object' || value === null) return {}\n const keys: Keyring = {}\n for (const [provider, key] of Object.entries(value)) {\n if (isProviderId(provider) && typeof key === 'string' && key.length > 0) {\n keys[provider] = key\n }\n }\n return keys\n}\n\nexport class ByokClient {\n readonly storage: KeyringStorage\n #keys: Keyring = {}\n #statuses: Partial<Record<string, KeyStatus>> = {}\n #locked: boolean\n #prompt: ByokPrompt | null = null\n #coverageAll = false\n #coverage: Record<string, boolean> = {}\n readonly #listeners = new Set<() => void>()\n #snapshot: ByokSnapshot\n #storageError: string | null = null\n readonly #ready: Promise<void>\n\n constructor(options: DefineByokOptions = {}) {\n this.storage = options.storage ?? memoryStorage()\n this.#locked = Boolean(this.storage.unlockable)\n this.#snapshot = this.#buildSnapshot()\n this.#ready = this.#hydrate()\n }\n\n /** Resolves when constructor hydration (peek/load) finishes. */\n ready = (): Promise<void> => this.#ready\n\n subscribe = (listener: () => void): (() => void) => {\n this.#listeners.add(listener)\n return () => {\n this.#listeners.delete(listener)\n }\n }\n\n getSnapshot = (): ByokSnapshot => this.#snapshot\n\n #buildSnapshot(): ByokSnapshot {\n return {\n status: { ...this.#statuses },\n locked: this.#locked,\n prompt: this.#prompt,\n storageError: this.#storageError,\n }\n }\n\n keys(): Keyring {\n return { ...this.#keys }\n }\n\n request(provider: ProviderId, reason: ByokPrompt['reason']): void {\n this.#prompt = { provider: requireProviderId(provider), reason }\n this.#emit()\n }\n\n /**\n * `true` — the server can fill any slug from env.\n * `false` — no env coverage.\n * A record merges per-id flags and clears the all/none boolean.\n */\n setServerCoverage(flags: boolean | Readonly<Record<string, boolean>>): void {\n if (typeof flags === 'boolean') {\n this.#coverageAll = flags\n this.#coverage = {}\n return\n }\n this.#coverageAll = false\n this.#coverage = { ...this.#coverage, ...flags }\n }\n\n #hasCoverage(provider: string): boolean {\n return this.#coverageAll || this.#coverage[provider] === true\n }\n\n headers(provider?: ProviderId): Record<string, string> {\n const headers: Record<string, string> = {}\n if (provider) {\n requireProviderId(provider)\n const key = this.#keys[provider]\n if (key) headers[byokHeaderName(provider)] = key\n return headers\n }\n for (const [id, key] of Object.entries(this.#keys)) {\n if (key) headers[byokHeaderName(id)] = key\n }\n return headers\n }\n\n async prepare(provider?: ProviderId): Promise<void> {\n await this.#ready\n if (this.storage.unlockable && this.#locked) {\n await this.unlock()\n }\n if (!provider) return\n requireProviderId(provider)\n if (this.#keys[provider]) return\n if (this.#hasCoverage(provider)) return\n this.request(provider, 'missing')\n throw new ByokBlockedError(provider, 'missing')\n }\n\n async update(\n providerOrKey: ProviderId | string,\n key?: string,\n ): Promise<void> {\n await this.#ready\n let provider: ProviderId\n let nextKey: string\n if (key === undefined) {\n if (!this.#prompt) {\n throw new Error('byok.update(key) cannot run when prompt is null')\n }\n provider = this.#prompt.provider\n nextKey = providerOrKey\n } else {\n provider = requireProviderId(providerOrKey)\n nextKey = key\n }\n nextKey = nextKey.trim()\n if (nextKey.length === 0) {\n throw new Error('BYOK key must be non-empty')\n }\n if (this.storage.unlockable && this.#locked) {\n await this.unlock()\n }\n const previousKeys = this.#keys\n const previousStatuses = { ...this.#statuses }\n const previousLocked = this.#locked\n const previousPrompt = this.#prompt\n const previousStorageError = this.#storageError\n const next = { ...this.#keys, [provider]: nextKey }\n try {\n await this.storage.save(next)\n } catch (error) {\n this.#keys = previousKeys\n this.#statuses = previousStatuses\n this.#locked = previousLocked\n this.#prompt = previousPrompt\n this.#storageError = previousStorageError\n this.#statuses[provider] = {\n state: 'error',\n masked: maskKey(nextKey),\n message: errorMessage(error, `Failed to persist ${provider} key`),\n }\n this.#emit()\n throw error\n }\n this.#keys = next\n this.#statuses[provider] = { state: 'set', masked: maskKey(nextKey) }\n this.#locked = false\n this.#prompt = null\n this.#storageError = null\n this.#emit()\n }\n\n async clear(provider?: ProviderId): Promise<void> {\n await this.#ready\n if (provider) {\n requireProviderId(provider)\n if (this.storage.unlockable && this.#locked) {\n await this.unlock()\n }\n const previousKeys = this.#keys\n const previousStatuses = { ...this.#statuses }\n const next = { ...this.#keys }\n delete next[provider]\n try {\n await this.storage.save(next)\n } catch (error) {\n this.#keys = previousKeys\n this.#statuses = previousStatuses\n this.#emit()\n throw error\n }\n this.#keys = next\n delete this.#statuses[provider]\n this.#emit()\n return\n }\n const previousKeys = this.#keys\n const previousStatuses = this.#statuses\n const previousLocked = this.#locked\n const previousPrompt = this.#prompt\n try {\n await this.storage.clear()\n } catch (error) {\n this.#keys = previousKeys\n this.#statuses = previousStatuses\n this.#locked = previousLocked\n this.#prompt = previousPrompt\n this.#emit()\n throw error\n }\n this.#keys = {}\n this.#statuses = {}\n this.#locked = false\n this.#prompt = null\n this.#storageError = null\n this.#emit()\n }\n\n async unlock(): Promise<void> {\n await this.#ready\n if (!this.storage.unlockable) return\n try {\n const loaded = sanitizeKeyring(await this.storage.load())\n this.#keys = { ...loaded, ...this.#keys }\n for (const [id, value] of Object.entries(loaded)) {\n if (!isProviderId(id) || !value) continue\n const existing = this.#statuses[id]\n if (!existing || existing.state === 'locked') {\n this.#statuses[id] = { state: 'set', masked: maskKey(value) }\n }\n }\n this.#locked = false\n this.#storageError = null\n this.#emit()\n } catch (error) {\n if (isByokCeremonyCancel(error)) {\n const provider = this.#lockedProvider()\n if (provider) {\n this.request(provider, 'locked')\n throw new ByokBlockedError(provider, 'locked')\n }\n }\n this.#storageError = errorMessage(error, 'Failed to unlock keyring')\n this.#emit()\n throw error instanceof Error ? error : new Error(String(error))\n }\n }\n\n #lockedProvider(): ProviderId | undefined {\n if (this.#prompt && isProviderId(this.#prompt.provider)) {\n return this.#prompt.provider\n }\n for (const [id, status] of Object.entries(this.#statuses)) {\n if (status?.state === 'locked' && isProviderId(id)) return id\n }\n return undefined\n }\n\n async #hydrate(): Promise<void> {\n if (this.storage.unlockable) {\n if (!this.storage.peek) return\n try {\n const preview = await this.storage.peek()\n for (const [id, last4] of Object.entries(preview)) {\n if (!isProviderId(id) || this.#statuses[id]) continue\n this.#statuses[id] = {\n state: 'locked',\n masked: last4 ? last4 : '••',\n }\n }\n this.#locked = Object.keys(preview).length > 0\n this.#storageError = null\n this.#emit()\n } catch (error) {\n this.#storageError = errorMessage(error, 'Failed to read keyring')\n this.#emit()\n }\n return\n }\n try {\n const loaded = sanitizeKeyring(await this.storage.load())\n this.#keys = { ...loaded, ...this.#keys }\n for (const [id, value] of Object.entries(loaded)) {\n if (!isProviderId(id) || !value) continue\n this.#statuses[id] = { state: 'set', masked: maskKey(value) }\n }\n this.#storageError = null\n this.#emit()\n } catch (error) {\n this.#storageError = errorMessage(error, 'Failed to read keyring')\n this.#emit()\n }\n }\n\n #emit(): void {\n this.#snapshot = this.#buildSnapshot()\n for (const listener of this.#listeners) listener()\n }\n}\n\nexport function defineByok(options: DefineByokOptions = {}): ByokClient {\n return new ByokClient(options)\n}\n"],"mappings":"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"}
@@ -0,0 +1,43 @@
1
+ import { Keyring, KeyringStorage } from './storage.js';
2
+ /**
3
+ * Whether the current environment exposes WebAuthn. Actual PRF support can
4
+ * only be confirmed during registration; `passkeyStorage` throws if the
5
+ * chosen authenticator does not support PRF.
6
+ */
7
+ export declare function isPasskeyStorageSupported(): boolean;
8
+ /** Derive a non-extractable AES-256-GCM key from a 32-byte PRF output. */
9
+ export declare function deriveAesKey(prfOutput: BufferSource): Promise<CryptoKey>;
10
+ export declare function encryptKeyring(key: CryptoKey, keys: Keyring): Promise<{
11
+ iv: ArrayBuffer;
12
+ ciphertext: ArrayBuffer;
13
+ }>;
14
+ export declare function decryptKeyring(key: CryptoKey, iv: BufferSource, ciphertext: BufferSource): Promise<Keyring>;
15
+ export interface PasskeyStorageOptions {
16
+ /** Relying-party name shown in the passkey prompt. */
17
+ rpName?: string;
18
+ /** Username label attached to the created passkey. */
19
+ userName?: string;
20
+ /**
21
+ * WebAuthn Relying Party ID. Omit to bind the passkey to the current origin's
22
+ * effective domain (the default — no central/hardcoded domain). Set it to a
23
+ * registrable parent domain to share the credential across subdomains of your
24
+ * own deployment. The encrypted keyring is never portable across unrelated
25
+ * domains.
26
+ */
27
+ rpId?: string;
28
+ /** IndexedDB database name. Defaults to `byok`. */
29
+ dbName?: string;
30
+ }
31
+ /**
32
+ * Passkey-encrypted persistence. `ByokClient` treats this as `unlockable`, so
33
+ * nothing is decrypted until the user calls `unlock()` (or saves a key, which
34
+ * registers a passkey on first use). The derived key is cached in memory for
35
+ * the session so repeated saves don't re-prompt.
36
+ */
37
+ export declare function passkeyStorage(options?: PasskeyStorageOptions): KeyringStorage;
38
+ /**
39
+ * Passkey-encrypted storage when WebAuthn is available in a secure context.
40
+ * Otherwise session memory, with a warning — this is not an automatic PRF
41
+ * fallback. First save still throws if the authenticator lacks PRF.
42
+ */
43
+ export declare function defaultByokStorage(options?: PasskeyStorageOptions): KeyringStorage;