@antseed/web-sdk 0.1.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.
package/README.md ADDED
@@ -0,0 +1,171 @@
1
+ # @antseed/web-sdk
2
+
3
+ Browser buyer SDK for AntSeed. Connects to unmodified sellers over WebRTC
4
+ DataChannels (signaled through an [@antseed/relay](../../apps/relay)) and pays
5
+ per request with in-browser EIP-712 signatures. No native modules; the only
6
+ runtime dependency is ethers.
7
+
8
+ ```ts
9
+ import { AntseedWebClient } from '@antseed/web-sdk';
10
+
11
+ const client = await AntseedWebClient.create({
12
+ relayUrl: 'https://relay.example.com',
13
+ privateKey: buyerHotWalletKey, // peerId = its EVM address
14
+ connection: {
15
+ iceServers: [
16
+ { urls: 'stun:stun.example.com' },
17
+ { urls: 'turns:turn.example.com', username, credential },
18
+ ],
19
+ onConnectionInfo: ({ path }) => showPath(path), // direct | relay | unknown
20
+ },
21
+ });
22
+
23
+ const sellers = await client.sellers();
24
+ const session = await client.connect(sellers[0].peerId);
25
+
26
+ const response = await session.request(
27
+ {
28
+ path: '/v1/chat/completions',
29
+ provider: 'openai',
30
+ headers: { accept: 'text/event-stream' },
31
+ body: JSON.stringify({ model: 'gpt-oss-120b', stream: true, messages: [...] }),
32
+ },
33
+ { onChunk: (data, done) => render(new TextDecoder().decode(data)) },
34
+ );
35
+ ```
36
+
37
+ ## Example page
38
+
39
+ A working single-page example (seller discovery, connect, streaming chat)
40
+ lives in [`examples/example.html`](examples/example.html):
41
+
42
+ ```bash
43
+ pnpm --filter @antseed/web-sdk run example
44
+ # open http://127.0.0.1:8974/example.html
45
+ ```
46
+
47
+ It expects a reachable relay (`node apps/relay/dist/index.js`, default port
48
+ 8917 — use `RELAY_STATIC_SELLERS=<peerId>@<host>:<port>` to pin a local
49
+ seller) and works unfunded against sellers that accept free requests.
50
+
51
+ ## Durable channel state and multiple tabs
52
+
53
+ By default the client uses an in-memory channel store: on reload the latest
54
+ signed SpendingAuth state is gone and reserved funds stay locked until the
55
+ session deadline passes. Production apps should inject a durable store:
56
+
57
+ ```ts
58
+ const client = new AntseedWebClient({ relayUrl, wallet, channelStore: myStore });
59
+ ```
60
+
61
+ `channelStore` takes any `BuyerChannelStore` (re-exported here). For
62
+ crash-safety implement the optional `flush()`: it is awaited after each newly
63
+ signed authorization is persisted and *before* it is transmitted, so it must
64
+ not resolve until the write is durably committed (e.g. IndexedDB behind a
65
+ synchronous in-memory cache). If several tabs can share the store, the
66
+ implementation must also serialize signing (e.g. `navigator.locks`) —
67
+ SpendingAuth amounts are cumulative per channel, and two tabs signing
68
+ concurrently corrupt the sequence. Without a shared store, each tab opens its
69
+ own channel and reserves its own budget.
70
+
71
+ ## ICE / TURN configuration
72
+
73
+ `connection.iceServers` accepts bare URL strings or full `RTCIceServer`
74
+ entries (for TURN credentials); `connection.iceTransportPolicy: 'relay'`
75
+ forces TURN-only paths. `connection.onConnectionInfo` reports the selected
76
+ path (`'direct' | 'relay' | 'unknown'`) once connected, without exposing
77
+ addresses or raw candidates.
78
+
79
+ ## Architecture
80
+
81
+ This package contains only the browser-specific pieces: relay signaling
82
+ (`hello` envelope, SDP offer, trickle ICE), the `RTCPeerConnection`
83
+ transport, keepalive, channel-store adapters, and cross-tab coordination.
84
+ Everything else is shared code, not a reimplementation. Production clients
85
+ use an IndexedDB store; the in-memory store remains available only through
86
+ the explicit `ephemeral()` API:
87
+
88
+ - **@antseed/protocol** — wire format: message types, frame/HTTP/payment
89
+ codecs, EIP-712 payment signatures, connection auth. Same modules
90
+ @antseed/node runs.
91
+ - **@antseed/buyer-core** — the buyer machinery: `BuyerRequestHandler`,
92
+ `BuyerPaymentManager`, `BuyerPaymentNegotiator`, `ProxyMux`/`PaymentMux`.
93
+ The exact classes behind the node/CLI buyer proxy, wired here to a WebRTC
94
+ DataChannel and the configured channel store (in-memory by default).
95
+
96
+ So 402 negotiation, ReserveAuth/AuthAck, NeedAuth cost validation, cumulative
97
+ SpendingAuth metadata, streaming, and chunked uploads behave identically to
98
+ the node buyer. One browser-specific override: uploads chunk above 192 KiB
99
+ (not 512 KiB) to stay under the ~256 KiB SCTP message ceiling browsers
100
+ negotiate with libdatachannel sellers. Defaults target Base mainnet; override
101
+ `payment.*` for other chains.
102
+
103
+ The relay bridge is closed as soon as the DataChannel opens — it is only
104
+ needed for signaling, and holding it would consume the relay's per-IP bridge
105
+ slots. There is no re-signaling path; a dropped DataChannel means a fresh
106
+ `connect()`.
107
+
108
+ ## Persistence and multiple tabs
109
+
110
+ `AntseedWebClient.create()` is asynchronous because it acquires an exclusive
111
+ Web Lock for the buyer identity and hydrates IndexedDB before the payment
112
+ manager may sign. Only one tab can use a buyer on the same chain/channels
113
+ contract at a time. A second tab fails with `BuyerAlreadyActiveError` by
114
+ default; set `persistence.waitForLock` to wait instead. A crashed tab releases
115
+ its Web Lock automatically, and its successor recovers the last durably
116
+ committed channel state.
117
+
118
+ Every ReserveAuth and SpendingAuth is committed to IndexedDB before it is
119
+ sent. The database contains channel identifiers, signatures, cumulative
120
+ amounts, metadata, and recovery context, but never the buyer private key.
121
+ Use `onPersistenceError` to surface background failures from ordinary
122
+ status/accounting writes immediately; awaited authorization commits and
123
+ `close()` also reject when persistence is unhealthy.
124
+ Call `await client.close()` when the application is done to flush storage and
125
+ release the lock. Page-unload handlers are not a channel recovery mechanism.
126
+
127
+ `AntseedWebClient.ephemeral()` retains in-memory behavior for tests and free
128
+ interoperability experiments. It must not be used for paid traffic because a
129
+ reload loses its channel state and it provides no multi-tab exclusion.
130
+
131
+ `SellerSession.close()` closes only the WebRTC transport. Use
132
+ `await session.closeChannel()` to request cooperative settlement from a
133
+ supporting seller. `client.listActiveChannels()` exposes locally unresolved
134
+ channels for recovery UI. If the seller is unavailable, call
135
+ `session.requestOnChainClose(operatorSigner)` and, after the contract grace
136
+ period, `session.withdrawTimedOutChannel(operatorSigner)`. The signer must be
137
+ the operator configured for the buyer in AntseedDeposits; the SDK does not
138
+ assume that the browser wallet or any particular backend owns that role.
139
+
140
+ The relay verifies DHT metadata before caching it, and the SDK rejects a
141
+ non-empty seller snapshot older than `maxSellerSnapshotAgeMs` (15 minutes by
142
+ default) before normalizing it into the shared buyer pricing/capability view.
143
+ The relay remains a trusted discovery-availability boundary; connection auth,
144
+ seller response authentication, and payment signatures remain end to end.
145
+
146
+ ## Known limitation: large non-streamed responses
147
+
148
+ Chunking currently protects the upload direction only. A seller sends a
149
+ non-streamed response body as a single DataChannel message, and browsers cap
150
+ received SCTP messages at ~256 KiB — a larger response (e.g. base64 image
151
+ output) will kill the DataChannel. Until seller-side response chunking
152
+ exists, prefer streaming (`accept: text/event-stream`) for potentially large
153
+ responses.
154
+
155
+ ## Prerequisites for paying sellers
156
+
157
+ The buyer address must have USDC deposited in AntseedDeposits (directly or
158
+ via the gasless EIP-3009 sweep). Deposit UX is out of scope for this SDK.
159
+
160
+ ## Testing
161
+
162
+ `vitest run` covers byte-for-byte parity with `@antseed/node` (hello
163
+ envelope, frame codec, EIP-712 signatures, metadata hashing) and an
164
+ end-to-end suite that drives the real `@antseed/node` seller listener through
165
+ a real relay instance, with `node-datachannel/polyfill` standing in for the
166
+ browser's `RTCPeerConnection`. `pnpm run test:browser` additionally launches
167
+ real Chromium and Firefox runtimes, connects their native WebRTC stacks to an
168
+ unchanged seller through the relay, and verifies multi-tab exclusion plus
169
+ crash takeover. The browser suite also completes paid 402 negotiation, verifies
170
+ the EIP-712 authorizations, reloads IndexedDB, simulates lost seller state, and
171
+ recovers the same on-chain channel without issuing a second reserve.
@@ -0,0 +1,50 @@
1
+ import type { BuyerChannelStore, ChannelKind, ChannelRole, ChannelStatus, StoredChannel } from '@antseed/buyer-core/channel-store-types';
2
+ import type { SpendingAuthMetadata, SpendingAuthServiceMetadata } from '@antseed/protocol/signatures';
3
+ export declare class MemoryChannelStore implements BuyerChannelStore {
4
+ protected channels: Map<string, StoredChannel>;
5
+ protected serviceTotals: Map<string, SpendingAuthServiceMetadata[]>;
6
+ upsertChannel(channel: StoredChannel): void;
7
+ getChannel(sessionId: string): StoredChannel | null;
8
+ getActiveChannelByPeerAndBuyer(peerId: string, role: ChannelRole, buyerEvmAddr: string, channelKind?: ChannelKind): StoredChannel | null;
9
+ getLatestChannelByPeerAndBuyer(peerId: string, role: ChannelRole, buyerEvmAddr: string, channelKind?: ChannelKind): StoredChannel | null;
10
+ getActiveChannelsByBuyer(role: ChannelRole, buyerEvmAddr: string, channelKind?: ChannelKind): StoredChannel[];
11
+ updateChannelStatus(sessionId: string, status: ChannelStatus, settledAmount?: string): void;
12
+ replaceMetadataServiceTotals(sessionId: string, services?: readonly SpendingAuthServiceMetadata[]): void;
13
+ getChannelMetadata(channel: StoredChannel): SpendingAuthMetadata;
14
+ commitAuthorization(channel: StoredChannel, services?: readonly SpendingAuthServiceMetadata[]): void;
15
+ flush(): Promise<void>;
16
+ protected latestMatching(predicate: (channel: StoredChannel) => boolean): StoredChannel | null;
17
+ }
18
+ export interface IndexedDbChannelStoreOptions {
19
+ databaseName: string;
20
+ indexedDB?: IDBFactory;
21
+ /** Called asynchronously as soon as a queued IndexedDB write fails. */
22
+ onWriteError?: (error: Error) => void;
23
+ }
24
+ /**
25
+ * IndexedDB-backed store with a synchronous in-memory read model. Call
26
+ * `open()` before constructing BuyerPaymentManager so its hydration pass sees
27
+ * all committed channels. Writes are serialized, and authorization commits
28
+ * update the channel and service totals in one IndexedDB transaction.
29
+ */
30
+ export declare class IndexedDbChannelStore extends MemoryChannelStore {
31
+ private readonly db;
32
+ private readonly onWriteError?;
33
+ private writeTail;
34
+ private fatalWriteError;
35
+ private closed;
36
+ private constructor();
37
+ static open(options: IndexedDbChannelStoreOptions): Promise<IndexedDbChannelStore>;
38
+ upsertChannel(channel: StoredChannel): void;
39
+ updateChannelStatus(sessionId: string, status: ChannelStatus, settledAmount?: string): void;
40
+ replaceMetadataServiceTotals(sessionId: string, services?: readonly SpendingAuthServiceMetadata[]): void;
41
+ commitAuthorization(channel: StoredChannel, services?: readonly SpendingAuthServiceMetadata[]): Promise<void>;
42
+ flush(): Promise<void>;
43
+ close(): Promise<void>;
44
+ private hydrate;
45
+ private replaceServiceRecords;
46
+ private enqueuePutChannel;
47
+ private enqueue;
48
+ private assertWritable;
49
+ }
50
+ //# sourceMappingURL=channel-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"channel-store.d.ts","sourceRoot":"","sources":["../src/channel-store.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,iBAAiB,EACjB,WAAW,EACX,WAAW,EACX,aAAa,EACb,aAAa,EACd,MAAM,yCAAyC,CAAC;AAEjD,OAAO,KAAK,EAAE,oBAAoB,EAAE,2BAA2B,EAAE,MAAM,8BAA8B,CAAC;AAEtG,qBAAa,kBAAmB,YAAW,iBAAiB;IAC1D,SAAS,CAAC,QAAQ,6BAAoC;IACtD,SAAS,CAAC,aAAa,6CAAoD;IAE3E,aAAa,CAAC,OAAO,EAAE,aAAa,GAAG,IAAI;IAI3C,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,aAAa,GAAG,IAAI;IAKnD,8BAA8B,CAC5B,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,WAAW,EACjB,YAAY,EAAE,MAAM,EACpB,WAAW,GAAE,WAA+B,GAC3C,aAAa,GAAG,IAAI;IAWvB,8BAA8B,CAC5B,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,WAAW,EACjB,YAAY,EAAE,MAAM,EACpB,WAAW,GAAE,WAA+B,GAC3C,aAAa,GAAG,IAAI;IAUvB,wBAAwB,CACtB,IAAI,EAAE,WAAW,EACjB,YAAY,EAAE,MAAM,EACpB,WAAW,GAAE,WAA+B,GAC3C,aAAa,EAAE;IAYlB,mBAAmB,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,aAAa,EAAE,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI;IAY3F,4BAA4B,CAC1B,SAAS,EAAE,MAAM,EACjB,QAAQ,GAAE,SAAS,2BAA2B,EAAO,GACpD,IAAI;IAIP,kBAAkB,CAAC,OAAO,EAAE,aAAa,GAAG,oBAAoB;IAgBhE,mBAAmB,CACjB,OAAO,EAAE,aAAa,EACtB,QAAQ,GAAE,SAAS,2BAA2B,EAAO,GACpD,IAAI;IAKD,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAE5B,SAAS,CAAC,cAAc,CAAC,SAAS,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,OAAO,GAAG,aAAa,GAAG,IAAI;CAQ/F;AAED,MAAM,WAAW,4BAA4B;IAC3C,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,UAAU,CAAC;IACvB,uEAAuE;IACvE,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;CACvC;AAkBD;;;;;GAKG;AACH,qBAAa,qBAAsB,SAAQ,kBAAkB;IAMzD,OAAO,CAAC,QAAQ,CAAC,EAAE;IACnB,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAC;IANhC,OAAO,CAAC,SAAS,CAAoC;IACrD,OAAO,CAAC,eAAe,CAAiB;IACxC,OAAO,CAAC,MAAM,CAAS;IAEvB,OAAO;WAOM,IAAI,CAAC,OAAO,EAAE,4BAA4B,GAAG,OAAO,CAAC,qBAAqB,CAAC;IAS/E,aAAa,CAAC,OAAO,EAAE,aAAa,GAAG,IAAI;IAO3C,mBAAmB,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,aAAa,EAAE,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI;IAQ3F,4BAA4B,CACnC,SAAS,EAAE,MAAM,EACjB,QAAQ,GAAE,SAAS,2BAA2B,EAAO,GACpD,IAAI;IAOQ,mBAAmB,CAChC,OAAO,EAAE,aAAa,EACtB,QAAQ,GAAE,SAAS,2BAA2B,EAAO,GACpD,OAAO,CAAC,IAAI,CAAC;IAiBD,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAO/B,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;YAOd,OAAO;IAwBrB,OAAO,CAAC,qBAAqB;IAU7B,OAAO,CAAC,iBAAiB;IASzB,OAAO,CAAC,OAAO;IAmBf,OAAO,CAAC,cAAc;CAMvB"}
@@ -0,0 +1,288 @@
1
+ import { CHANNEL_KIND, CHANNEL_STATUS } from '@antseed/buyer-core/channel-store-types';
2
+ export class MemoryChannelStore {
3
+ channels = new Map();
4
+ serviceTotals = new Map();
5
+ upsertChannel(channel) {
6
+ this.channels.set(channel.sessionId, cloneChannel(channel));
7
+ }
8
+ getChannel(sessionId) {
9
+ const channel = this.channels.get(sessionId);
10
+ return channel ? cloneChannel(channel) : null;
11
+ }
12
+ getActiveChannelByPeerAndBuyer(peerId, role, buyerEvmAddr, channelKind = CHANNEL_KIND.PAID) {
13
+ return this.latestMatching((c) => c.peerId === peerId &&
14
+ c.role === role &&
15
+ sameAddress(c.buyerEvmAddr, buyerEvmAddr) &&
16
+ (c.channelKind ?? CHANNEL_KIND.PAID) === channelKind &&
17
+ c.status === CHANNEL_STATUS.ACTIVE);
18
+ }
19
+ getLatestChannelByPeerAndBuyer(peerId, role, buyerEvmAddr, channelKind = CHANNEL_KIND.PAID) {
20
+ return this.latestMatching((c) => c.peerId === peerId &&
21
+ c.role === role &&
22
+ sameAddress(c.buyerEvmAddr, buyerEvmAddr) &&
23
+ (c.channelKind ?? CHANNEL_KIND.PAID) === channelKind);
24
+ }
25
+ getActiveChannelsByBuyer(role, buyerEvmAddr, channelKind = CHANNEL_KIND.PAID) {
26
+ return [...this.channels.values()]
27
+ .filter((c) => c.role === role &&
28
+ sameAddress(c.buyerEvmAddr, buyerEvmAddr) &&
29
+ (c.channelKind ?? CHANNEL_KIND.PAID) === channelKind &&
30
+ c.status === CHANNEL_STATUS.ACTIVE)
31
+ .map(cloneChannel);
32
+ }
33
+ updateChannelStatus(sessionId, status, settledAmount) {
34
+ const channel = this.channels.get(sessionId);
35
+ if (!channel)
36
+ return;
37
+ this.channels.set(sessionId, {
38
+ ...channel,
39
+ status,
40
+ settledAmount: settledAmount ?? channel.settledAmount,
41
+ settledAt: status === CHANNEL_STATUS.SETTLED ? Date.now() : channel.settledAt,
42
+ updatedAt: Date.now(),
43
+ });
44
+ }
45
+ replaceMetadataServiceTotals(sessionId, services = []) {
46
+ this.serviceTotals.set(sessionId, services.map(cloneService));
47
+ }
48
+ getChannelMetadata(channel) {
49
+ const services = (this.serviceTotals.get(channel.sessionId) ?? []).map(cloneService);
50
+ return {
51
+ cumulativeInputTokens: BigInt(channel.tokensDelivered),
52
+ cumulativeOutputTokens: BigInt(channel.previousConsumption),
53
+ cumulativeRequestCount: BigInt(channel.requestCount),
54
+ // StoredChannel has no top-level image column, so recover it from the
55
+ // per-service totals just like the SQLite channel store.
56
+ cumulativeOutputImages: services.reduce((sum, service) => sum + (service.cumulativeOutputImages ?? 0n), 0n),
57
+ services,
58
+ };
59
+ }
60
+ commitAuthorization(channel, services = []) {
61
+ this.upsertChannel(channel);
62
+ this.replaceMetadataServiceTotals(channel.sessionId, services);
63
+ }
64
+ async flush() { }
65
+ latestMatching(predicate) {
66
+ let latest = null;
67
+ for (const channel of this.channels.values()) {
68
+ if (!predicate(channel))
69
+ continue;
70
+ if (!latest || channel.createdAt > latest.createdAt)
71
+ latest = channel;
72
+ }
73
+ return latest ? cloneChannel(latest) : null;
74
+ }
75
+ }
76
+ const DATABASE_VERSION = 1;
77
+ const CHANNELS_STORE = 'channels';
78
+ const SERVICE_TOTALS_STORE = 'serviceTotals';
79
+ const SESSION_ID_INDEX = 'sessionId';
80
+ /**
81
+ * IndexedDB-backed store with a synchronous in-memory read model. Call
82
+ * `open()` before constructing BuyerPaymentManager so its hydration pass sees
83
+ * all committed channels. Writes are serialized, and authorization commits
84
+ * update the channel and service totals in one IndexedDB transaction.
85
+ */
86
+ export class IndexedDbChannelStore extends MemoryChannelStore {
87
+ db;
88
+ onWriteError;
89
+ writeTail = Promise.resolve();
90
+ fatalWriteError = null;
91
+ closed = false;
92
+ constructor(db, onWriteError) {
93
+ super();
94
+ this.db = db;
95
+ this.onWriteError = onWriteError;
96
+ }
97
+ static async open(options) {
98
+ const factory = options.indexedDB ?? globalThis.indexedDB;
99
+ if (!factory)
100
+ throw new Error('IndexedDB unavailable');
101
+ const db = await openDatabase(factory, options.databaseName);
102
+ const store = new IndexedDbChannelStore(db, options.onWriteError);
103
+ await store.hydrate();
104
+ return store;
105
+ }
106
+ upsertChannel(channel) {
107
+ this.assertWritable();
108
+ const copy = cloneChannel(channel);
109
+ super.upsertChannel(copy);
110
+ this.enqueuePutChannel(copy);
111
+ }
112
+ updateChannelStatus(sessionId, status, settledAmount) {
113
+ this.assertWritable();
114
+ super.updateChannelStatus(sessionId, status, settledAmount);
115
+ const channel = this.channels.get(sessionId);
116
+ if (!channel)
117
+ return;
118
+ this.enqueuePutChannel(cloneChannel(channel));
119
+ }
120
+ replaceMetadataServiceTotals(sessionId, services = []) {
121
+ this.assertWritable();
122
+ const copies = services.map(cloneService);
123
+ super.replaceMetadataServiceTotals(sessionId, copies);
124
+ this.enqueue(() => this.replaceServiceRecords(sessionId, copies));
125
+ }
126
+ async commitAuthorization(channel, services = []) {
127
+ this.assertWritable();
128
+ const channelCopy = cloneChannel(channel);
129
+ const serviceCopies = services.map(cloneService);
130
+ super.upsertChannel(channelCopy);
131
+ super.replaceMetadataServiceTotals(channelCopy.sessionId, serviceCopies);
132
+ this.enqueue(async () => {
133
+ const tx = this.db.transaction([CHANNELS_STORE, SERVICE_TOTALS_STORE], 'readwrite');
134
+ const done = transactionDone(tx);
135
+ tx.objectStore(CHANNELS_STORE).put(channelCopy);
136
+ await replaceServiceRecordsInTransaction(tx, channelCopy.sessionId, serviceCopies);
137
+ await done;
138
+ });
139
+ await this.flush();
140
+ }
141
+ async flush() {
142
+ await this.writeTail;
143
+ if (this.fatalWriteError) {
144
+ throw new Error('IndexedDB channel persistence failed', { cause: this.fatalWriteError });
145
+ }
146
+ }
147
+ async close() {
148
+ if (this.closed)
149
+ return;
150
+ await this.flush();
151
+ this.closed = true;
152
+ this.db.close();
153
+ }
154
+ async hydrate() {
155
+ const tx = this.db.transaction([CHANNELS_STORE, SERVICE_TOTALS_STORE], 'readonly');
156
+ const done = transactionDone(tx);
157
+ const channelsRequest = requestResult(tx.objectStore(CHANNELS_STORE).getAll());
158
+ const totalsRequest = requestResult(tx.objectStore(SERVICE_TOTALS_STORE).getAll());
159
+ const [channels, totals] = await Promise.all([channelsRequest, totalsRequest]);
160
+ await done;
161
+ for (const channel of channels) {
162
+ super.upsertChannel(channel);
163
+ }
164
+ const grouped = new Map();
165
+ for (const record of totals) {
166
+ const services = grouped.get(record.sessionId) ?? [];
167
+ services.push(serviceFromRecord(record));
168
+ grouped.set(record.sessionId, services);
169
+ }
170
+ for (const [sessionId, services] of grouped) {
171
+ super.replaceMetadataServiceTotals(sessionId, services);
172
+ }
173
+ }
174
+ replaceServiceRecords(sessionId, services) {
175
+ const tx = this.db.transaction(SERVICE_TOTALS_STORE, 'readwrite');
176
+ const done = transactionDone(tx);
177
+ return replaceServiceRecordsInTransaction(tx, sessionId, services)
178
+ .then(() => done);
179
+ }
180
+ enqueuePutChannel(copy) {
181
+ this.enqueue(async () => {
182
+ const tx = this.db.transaction(CHANNELS_STORE, 'readwrite');
183
+ const done = transactionDone(tx);
184
+ tx.objectStore(CHANNELS_STORE).put(copy);
185
+ await done;
186
+ });
187
+ }
188
+ enqueue(operation) {
189
+ this.writeTail = this.writeTail
190
+ .then(() => {
191
+ if (this.fatalWriteError)
192
+ throw this.fatalWriteError;
193
+ return operation();
194
+ })
195
+ .catch((error) => {
196
+ if (this.fatalWriteError)
197
+ return;
198
+ this.fatalWriteError = error;
199
+ try {
200
+ this.onWriteError?.(error instanceof Error ? error : new Error(String(error)));
201
+ }
202
+ catch {
203
+ // Persistence observers are diagnostic only.
204
+ }
205
+ });
206
+ }
207
+ assertWritable() {
208
+ if (this.closed)
209
+ throw new Error('IndexedDB channel store is closed');
210
+ if (this.fatalWriteError) {
211
+ throw new Error('IndexedDB channel persistence failed', { cause: this.fatalWriteError });
212
+ }
213
+ }
214
+ }
215
+ function cloneChannel(channel) {
216
+ return { ...channel };
217
+ }
218
+ function cloneService(service) {
219
+ return { ...service };
220
+ }
221
+ function sameAddress(left, right) {
222
+ return left.toLowerCase() === right.toLowerCase();
223
+ }
224
+ function serviceToRecord(sessionId, service) {
225
+ return {
226
+ sessionId,
227
+ serviceId: service.serviceId,
228
+ cumulativeAmount: service.cumulativeAmount.toString(),
229
+ cumulativeInputTokens: service.cumulativeInputTokens.toString(),
230
+ cumulativeCachedInputTokens: service.cumulativeCachedInputTokens.toString(),
231
+ cumulativeOutputTokens: service.cumulativeOutputTokens.toString(),
232
+ cumulativeRequestCount: service.cumulativeRequestCount.toString(),
233
+ cumulativeOutputImages: (service.cumulativeOutputImages ?? 0n).toString(),
234
+ };
235
+ }
236
+ function serviceFromRecord(record) {
237
+ return {
238
+ serviceId: record.serviceId,
239
+ cumulativeAmount: BigInt(record.cumulativeAmount),
240
+ cumulativeInputTokens: BigInt(record.cumulativeInputTokens),
241
+ cumulativeCachedInputTokens: BigInt(record.cumulativeCachedInputTokens),
242
+ cumulativeOutputTokens: BigInt(record.cumulativeOutputTokens),
243
+ cumulativeRequestCount: BigInt(record.cumulativeRequestCount),
244
+ cumulativeOutputImages: BigInt(record.cumulativeOutputImages ?? '0'),
245
+ };
246
+ }
247
+ async function replaceServiceRecordsInTransaction(tx, sessionId, services) {
248
+ const store = tx.objectStore(SERVICE_TOTALS_STORE);
249
+ const keys = await requestResult(store.index(SESSION_ID_INDEX).getAllKeys(sessionId));
250
+ for (const key of keys)
251
+ store.delete(key);
252
+ for (const service of services)
253
+ store.put(serviceToRecord(sessionId, service));
254
+ }
255
+ function openDatabase(factory, name) {
256
+ return new Promise((resolve, reject) => {
257
+ const request = factory.open(name, DATABASE_VERSION);
258
+ request.onupgradeneeded = () => {
259
+ const db = request.result;
260
+ if (!db.objectStoreNames.contains(CHANNELS_STORE)) {
261
+ db.createObjectStore(CHANNELS_STORE, { keyPath: 'sessionId' });
262
+ }
263
+ if (!db.objectStoreNames.contains(SERVICE_TOTALS_STORE)) {
264
+ const totals = db.createObjectStore(SERVICE_TOTALS_STORE, {
265
+ keyPath: ['sessionId', 'serviceId'],
266
+ });
267
+ totals.createIndex(SESSION_ID_INDEX, 'sessionId', { unique: false });
268
+ }
269
+ };
270
+ request.onsuccess = () => resolve(request.result);
271
+ request.onerror = () => reject(request.error ?? new Error('failed to open IndexedDB'));
272
+ request.onblocked = () => reject(new Error('IndexedDB upgrade blocked by another tab'));
273
+ });
274
+ }
275
+ function requestResult(request) {
276
+ return new Promise((resolve, reject) => {
277
+ request.onsuccess = () => resolve(request.result);
278
+ request.onerror = () => reject(request.error ?? new Error('IndexedDB request failed'));
279
+ });
280
+ }
281
+ function transactionDone(tx) {
282
+ return new Promise((resolve, reject) => {
283
+ tx.oncomplete = () => resolve();
284
+ tx.onerror = () => reject(tx.error ?? new Error('IndexedDB transaction failed'));
285
+ tx.onabort = () => reject(tx.error ?? new Error('IndexedDB transaction aborted'));
286
+ });
287
+ }
288
+ //# sourceMappingURL=channel-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"channel-store.js","sourceRoot":"","sources":["../src/channel-store.ts"],"names":[],"mappings":"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"}