@bounded-sh/core 0.0.55 → 0.0.56

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 (29) hide show
  1. package/dist/{index-BXvTkFw2.js → index-2e3ksoSR.js} +7 -7
  2. package/dist/{index-BXvTkFw2.js.map → index-2e3ksoSR.js.map} +1 -1
  3. package/dist/{index-qcTp-5Mt.mjs → index-BZhK47ZR.mjs} +7 -7
  4. package/dist/{index-qcTp-5Mt.mjs.map → index-BZhK47ZR.mjs.map} +1 -1
  5. package/dist/index.d.ts +1 -2
  6. package/dist/index.js +1 -1
  7. package/dist/index.mjs +1 -1
  8. package/dist/{realtime-store-DmzboN-H.js → realtime-store-Cp7xYNS3.js} +2 -2
  9. package/dist/{realtime-store-DmzboN-H.js.map → realtime-store-Cp7xYNS3.js.map} +1 -1
  10. package/dist/{realtime-store-BzKEmyxq.mjs → realtime-store-CpdBV8Ma.mjs} +2 -2
  11. package/dist/{realtime-store-BzKEmyxq.mjs.map → realtime-store-CpdBV8Ma.mjs.map} +1 -1
  12. package/dist/{sol-utils-oBMCMbe_.js → sol-utils-Cwxyl2h6.js} +2 -13
  13. package/dist/sol-utils-Cwxyl2h6.js.map +1 -0
  14. package/dist/{sol-utils-BhjxvQUj.mjs → sol-utils-DVjPeoMy.mjs} +2 -13
  15. package/dist/sol-utils-DVjPeoMy.mjs.map +1 -0
  16. package/dist/{solana-message-XxVgbtp2.mjs → solana-message-Cfg_S81A.mjs} +2 -2
  17. package/dist/{solana-message-XxVgbtp2.mjs.map → solana-message-Cfg_S81A.mjs.map} +1 -1
  18. package/dist/{solana-message-I9w1TbAj.js → solana-message-DHJ8xSuS.js} +2 -2
  19. package/dist/{solana-message-I9w1TbAj.js.map → solana-message-DHJ8xSuS.js.map} +1 -1
  20. package/dist/{solana-operations-ehEsfO5g.mjs → solana-operations-BsnDXoeq.mjs} +4 -4
  21. package/dist/{solana-operations-ehEsfO5g.mjs.map → solana-operations-BsnDXoeq.mjs.map} +1 -1
  22. package/dist/{solana-operations-BUJkyVxN.js → solana-operations-DcjCYmuL.js} +4 -4
  23. package/dist/{solana-operations-BUJkyVxN.js.map → solana-operations-DcjCYmuL.js.map} +1 -1
  24. package/dist/utils/sol/{poof4b5pk1L9tmThvBmaABjcyjfhFGbMbQP5BXk2QZpDevnet-program.d.ts → openTv7fbpYSseNHYmCZFZ1CZgj4r8D9fKNgEz1qo6FDevnet-program.d.ts} +97 -113
  25. package/dist/utils/sol/{poof4b5pk1L9tmThvBmaABjcyjfhFGbMbQP5BXk2QZpMainnet-program.d.ts → openTv7fbpYSseNHYmCZFZ1CZgj4r8D9fKNgEz1qo6FMainnet-program.d.ts} +97 -113
  26. package/package.json +1 -1
  27. package/dist/sol-utils-BhjxvQUj.mjs.map +0 -1
  28. package/dist/sol-utils-oBMCMbe_.js.map +0 -1
  29. package/dist/utils/sol/taro6CvKqwrYrDc16ufYgzQ2NZcyyVKStffbtudrhRuDevnet-program.d.ts +0 -1161
package/dist/index.d.ts CHANGED
@@ -24,8 +24,7 @@ export { getActiveSessionManager } from './utils/session-manager';
24
24
  export type { ClientSessionManager } from './utils/session-manager';
25
25
  export { ServerSessionManager } from './utils/server-session-manager';
26
26
  export { createSessionWithSignature, refreshSession, revokeSession, signSessionCreateMessage, genAuthNonce } from './utils/auth-api';
27
- export type { Tarobase as Bounded6 } from './utils/sol/taro6CvKqwrYrDc16ufYgzQ2NZcyyVKStffbtudrhRuDevnet-program';
28
- export type { Tarobase as BoundedPoof } from './utils/sol/poof4b5pk1L9tmThvBmaABjcyjfhFGbMbQP5BXk2QZpMainnet-program';
27
+ export type { Tarobase as BoundedPoof } from './utils/sol/openTv7fbpYSseNHYmCZFZ1CZgj4r8D9fKNgEz1qo6FMainnet-program';
29
28
  export { buildSetDocumentsTransaction, convertRemainingAccounts, genSolanaMessage } from './utils/sol/sol-accessors';
30
29
  export type { RemainingAccount } from './utils/sol/sol-accessors';
31
30
  export { genSiweMessage, normalizeEvmAddress, toLowerEvmAddress } from './utils/evm/evm-utils';
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var index = require('./index-BXvTkFw2.js');
3
+ var index = require('./index-2e3ksoSR.js');
4
4
  require('axios');
5
5
  require('tweetnacl');
6
6
  require('reconnecting-websocket');
package/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- export { ae as BOUNDED_RUNTIME_SET_DOCUMENTS_ATTESTED_SELECTOR, ad as BOUNDED_RUNTIME_SET_DOCUMENTS_SELECTOR, H as BoundedDeclineError, L as DECLINE_EVENT_NAME, G as DEFAULT_QUERY_POLL_INTERVAL_MS, aq as EFFECT_INTENT_ADDRESS, ah as FunctionInvokeError, I as InsufficientBalanceError, am as LiveIntentError, M as MIN_QUERY_POLL_INTERVAL_MS, R as RATE_LIMIT_EVENT_NAME, Z as ReactNativeSessionManager, S as ScopedClientReadOnlyError, $ as ServerSessionManager, Y as WebSessionManager, v as aggregate, ab as assertCanonicalBoundedRuntimeWriteCalldata, ac as assertEvmTransactionMatchesWrite, a5 as buildSetDocumentsTransaction, E as canonicalizeArgs, F as canonicalizeResult, y as clearCache, D as closeAllQuerySubscriptions, x as closeAllSubscriptions, a6 as convertRemainingAccounts, u as count, B as createClient, a0 as createSessionWithSignature, ap as defineLiveModule, J as deriveDecline, X as deriveUserIdentityFromIdToken, K as dispatchDeclineEvent, N as dispatchRateLimitEvent, e as docId, af as functions, a4 as genAuthNonce, a8 as genSiweMessage, a7 as genSolanaMessage, c as get, _ as getActiveSessionManager, z as getCachedData, g as getConfig, j as getFiles, W as getIdToken, d as getMany, a as getWebhookKeysUrl, O as hasActiveConnection, P as increment, i as init, ag as invokeFunction, ao as isEffectResult, ai as live, aj as liveIntent, ak as liveStatus, a9 as normalizeEvmAddress, T as now, q as queryAggregate, A as reconnectWithNewAuth, a1 as refreshSession, a2 as revokeSession, m as runExpression, n as runExpressionMany, r as runQuery, l as runQueryMany, k as search, Q as serverTimestamp, s as set, h as setFile, f as setMany, t as signAndSubmitTransaction, o as signMessage, a3 as signSessionCreateMessage, p as signTransaction, w as subscribe, al as subscribeLiveView, C as subscribeQuery, aa as toLowerEvmAddress, V as toMillis, U as toSeconds, an as withEffects } from './index-qcTp-5Mt.mjs';
1
+ export { ae as BOUNDED_RUNTIME_SET_DOCUMENTS_ATTESTED_SELECTOR, ad as BOUNDED_RUNTIME_SET_DOCUMENTS_SELECTOR, H as BoundedDeclineError, L as DECLINE_EVENT_NAME, G as DEFAULT_QUERY_POLL_INTERVAL_MS, aq as EFFECT_INTENT_ADDRESS, ah as FunctionInvokeError, I as InsufficientBalanceError, am as LiveIntentError, M as MIN_QUERY_POLL_INTERVAL_MS, R as RATE_LIMIT_EVENT_NAME, Z as ReactNativeSessionManager, S as ScopedClientReadOnlyError, $ as ServerSessionManager, Y as WebSessionManager, v as aggregate, ab as assertCanonicalBoundedRuntimeWriteCalldata, ac as assertEvmTransactionMatchesWrite, a5 as buildSetDocumentsTransaction, E as canonicalizeArgs, F as canonicalizeResult, y as clearCache, D as closeAllQuerySubscriptions, x as closeAllSubscriptions, a6 as convertRemainingAccounts, u as count, B as createClient, a0 as createSessionWithSignature, ap as defineLiveModule, J as deriveDecline, X as deriveUserIdentityFromIdToken, K as dispatchDeclineEvent, N as dispatchRateLimitEvent, e as docId, af as functions, a4 as genAuthNonce, a8 as genSiweMessage, a7 as genSolanaMessage, c as get, _ as getActiveSessionManager, z as getCachedData, g as getConfig, j as getFiles, W as getIdToken, d as getMany, a as getWebhookKeysUrl, O as hasActiveConnection, P as increment, i as init, ag as invokeFunction, ao as isEffectResult, ai as live, aj as liveIntent, ak as liveStatus, a9 as normalizeEvmAddress, T as now, q as queryAggregate, A as reconnectWithNewAuth, a1 as refreshSession, a2 as revokeSession, m as runExpression, n as runExpressionMany, r as runQuery, l as runQueryMany, k as search, Q as serverTimestamp, s as set, h as setFile, f as setMany, t as signAndSubmitTransaction, o as signMessage, a3 as signSessionCreateMessage, p as signTransaction, w as subscribe, al as subscribeLiveView, C as subscribeQuery, aa as toLowerEvmAddress, V as toMillis, U as toSeconds, an as withEffects } from './index-BZhK47ZR.mjs';
2
2
  import 'axios';
3
3
  import 'tweetnacl';
4
4
  import 'reconnecting-websocket';
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- require('./index-BXvTkFw2.js');
3
+ require('./index-2e3ksoSR.js');
4
4
  require('axios');
5
5
  require('tweetnacl');
6
6
  require('reconnecting-websocket');
@@ -16,4 +16,4 @@ async function reconnectRealtimeStoreWithNewAuth() {
16
16
  }
17
17
 
18
18
  exports.reconnectRealtimeStoreWithNewAuth = reconnectRealtimeStoreWithNewAuth;
19
- //# sourceMappingURL=realtime-store-DmzboN-H.js.map
19
+ //# sourceMappingURL=realtime-store-Cp7xYNS3.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"realtime-store-DmzboN-H.js","sources":["../.rollup-tmp/client/realtime-store.js"],"sourcesContent":["// ---------------------------------------------------------------------------\n// realtime-store.ts — Client-side state manager for realtime apps.\n//\n// Manages: WS connection, in-memory state, IDB persistence, optimistic\n// writes, delta accumulation, loading states, ephemeral/durable tiers.\n// ---------------------------------------------------------------------------\nimport { getConfig, isBoundedNetwork } from './config';\n// ---------------------------------------------------------------------------\n// IDB helpers (lazy-loaded, non-blocking)\n// ---------------------------------------------------------------------------\nconst IDB_NAME = 'bounded-realtime';\nconst IDB_STORE = 'subscriptions';\nconst IDB_VERSION = 1;\nlet idbPromise = null;\nfunction getIDB() {\n if (idbPromise)\n return idbPromise;\n if (typeof indexedDB === 'undefined') {\n return Promise.reject(new Error('IndexedDB not available'));\n }\n idbPromise = new Promise((resolve, reject) => {\n const req = indexedDB.open(IDB_NAME, IDB_VERSION);\n req.onupgradeneeded = () => {\n const db = req.result;\n if (!db.objectStoreNames.contains(IDB_STORE)) {\n db.createObjectStore(IDB_STORE);\n }\n };\n req.onsuccess = () => resolve(req.result);\n req.onerror = () => reject(req.error);\n });\n return idbPromise;\n}\nasync function idbGet(key) {\n try {\n const db = await getIDB();\n return new Promise((resolve) => {\n const tx = db.transaction(IDB_STORE, 'readonly');\n const store = tx.objectStore(IDB_STORE);\n const req = store.get(key);\n req.onsuccess = () => { var _a; return resolve((_a = req.result) !== null && _a !== void 0 ? _a : null); };\n req.onerror = () => resolve(null);\n });\n }\n catch (_a) {\n return null;\n }\n}\nasync function idbSet(key, value) {\n try {\n const db = await getIDB();\n return new Promise((resolve) => {\n const tx = db.transaction(IDB_STORE, 'readwrite');\n const store = tx.objectStore(IDB_STORE);\n store.put(value, key);\n tx.oncomplete = () => resolve();\n tx.onerror = () => resolve();\n });\n }\n catch (_a) {\n // Best-effort persistence\n }\n}\nasync function idbDelete(key) {\n try {\n const db = await getIDB();\n return new Promise((resolve) => {\n const tx = db.transaction(IDB_STORE, 'readwrite');\n const store = tx.objectStore(IDB_STORE);\n store.delete(key);\n tx.oncomplete = () => resolve();\n tx.onerror = () => resolve();\n });\n }\n catch (_a) {\n // Best-effort\n }\n}\n// ---------------------------------------------------------------------------\n// RealtimeStore\n// ---------------------------------------------------------------------------\nlet nextRequestId = 1;\nfunction hashForKey(value) {\n let h = 5381;\n for (let i = 0; i < value.length; i++) {\n h = ((h << 5) + h + value.charCodeAt(i)) & 0x7fffffff;\n }\n return h.toString(36);\n}\nfunction principalFromToken(token) {\n return token ? `t${hashForKey(token)}` : 'anon';\n}\nexport class RealtimeStore {\n constructor() {\n this.ws = null;\n this.wsUrl = '';\n this.appId = '';\n this.subscriptions = new Map();\n this.pendingRequests = new Map();\n this.connectPromise = null;\n this.reconnectTimer = null;\n this.reconnectDelay = 1000;\n this.maxReconnectDelay = 30000;\n this.idbFlushTimer = null;\n this.idbDirtyKeys = new Set();\n this.closed = false;\n this.authToken = null;\n this.authPrincipalKey = 'anon';\n this.authenticating = false;\n this.suppressNextReconnect = false;\n this.isServer = false;\n this.tokenRefreshTimer = null;\n // -----------------------------------------------------------------------\n // WebSocket connection\n // -----------------------------------------------------------------------\n this.initPromise = null;\n }\n // -----------------------------------------------------------------------\n // Initialization\n // -----------------------------------------------------------------------\n async init() {\n const config = await getConfig();\n this.appId = config.appId;\n this.wsUrl = config.wsApiUrl;\n this.isServer = config.isServer;\n await this.refreshToken();\n this.startTokenRefresh();\n }\n async refreshToken() {\n let token = null;\n try {\n const { getIdToken } = await import('../utils/utils');\n token = await getIdToken(this.isServer);\n }\n catch ( /* no auth available */_a) { /* no auth available */ }\n this.authToken = token !== null && token !== void 0 ? token : null;\n this.authPrincipalKey = principalFromToken(this.authToken);\n }\n startTokenRefresh() {\n if (this.tokenRefreshTimer)\n return;\n this.tokenRefreshTimer = setInterval(async () => {\n const prevPrincipal = this.authPrincipalKey;\n await this.refreshToken();\n if (this.authPrincipalKey !== prevPrincipal) {\n await this.applyAuthPrincipalChange();\n if (this.subscriptions.size > 0) {\n await this.ensureConnected().catch(() => {\n this.setAllSubscriptionStatus('error');\n });\n }\n }\n }, 5 * 60 * 1000); // Check every 5 minutes\n }\n async ensureInitialized() {\n if (this.appId)\n return;\n if (!this.initPromise)\n this.initPromise = this.init();\n await this.initPromise;\n }\n async ensureCurrentAuth() {\n await this.ensureInitialized();\n const prevPrincipal = this.authPrincipalKey;\n await this.refreshToken();\n if (this.authPrincipalKey !== prevPrincipal) {\n await this.applyAuthPrincipalChange();\n }\n }\n rekeySubscriptionsForPrincipal() {\n const subs = Array.from(this.subscriptions.values());\n this.subscriptions.clear();\n for (const sub of subs) {\n this.subscriptions.set(this.getSubKey(sub.path, sub.options), sub);\n }\n }\n async applyAuthPrincipalChange() {\n if (this.idbFlushTimer) {\n clearTimeout(this.idbFlushTimer);\n this.idbFlushTimer = null;\n }\n this.idbDirtyKeys.clear();\n this.rekeySubscriptionsForPrincipal();\n for (const sub of this.subscriptions.values()) {\n sub.docs.clear();\n sub.ref.current = sub.docs;\n sub.error = null;\n sub.isStale = false;\n let loaded = false;\n if (this.shouldUseIdb(sub.tier, sub.options)) {\n const cached = await idbGet(this.idbKey(sub.path));\n if (cached && Array.isArray(cached)) {\n for (const doc of cached) {\n if (doc && doc._id)\n sub.docs.set(doc._id, doc);\n }\n sub.ref.current = sub.docs;\n loaded = sub.docs.size > 0;\n }\n }\n sub.status = loaded ? 'cached' : 'loading';\n sub.isStale = loaded;\n if (loaded)\n this.notifySubscription(sub);\n else\n this.notifyState(sub);\n }\n if (this.ws) {\n const ws = this.ws;\n this.ws = null;\n this.connectPromise = null;\n this.suppressNextReconnect = true;\n try {\n ws.close(1000, 'Auth changed');\n }\n catch ( /* ignore */_a) { /* ignore */ }\n }\n }\n async ensureConnected() {\n var _a;\n await this.ensureCurrentAuth();\n if (((_a = this.ws) === null || _a === void 0 ? void 0 : _a.readyState) === WebSocket.OPEN && !this.authenticating)\n return;\n if (this.connectPromise)\n return this.connectPromise;\n this.connectPromise = this.connect();\n return this.connectPromise;\n }\n connect() {\n return new Promise((resolve, reject) => {\n if (this.closed) {\n reject(new Error('Store closed'));\n return;\n }\n const params = new URLSearchParams();\n params.set('appId', this.appId);\n const url = `${this.wsUrl}?${params.toString()}`;\n const ws = new WebSocket(url);\n this.ws = ws;\n let authTimer = null;\n const finishConnected = () => {\n if (authTimer) {\n clearTimeout(authTimer);\n authTimer = null;\n }\n this.authenticating = false;\n ws.removeEventListener('error', onError);\n this.reconnectDelay = 1000;\n this.connectPromise = null;\n this.resubscribeAll();\n resolve();\n };\n const onOpen = () => {\n if (!this.authToken) {\n finishConnected();\n return;\n }\n this.authenticating = true;\n authTimer = setTimeout(() => {\n this.authenticating = false;\n this.connectPromise = null;\n try {\n ws.close(1008, 'Authentication timeout');\n }\n catch ( /* ignore */_a) { /* ignore */ }\n reject(new Error('WebSocket authentication timeout'));\n }, 10000);\n try {\n ws.send(JSON.stringify({ type: 'auth', token: this.authToken }));\n }\n catch (e) {\n if (authTimer)\n clearTimeout(authTimer);\n this.authenticating = false;\n this.connectPromise = null;\n reject(e);\n }\n };\n const onError = (e) => {\n if (authTimer)\n clearTimeout(authTimer);\n this.authenticating = false;\n ws.removeEventListener('open', onOpen);\n this.connectPromise = null;\n reject(new Error('WebSocket connection failed'));\n };\n ws.addEventListener('open', onOpen, { once: true });\n ws.addEventListener('error', onError, { once: true });\n ws.addEventListener('message', (event) => {\n if (this.authenticating) {\n try {\n const msg = JSON.parse(typeof event.data === 'string' ? event.data : new TextDecoder().decode(event.data));\n if ((msg === null || msg === void 0 ? void 0 : msg.type) === 'authenticated') {\n finishConnected();\n return;\n }\n }\n catch ( /* fall through to normal handling */_a) { /* fall through to normal handling */ }\n }\n this.handleMessage(event.data);\n });\n ws.addEventListener('close', () => {\n if (authTimer)\n clearTimeout(authTimer);\n if (this.ws !== ws) {\n if (this.suppressNextReconnect)\n this.suppressNextReconnect = false;\n return;\n }\n this.authenticating = false;\n this.ws = null;\n this.connectPromise = null;\n this.rejectAllPending('WebSocket closed');\n this.setAllSubscriptionStatus('reconnecting');\n if (this.suppressNextReconnect) {\n this.suppressNextReconnect = false;\n return;\n }\n this.scheduleReconnect();\n });\n });\n }\n scheduleReconnect() {\n if (this.closed)\n return;\n if (this.reconnectTimer)\n clearTimeout(this.reconnectTimer);\n this.reconnectTimer = setTimeout(() => {\n this.ensureConnected().catch(() => {\n this.reconnectDelay = Math.min(this.reconnectDelay * 2, this.maxReconnectDelay);\n this.scheduleReconnect();\n });\n }, this.reconnectDelay);\n }\n resubscribeAll() {\n for (const sub of this.subscriptions.values()) {\n this.sendSubscribe(sub);\n }\n }\n // -----------------------------------------------------------------------\n // Message handling\n // -----------------------------------------------------------------------\n handleMessage(raw) {\n const text = typeof raw === 'string' ? raw : new TextDecoder().decode(raw);\n let msg;\n try {\n msg = JSON.parse(text);\n }\n catch (_a) {\n return;\n }\n switch (msg.type) {\n case 'snapshot':\n this.handleSnapshot(msg);\n break;\n case 'delta':\n this.handleDelta(msg);\n break;\n case 'result':\n this.handleResult(msg);\n break;\n case 'error':\n this.handleError(msg);\n break;\n case 'pong':\n break;\n case 'authenticated':\n break;\n // v1 compat: handle legacy message types during transition\n case 'subscribed':\n this.handleSnapshot(Object.assign(Object.assign({}, msg), { type: 'snapshot', docs: msg.data }));\n break;\n case 'data':\n // Legacy full-snapshot delta — treat as snapshot replacement\n this.handleLegacyData(msg);\n break;\n case 'response':\n this.handleResult(Object.assign(Object.assign({}, msg), { type: 'result', ok: msg.status === 200, doc: msg.data }));\n break;\n }\n }\n handleSnapshot(msg) {\n var _a, _b, _c;\n const subId = (_a = msg.id) !== null && _a !== void 0 ? _a : msg.subscriptionId;\n if (!subId)\n return;\n const sub = this.findSubscriptionById(subId);\n if (!sub)\n return;\n const docs = (_c = (_b = msg.docs) !== null && _b !== void 0 ? _b : msg.data) !== null && _c !== void 0 ? _c : [];\n const docsArray = Array.isArray(docs) ? docs : [docs];\n sub.docs.clear();\n for (const doc of docsArray) {\n if (doc && doc._id) {\n sub.docs.set(doc._id, doc);\n }\n }\n sub.ref.current = sub.docs;\n sub.status = 'live';\n sub.isStale = false;\n sub.error = null;\n this.notifySubscription(sub);\n this.markIdbDirty(sub.path);\n }\n handleDelta(msg) {\n var _a, _b;\n const subId = (_a = msg.id) !== null && _a !== void 0 ? _a : msg.subscriptionId;\n if (!subId)\n return;\n const sub = this.findSubscriptionById(subId);\n if (!sub)\n return;\n if (sub.tier === 'ephemeral') {\n // Ephemeral: just overwrite, no accumulation logic\n if (msg.change === 'removed' && msg.docId) {\n sub.docs.delete(msg.docId);\n }\n else if (msg.doc && msg.doc._id) {\n sub.docs.set(msg.doc._id, msg.doc);\n }\n sub.ref.current = sub.docs;\n if (sub.options.mode !== 'ref') {\n this.notifySubscription(sub);\n }\n return;\n }\n // Durable/checkpointed: full delta handling\n switch (msg.change) {\n case 'added':\n case 'modified':\n if (msg.doc && msg.doc._id) {\n sub.docs.set(msg.doc._id, msg.doc);\n }\n break;\n case 'removed':\n if (msg.docId) {\n sub.docs.delete(msg.docId);\n }\n else if ((_b = msg.doc) === null || _b === void 0 ? void 0 : _b._id) {\n sub.docs.delete(msg.doc._id);\n }\n break;\n }\n sub.ref.current = sub.docs;\n this.notifySubscription(sub);\n this.markIdbDirty(sub.path);\n }\n handleLegacyData(msg) {\n // Legacy v1 format: 'data' message with full snapshot or single doc\n const subId = msg.subscriptionId;\n if (!subId)\n return;\n const sub = this.findSubscriptionById(subId);\n if (!sub)\n return;\n if (Array.isArray(msg.data)) {\n // Full snapshot replacement\n sub.docs.clear();\n for (const doc of msg.data) {\n if (doc && doc._id)\n sub.docs.set(doc._id, doc);\n }\n }\n else if (msg.data && msg.data._id) {\n // Single doc update\n sub.docs.set(msg.data._id, msg.data);\n }\n else if (msg.data === null) {\n // Removal — but we don't know which doc. Re-fetch needed.\n }\n sub.ref.current = sub.docs;\n sub.status = 'live';\n sub.isStale = false;\n this.notifySubscription(sub);\n this.markIdbDirty(sub.path);\n }\n handleResult(msg) {\n var _a, _b, _c, _d;\n const requestId = msg.requestId;\n if (!requestId)\n return;\n const pending = this.pendingRequests.get(requestId);\n if (!pending)\n return;\n this.pendingRequests.delete(requestId);\n clearTimeout(pending.timeout);\n const ok = (_a = msg.ok) !== null && _a !== void 0 ? _a : (msg.status === 200);\n if (ok) {\n pending.resolve((_c = (_b = msg.doc) !== null && _b !== void 0 ? _b : msg.data) !== null && _c !== void 0 ? _c : true);\n }\n else {\n pending.reject(new Error((_d = msg.error) !== null && _d !== void 0 ? _d : 'Operation failed'));\n }\n }\n handleError(msg) {\n var _a, _b, _c;\n const error = new Error((_a = msg.message) !== null && _a !== void 0 ? _a : (msg.code ? `${msg.code}: Server error` : 'Server error'));\n if (msg.code)\n error.code = msg.code;\n if (msg.subscriptionId || msg.id)\n error.subscriptionId = (_b = msg.subscriptionId) !== null && _b !== void 0 ? _b : msg.id;\n const requestId = msg.requestId;\n if (requestId) {\n const pending = this.pendingRequests.get(requestId);\n if (pending) {\n this.pendingRequests.delete(requestId);\n clearTimeout(pending.timeout);\n pending.reject(error);\n }\n }\n const subId = (_c = msg.subscriptionId) !== null && _c !== void 0 ? _c : msg.id;\n if (subId) {\n const sub = this.findSubscriptionById(subId);\n if (sub) {\n sub.status = 'error';\n sub.error = error;\n this.notifyState(sub);\n for (const callback of Array.from(sub.errorCallbacks)) {\n try {\n callback(error);\n }\n catch ( /* swallow */_d) { /* swallow */ }\n }\n }\n }\n }\n // -----------------------------------------------------------------------\n // Subscribe\n // -----------------------------------------------------------------------\n async subscribe(path, opts = {}) {\n var _a;\n await this.ensureCurrentAuth();\n const tier = (_a = opts.tier) !== null && _a !== void 0 ? _a : 'durable';\n const subKey = this.getSubKey(path, opts);\n let sub = this.subscriptions.get(subKey);\n if (sub) {\n // Existing subscription — add callback\n if (opts.onData)\n sub.callbacks.add(opts.onData);\n if (opts.onState)\n sub.stateCallbacks.add(opts.onState);\n if (opts.onError)\n sub.errorCallbacks.add(opts.onError);\n if (opts.cache && !sub.options.cache) {\n sub.options = Object.assign(Object.assign({}, sub.options), { cache: true });\n }\n // Immediately deliver current state\n if (opts.onData && sub.docs.size > 0) {\n opts.onData(this.docsToArray(sub));\n }\n if (opts.onState) {\n opts.onState(this.getState(sub));\n }\n return this.createUnsubscribe(subKey, sub.id, opts.onData, opts.onState, opts.onError);\n }\n // New subscription\n const subId = `sub_${nextRequestId++}`;\n sub = {\n id: subId,\n path,\n tier,\n options: opts,\n docs: new Map(),\n status: 'idle',\n isStale: false,\n error: null,\n callbacks: new Set(opts.onData ? [opts.onData] : []),\n stateCallbacks: new Set(opts.onState ? [opts.onState] : []),\n errorCallbacks: new Set(opts.onError ? [opts.onError] : []),\n ref: { current: new Map() },\n };\n this.subscriptions.set(subKey, sub);\n // Step 1: Load from IDB only when explicitly enabled and principal-bound.\n if (this.shouldUseIdb(tier, opts)) {\n const cached = await idbGet(this.idbKey(path));\n if (cached && Array.isArray(cached)) {\n for (const doc of cached) {\n if (doc && doc._id)\n sub.docs.set(doc._id, doc);\n }\n sub.ref.current = sub.docs;\n sub.status = 'cached';\n sub.isStale = true;\n this.notifySubscription(sub);\n }\n }\n // Step 2: Connect and subscribe via WS\n sub.status = sub.docs.size > 0 ? 'cached' : 'loading';\n this.notifyState(sub);\n try {\n await this.ensureConnected();\n this.sendSubscribe(sub);\n }\n catch (_b) {\n sub.status = 'error';\n sub.error = new Error('Connection failed');\n this.notifyState(sub);\n }\n return this.createUnsubscribe(subKey, sub.id, opts.onData, opts.onState, opts.onError);\n }\n getRef(path, opts = {}) {\n var _a;\n const subKey = this.getSubKey(path, opts);\n const sub = this.subscriptions.get(subKey);\n if (sub)\n return sub.ref;\n // Auto-subscribe in ref mode\n const ref = { current: new Map() };\n this.subscribe(path, Object.assign(Object.assign({}, opts), { mode: 'ref', tier: 'ephemeral' })).catch(() => { });\n const newSub = this.subscriptions.get(this.getSubKey(path, Object.assign(Object.assign({}, opts), { tier: 'ephemeral' })));\n return (_a = newSub === null || newSub === void 0 ? void 0 : newSub.ref) !== null && _a !== void 0 ? _a : ref;\n }\n // -----------------------------------------------------------------------\n // CRUD operations\n // -----------------------------------------------------------------------\n async set(path, doc) {\n var _a;\n await this.ensureConnected();\n // Resolve operations (Increment, Time.Now) client-side for optimistic update\n const resolvedDoc = this.resolveOperations(doc, path);\n // Optimistic update: apply to local state immediately\n const normalizedPath = path.startsWith('/') ? path.slice(1) : path;\n const collectionPath = this.getCollectionPath(normalizedPath);\n const optimisticDoc = Object.assign(Object.assign({ _id: normalizedPath, pathId: normalizedPath }, resolvedDoc), { \n // System timestamp field name: the Bounded worker stamps the neutral\n // `_updatedAt`; the underscore-prefixed `_updated_at` metadata mirror.\n // Match it so the optimistic doc lines up with the server's confirmation.\n [isBoundedNetwork() ? '_updatedAt' : '_updated_at']: Date.now() });\n const sub = this.findSubscriptionByPath(collectionPath);\n let prevDoc = null;\n if (sub) {\n prevDoc = (_a = sub.docs.get(normalizedPath)) !== null && _a !== void 0 ? _a : null;\n sub.docs.set(normalizedPath, optimisticDoc);\n sub.ref.current = sub.docs;\n this.notifySubscription(sub);\n }\n // Send to server\n const requestId = `r_${nextRequestId++}`;\n try {\n const result = await this.sendRequest(requestId, {\n type: 'set',\n requestId,\n documents: [{ destinationPath: normalizedPath, document: doc }],\n });\n // Replace optimistic doc with server-confirmed version\n if (sub && result && typeof result === 'object') {\n const serverDoc = Array.isArray(result) ? result[0] : result;\n if (serverDoc && serverDoc._id) {\n sub.docs.set(serverDoc._id, serverDoc);\n sub.ref.current = sub.docs;\n this.notifySubscription(sub);\n this.markIdbDirty(collectionPath);\n }\n }\n return Array.isArray(result) ? result[0] : result;\n }\n catch (err) {\n // Revert optimistic update\n if (sub) {\n if (prevDoc) {\n sub.docs.set(normalizedPath, prevDoc);\n }\n else {\n sub.docs.delete(normalizedPath);\n }\n sub.ref.current = sub.docs;\n this.notifySubscription(sub);\n }\n throw err;\n }\n }\n async get(path) {\n await this.ensureCurrentAuth();\n const normalizedPath = path.startsWith('/') ? path.slice(1) : path;\n // Check local subscriptions first\n const collectionPath = this.getCollectionPath(normalizedPath);\n const sub = this.findSubscriptionByPath(collectionPath);\n if (sub && sub.status === 'live') {\n const doc = sub.docs.get(normalizedPath);\n return doc !== null && doc !== void 0 ? doc : null;\n }\n // One-shot WS fetch\n await this.ensureConnected();\n const requestId = `r_${nextRequestId++}`;\n return this.sendRequest(requestId, {\n type: 'get',\n requestId,\n path: normalizedPath,\n });\n }\n async getMany(paths) {\n await this.ensureConnected();\n const normalizedPaths = paths.map(p => p.startsWith('/') ? p.slice(1) : p);\n const requestId = `r_${nextRequestId++}`;\n return this.sendRequest(requestId, {\n type: 'getMany',\n requestId,\n paths: normalizedPaths,\n });\n }\n async delete(path) {\n var _a;\n await this.ensureConnected();\n const normalizedPath = path.startsWith('/') ? path.slice(1) : path;\n // Optimistic: remove from local state\n const collectionPath = this.getCollectionPath(normalizedPath);\n const sub = this.findSubscriptionByPath(collectionPath);\n let prevDoc = null;\n if (sub) {\n prevDoc = (_a = sub.docs.get(normalizedPath)) !== null && _a !== void 0 ? _a : null;\n sub.docs.delete(normalizedPath);\n sub.ref.current = sub.docs;\n this.notifySubscription(sub);\n }\n const requestId = `r_${nextRequestId++}`;\n try {\n await this.sendRequest(requestId, {\n type: 'delete',\n requestId,\n path: normalizedPath,\n });\n if (sub)\n this.markIdbDirty(collectionPath);\n }\n catch (err) {\n // Revert\n if (sub && prevDoc) {\n sub.docs.set(normalizedPath, prevDoc);\n sub.ref.current = sub.docs;\n this.notifySubscription(sub);\n }\n throw err;\n }\n }\n async query(path, opts) {\n await this.ensureConnected();\n const normalizedPath = path.startsWith('/') ? path.slice(1) : path;\n const requestId = `r_${nextRequestId++}`;\n return this.sendRequest(requestId, Object.assign(Object.assign(Object.assign(Object.assign({ type: 'query', requestId, path: normalizedPath }, ((opts === null || opts === void 0 ? void 0 : opts.filter) ? { filter: opts.filter } : {})), ((opts === null || opts === void 0 ? void 0 : opts.sort) ? { sort: opts.sort } : {})), ((opts === null || opts === void 0 ? void 0 : opts.limit) !== undefined ? { limit: opts.limit } : {})), ((opts === null || opts === void 0 ? void 0 : opts.includeSubPaths) ? { includeSubPaths: true } : {})));\n }\n async count(path) {\n var _a;\n await this.ensureConnected();\n const normalizedPath = path.startsWith('/') ? path.slice(1) : path;\n const requestId = `r_${nextRequestId++}`;\n const result = await this.sendRequest(requestId, {\n type: 'count',\n requestId,\n path: normalizedPath,\n });\n return typeof result === 'number' ? result : ((_a = result === null || result === void 0 ? void 0 : result.value) !== null && _a !== void 0 ? _a : 0);\n }\n async aggregate(path, operation, opts) {\n await this.ensureConnected();\n const normalizedPath = path.startsWith('/') ? path.slice(1) : path;\n const requestId = `r_${nextRequestId++}`;\n return this.sendRequest(requestId, Object.assign({ type: 'aggregate', requestId, path: normalizedPath, operation }, ((opts === null || opts === void 0 ? void 0 : opts.field) ? { field: opts.field } : {})));\n }\n // -----------------------------------------------------------------------\n // Helpers\n // -----------------------------------------------------------------------\n sendSubscribe(sub) {\n if (!this.ws || this.ws.readyState !== WebSocket.OPEN)\n return;\n const msg = {\n type: 'subscribe',\n subscriptionId: sub.id,\n path: sub.path,\n };\n if (sub.options.filter)\n msg.filter = sub.options.filter;\n if (sub.options.includeSubPaths)\n msg.includeSubPaths = true;\n if (sub.options.limit)\n msg.limit = sub.options.limit;\n if (sub.options.prompt)\n msg.prompt = sub.options.prompt;\n this.ws.send(JSON.stringify(msg));\n }\n sendRequest(requestId, msg) {\n return new Promise((resolve, reject) => {\n const timeout = setTimeout(() => {\n this.pendingRequests.delete(requestId);\n reject(new Error('Request timed out'));\n }, 30000);\n this.pendingRequests.set(requestId, { resolve, reject, timeout });\n if (this.ws && this.ws.readyState === WebSocket.OPEN) {\n this.ws.send(JSON.stringify(msg));\n }\n else {\n this.pendingRequests.delete(requestId);\n clearTimeout(timeout);\n reject(new Error('WebSocket not connected'));\n }\n });\n }\n notifySubscription(sub) {\n const data = this.docsToArray(sub);\n const callbacks = Array.from(sub.callbacks);\n for (const cb of callbacks) {\n try {\n cb(data);\n }\n catch ( /* swallow callback errors */_a) { /* swallow callback errors */ }\n }\n this.notifyState(sub);\n }\n notifyState(sub) {\n const state = this.getState(sub);\n const callbacks = Array.from(sub.stateCallbacks);\n for (const cb of callbacks) {\n try {\n cb(state);\n }\n catch ( /* swallow */_a) { /* swallow */ }\n }\n }\n getState(sub) {\n return {\n data: this.docsToArray(sub),\n status: sub.status,\n isStale: sub.isStale,\n error: sub.error,\n };\n }\n docsToArray(sub) {\n return Array.from(sub.docs.values());\n }\n findSubscriptionById(id) {\n for (const sub of this.subscriptions.values()) {\n if (sub.id === id)\n return sub;\n }\n return undefined;\n }\n findSubscriptionByPath(collectionPath) {\n for (const sub of this.subscriptions.values()) {\n const subPath = sub.path.startsWith('/') ? sub.path.slice(1) : sub.path;\n if (subPath === collectionPath)\n return sub;\n if (collectionPath.startsWith(subPath + '/'))\n return sub;\n }\n return undefined;\n }\n getCollectionPath(docPath) {\n const segments = docPath.split('/');\n if (segments.length % 2 === 0) {\n return segments.slice(0, -1).join('/');\n }\n return docPath;\n }\n getSubKey(path, opts) {\n const parts = [this.appId, this.authPrincipalKey, path];\n if (opts.filter)\n parts.push(JSON.stringify(opts.filter));\n if (opts.prompt)\n parts.push(opts.prompt);\n if (opts.tier)\n parts.push(opts.tier);\n return parts.join('::');\n }\n idbKey(path) {\n return `${this.appId}:${this.authPrincipalKey}:${path}`;\n }\n shouldUseIdb(tier, opts) {\n return opts.cache === true && tier !== 'ephemeral' && this.authPrincipalKey !== 'anon';\n }\n markIdbDirty(path) {\n const sub = this.findSubscriptionByPath(path);\n if (!sub || !this.shouldUseIdb(sub.tier, sub.options))\n return;\n this.idbDirtyKeys.add(path);\n if (!this.idbFlushTimer) {\n this.idbFlushTimer = setTimeout(() => {\n this.flushIdb();\n this.idbFlushTimer = null;\n }, 500);\n }\n }\n async flushIdb() {\n const keys = Array.from(this.idbDirtyKeys);\n this.idbDirtyKeys.clear();\n for (const path of keys) {\n const sub = this.findSubscriptionByPath(path);\n if (sub && this.shouldUseIdb(sub.tier, sub.options)) {\n const docs = this.docsToArray(sub);\n await idbSet(this.idbKey(path), docs);\n }\n }\n }\n createUnsubscribe(subKey, subId, onData, onState, onError) {\n return async () => {\n var _a;\n const sub = (_a = this.subscriptions.get(subKey)) !== null && _a !== void 0 ? _a : this.findSubscriptionById(subId);\n if (!sub)\n return;\n const currentSubKey = this.getSubKey(sub.path, sub.options);\n if (onData)\n sub.callbacks.delete(onData);\n if (onState)\n sub.stateCallbacks.delete(onState);\n if (onError)\n sub.errorCallbacks.delete(onError);\n // If no more callbacks, unsubscribe entirely\n if (sub.callbacks.size === 0 && sub.stateCallbacks.size === 0 && sub.errorCallbacks.size === 0) {\n this.subscriptions.delete(subKey);\n this.subscriptions.delete(currentSubKey);\n if (this.ws && this.ws.readyState === WebSocket.OPEN) {\n this.ws.send(JSON.stringify({\n type: 'unsubscribe',\n subscriptionId: sub.id,\n }));\n }\n }\n };\n }\n resolveOperations(doc, path) {\n var _a;\n if (!doc || typeof doc !== 'object')\n return doc;\n const resolved = {};\n for (const [key, value] of Object.entries(doc)) {\n if (value && typeof value === 'object' && !Array.isArray(value) && value.operation) {\n const op = value;\n if (op.operation === 'time' && op.value === 'now') {\n resolved[key] = Math.floor(Date.now() / 1000);\n }\n else if (op.operation === 'increment') {\n // For optimistic: get current value and add\n const normalizedPath = path.startsWith('/') ? path.slice(1) : path;\n const collectionPath = this.getCollectionPath(normalizedPath);\n const sub = this.findSubscriptionByPath(collectionPath);\n const existing = sub === null || sub === void 0 ? void 0 : sub.docs.get(normalizedPath);\n const current = (_a = existing === null || existing === void 0 ? void 0 : existing[key]) !== null && _a !== void 0 ? _a : 0;\n resolved[key] = (typeof current === 'number' ? current : 0) + op.value;\n }\n else {\n resolved[key] = value;\n }\n }\n else {\n resolved[key] = value;\n }\n }\n return resolved;\n }\n rejectAllPending(reason) {\n for (const [requestId, pending] of this.pendingRequests) {\n clearTimeout(pending.timeout);\n pending.reject(new Error(reason));\n }\n this.pendingRequests.clear();\n }\n setAllSubscriptionStatus(status) {\n for (const sub of this.subscriptions.values()) {\n sub.status = status;\n this.notifyState(sub);\n }\n }\n // -----------------------------------------------------------------------\n // Lifecycle\n // -----------------------------------------------------------------------\n close() {\n this.closed = true;\n if (this.reconnectTimer)\n clearTimeout(this.reconnectTimer);\n if (this.idbFlushTimer)\n clearTimeout(this.idbFlushTimer);\n if (this.tokenRefreshTimer)\n clearInterval(this.tokenRefreshTimer);\n this.flushIdb();\n if (this.ws) {\n this.ws.close(1000, 'Store closed');\n this.ws = null;\n }\n this.rejectAllPending('Store closed');\n this.subscriptions.clear();\n }\n async reconnectWithNewAuth() {\n if (this.closed)\n return;\n await this.ensureInitialized();\n await this.refreshToken();\n await this.applyAuthPrincipalChange();\n if (this.subscriptions.size > 0) {\n await this.ensureConnected().catch((error) => {\n this.setAllSubscriptionStatus('error');\n for (const sub of this.subscriptions.values()) {\n sub.error = error instanceof Error ? error : new Error(String(error));\n this.notifyState(sub);\n }\n });\n }\n }\n}\n// ---------------------------------------------------------------------------\n// Singleton instance\n// ---------------------------------------------------------------------------\nlet storeInstance = null;\nexport function getRealtimeStore() {\n if (!storeInstance) {\n storeInstance = new RealtimeStore();\n }\n return storeInstance;\n}\nexport function resetRealtimeStore() {\n if (storeInstance) {\n storeInstance.close();\n storeInstance = null;\n }\n}\nexport async function reconnectRealtimeStoreWithNewAuth() {\n if (storeInstance) {\n await storeInstance.reconnectWithNewAuth();\n }\n}\n"],"names":[],"mappings":";;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AA++BO,eAAe,iCAAiC,GAAG;AAI1D;;;;"}
1
+ {"version":3,"file":"realtime-store-Cp7xYNS3.js","sources":["../.rollup-tmp/client/realtime-store.js"],"sourcesContent":["// ---------------------------------------------------------------------------\n// realtime-store.ts — Client-side state manager for realtime apps.\n//\n// Manages: WS connection, in-memory state, IDB persistence, optimistic\n// writes, delta accumulation, loading states, ephemeral/durable tiers.\n// ---------------------------------------------------------------------------\nimport { getConfig, isBoundedNetwork } from './config';\n// ---------------------------------------------------------------------------\n// IDB helpers (lazy-loaded, non-blocking)\n// ---------------------------------------------------------------------------\nconst IDB_NAME = 'bounded-realtime';\nconst IDB_STORE = 'subscriptions';\nconst IDB_VERSION = 1;\nlet idbPromise = null;\nfunction getIDB() {\n if (idbPromise)\n return idbPromise;\n if (typeof indexedDB === 'undefined') {\n return Promise.reject(new Error('IndexedDB not available'));\n }\n idbPromise = new Promise((resolve, reject) => {\n const req = indexedDB.open(IDB_NAME, IDB_VERSION);\n req.onupgradeneeded = () => {\n const db = req.result;\n if (!db.objectStoreNames.contains(IDB_STORE)) {\n db.createObjectStore(IDB_STORE);\n }\n };\n req.onsuccess = () => resolve(req.result);\n req.onerror = () => reject(req.error);\n });\n return idbPromise;\n}\nasync function idbGet(key) {\n try {\n const db = await getIDB();\n return new Promise((resolve) => {\n const tx = db.transaction(IDB_STORE, 'readonly');\n const store = tx.objectStore(IDB_STORE);\n const req = store.get(key);\n req.onsuccess = () => { var _a; return resolve((_a = req.result) !== null && _a !== void 0 ? _a : null); };\n req.onerror = () => resolve(null);\n });\n }\n catch (_a) {\n return null;\n }\n}\nasync function idbSet(key, value) {\n try {\n const db = await getIDB();\n return new Promise((resolve) => {\n const tx = db.transaction(IDB_STORE, 'readwrite');\n const store = tx.objectStore(IDB_STORE);\n store.put(value, key);\n tx.oncomplete = () => resolve();\n tx.onerror = () => resolve();\n });\n }\n catch (_a) {\n // Best-effort persistence\n }\n}\nasync function idbDelete(key) {\n try {\n const db = await getIDB();\n return new Promise((resolve) => {\n const tx = db.transaction(IDB_STORE, 'readwrite');\n const store = tx.objectStore(IDB_STORE);\n store.delete(key);\n tx.oncomplete = () => resolve();\n tx.onerror = () => resolve();\n });\n }\n catch (_a) {\n // Best-effort\n }\n}\n// ---------------------------------------------------------------------------\n// RealtimeStore\n// ---------------------------------------------------------------------------\nlet nextRequestId = 1;\nfunction hashForKey(value) {\n let h = 5381;\n for (let i = 0; i < value.length; i++) {\n h = ((h << 5) + h + value.charCodeAt(i)) & 0x7fffffff;\n }\n return h.toString(36);\n}\nfunction principalFromToken(token) {\n return token ? `t${hashForKey(token)}` : 'anon';\n}\nexport class RealtimeStore {\n constructor() {\n this.ws = null;\n this.wsUrl = '';\n this.appId = '';\n this.subscriptions = new Map();\n this.pendingRequests = new Map();\n this.connectPromise = null;\n this.reconnectTimer = null;\n this.reconnectDelay = 1000;\n this.maxReconnectDelay = 30000;\n this.idbFlushTimer = null;\n this.idbDirtyKeys = new Set();\n this.closed = false;\n this.authToken = null;\n this.authPrincipalKey = 'anon';\n this.authenticating = false;\n this.suppressNextReconnect = false;\n this.isServer = false;\n this.tokenRefreshTimer = null;\n // -----------------------------------------------------------------------\n // WebSocket connection\n // -----------------------------------------------------------------------\n this.initPromise = null;\n }\n // -----------------------------------------------------------------------\n // Initialization\n // -----------------------------------------------------------------------\n async init() {\n const config = await getConfig();\n this.appId = config.appId;\n this.wsUrl = config.wsApiUrl;\n this.isServer = config.isServer;\n await this.refreshToken();\n this.startTokenRefresh();\n }\n async refreshToken() {\n let token = null;\n try {\n const { getIdToken } = await import('../utils/utils');\n token = await getIdToken(this.isServer);\n }\n catch ( /* no auth available */_a) { /* no auth available */ }\n this.authToken = token !== null && token !== void 0 ? token : null;\n this.authPrincipalKey = principalFromToken(this.authToken);\n }\n startTokenRefresh() {\n if (this.tokenRefreshTimer)\n return;\n this.tokenRefreshTimer = setInterval(async () => {\n const prevPrincipal = this.authPrincipalKey;\n await this.refreshToken();\n if (this.authPrincipalKey !== prevPrincipal) {\n await this.applyAuthPrincipalChange();\n if (this.subscriptions.size > 0) {\n await this.ensureConnected().catch(() => {\n this.setAllSubscriptionStatus('error');\n });\n }\n }\n }, 5 * 60 * 1000); // Check every 5 minutes\n }\n async ensureInitialized() {\n if (this.appId)\n return;\n if (!this.initPromise)\n this.initPromise = this.init();\n await this.initPromise;\n }\n async ensureCurrentAuth() {\n await this.ensureInitialized();\n const prevPrincipal = this.authPrincipalKey;\n await this.refreshToken();\n if (this.authPrincipalKey !== prevPrincipal) {\n await this.applyAuthPrincipalChange();\n }\n }\n rekeySubscriptionsForPrincipal() {\n const subs = Array.from(this.subscriptions.values());\n this.subscriptions.clear();\n for (const sub of subs) {\n this.subscriptions.set(this.getSubKey(sub.path, sub.options), sub);\n }\n }\n async applyAuthPrincipalChange() {\n if (this.idbFlushTimer) {\n clearTimeout(this.idbFlushTimer);\n this.idbFlushTimer = null;\n }\n this.idbDirtyKeys.clear();\n this.rekeySubscriptionsForPrincipal();\n for (const sub of this.subscriptions.values()) {\n sub.docs.clear();\n sub.ref.current = sub.docs;\n sub.error = null;\n sub.isStale = false;\n let loaded = false;\n if (this.shouldUseIdb(sub.tier, sub.options)) {\n const cached = await idbGet(this.idbKey(sub.path));\n if (cached && Array.isArray(cached)) {\n for (const doc of cached) {\n if (doc && doc._id)\n sub.docs.set(doc._id, doc);\n }\n sub.ref.current = sub.docs;\n loaded = sub.docs.size > 0;\n }\n }\n sub.status = loaded ? 'cached' : 'loading';\n sub.isStale = loaded;\n if (loaded)\n this.notifySubscription(sub);\n else\n this.notifyState(sub);\n }\n if (this.ws) {\n const ws = this.ws;\n this.ws = null;\n this.connectPromise = null;\n this.suppressNextReconnect = true;\n try {\n ws.close(1000, 'Auth changed');\n }\n catch ( /* ignore */_a) { /* ignore */ }\n }\n }\n async ensureConnected() {\n var _a;\n await this.ensureCurrentAuth();\n if (((_a = this.ws) === null || _a === void 0 ? void 0 : _a.readyState) === WebSocket.OPEN && !this.authenticating)\n return;\n if (this.connectPromise)\n return this.connectPromise;\n this.connectPromise = this.connect();\n return this.connectPromise;\n }\n connect() {\n return new Promise((resolve, reject) => {\n if (this.closed) {\n reject(new Error('Store closed'));\n return;\n }\n const params = new URLSearchParams();\n params.set('appId', this.appId);\n const url = `${this.wsUrl}?${params.toString()}`;\n const ws = new WebSocket(url);\n this.ws = ws;\n let authTimer = null;\n const finishConnected = () => {\n if (authTimer) {\n clearTimeout(authTimer);\n authTimer = null;\n }\n this.authenticating = false;\n ws.removeEventListener('error', onError);\n this.reconnectDelay = 1000;\n this.connectPromise = null;\n this.resubscribeAll();\n resolve();\n };\n const onOpen = () => {\n if (!this.authToken) {\n finishConnected();\n return;\n }\n this.authenticating = true;\n authTimer = setTimeout(() => {\n this.authenticating = false;\n this.connectPromise = null;\n try {\n ws.close(1008, 'Authentication timeout');\n }\n catch ( /* ignore */_a) { /* ignore */ }\n reject(new Error('WebSocket authentication timeout'));\n }, 10000);\n try {\n ws.send(JSON.stringify({ type: 'auth', token: this.authToken }));\n }\n catch (e) {\n if (authTimer)\n clearTimeout(authTimer);\n this.authenticating = false;\n this.connectPromise = null;\n reject(e);\n }\n };\n const onError = (e) => {\n if (authTimer)\n clearTimeout(authTimer);\n this.authenticating = false;\n ws.removeEventListener('open', onOpen);\n this.connectPromise = null;\n reject(new Error('WebSocket connection failed'));\n };\n ws.addEventListener('open', onOpen, { once: true });\n ws.addEventListener('error', onError, { once: true });\n ws.addEventListener('message', (event) => {\n if (this.authenticating) {\n try {\n const msg = JSON.parse(typeof event.data === 'string' ? event.data : new TextDecoder().decode(event.data));\n if ((msg === null || msg === void 0 ? void 0 : msg.type) === 'authenticated') {\n finishConnected();\n return;\n }\n }\n catch ( /* fall through to normal handling */_a) { /* fall through to normal handling */ }\n }\n this.handleMessage(event.data);\n });\n ws.addEventListener('close', () => {\n if (authTimer)\n clearTimeout(authTimer);\n if (this.ws !== ws) {\n if (this.suppressNextReconnect)\n this.suppressNextReconnect = false;\n return;\n }\n this.authenticating = false;\n this.ws = null;\n this.connectPromise = null;\n this.rejectAllPending('WebSocket closed');\n this.setAllSubscriptionStatus('reconnecting');\n if (this.suppressNextReconnect) {\n this.suppressNextReconnect = false;\n return;\n }\n this.scheduleReconnect();\n });\n });\n }\n scheduleReconnect() {\n if (this.closed)\n return;\n if (this.reconnectTimer)\n clearTimeout(this.reconnectTimer);\n this.reconnectTimer = setTimeout(() => {\n this.ensureConnected().catch(() => {\n this.reconnectDelay = Math.min(this.reconnectDelay * 2, this.maxReconnectDelay);\n this.scheduleReconnect();\n });\n }, this.reconnectDelay);\n }\n resubscribeAll() {\n for (const sub of this.subscriptions.values()) {\n this.sendSubscribe(sub);\n }\n }\n // -----------------------------------------------------------------------\n // Message handling\n // -----------------------------------------------------------------------\n handleMessage(raw) {\n const text = typeof raw === 'string' ? raw : new TextDecoder().decode(raw);\n let msg;\n try {\n msg = JSON.parse(text);\n }\n catch (_a) {\n return;\n }\n switch (msg.type) {\n case 'snapshot':\n this.handleSnapshot(msg);\n break;\n case 'delta':\n this.handleDelta(msg);\n break;\n case 'result':\n this.handleResult(msg);\n break;\n case 'error':\n this.handleError(msg);\n break;\n case 'pong':\n break;\n case 'authenticated':\n break;\n // v1 compat: handle legacy message types during transition\n case 'subscribed':\n this.handleSnapshot(Object.assign(Object.assign({}, msg), { type: 'snapshot', docs: msg.data }));\n break;\n case 'data':\n // Legacy full-snapshot delta — treat as snapshot replacement\n this.handleLegacyData(msg);\n break;\n case 'response':\n this.handleResult(Object.assign(Object.assign({}, msg), { type: 'result', ok: msg.status === 200, doc: msg.data }));\n break;\n }\n }\n handleSnapshot(msg) {\n var _a, _b, _c;\n const subId = (_a = msg.id) !== null && _a !== void 0 ? _a : msg.subscriptionId;\n if (!subId)\n return;\n const sub = this.findSubscriptionById(subId);\n if (!sub)\n return;\n const docs = (_c = (_b = msg.docs) !== null && _b !== void 0 ? _b : msg.data) !== null && _c !== void 0 ? _c : [];\n const docsArray = Array.isArray(docs) ? docs : [docs];\n sub.docs.clear();\n for (const doc of docsArray) {\n if (doc && doc._id) {\n sub.docs.set(doc._id, doc);\n }\n }\n sub.ref.current = sub.docs;\n sub.status = 'live';\n sub.isStale = false;\n sub.error = null;\n this.notifySubscription(sub);\n this.markIdbDirty(sub.path);\n }\n handleDelta(msg) {\n var _a, _b;\n const subId = (_a = msg.id) !== null && _a !== void 0 ? _a : msg.subscriptionId;\n if (!subId)\n return;\n const sub = this.findSubscriptionById(subId);\n if (!sub)\n return;\n if (sub.tier === 'ephemeral') {\n // Ephemeral: just overwrite, no accumulation logic\n if (msg.change === 'removed' && msg.docId) {\n sub.docs.delete(msg.docId);\n }\n else if (msg.doc && msg.doc._id) {\n sub.docs.set(msg.doc._id, msg.doc);\n }\n sub.ref.current = sub.docs;\n if (sub.options.mode !== 'ref') {\n this.notifySubscription(sub);\n }\n return;\n }\n // Durable/checkpointed: full delta handling\n switch (msg.change) {\n case 'added':\n case 'modified':\n if (msg.doc && msg.doc._id) {\n sub.docs.set(msg.doc._id, msg.doc);\n }\n break;\n case 'removed':\n if (msg.docId) {\n sub.docs.delete(msg.docId);\n }\n else if ((_b = msg.doc) === null || _b === void 0 ? void 0 : _b._id) {\n sub.docs.delete(msg.doc._id);\n }\n break;\n }\n sub.ref.current = sub.docs;\n this.notifySubscription(sub);\n this.markIdbDirty(sub.path);\n }\n handleLegacyData(msg) {\n // Legacy v1 format: 'data' message with full snapshot or single doc\n const subId = msg.subscriptionId;\n if (!subId)\n return;\n const sub = this.findSubscriptionById(subId);\n if (!sub)\n return;\n if (Array.isArray(msg.data)) {\n // Full snapshot replacement\n sub.docs.clear();\n for (const doc of msg.data) {\n if (doc && doc._id)\n sub.docs.set(doc._id, doc);\n }\n }\n else if (msg.data && msg.data._id) {\n // Single doc update\n sub.docs.set(msg.data._id, msg.data);\n }\n else if (msg.data === null) {\n // Removal — but we don't know which doc. Re-fetch needed.\n }\n sub.ref.current = sub.docs;\n sub.status = 'live';\n sub.isStale = false;\n this.notifySubscription(sub);\n this.markIdbDirty(sub.path);\n }\n handleResult(msg) {\n var _a, _b, _c, _d;\n const requestId = msg.requestId;\n if (!requestId)\n return;\n const pending = this.pendingRequests.get(requestId);\n if (!pending)\n return;\n this.pendingRequests.delete(requestId);\n clearTimeout(pending.timeout);\n const ok = (_a = msg.ok) !== null && _a !== void 0 ? _a : (msg.status === 200);\n if (ok) {\n pending.resolve((_c = (_b = msg.doc) !== null && _b !== void 0 ? _b : msg.data) !== null && _c !== void 0 ? _c : true);\n }\n else {\n pending.reject(new Error((_d = msg.error) !== null && _d !== void 0 ? _d : 'Operation failed'));\n }\n }\n handleError(msg) {\n var _a, _b, _c;\n const error = new Error((_a = msg.message) !== null && _a !== void 0 ? _a : (msg.code ? `${msg.code}: Server error` : 'Server error'));\n if (msg.code)\n error.code = msg.code;\n if (msg.subscriptionId || msg.id)\n error.subscriptionId = (_b = msg.subscriptionId) !== null && _b !== void 0 ? _b : msg.id;\n const requestId = msg.requestId;\n if (requestId) {\n const pending = this.pendingRequests.get(requestId);\n if (pending) {\n this.pendingRequests.delete(requestId);\n clearTimeout(pending.timeout);\n pending.reject(error);\n }\n }\n const subId = (_c = msg.subscriptionId) !== null && _c !== void 0 ? _c : msg.id;\n if (subId) {\n const sub = this.findSubscriptionById(subId);\n if (sub) {\n sub.status = 'error';\n sub.error = error;\n this.notifyState(sub);\n for (const callback of Array.from(sub.errorCallbacks)) {\n try {\n callback(error);\n }\n catch ( /* swallow */_d) { /* swallow */ }\n }\n }\n }\n }\n // -----------------------------------------------------------------------\n // Subscribe\n // -----------------------------------------------------------------------\n async subscribe(path, opts = {}) {\n var _a;\n await this.ensureCurrentAuth();\n const tier = (_a = opts.tier) !== null && _a !== void 0 ? _a : 'durable';\n const subKey = this.getSubKey(path, opts);\n let sub = this.subscriptions.get(subKey);\n if (sub) {\n // Existing subscription — add callback\n if (opts.onData)\n sub.callbacks.add(opts.onData);\n if (opts.onState)\n sub.stateCallbacks.add(opts.onState);\n if (opts.onError)\n sub.errorCallbacks.add(opts.onError);\n if (opts.cache && !sub.options.cache) {\n sub.options = Object.assign(Object.assign({}, sub.options), { cache: true });\n }\n // Immediately deliver current state\n if (opts.onData && sub.docs.size > 0) {\n opts.onData(this.docsToArray(sub));\n }\n if (opts.onState) {\n opts.onState(this.getState(sub));\n }\n return this.createUnsubscribe(subKey, sub.id, opts.onData, opts.onState, opts.onError);\n }\n // New subscription\n const subId = `sub_${nextRequestId++}`;\n sub = {\n id: subId,\n path,\n tier,\n options: opts,\n docs: new Map(),\n status: 'idle',\n isStale: false,\n error: null,\n callbacks: new Set(opts.onData ? [opts.onData] : []),\n stateCallbacks: new Set(opts.onState ? [opts.onState] : []),\n errorCallbacks: new Set(opts.onError ? [opts.onError] : []),\n ref: { current: new Map() },\n };\n this.subscriptions.set(subKey, sub);\n // Step 1: Load from IDB only when explicitly enabled and principal-bound.\n if (this.shouldUseIdb(tier, opts)) {\n const cached = await idbGet(this.idbKey(path));\n if (cached && Array.isArray(cached)) {\n for (const doc of cached) {\n if (doc && doc._id)\n sub.docs.set(doc._id, doc);\n }\n sub.ref.current = sub.docs;\n sub.status = 'cached';\n sub.isStale = true;\n this.notifySubscription(sub);\n }\n }\n // Step 2: Connect and subscribe via WS\n sub.status = sub.docs.size > 0 ? 'cached' : 'loading';\n this.notifyState(sub);\n try {\n await this.ensureConnected();\n this.sendSubscribe(sub);\n }\n catch (_b) {\n sub.status = 'error';\n sub.error = new Error('Connection failed');\n this.notifyState(sub);\n }\n return this.createUnsubscribe(subKey, sub.id, opts.onData, opts.onState, opts.onError);\n }\n getRef(path, opts = {}) {\n var _a;\n const subKey = this.getSubKey(path, opts);\n const sub = this.subscriptions.get(subKey);\n if (sub)\n return sub.ref;\n // Auto-subscribe in ref mode\n const ref = { current: new Map() };\n this.subscribe(path, Object.assign(Object.assign({}, opts), { mode: 'ref', tier: 'ephemeral' })).catch(() => { });\n const newSub = this.subscriptions.get(this.getSubKey(path, Object.assign(Object.assign({}, opts), { tier: 'ephemeral' })));\n return (_a = newSub === null || newSub === void 0 ? void 0 : newSub.ref) !== null && _a !== void 0 ? _a : ref;\n }\n // -----------------------------------------------------------------------\n // CRUD operations\n // -----------------------------------------------------------------------\n async set(path, doc) {\n var _a;\n await this.ensureConnected();\n // Resolve operations (Increment, Time.Now) client-side for optimistic update\n const resolvedDoc = this.resolveOperations(doc, path);\n // Optimistic update: apply to local state immediately\n const normalizedPath = path.startsWith('/') ? path.slice(1) : path;\n const collectionPath = this.getCollectionPath(normalizedPath);\n const optimisticDoc = Object.assign(Object.assign({ _id: normalizedPath, pathId: normalizedPath }, resolvedDoc), { \n // System timestamp field name: the Bounded worker stamps the neutral\n // `_updatedAt`; the underscore-prefixed `_updated_at` metadata mirror.\n // Match it so the optimistic doc lines up with the server's confirmation.\n [isBoundedNetwork() ? '_updatedAt' : '_updated_at']: Date.now() });\n const sub = this.findSubscriptionByPath(collectionPath);\n let prevDoc = null;\n if (sub) {\n prevDoc = (_a = sub.docs.get(normalizedPath)) !== null && _a !== void 0 ? _a : null;\n sub.docs.set(normalizedPath, optimisticDoc);\n sub.ref.current = sub.docs;\n this.notifySubscription(sub);\n }\n // Send to server\n const requestId = `r_${nextRequestId++}`;\n try {\n const result = await this.sendRequest(requestId, {\n type: 'set',\n requestId,\n documents: [{ destinationPath: normalizedPath, document: doc }],\n });\n // Replace optimistic doc with server-confirmed version\n if (sub && result && typeof result === 'object') {\n const serverDoc = Array.isArray(result) ? result[0] : result;\n if (serverDoc && serverDoc._id) {\n sub.docs.set(serverDoc._id, serverDoc);\n sub.ref.current = sub.docs;\n this.notifySubscription(sub);\n this.markIdbDirty(collectionPath);\n }\n }\n return Array.isArray(result) ? result[0] : result;\n }\n catch (err) {\n // Revert optimistic update\n if (sub) {\n if (prevDoc) {\n sub.docs.set(normalizedPath, prevDoc);\n }\n else {\n sub.docs.delete(normalizedPath);\n }\n sub.ref.current = sub.docs;\n this.notifySubscription(sub);\n }\n throw err;\n }\n }\n async get(path) {\n await this.ensureCurrentAuth();\n const normalizedPath = path.startsWith('/') ? path.slice(1) : path;\n // Check local subscriptions first\n const collectionPath = this.getCollectionPath(normalizedPath);\n const sub = this.findSubscriptionByPath(collectionPath);\n if (sub && sub.status === 'live') {\n const doc = sub.docs.get(normalizedPath);\n return doc !== null && doc !== void 0 ? doc : null;\n }\n // One-shot WS fetch\n await this.ensureConnected();\n const requestId = `r_${nextRequestId++}`;\n return this.sendRequest(requestId, {\n type: 'get',\n requestId,\n path: normalizedPath,\n });\n }\n async getMany(paths) {\n await this.ensureConnected();\n const normalizedPaths = paths.map(p => p.startsWith('/') ? p.slice(1) : p);\n const requestId = `r_${nextRequestId++}`;\n return this.sendRequest(requestId, {\n type: 'getMany',\n requestId,\n paths: normalizedPaths,\n });\n }\n async delete(path) {\n var _a;\n await this.ensureConnected();\n const normalizedPath = path.startsWith('/') ? path.slice(1) : path;\n // Optimistic: remove from local state\n const collectionPath = this.getCollectionPath(normalizedPath);\n const sub = this.findSubscriptionByPath(collectionPath);\n let prevDoc = null;\n if (sub) {\n prevDoc = (_a = sub.docs.get(normalizedPath)) !== null && _a !== void 0 ? _a : null;\n sub.docs.delete(normalizedPath);\n sub.ref.current = sub.docs;\n this.notifySubscription(sub);\n }\n const requestId = `r_${nextRequestId++}`;\n try {\n await this.sendRequest(requestId, {\n type: 'delete',\n requestId,\n path: normalizedPath,\n });\n if (sub)\n this.markIdbDirty(collectionPath);\n }\n catch (err) {\n // Revert\n if (sub && prevDoc) {\n sub.docs.set(normalizedPath, prevDoc);\n sub.ref.current = sub.docs;\n this.notifySubscription(sub);\n }\n throw err;\n }\n }\n async query(path, opts) {\n await this.ensureConnected();\n const normalizedPath = path.startsWith('/') ? path.slice(1) : path;\n const requestId = `r_${nextRequestId++}`;\n return this.sendRequest(requestId, Object.assign(Object.assign(Object.assign(Object.assign({ type: 'query', requestId, path: normalizedPath }, ((opts === null || opts === void 0 ? void 0 : opts.filter) ? { filter: opts.filter } : {})), ((opts === null || opts === void 0 ? void 0 : opts.sort) ? { sort: opts.sort } : {})), ((opts === null || opts === void 0 ? void 0 : opts.limit) !== undefined ? { limit: opts.limit } : {})), ((opts === null || opts === void 0 ? void 0 : opts.includeSubPaths) ? { includeSubPaths: true } : {})));\n }\n async count(path) {\n var _a;\n await this.ensureConnected();\n const normalizedPath = path.startsWith('/') ? path.slice(1) : path;\n const requestId = `r_${nextRequestId++}`;\n const result = await this.sendRequest(requestId, {\n type: 'count',\n requestId,\n path: normalizedPath,\n });\n return typeof result === 'number' ? result : ((_a = result === null || result === void 0 ? void 0 : result.value) !== null && _a !== void 0 ? _a : 0);\n }\n async aggregate(path, operation, opts) {\n await this.ensureConnected();\n const normalizedPath = path.startsWith('/') ? path.slice(1) : path;\n const requestId = `r_${nextRequestId++}`;\n return this.sendRequest(requestId, Object.assign({ type: 'aggregate', requestId, path: normalizedPath, operation }, ((opts === null || opts === void 0 ? void 0 : opts.field) ? { field: opts.field } : {})));\n }\n // -----------------------------------------------------------------------\n // Helpers\n // -----------------------------------------------------------------------\n sendSubscribe(sub) {\n if (!this.ws || this.ws.readyState !== WebSocket.OPEN)\n return;\n const msg = {\n type: 'subscribe',\n subscriptionId: sub.id,\n path: sub.path,\n };\n if (sub.options.filter)\n msg.filter = sub.options.filter;\n if (sub.options.includeSubPaths)\n msg.includeSubPaths = true;\n if (sub.options.limit)\n msg.limit = sub.options.limit;\n if (sub.options.prompt)\n msg.prompt = sub.options.prompt;\n this.ws.send(JSON.stringify(msg));\n }\n sendRequest(requestId, msg) {\n return new Promise((resolve, reject) => {\n const timeout = setTimeout(() => {\n this.pendingRequests.delete(requestId);\n reject(new Error('Request timed out'));\n }, 30000);\n this.pendingRequests.set(requestId, { resolve, reject, timeout });\n if (this.ws && this.ws.readyState === WebSocket.OPEN) {\n this.ws.send(JSON.stringify(msg));\n }\n else {\n this.pendingRequests.delete(requestId);\n clearTimeout(timeout);\n reject(new Error('WebSocket not connected'));\n }\n });\n }\n notifySubscription(sub) {\n const data = this.docsToArray(sub);\n const callbacks = Array.from(sub.callbacks);\n for (const cb of callbacks) {\n try {\n cb(data);\n }\n catch ( /* swallow callback errors */_a) { /* swallow callback errors */ }\n }\n this.notifyState(sub);\n }\n notifyState(sub) {\n const state = this.getState(sub);\n const callbacks = Array.from(sub.stateCallbacks);\n for (const cb of callbacks) {\n try {\n cb(state);\n }\n catch ( /* swallow */_a) { /* swallow */ }\n }\n }\n getState(sub) {\n return {\n data: this.docsToArray(sub),\n status: sub.status,\n isStale: sub.isStale,\n error: sub.error,\n };\n }\n docsToArray(sub) {\n return Array.from(sub.docs.values());\n }\n findSubscriptionById(id) {\n for (const sub of this.subscriptions.values()) {\n if (sub.id === id)\n return sub;\n }\n return undefined;\n }\n findSubscriptionByPath(collectionPath) {\n for (const sub of this.subscriptions.values()) {\n const subPath = sub.path.startsWith('/') ? sub.path.slice(1) : sub.path;\n if (subPath === collectionPath)\n return sub;\n if (collectionPath.startsWith(subPath + '/'))\n return sub;\n }\n return undefined;\n }\n getCollectionPath(docPath) {\n const segments = docPath.split('/');\n if (segments.length % 2 === 0) {\n return segments.slice(0, -1).join('/');\n }\n return docPath;\n }\n getSubKey(path, opts) {\n const parts = [this.appId, this.authPrincipalKey, path];\n if (opts.filter)\n parts.push(JSON.stringify(opts.filter));\n if (opts.prompt)\n parts.push(opts.prompt);\n if (opts.tier)\n parts.push(opts.tier);\n return parts.join('::');\n }\n idbKey(path) {\n return `${this.appId}:${this.authPrincipalKey}:${path}`;\n }\n shouldUseIdb(tier, opts) {\n return opts.cache === true && tier !== 'ephemeral' && this.authPrincipalKey !== 'anon';\n }\n markIdbDirty(path) {\n const sub = this.findSubscriptionByPath(path);\n if (!sub || !this.shouldUseIdb(sub.tier, sub.options))\n return;\n this.idbDirtyKeys.add(path);\n if (!this.idbFlushTimer) {\n this.idbFlushTimer = setTimeout(() => {\n this.flushIdb();\n this.idbFlushTimer = null;\n }, 500);\n }\n }\n async flushIdb() {\n const keys = Array.from(this.idbDirtyKeys);\n this.idbDirtyKeys.clear();\n for (const path of keys) {\n const sub = this.findSubscriptionByPath(path);\n if (sub && this.shouldUseIdb(sub.tier, sub.options)) {\n const docs = this.docsToArray(sub);\n await idbSet(this.idbKey(path), docs);\n }\n }\n }\n createUnsubscribe(subKey, subId, onData, onState, onError) {\n return async () => {\n var _a;\n const sub = (_a = this.subscriptions.get(subKey)) !== null && _a !== void 0 ? _a : this.findSubscriptionById(subId);\n if (!sub)\n return;\n const currentSubKey = this.getSubKey(sub.path, sub.options);\n if (onData)\n sub.callbacks.delete(onData);\n if (onState)\n sub.stateCallbacks.delete(onState);\n if (onError)\n sub.errorCallbacks.delete(onError);\n // If no more callbacks, unsubscribe entirely\n if (sub.callbacks.size === 0 && sub.stateCallbacks.size === 0 && sub.errorCallbacks.size === 0) {\n this.subscriptions.delete(subKey);\n this.subscriptions.delete(currentSubKey);\n if (this.ws && this.ws.readyState === WebSocket.OPEN) {\n this.ws.send(JSON.stringify({\n type: 'unsubscribe',\n subscriptionId: sub.id,\n }));\n }\n }\n };\n }\n resolveOperations(doc, path) {\n var _a;\n if (!doc || typeof doc !== 'object')\n return doc;\n const resolved = {};\n for (const [key, value] of Object.entries(doc)) {\n if (value && typeof value === 'object' && !Array.isArray(value) && value.operation) {\n const op = value;\n if (op.operation === 'time' && op.value === 'now') {\n resolved[key] = Math.floor(Date.now() / 1000);\n }\n else if (op.operation === 'increment') {\n // For optimistic: get current value and add\n const normalizedPath = path.startsWith('/') ? path.slice(1) : path;\n const collectionPath = this.getCollectionPath(normalizedPath);\n const sub = this.findSubscriptionByPath(collectionPath);\n const existing = sub === null || sub === void 0 ? void 0 : sub.docs.get(normalizedPath);\n const current = (_a = existing === null || existing === void 0 ? void 0 : existing[key]) !== null && _a !== void 0 ? _a : 0;\n resolved[key] = (typeof current === 'number' ? current : 0) + op.value;\n }\n else {\n resolved[key] = value;\n }\n }\n else {\n resolved[key] = value;\n }\n }\n return resolved;\n }\n rejectAllPending(reason) {\n for (const [requestId, pending] of this.pendingRequests) {\n clearTimeout(pending.timeout);\n pending.reject(new Error(reason));\n }\n this.pendingRequests.clear();\n }\n setAllSubscriptionStatus(status) {\n for (const sub of this.subscriptions.values()) {\n sub.status = status;\n this.notifyState(sub);\n }\n }\n // -----------------------------------------------------------------------\n // Lifecycle\n // -----------------------------------------------------------------------\n close() {\n this.closed = true;\n if (this.reconnectTimer)\n clearTimeout(this.reconnectTimer);\n if (this.idbFlushTimer)\n clearTimeout(this.idbFlushTimer);\n if (this.tokenRefreshTimer)\n clearInterval(this.tokenRefreshTimer);\n this.flushIdb();\n if (this.ws) {\n this.ws.close(1000, 'Store closed');\n this.ws = null;\n }\n this.rejectAllPending('Store closed');\n this.subscriptions.clear();\n }\n async reconnectWithNewAuth() {\n if (this.closed)\n return;\n await this.ensureInitialized();\n await this.refreshToken();\n await this.applyAuthPrincipalChange();\n if (this.subscriptions.size > 0) {\n await this.ensureConnected().catch((error) => {\n this.setAllSubscriptionStatus('error');\n for (const sub of this.subscriptions.values()) {\n sub.error = error instanceof Error ? error : new Error(String(error));\n this.notifyState(sub);\n }\n });\n }\n }\n}\n// ---------------------------------------------------------------------------\n// Singleton instance\n// ---------------------------------------------------------------------------\nlet storeInstance = null;\nexport function getRealtimeStore() {\n if (!storeInstance) {\n storeInstance = new RealtimeStore();\n }\n return storeInstance;\n}\nexport function resetRealtimeStore() {\n if (storeInstance) {\n storeInstance.close();\n storeInstance = null;\n }\n}\nexport async function reconnectRealtimeStoreWithNewAuth() {\n if (storeInstance) {\n await storeInstance.reconnectWithNewAuth();\n }\n}\n"],"names":[],"mappings":";;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AA++BO,eAAe,iCAAiC,GAAG;AAI1D;;;;"}
@@ -1,4 +1,4 @@
1
- import './index-qcTp-5Mt.mjs';
1
+ import './index-BZhK47ZR.mjs';
2
2
  import 'axios';
3
3
  import 'tweetnacl';
4
4
  import 'reconnecting-websocket';
@@ -14,4 +14,4 @@ async function reconnectRealtimeStoreWithNewAuth() {
14
14
  }
15
15
 
16
16
  export { reconnectRealtimeStoreWithNewAuth };
17
- //# sourceMappingURL=realtime-store-BzKEmyxq.mjs.map
17
+ //# sourceMappingURL=realtime-store-CpdBV8Ma.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"realtime-store-BzKEmyxq.mjs","sources":["../.rollup-tmp/client/realtime-store.js"],"sourcesContent":["// ---------------------------------------------------------------------------\n// realtime-store.ts — Client-side state manager for realtime apps.\n//\n// Manages: WS connection, in-memory state, IDB persistence, optimistic\n// writes, delta accumulation, loading states, ephemeral/durable tiers.\n// ---------------------------------------------------------------------------\nimport { getConfig, isBoundedNetwork } from './config';\n// ---------------------------------------------------------------------------\n// IDB helpers (lazy-loaded, non-blocking)\n// ---------------------------------------------------------------------------\nconst IDB_NAME = 'bounded-realtime';\nconst IDB_STORE = 'subscriptions';\nconst IDB_VERSION = 1;\nlet idbPromise = null;\nfunction getIDB() {\n if (idbPromise)\n return idbPromise;\n if (typeof indexedDB === 'undefined') {\n return Promise.reject(new Error('IndexedDB not available'));\n }\n idbPromise = new Promise((resolve, reject) => {\n const req = indexedDB.open(IDB_NAME, IDB_VERSION);\n req.onupgradeneeded = () => {\n const db = req.result;\n if (!db.objectStoreNames.contains(IDB_STORE)) {\n db.createObjectStore(IDB_STORE);\n }\n };\n req.onsuccess = () => resolve(req.result);\n req.onerror = () => reject(req.error);\n });\n return idbPromise;\n}\nasync function idbGet(key) {\n try {\n const db = await getIDB();\n return new Promise((resolve) => {\n const tx = db.transaction(IDB_STORE, 'readonly');\n const store = tx.objectStore(IDB_STORE);\n const req = store.get(key);\n req.onsuccess = () => { var _a; return resolve((_a = req.result) !== null && _a !== void 0 ? _a : null); };\n req.onerror = () => resolve(null);\n });\n }\n catch (_a) {\n return null;\n }\n}\nasync function idbSet(key, value) {\n try {\n const db = await getIDB();\n return new Promise((resolve) => {\n const tx = db.transaction(IDB_STORE, 'readwrite');\n const store = tx.objectStore(IDB_STORE);\n store.put(value, key);\n tx.oncomplete = () => resolve();\n tx.onerror = () => resolve();\n });\n }\n catch (_a) {\n // Best-effort persistence\n }\n}\nasync function idbDelete(key) {\n try {\n const db = await getIDB();\n return new Promise((resolve) => {\n const tx = db.transaction(IDB_STORE, 'readwrite');\n const store = tx.objectStore(IDB_STORE);\n store.delete(key);\n tx.oncomplete = () => resolve();\n tx.onerror = () => resolve();\n });\n }\n catch (_a) {\n // Best-effort\n }\n}\n// ---------------------------------------------------------------------------\n// RealtimeStore\n// ---------------------------------------------------------------------------\nlet nextRequestId = 1;\nfunction hashForKey(value) {\n let h = 5381;\n for (let i = 0; i < value.length; i++) {\n h = ((h << 5) + h + value.charCodeAt(i)) & 0x7fffffff;\n }\n return h.toString(36);\n}\nfunction principalFromToken(token) {\n return token ? `t${hashForKey(token)}` : 'anon';\n}\nexport class RealtimeStore {\n constructor() {\n this.ws = null;\n this.wsUrl = '';\n this.appId = '';\n this.subscriptions = new Map();\n this.pendingRequests = new Map();\n this.connectPromise = null;\n this.reconnectTimer = null;\n this.reconnectDelay = 1000;\n this.maxReconnectDelay = 30000;\n this.idbFlushTimer = null;\n this.idbDirtyKeys = new Set();\n this.closed = false;\n this.authToken = null;\n this.authPrincipalKey = 'anon';\n this.authenticating = false;\n this.suppressNextReconnect = false;\n this.isServer = false;\n this.tokenRefreshTimer = null;\n // -----------------------------------------------------------------------\n // WebSocket connection\n // -----------------------------------------------------------------------\n this.initPromise = null;\n }\n // -----------------------------------------------------------------------\n // Initialization\n // -----------------------------------------------------------------------\n async init() {\n const config = await getConfig();\n this.appId = config.appId;\n this.wsUrl = config.wsApiUrl;\n this.isServer = config.isServer;\n await this.refreshToken();\n this.startTokenRefresh();\n }\n async refreshToken() {\n let token = null;\n try {\n const { getIdToken } = await import('../utils/utils');\n token = await getIdToken(this.isServer);\n }\n catch ( /* no auth available */_a) { /* no auth available */ }\n this.authToken = token !== null && token !== void 0 ? token : null;\n this.authPrincipalKey = principalFromToken(this.authToken);\n }\n startTokenRefresh() {\n if (this.tokenRefreshTimer)\n return;\n this.tokenRefreshTimer = setInterval(async () => {\n const prevPrincipal = this.authPrincipalKey;\n await this.refreshToken();\n if (this.authPrincipalKey !== prevPrincipal) {\n await this.applyAuthPrincipalChange();\n if (this.subscriptions.size > 0) {\n await this.ensureConnected().catch(() => {\n this.setAllSubscriptionStatus('error');\n });\n }\n }\n }, 5 * 60 * 1000); // Check every 5 minutes\n }\n async ensureInitialized() {\n if (this.appId)\n return;\n if (!this.initPromise)\n this.initPromise = this.init();\n await this.initPromise;\n }\n async ensureCurrentAuth() {\n await this.ensureInitialized();\n const prevPrincipal = this.authPrincipalKey;\n await this.refreshToken();\n if (this.authPrincipalKey !== prevPrincipal) {\n await this.applyAuthPrincipalChange();\n }\n }\n rekeySubscriptionsForPrincipal() {\n const subs = Array.from(this.subscriptions.values());\n this.subscriptions.clear();\n for (const sub of subs) {\n this.subscriptions.set(this.getSubKey(sub.path, sub.options), sub);\n }\n }\n async applyAuthPrincipalChange() {\n if (this.idbFlushTimer) {\n clearTimeout(this.idbFlushTimer);\n this.idbFlushTimer = null;\n }\n this.idbDirtyKeys.clear();\n this.rekeySubscriptionsForPrincipal();\n for (const sub of this.subscriptions.values()) {\n sub.docs.clear();\n sub.ref.current = sub.docs;\n sub.error = null;\n sub.isStale = false;\n let loaded = false;\n if (this.shouldUseIdb(sub.tier, sub.options)) {\n const cached = await idbGet(this.idbKey(sub.path));\n if (cached && Array.isArray(cached)) {\n for (const doc of cached) {\n if (doc && doc._id)\n sub.docs.set(doc._id, doc);\n }\n sub.ref.current = sub.docs;\n loaded = sub.docs.size > 0;\n }\n }\n sub.status = loaded ? 'cached' : 'loading';\n sub.isStale = loaded;\n if (loaded)\n this.notifySubscription(sub);\n else\n this.notifyState(sub);\n }\n if (this.ws) {\n const ws = this.ws;\n this.ws = null;\n this.connectPromise = null;\n this.suppressNextReconnect = true;\n try {\n ws.close(1000, 'Auth changed');\n }\n catch ( /* ignore */_a) { /* ignore */ }\n }\n }\n async ensureConnected() {\n var _a;\n await this.ensureCurrentAuth();\n if (((_a = this.ws) === null || _a === void 0 ? void 0 : _a.readyState) === WebSocket.OPEN && !this.authenticating)\n return;\n if (this.connectPromise)\n return this.connectPromise;\n this.connectPromise = this.connect();\n return this.connectPromise;\n }\n connect() {\n return new Promise((resolve, reject) => {\n if (this.closed) {\n reject(new Error('Store closed'));\n return;\n }\n const params = new URLSearchParams();\n params.set('appId', this.appId);\n const url = `${this.wsUrl}?${params.toString()}`;\n const ws = new WebSocket(url);\n this.ws = ws;\n let authTimer = null;\n const finishConnected = () => {\n if (authTimer) {\n clearTimeout(authTimer);\n authTimer = null;\n }\n this.authenticating = false;\n ws.removeEventListener('error', onError);\n this.reconnectDelay = 1000;\n this.connectPromise = null;\n this.resubscribeAll();\n resolve();\n };\n const onOpen = () => {\n if (!this.authToken) {\n finishConnected();\n return;\n }\n this.authenticating = true;\n authTimer = setTimeout(() => {\n this.authenticating = false;\n this.connectPromise = null;\n try {\n ws.close(1008, 'Authentication timeout');\n }\n catch ( /* ignore */_a) { /* ignore */ }\n reject(new Error('WebSocket authentication timeout'));\n }, 10000);\n try {\n ws.send(JSON.stringify({ type: 'auth', token: this.authToken }));\n }\n catch (e) {\n if (authTimer)\n clearTimeout(authTimer);\n this.authenticating = false;\n this.connectPromise = null;\n reject(e);\n }\n };\n const onError = (e) => {\n if (authTimer)\n clearTimeout(authTimer);\n this.authenticating = false;\n ws.removeEventListener('open', onOpen);\n this.connectPromise = null;\n reject(new Error('WebSocket connection failed'));\n };\n ws.addEventListener('open', onOpen, { once: true });\n ws.addEventListener('error', onError, { once: true });\n ws.addEventListener('message', (event) => {\n if (this.authenticating) {\n try {\n const msg = JSON.parse(typeof event.data === 'string' ? event.data : new TextDecoder().decode(event.data));\n if ((msg === null || msg === void 0 ? void 0 : msg.type) === 'authenticated') {\n finishConnected();\n return;\n }\n }\n catch ( /* fall through to normal handling */_a) { /* fall through to normal handling */ }\n }\n this.handleMessage(event.data);\n });\n ws.addEventListener('close', () => {\n if (authTimer)\n clearTimeout(authTimer);\n if (this.ws !== ws) {\n if (this.suppressNextReconnect)\n this.suppressNextReconnect = false;\n return;\n }\n this.authenticating = false;\n this.ws = null;\n this.connectPromise = null;\n this.rejectAllPending('WebSocket closed');\n this.setAllSubscriptionStatus('reconnecting');\n if (this.suppressNextReconnect) {\n this.suppressNextReconnect = false;\n return;\n }\n this.scheduleReconnect();\n });\n });\n }\n scheduleReconnect() {\n if (this.closed)\n return;\n if (this.reconnectTimer)\n clearTimeout(this.reconnectTimer);\n this.reconnectTimer = setTimeout(() => {\n this.ensureConnected().catch(() => {\n this.reconnectDelay = Math.min(this.reconnectDelay * 2, this.maxReconnectDelay);\n this.scheduleReconnect();\n });\n }, this.reconnectDelay);\n }\n resubscribeAll() {\n for (const sub of this.subscriptions.values()) {\n this.sendSubscribe(sub);\n }\n }\n // -----------------------------------------------------------------------\n // Message handling\n // -----------------------------------------------------------------------\n handleMessage(raw) {\n const text = typeof raw === 'string' ? raw : new TextDecoder().decode(raw);\n let msg;\n try {\n msg = JSON.parse(text);\n }\n catch (_a) {\n return;\n }\n switch (msg.type) {\n case 'snapshot':\n this.handleSnapshot(msg);\n break;\n case 'delta':\n this.handleDelta(msg);\n break;\n case 'result':\n this.handleResult(msg);\n break;\n case 'error':\n this.handleError(msg);\n break;\n case 'pong':\n break;\n case 'authenticated':\n break;\n // v1 compat: handle legacy message types during transition\n case 'subscribed':\n this.handleSnapshot(Object.assign(Object.assign({}, msg), { type: 'snapshot', docs: msg.data }));\n break;\n case 'data':\n // Legacy full-snapshot delta — treat as snapshot replacement\n this.handleLegacyData(msg);\n break;\n case 'response':\n this.handleResult(Object.assign(Object.assign({}, msg), { type: 'result', ok: msg.status === 200, doc: msg.data }));\n break;\n }\n }\n handleSnapshot(msg) {\n var _a, _b, _c;\n const subId = (_a = msg.id) !== null && _a !== void 0 ? _a : msg.subscriptionId;\n if (!subId)\n return;\n const sub = this.findSubscriptionById(subId);\n if (!sub)\n return;\n const docs = (_c = (_b = msg.docs) !== null && _b !== void 0 ? _b : msg.data) !== null && _c !== void 0 ? _c : [];\n const docsArray = Array.isArray(docs) ? docs : [docs];\n sub.docs.clear();\n for (const doc of docsArray) {\n if (doc && doc._id) {\n sub.docs.set(doc._id, doc);\n }\n }\n sub.ref.current = sub.docs;\n sub.status = 'live';\n sub.isStale = false;\n sub.error = null;\n this.notifySubscription(sub);\n this.markIdbDirty(sub.path);\n }\n handleDelta(msg) {\n var _a, _b;\n const subId = (_a = msg.id) !== null && _a !== void 0 ? _a : msg.subscriptionId;\n if (!subId)\n return;\n const sub = this.findSubscriptionById(subId);\n if (!sub)\n return;\n if (sub.tier === 'ephemeral') {\n // Ephemeral: just overwrite, no accumulation logic\n if (msg.change === 'removed' && msg.docId) {\n sub.docs.delete(msg.docId);\n }\n else if (msg.doc && msg.doc._id) {\n sub.docs.set(msg.doc._id, msg.doc);\n }\n sub.ref.current = sub.docs;\n if (sub.options.mode !== 'ref') {\n this.notifySubscription(sub);\n }\n return;\n }\n // Durable/checkpointed: full delta handling\n switch (msg.change) {\n case 'added':\n case 'modified':\n if (msg.doc && msg.doc._id) {\n sub.docs.set(msg.doc._id, msg.doc);\n }\n break;\n case 'removed':\n if (msg.docId) {\n sub.docs.delete(msg.docId);\n }\n else if ((_b = msg.doc) === null || _b === void 0 ? void 0 : _b._id) {\n sub.docs.delete(msg.doc._id);\n }\n break;\n }\n sub.ref.current = sub.docs;\n this.notifySubscription(sub);\n this.markIdbDirty(sub.path);\n }\n handleLegacyData(msg) {\n // Legacy v1 format: 'data' message with full snapshot or single doc\n const subId = msg.subscriptionId;\n if (!subId)\n return;\n const sub = this.findSubscriptionById(subId);\n if (!sub)\n return;\n if (Array.isArray(msg.data)) {\n // Full snapshot replacement\n sub.docs.clear();\n for (const doc of msg.data) {\n if (doc && doc._id)\n sub.docs.set(doc._id, doc);\n }\n }\n else if (msg.data && msg.data._id) {\n // Single doc update\n sub.docs.set(msg.data._id, msg.data);\n }\n else if (msg.data === null) {\n // Removal — but we don't know which doc. Re-fetch needed.\n }\n sub.ref.current = sub.docs;\n sub.status = 'live';\n sub.isStale = false;\n this.notifySubscription(sub);\n this.markIdbDirty(sub.path);\n }\n handleResult(msg) {\n var _a, _b, _c, _d;\n const requestId = msg.requestId;\n if (!requestId)\n return;\n const pending = this.pendingRequests.get(requestId);\n if (!pending)\n return;\n this.pendingRequests.delete(requestId);\n clearTimeout(pending.timeout);\n const ok = (_a = msg.ok) !== null && _a !== void 0 ? _a : (msg.status === 200);\n if (ok) {\n pending.resolve((_c = (_b = msg.doc) !== null && _b !== void 0 ? _b : msg.data) !== null && _c !== void 0 ? _c : true);\n }\n else {\n pending.reject(new Error((_d = msg.error) !== null && _d !== void 0 ? _d : 'Operation failed'));\n }\n }\n handleError(msg) {\n var _a, _b, _c;\n const error = new Error((_a = msg.message) !== null && _a !== void 0 ? _a : (msg.code ? `${msg.code}: Server error` : 'Server error'));\n if (msg.code)\n error.code = msg.code;\n if (msg.subscriptionId || msg.id)\n error.subscriptionId = (_b = msg.subscriptionId) !== null && _b !== void 0 ? _b : msg.id;\n const requestId = msg.requestId;\n if (requestId) {\n const pending = this.pendingRequests.get(requestId);\n if (pending) {\n this.pendingRequests.delete(requestId);\n clearTimeout(pending.timeout);\n pending.reject(error);\n }\n }\n const subId = (_c = msg.subscriptionId) !== null && _c !== void 0 ? _c : msg.id;\n if (subId) {\n const sub = this.findSubscriptionById(subId);\n if (sub) {\n sub.status = 'error';\n sub.error = error;\n this.notifyState(sub);\n for (const callback of Array.from(sub.errorCallbacks)) {\n try {\n callback(error);\n }\n catch ( /* swallow */_d) { /* swallow */ }\n }\n }\n }\n }\n // -----------------------------------------------------------------------\n // Subscribe\n // -----------------------------------------------------------------------\n async subscribe(path, opts = {}) {\n var _a;\n await this.ensureCurrentAuth();\n const tier = (_a = opts.tier) !== null && _a !== void 0 ? _a : 'durable';\n const subKey = this.getSubKey(path, opts);\n let sub = this.subscriptions.get(subKey);\n if (sub) {\n // Existing subscription — add callback\n if (opts.onData)\n sub.callbacks.add(opts.onData);\n if (opts.onState)\n sub.stateCallbacks.add(opts.onState);\n if (opts.onError)\n sub.errorCallbacks.add(opts.onError);\n if (opts.cache && !sub.options.cache) {\n sub.options = Object.assign(Object.assign({}, sub.options), { cache: true });\n }\n // Immediately deliver current state\n if (opts.onData && sub.docs.size > 0) {\n opts.onData(this.docsToArray(sub));\n }\n if (opts.onState) {\n opts.onState(this.getState(sub));\n }\n return this.createUnsubscribe(subKey, sub.id, opts.onData, opts.onState, opts.onError);\n }\n // New subscription\n const subId = `sub_${nextRequestId++}`;\n sub = {\n id: subId,\n path,\n tier,\n options: opts,\n docs: new Map(),\n status: 'idle',\n isStale: false,\n error: null,\n callbacks: new Set(opts.onData ? [opts.onData] : []),\n stateCallbacks: new Set(opts.onState ? [opts.onState] : []),\n errorCallbacks: new Set(opts.onError ? [opts.onError] : []),\n ref: { current: new Map() },\n };\n this.subscriptions.set(subKey, sub);\n // Step 1: Load from IDB only when explicitly enabled and principal-bound.\n if (this.shouldUseIdb(tier, opts)) {\n const cached = await idbGet(this.idbKey(path));\n if (cached && Array.isArray(cached)) {\n for (const doc of cached) {\n if (doc && doc._id)\n sub.docs.set(doc._id, doc);\n }\n sub.ref.current = sub.docs;\n sub.status = 'cached';\n sub.isStale = true;\n this.notifySubscription(sub);\n }\n }\n // Step 2: Connect and subscribe via WS\n sub.status = sub.docs.size > 0 ? 'cached' : 'loading';\n this.notifyState(sub);\n try {\n await this.ensureConnected();\n this.sendSubscribe(sub);\n }\n catch (_b) {\n sub.status = 'error';\n sub.error = new Error('Connection failed');\n this.notifyState(sub);\n }\n return this.createUnsubscribe(subKey, sub.id, opts.onData, opts.onState, opts.onError);\n }\n getRef(path, opts = {}) {\n var _a;\n const subKey = this.getSubKey(path, opts);\n const sub = this.subscriptions.get(subKey);\n if (sub)\n return sub.ref;\n // Auto-subscribe in ref mode\n const ref = { current: new Map() };\n this.subscribe(path, Object.assign(Object.assign({}, opts), { mode: 'ref', tier: 'ephemeral' })).catch(() => { });\n const newSub = this.subscriptions.get(this.getSubKey(path, Object.assign(Object.assign({}, opts), { tier: 'ephemeral' })));\n return (_a = newSub === null || newSub === void 0 ? void 0 : newSub.ref) !== null && _a !== void 0 ? _a : ref;\n }\n // -----------------------------------------------------------------------\n // CRUD operations\n // -----------------------------------------------------------------------\n async set(path, doc) {\n var _a;\n await this.ensureConnected();\n // Resolve operations (Increment, Time.Now) client-side for optimistic update\n const resolvedDoc = this.resolveOperations(doc, path);\n // Optimistic update: apply to local state immediately\n const normalizedPath = path.startsWith('/') ? path.slice(1) : path;\n const collectionPath = this.getCollectionPath(normalizedPath);\n const optimisticDoc = Object.assign(Object.assign({ _id: normalizedPath, pathId: normalizedPath }, resolvedDoc), { \n // System timestamp field name: the Bounded worker stamps the neutral\n // `_updatedAt`; the underscore-prefixed `_updated_at` metadata mirror.\n // Match it so the optimistic doc lines up with the server's confirmation.\n [isBoundedNetwork() ? '_updatedAt' : '_updated_at']: Date.now() });\n const sub = this.findSubscriptionByPath(collectionPath);\n let prevDoc = null;\n if (sub) {\n prevDoc = (_a = sub.docs.get(normalizedPath)) !== null && _a !== void 0 ? _a : null;\n sub.docs.set(normalizedPath, optimisticDoc);\n sub.ref.current = sub.docs;\n this.notifySubscription(sub);\n }\n // Send to server\n const requestId = `r_${nextRequestId++}`;\n try {\n const result = await this.sendRequest(requestId, {\n type: 'set',\n requestId,\n documents: [{ destinationPath: normalizedPath, document: doc }],\n });\n // Replace optimistic doc with server-confirmed version\n if (sub && result && typeof result === 'object') {\n const serverDoc = Array.isArray(result) ? result[0] : result;\n if (serverDoc && serverDoc._id) {\n sub.docs.set(serverDoc._id, serverDoc);\n sub.ref.current = sub.docs;\n this.notifySubscription(sub);\n this.markIdbDirty(collectionPath);\n }\n }\n return Array.isArray(result) ? result[0] : result;\n }\n catch (err) {\n // Revert optimistic update\n if (sub) {\n if (prevDoc) {\n sub.docs.set(normalizedPath, prevDoc);\n }\n else {\n sub.docs.delete(normalizedPath);\n }\n sub.ref.current = sub.docs;\n this.notifySubscription(sub);\n }\n throw err;\n }\n }\n async get(path) {\n await this.ensureCurrentAuth();\n const normalizedPath = path.startsWith('/') ? path.slice(1) : path;\n // Check local subscriptions first\n const collectionPath = this.getCollectionPath(normalizedPath);\n const sub = this.findSubscriptionByPath(collectionPath);\n if (sub && sub.status === 'live') {\n const doc = sub.docs.get(normalizedPath);\n return doc !== null && doc !== void 0 ? doc : null;\n }\n // One-shot WS fetch\n await this.ensureConnected();\n const requestId = `r_${nextRequestId++}`;\n return this.sendRequest(requestId, {\n type: 'get',\n requestId,\n path: normalizedPath,\n });\n }\n async getMany(paths) {\n await this.ensureConnected();\n const normalizedPaths = paths.map(p => p.startsWith('/') ? p.slice(1) : p);\n const requestId = `r_${nextRequestId++}`;\n return this.sendRequest(requestId, {\n type: 'getMany',\n requestId,\n paths: normalizedPaths,\n });\n }\n async delete(path) {\n var _a;\n await this.ensureConnected();\n const normalizedPath = path.startsWith('/') ? path.slice(1) : path;\n // Optimistic: remove from local state\n const collectionPath = this.getCollectionPath(normalizedPath);\n const sub = this.findSubscriptionByPath(collectionPath);\n let prevDoc = null;\n if (sub) {\n prevDoc = (_a = sub.docs.get(normalizedPath)) !== null && _a !== void 0 ? _a : null;\n sub.docs.delete(normalizedPath);\n sub.ref.current = sub.docs;\n this.notifySubscription(sub);\n }\n const requestId = `r_${nextRequestId++}`;\n try {\n await this.sendRequest(requestId, {\n type: 'delete',\n requestId,\n path: normalizedPath,\n });\n if (sub)\n this.markIdbDirty(collectionPath);\n }\n catch (err) {\n // Revert\n if (sub && prevDoc) {\n sub.docs.set(normalizedPath, prevDoc);\n sub.ref.current = sub.docs;\n this.notifySubscription(sub);\n }\n throw err;\n }\n }\n async query(path, opts) {\n await this.ensureConnected();\n const normalizedPath = path.startsWith('/') ? path.slice(1) : path;\n const requestId = `r_${nextRequestId++}`;\n return this.sendRequest(requestId, Object.assign(Object.assign(Object.assign(Object.assign({ type: 'query', requestId, path: normalizedPath }, ((opts === null || opts === void 0 ? void 0 : opts.filter) ? { filter: opts.filter } : {})), ((opts === null || opts === void 0 ? void 0 : opts.sort) ? { sort: opts.sort } : {})), ((opts === null || opts === void 0 ? void 0 : opts.limit) !== undefined ? { limit: opts.limit } : {})), ((opts === null || opts === void 0 ? void 0 : opts.includeSubPaths) ? { includeSubPaths: true } : {})));\n }\n async count(path) {\n var _a;\n await this.ensureConnected();\n const normalizedPath = path.startsWith('/') ? path.slice(1) : path;\n const requestId = `r_${nextRequestId++}`;\n const result = await this.sendRequest(requestId, {\n type: 'count',\n requestId,\n path: normalizedPath,\n });\n return typeof result === 'number' ? result : ((_a = result === null || result === void 0 ? void 0 : result.value) !== null && _a !== void 0 ? _a : 0);\n }\n async aggregate(path, operation, opts) {\n await this.ensureConnected();\n const normalizedPath = path.startsWith('/') ? path.slice(1) : path;\n const requestId = `r_${nextRequestId++}`;\n return this.sendRequest(requestId, Object.assign({ type: 'aggregate', requestId, path: normalizedPath, operation }, ((opts === null || opts === void 0 ? void 0 : opts.field) ? { field: opts.field } : {})));\n }\n // -----------------------------------------------------------------------\n // Helpers\n // -----------------------------------------------------------------------\n sendSubscribe(sub) {\n if (!this.ws || this.ws.readyState !== WebSocket.OPEN)\n return;\n const msg = {\n type: 'subscribe',\n subscriptionId: sub.id,\n path: sub.path,\n };\n if (sub.options.filter)\n msg.filter = sub.options.filter;\n if (sub.options.includeSubPaths)\n msg.includeSubPaths = true;\n if (sub.options.limit)\n msg.limit = sub.options.limit;\n if (sub.options.prompt)\n msg.prompt = sub.options.prompt;\n this.ws.send(JSON.stringify(msg));\n }\n sendRequest(requestId, msg) {\n return new Promise((resolve, reject) => {\n const timeout = setTimeout(() => {\n this.pendingRequests.delete(requestId);\n reject(new Error('Request timed out'));\n }, 30000);\n this.pendingRequests.set(requestId, { resolve, reject, timeout });\n if (this.ws && this.ws.readyState === WebSocket.OPEN) {\n this.ws.send(JSON.stringify(msg));\n }\n else {\n this.pendingRequests.delete(requestId);\n clearTimeout(timeout);\n reject(new Error('WebSocket not connected'));\n }\n });\n }\n notifySubscription(sub) {\n const data = this.docsToArray(sub);\n const callbacks = Array.from(sub.callbacks);\n for (const cb of callbacks) {\n try {\n cb(data);\n }\n catch ( /* swallow callback errors */_a) { /* swallow callback errors */ }\n }\n this.notifyState(sub);\n }\n notifyState(sub) {\n const state = this.getState(sub);\n const callbacks = Array.from(sub.stateCallbacks);\n for (const cb of callbacks) {\n try {\n cb(state);\n }\n catch ( /* swallow */_a) { /* swallow */ }\n }\n }\n getState(sub) {\n return {\n data: this.docsToArray(sub),\n status: sub.status,\n isStale: sub.isStale,\n error: sub.error,\n };\n }\n docsToArray(sub) {\n return Array.from(sub.docs.values());\n }\n findSubscriptionById(id) {\n for (const sub of this.subscriptions.values()) {\n if (sub.id === id)\n return sub;\n }\n return undefined;\n }\n findSubscriptionByPath(collectionPath) {\n for (const sub of this.subscriptions.values()) {\n const subPath = sub.path.startsWith('/') ? sub.path.slice(1) : sub.path;\n if (subPath === collectionPath)\n return sub;\n if (collectionPath.startsWith(subPath + '/'))\n return sub;\n }\n return undefined;\n }\n getCollectionPath(docPath) {\n const segments = docPath.split('/');\n if (segments.length % 2 === 0) {\n return segments.slice(0, -1).join('/');\n }\n return docPath;\n }\n getSubKey(path, opts) {\n const parts = [this.appId, this.authPrincipalKey, path];\n if (opts.filter)\n parts.push(JSON.stringify(opts.filter));\n if (opts.prompt)\n parts.push(opts.prompt);\n if (opts.tier)\n parts.push(opts.tier);\n return parts.join('::');\n }\n idbKey(path) {\n return `${this.appId}:${this.authPrincipalKey}:${path}`;\n }\n shouldUseIdb(tier, opts) {\n return opts.cache === true && tier !== 'ephemeral' && this.authPrincipalKey !== 'anon';\n }\n markIdbDirty(path) {\n const sub = this.findSubscriptionByPath(path);\n if (!sub || !this.shouldUseIdb(sub.tier, sub.options))\n return;\n this.idbDirtyKeys.add(path);\n if (!this.idbFlushTimer) {\n this.idbFlushTimer = setTimeout(() => {\n this.flushIdb();\n this.idbFlushTimer = null;\n }, 500);\n }\n }\n async flushIdb() {\n const keys = Array.from(this.idbDirtyKeys);\n this.idbDirtyKeys.clear();\n for (const path of keys) {\n const sub = this.findSubscriptionByPath(path);\n if (sub && this.shouldUseIdb(sub.tier, sub.options)) {\n const docs = this.docsToArray(sub);\n await idbSet(this.idbKey(path), docs);\n }\n }\n }\n createUnsubscribe(subKey, subId, onData, onState, onError) {\n return async () => {\n var _a;\n const sub = (_a = this.subscriptions.get(subKey)) !== null && _a !== void 0 ? _a : this.findSubscriptionById(subId);\n if (!sub)\n return;\n const currentSubKey = this.getSubKey(sub.path, sub.options);\n if (onData)\n sub.callbacks.delete(onData);\n if (onState)\n sub.stateCallbacks.delete(onState);\n if (onError)\n sub.errorCallbacks.delete(onError);\n // If no more callbacks, unsubscribe entirely\n if (sub.callbacks.size === 0 && sub.stateCallbacks.size === 0 && sub.errorCallbacks.size === 0) {\n this.subscriptions.delete(subKey);\n this.subscriptions.delete(currentSubKey);\n if (this.ws && this.ws.readyState === WebSocket.OPEN) {\n this.ws.send(JSON.stringify({\n type: 'unsubscribe',\n subscriptionId: sub.id,\n }));\n }\n }\n };\n }\n resolveOperations(doc, path) {\n var _a;\n if (!doc || typeof doc !== 'object')\n return doc;\n const resolved = {};\n for (const [key, value] of Object.entries(doc)) {\n if (value && typeof value === 'object' && !Array.isArray(value) && value.operation) {\n const op = value;\n if (op.operation === 'time' && op.value === 'now') {\n resolved[key] = Math.floor(Date.now() / 1000);\n }\n else if (op.operation === 'increment') {\n // For optimistic: get current value and add\n const normalizedPath = path.startsWith('/') ? path.slice(1) : path;\n const collectionPath = this.getCollectionPath(normalizedPath);\n const sub = this.findSubscriptionByPath(collectionPath);\n const existing = sub === null || sub === void 0 ? void 0 : sub.docs.get(normalizedPath);\n const current = (_a = existing === null || existing === void 0 ? void 0 : existing[key]) !== null && _a !== void 0 ? _a : 0;\n resolved[key] = (typeof current === 'number' ? current : 0) + op.value;\n }\n else {\n resolved[key] = value;\n }\n }\n else {\n resolved[key] = value;\n }\n }\n return resolved;\n }\n rejectAllPending(reason) {\n for (const [requestId, pending] of this.pendingRequests) {\n clearTimeout(pending.timeout);\n pending.reject(new Error(reason));\n }\n this.pendingRequests.clear();\n }\n setAllSubscriptionStatus(status) {\n for (const sub of this.subscriptions.values()) {\n sub.status = status;\n this.notifyState(sub);\n }\n }\n // -----------------------------------------------------------------------\n // Lifecycle\n // -----------------------------------------------------------------------\n close() {\n this.closed = true;\n if (this.reconnectTimer)\n clearTimeout(this.reconnectTimer);\n if (this.idbFlushTimer)\n clearTimeout(this.idbFlushTimer);\n if (this.tokenRefreshTimer)\n clearInterval(this.tokenRefreshTimer);\n this.flushIdb();\n if (this.ws) {\n this.ws.close(1000, 'Store closed');\n this.ws = null;\n }\n this.rejectAllPending('Store closed');\n this.subscriptions.clear();\n }\n async reconnectWithNewAuth() {\n if (this.closed)\n return;\n await this.ensureInitialized();\n await this.refreshToken();\n await this.applyAuthPrincipalChange();\n if (this.subscriptions.size > 0) {\n await this.ensureConnected().catch((error) => {\n this.setAllSubscriptionStatus('error');\n for (const sub of this.subscriptions.values()) {\n sub.error = error instanceof Error ? error : new Error(String(error));\n this.notifyState(sub);\n }\n });\n }\n }\n}\n// ---------------------------------------------------------------------------\n// Singleton instance\n// ---------------------------------------------------------------------------\nlet storeInstance = null;\nexport function getRealtimeStore() {\n if (!storeInstance) {\n storeInstance = new RealtimeStore();\n }\n return storeInstance;\n}\nexport function resetRealtimeStore() {\n if (storeInstance) {\n storeInstance.close();\n storeInstance = null;\n }\n}\nexport async function reconnectRealtimeStoreWithNewAuth() {\n if (storeInstance) {\n await storeInstance.reconnectWithNewAuth();\n }\n}\n"],"names":[],"mappings":";;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AA++BO,eAAe,iCAAiC,GAAG;AAI1D;;;;"}
1
+ {"version":3,"file":"realtime-store-CpdBV8Ma.mjs","sources":["../.rollup-tmp/client/realtime-store.js"],"sourcesContent":["// ---------------------------------------------------------------------------\n// realtime-store.ts — Client-side state manager for realtime apps.\n//\n// Manages: WS connection, in-memory state, IDB persistence, optimistic\n// writes, delta accumulation, loading states, ephemeral/durable tiers.\n// ---------------------------------------------------------------------------\nimport { getConfig, isBoundedNetwork } from './config';\n// ---------------------------------------------------------------------------\n// IDB helpers (lazy-loaded, non-blocking)\n// ---------------------------------------------------------------------------\nconst IDB_NAME = 'bounded-realtime';\nconst IDB_STORE = 'subscriptions';\nconst IDB_VERSION = 1;\nlet idbPromise = null;\nfunction getIDB() {\n if (idbPromise)\n return idbPromise;\n if (typeof indexedDB === 'undefined') {\n return Promise.reject(new Error('IndexedDB not available'));\n }\n idbPromise = new Promise((resolve, reject) => {\n const req = indexedDB.open(IDB_NAME, IDB_VERSION);\n req.onupgradeneeded = () => {\n const db = req.result;\n if (!db.objectStoreNames.contains(IDB_STORE)) {\n db.createObjectStore(IDB_STORE);\n }\n };\n req.onsuccess = () => resolve(req.result);\n req.onerror = () => reject(req.error);\n });\n return idbPromise;\n}\nasync function idbGet(key) {\n try {\n const db = await getIDB();\n return new Promise((resolve) => {\n const tx = db.transaction(IDB_STORE, 'readonly');\n const store = tx.objectStore(IDB_STORE);\n const req = store.get(key);\n req.onsuccess = () => { var _a; return resolve((_a = req.result) !== null && _a !== void 0 ? _a : null); };\n req.onerror = () => resolve(null);\n });\n }\n catch (_a) {\n return null;\n }\n}\nasync function idbSet(key, value) {\n try {\n const db = await getIDB();\n return new Promise((resolve) => {\n const tx = db.transaction(IDB_STORE, 'readwrite');\n const store = tx.objectStore(IDB_STORE);\n store.put(value, key);\n tx.oncomplete = () => resolve();\n tx.onerror = () => resolve();\n });\n }\n catch (_a) {\n // Best-effort persistence\n }\n}\nasync function idbDelete(key) {\n try {\n const db = await getIDB();\n return new Promise((resolve) => {\n const tx = db.transaction(IDB_STORE, 'readwrite');\n const store = tx.objectStore(IDB_STORE);\n store.delete(key);\n tx.oncomplete = () => resolve();\n tx.onerror = () => resolve();\n });\n }\n catch (_a) {\n // Best-effort\n }\n}\n// ---------------------------------------------------------------------------\n// RealtimeStore\n// ---------------------------------------------------------------------------\nlet nextRequestId = 1;\nfunction hashForKey(value) {\n let h = 5381;\n for (let i = 0; i < value.length; i++) {\n h = ((h << 5) + h + value.charCodeAt(i)) & 0x7fffffff;\n }\n return h.toString(36);\n}\nfunction principalFromToken(token) {\n return token ? `t${hashForKey(token)}` : 'anon';\n}\nexport class RealtimeStore {\n constructor() {\n this.ws = null;\n this.wsUrl = '';\n this.appId = '';\n this.subscriptions = new Map();\n this.pendingRequests = new Map();\n this.connectPromise = null;\n this.reconnectTimer = null;\n this.reconnectDelay = 1000;\n this.maxReconnectDelay = 30000;\n this.idbFlushTimer = null;\n this.idbDirtyKeys = new Set();\n this.closed = false;\n this.authToken = null;\n this.authPrincipalKey = 'anon';\n this.authenticating = false;\n this.suppressNextReconnect = false;\n this.isServer = false;\n this.tokenRefreshTimer = null;\n // -----------------------------------------------------------------------\n // WebSocket connection\n // -----------------------------------------------------------------------\n this.initPromise = null;\n }\n // -----------------------------------------------------------------------\n // Initialization\n // -----------------------------------------------------------------------\n async init() {\n const config = await getConfig();\n this.appId = config.appId;\n this.wsUrl = config.wsApiUrl;\n this.isServer = config.isServer;\n await this.refreshToken();\n this.startTokenRefresh();\n }\n async refreshToken() {\n let token = null;\n try {\n const { getIdToken } = await import('../utils/utils');\n token = await getIdToken(this.isServer);\n }\n catch ( /* no auth available */_a) { /* no auth available */ }\n this.authToken = token !== null && token !== void 0 ? token : null;\n this.authPrincipalKey = principalFromToken(this.authToken);\n }\n startTokenRefresh() {\n if (this.tokenRefreshTimer)\n return;\n this.tokenRefreshTimer = setInterval(async () => {\n const prevPrincipal = this.authPrincipalKey;\n await this.refreshToken();\n if (this.authPrincipalKey !== prevPrincipal) {\n await this.applyAuthPrincipalChange();\n if (this.subscriptions.size > 0) {\n await this.ensureConnected().catch(() => {\n this.setAllSubscriptionStatus('error');\n });\n }\n }\n }, 5 * 60 * 1000); // Check every 5 minutes\n }\n async ensureInitialized() {\n if (this.appId)\n return;\n if (!this.initPromise)\n this.initPromise = this.init();\n await this.initPromise;\n }\n async ensureCurrentAuth() {\n await this.ensureInitialized();\n const prevPrincipal = this.authPrincipalKey;\n await this.refreshToken();\n if (this.authPrincipalKey !== prevPrincipal) {\n await this.applyAuthPrincipalChange();\n }\n }\n rekeySubscriptionsForPrincipal() {\n const subs = Array.from(this.subscriptions.values());\n this.subscriptions.clear();\n for (const sub of subs) {\n this.subscriptions.set(this.getSubKey(sub.path, sub.options), sub);\n }\n }\n async applyAuthPrincipalChange() {\n if (this.idbFlushTimer) {\n clearTimeout(this.idbFlushTimer);\n this.idbFlushTimer = null;\n }\n this.idbDirtyKeys.clear();\n this.rekeySubscriptionsForPrincipal();\n for (const sub of this.subscriptions.values()) {\n sub.docs.clear();\n sub.ref.current = sub.docs;\n sub.error = null;\n sub.isStale = false;\n let loaded = false;\n if (this.shouldUseIdb(sub.tier, sub.options)) {\n const cached = await idbGet(this.idbKey(sub.path));\n if (cached && Array.isArray(cached)) {\n for (const doc of cached) {\n if (doc && doc._id)\n sub.docs.set(doc._id, doc);\n }\n sub.ref.current = sub.docs;\n loaded = sub.docs.size > 0;\n }\n }\n sub.status = loaded ? 'cached' : 'loading';\n sub.isStale = loaded;\n if (loaded)\n this.notifySubscription(sub);\n else\n this.notifyState(sub);\n }\n if (this.ws) {\n const ws = this.ws;\n this.ws = null;\n this.connectPromise = null;\n this.suppressNextReconnect = true;\n try {\n ws.close(1000, 'Auth changed');\n }\n catch ( /* ignore */_a) { /* ignore */ }\n }\n }\n async ensureConnected() {\n var _a;\n await this.ensureCurrentAuth();\n if (((_a = this.ws) === null || _a === void 0 ? void 0 : _a.readyState) === WebSocket.OPEN && !this.authenticating)\n return;\n if (this.connectPromise)\n return this.connectPromise;\n this.connectPromise = this.connect();\n return this.connectPromise;\n }\n connect() {\n return new Promise((resolve, reject) => {\n if (this.closed) {\n reject(new Error('Store closed'));\n return;\n }\n const params = new URLSearchParams();\n params.set('appId', this.appId);\n const url = `${this.wsUrl}?${params.toString()}`;\n const ws = new WebSocket(url);\n this.ws = ws;\n let authTimer = null;\n const finishConnected = () => {\n if (authTimer) {\n clearTimeout(authTimer);\n authTimer = null;\n }\n this.authenticating = false;\n ws.removeEventListener('error', onError);\n this.reconnectDelay = 1000;\n this.connectPromise = null;\n this.resubscribeAll();\n resolve();\n };\n const onOpen = () => {\n if (!this.authToken) {\n finishConnected();\n return;\n }\n this.authenticating = true;\n authTimer = setTimeout(() => {\n this.authenticating = false;\n this.connectPromise = null;\n try {\n ws.close(1008, 'Authentication timeout');\n }\n catch ( /* ignore */_a) { /* ignore */ }\n reject(new Error('WebSocket authentication timeout'));\n }, 10000);\n try {\n ws.send(JSON.stringify({ type: 'auth', token: this.authToken }));\n }\n catch (e) {\n if (authTimer)\n clearTimeout(authTimer);\n this.authenticating = false;\n this.connectPromise = null;\n reject(e);\n }\n };\n const onError = (e) => {\n if (authTimer)\n clearTimeout(authTimer);\n this.authenticating = false;\n ws.removeEventListener('open', onOpen);\n this.connectPromise = null;\n reject(new Error('WebSocket connection failed'));\n };\n ws.addEventListener('open', onOpen, { once: true });\n ws.addEventListener('error', onError, { once: true });\n ws.addEventListener('message', (event) => {\n if (this.authenticating) {\n try {\n const msg = JSON.parse(typeof event.data === 'string' ? event.data : new TextDecoder().decode(event.data));\n if ((msg === null || msg === void 0 ? void 0 : msg.type) === 'authenticated') {\n finishConnected();\n return;\n }\n }\n catch ( /* fall through to normal handling */_a) { /* fall through to normal handling */ }\n }\n this.handleMessage(event.data);\n });\n ws.addEventListener('close', () => {\n if (authTimer)\n clearTimeout(authTimer);\n if (this.ws !== ws) {\n if (this.suppressNextReconnect)\n this.suppressNextReconnect = false;\n return;\n }\n this.authenticating = false;\n this.ws = null;\n this.connectPromise = null;\n this.rejectAllPending('WebSocket closed');\n this.setAllSubscriptionStatus('reconnecting');\n if (this.suppressNextReconnect) {\n this.suppressNextReconnect = false;\n return;\n }\n this.scheduleReconnect();\n });\n });\n }\n scheduleReconnect() {\n if (this.closed)\n return;\n if (this.reconnectTimer)\n clearTimeout(this.reconnectTimer);\n this.reconnectTimer = setTimeout(() => {\n this.ensureConnected().catch(() => {\n this.reconnectDelay = Math.min(this.reconnectDelay * 2, this.maxReconnectDelay);\n this.scheduleReconnect();\n });\n }, this.reconnectDelay);\n }\n resubscribeAll() {\n for (const sub of this.subscriptions.values()) {\n this.sendSubscribe(sub);\n }\n }\n // -----------------------------------------------------------------------\n // Message handling\n // -----------------------------------------------------------------------\n handleMessage(raw) {\n const text = typeof raw === 'string' ? raw : new TextDecoder().decode(raw);\n let msg;\n try {\n msg = JSON.parse(text);\n }\n catch (_a) {\n return;\n }\n switch (msg.type) {\n case 'snapshot':\n this.handleSnapshot(msg);\n break;\n case 'delta':\n this.handleDelta(msg);\n break;\n case 'result':\n this.handleResult(msg);\n break;\n case 'error':\n this.handleError(msg);\n break;\n case 'pong':\n break;\n case 'authenticated':\n break;\n // v1 compat: handle legacy message types during transition\n case 'subscribed':\n this.handleSnapshot(Object.assign(Object.assign({}, msg), { type: 'snapshot', docs: msg.data }));\n break;\n case 'data':\n // Legacy full-snapshot delta — treat as snapshot replacement\n this.handleLegacyData(msg);\n break;\n case 'response':\n this.handleResult(Object.assign(Object.assign({}, msg), { type: 'result', ok: msg.status === 200, doc: msg.data }));\n break;\n }\n }\n handleSnapshot(msg) {\n var _a, _b, _c;\n const subId = (_a = msg.id) !== null && _a !== void 0 ? _a : msg.subscriptionId;\n if (!subId)\n return;\n const sub = this.findSubscriptionById(subId);\n if (!sub)\n return;\n const docs = (_c = (_b = msg.docs) !== null && _b !== void 0 ? _b : msg.data) !== null && _c !== void 0 ? _c : [];\n const docsArray = Array.isArray(docs) ? docs : [docs];\n sub.docs.clear();\n for (const doc of docsArray) {\n if (doc && doc._id) {\n sub.docs.set(doc._id, doc);\n }\n }\n sub.ref.current = sub.docs;\n sub.status = 'live';\n sub.isStale = false;\n sub.error = null;\n this.notifySubscription(sub);\n this.markIdbDirty(sub.path);\n }\n handleDelta(msg) {\n var _a, _b;\n const subId = (_a = msg.id) !== null && _a !== void 0 ? _a : msg.subscriptionId;\n if (!subId)\n return;\n const sub = this.findSubscriptionById(subId);\n if (!sub)\n return;\n if (sub.tier === 'ephemeral') {\n // Ephemeral: just overwrite, no accumulation logic\n if (msg.change === 'removed' && msg.docId) {\n sub.docs.delete(msg.docId);\n }\n else if (msg.doc && msg.doc._id) {\n sub.docs.set(msg.doc._id, msg.doc);\n }\n sub.ref.current = sub.docs;\n if (sub.options.mode !== 'ref') {\n this.notifySubscription(sub);\n }\n return;\n }\n // Durable/checkpointed: full delta handling\n switch (msg.change) {\n case 'added':\n case 'modified':\n if (msg.doc && msg.doc._id) {\n sub.docs.set(msg.doc._id, msg.doc);\n }\n break;\n case 'removed':\n if (msg.docId) {\n sub.docs.delete(msg.docId);\n }\n else if ((_b = msg.doc) === null || _b === void 0 ? void 0 : _b._id) {\n sub.docs.delete(msg.doc._id);\n }\n break;\n }\n sub.ref.current = sub.docs;\n this.notifySubscription(sub);\n this.markIdbDirty(sub.path);\n }\n handleLegacyData(msg) {\n // Legacy v1 format: 'data' message with full snapshot or single doc\n const subId = msg.subscriptionId;\n if (!subId)\n return;\n const sub = this.findSubscriptionById(subId);\n if (!sub)\n return;\n if (Array.isArray(msg.data)) {\n // Full snapshot replacement\n sub.docs.clear();\n for (const doc of msg.data) {\n if (doc && doc._id)\n sub.docs.set(doc._id, doc);\n }\n }\n else if (msg.data && msg.data._id) {\n // Single doc update\n sub.docs.set(msg.data._id, msg.data);\n }\n else if (msg.data === null) {\n // Removal — but we don't know which doc. Re-fetch needed.\n }\n sub.ref.current = sub.docs;\n sub.status = 'live';\n sub.isStale = false;\n this.notifySubscription(sub);\n this.markIdbDirty(sub.path);\n }\n handleResult(msg) {\n var _a, _b, _c, _d;\n const requestId = msg.requestId;\n if (!requestId)\n return;\n const pending = this.pendingRequests.get(requestId);\n if (!pending)\n return;\n this.pendingRequests.delete(requestId);\n clearTimeout(pending.timeout);\n const ok = (_a = msg.ok) !== null && _a !== void 0 ? _a : (msg.status === 200);\n if (ok) {\n pending.resolve((_c = (_b = msg.doc) !== null && _b !== void 0 ? _b : msg.data) !== null && _c !== void 0 ? _c : true);\n }\n else {\n pending.reject(new Error((_d = msg.error) !== null && _d !== void 0 ? _d : 'Operation failed'));\n }\n }\n handleError(msg) {\n var _a, _b, _c;\n const error = new Error((_a = msg.message) !== null && _a !== void 0 ? _a : (msg.code ? `${msg.code}: Server error` : 'Server error'));\n if (msg.code)\n error.code = msg.code;\n if (msg.subscriptionId || msg.id)\n error.subscriptionId = (_b = msg.subscriptionId) !== null && _b !== void 0 ? _b : msg.id;\n const requestId = msg.requestId;\n if (requestId) {\n const pending = this.pendingRequests.get(requestId);\n if (pending) {\n this.pendingRequests.delete(requestId);\n clearTimeout(pending.timeout);\n pending.reject(error);\n }\n }\n const subId = (_c = msg.subscriptionId) !== null && _c !== void 0 ? _c : msg.id;\n if (subId) {\n const sub = this.findSubscriptionById(subId);\n if (sub) {\n sub.status = 'error';\n sub.error = error;\n this.notifyState(sub);\n for (const callback of Array.from(sub.errorCallbacks)) {\n try {\n callback(error);\n }\n catch ( /* swallow */_d) { /* swallow */ }\n }\n }\n }\n }\n // -----------------------------------------------------------------------\n // Subscribe\n // -----------------------------------------------------------------------\n async subscribe(path, opts = {}) {\n var _a;\n await this.ensureCurrentAuth();\n const tier = (_a = opts.tier) !== null && _a !== void 0 ? _a : 'durable';\n const subKey = this.getSubKey(path, opts);\n let sub = this.subscriptions.get(subKey);\n if (sub) {\n // Existing subscription — add callback\n if (opts.onData)\n sub.callbacks.add(opts.onData);\n if (opts.onState)\n sub.stateCallbacks.add(opts.onState);\n if (opts.onError)\n sub.errorCallbacks.add(opts.onError);\n if (opts.cache && !sub.options.cache) {\n sub.options = Object.assign(Object.assign({}, sub.options), { cache: true });\n }\n // Immediately deliver current state\n if (opts.onData && sub.docs.size > 0) {\n opts.onData(this.docsToArray(sub));\n }\n if (opts.onState) {\n opts.onState(this.getState(sub));\n }\n return this.createUnsubscribe(subKey, sub.id, opts.onData, opts.onState, opts.onError);\n }\n // New subscription\n const subId = `sub_${nextRequestId++}`;\n sub = {\n id: subId,\n path,\n tier,\n options: opts,\n docs: new Map(),\n status: 'idle',\n isStale: false,\n error: null,\n callbacks: new Set(opts.onData ? [opts.onData] : []),\n stateCallbacks: new Set(opts.onState ? [opts.onState] : []),\n errorCallbacks: new Set(opts.onError ? [opts.onError] : []),\n ref: { current: new Map() },\n };\n this.subscriptions.set(subKey, sub);\n // Step 1: Load from IDB only when explicitly enabled and principal-bound.\n if (this.shouldUseIdb(tier, opts)) {\n const cached = await idbGet(this.idbKey(path));\n if (cached && Array.isArray(cached)) {\n for (const doc of cached) {\n if (doc && doc._id)\n sub.docs.set(doc._id, doc);\n }\n sub.ref.current = sub.docs;\n sub.status = 'cached';\n sub.isStale = true;\n this.notifySubscription(sub);\n }\n }\n // Step 2: Connect and subscribe via WS\n sub.status = sub.docs.size > 0 ? 'cached' : 'loading';\n this.notifyState(sub);\n try {\n await this.ensureConnected();\n this.sendSubscribe(sub);\n }\n catch (_b) {\n sub.status = 'error';\n sub.error = new Error('Connection failed');\n this.notifyState(sub);\n }\n return this.createUnsubscribe(subKey, sub.id, opts.onData, opts.onState, opts.onError);\n }\n getRef(path, opts = {}) {\n var _a;\n const subKey = this.getSubKey(path, opts);\n const sub = this.subscriptions.get(subKey);\n if (sub)\n return sub.ref;\n // Auto-subscribe in ref mode\n const ref = { current: new Map() };\n this.subscribe(path, Object.assign(Object.assign({}, opts), { mode: 'ref', tier: 'ephemeral' })).catch(() => { });\n const newSub = this.subscriptions.get(this.getSubKey(path, Object.assign(Object.assign({}, opts), { tier: 'ephemeral' })));\n return (_a = newSub === null || newSub === void 0 ? void 0 : newSub.ref) !== null && _a !== void 0 ? _a : ref;\n }\n // -----------------------------------------------------------------------\n // CRUD operations\n // -----------------------------------------------------------------------\n async set(path, doc) {\n var _a;\n await this.ensureConnected();\n // Resolve operations (Increment, Time.Now) client-side for optimistic update\n const resolvedDoc = this.resolveOperations(doc, path);\n // Optimistic update: apply to local state immediately\n const normalizedPath = path.startsWith('/') ? path.slice(1) : path;\n const collectionPath = this.getCollectionPath(normalizedPath);\n const optimisticDoc = Object.assign(Object.assign({ _id: normalizedPath, pathId: normalizedPath }, resolvedDoc), { \n // System timestamp field name: the Bounded worker stamps the neutral\n // `_updatedAt`; the underscore-prefixed `_updated_at` metadata mirror.\n // Match it so the optimistic doc lines up with the server's confirmation.\n [isBoundedNetwork() ? '_updatedAt' : '_updated_at']: Date.now() });\n const sub = this.findSubscriptionByPath(collectionPath);\n let prevDoc = null;\n if (sub) {\n prevDoc = (_a = sub.docs.get(normalizedPath)) !== null && _a !== void 0 ? _a : null;\n sub.docs.set(normalizedPath, optimisticDoc);\n sub.ref.current = sub.docs;\n this.notifySubscription(sub);\n }\n // Send to server\n const requestId = `r_${nextRequestId++}`;\n try {\n const result = await this.sendRequest(requestId, {\n type: 'set',\n requestId,\n documents: [{ destinationPath: normalizedPath, document: doc }],\n });\n // Replace optimistic doc with server-confirmed version\n if (sub && result && typeof result === 'object') {\n const serverDoc = Array.isArray(result) ? result[0] : result;\n if (serverDoc && serverDoc._id) {\n sub.docs.set(serverDoc._id, serverDoc);\n sub.ref.current = sub.docs;\n this.notifySubscription(sub);\n this.markIdbDirty(collectionPath);\n }\n }\n return Array.isArray(result) ? result[0] : result;\n }\n catch (err) {\n // Revert optimistic update\n if (sub) {\n if (prevDoc) {\n sub.docs.set(normalizedPath, prevDoc);\n }\n else {\n sub.docs.delete(normalizedPath);\n }\n sub.ref.current = sub.docs;\n this.notifySubscription(sub);\n }\n throw err;\n }\n }\n async get(path) {\n await this.ensureCurrentAuth();\n const normalizedPath = path.startsWith('/') ? path.slice(1) : path;\n // Check local subscriptions first\n const collectionPath = this.getCollectionPath(normalizedPath);\n const sub = this.findSubscriptionByPath(collectionPath);\n if (sub && sub.status === 'live') {\n const doc = sub.docs.get(normalizedPath);\n return doc !== null && doc !== void 0 ? doc : null;\n }\n // One-shot WS fetch\n await this.ensureConnected();\n const requestId = `r_${nextRequestId++}`;\n return this.sendRequest(requestId, {\n type: 'get',\n requestId,\n path: normalizedPath,\n });\n }\n async getMany(paths) {\n await this.ensureConnected();\n const normalizedPaths = paths.map(p => p.startsWith('/') ? p.slice(1) : p);\n const requestId = `r_${nextRequestId++}`;\n return this.sendRequest(requestId, {\n type: 'getMany',\n requestId,\n paths: normalizedPaths,\n });\n }\n async delete(path) {\n var _a;\n await this.ensureConnected();\n const normalizedPath = path.startsWith('/') ? path.slice(1) : path;\n // Optimistic: remove from local state\n const collectionPath = this.getCollectionPath(normalizedPath);\n const sub = this.findSubscriptionByPath(collectionPath);\n let prevDoc = null;\n if (sub) {\n prevDoc = (_a = sub.docs.get(normalizedPath)) !== null && _a !== void 0 ? _a : null;\n sub.docs.delete(normalizedPath);\n sub.ref.current = sub.docs;\n this.notifySubscription(sub);\n }\n const requestId = `r_${nextRequestId++}`;\n try {\n await this.sendRequest(requestId, {\n type: 'delete',\n requestId,\n path: normalizedPath,\n });\n if (sub)\n this.markIdbDirty(collectionPath);\n }\n catch (err) {\n // Revert\n if (sub && prevDoc) {\n sub.docs.set(normalizedPath, prevDoc);\n sub.ref.current = sub.docs;\n this.notifySubscription(sub);\n }\n throw err;\n }\n }\n async query(path, opts) {\n await this.ensureConnected();\n const normalizedPath = path.startsWith('/') ? path.slice(1) : path;\n const requestId = `r_${nextRequestId++}`;\n return this.sendRequest(requestId, Object.assign(Object.assign(Object.assign(Object.assign({ type: 'query', requestId, path: normalizedPath }, ((opts === null || opts === void 0 ? void 0 : opts.filter) ? { filter: opts.filter } : {})), ((opts === null || opts === void 0 ? void 0 : opts.sort) ? { sort: opts.sort } : {})), ((opts === null || opts === void 0 ? void 0 : opts.limit) !== undefined ? { limit: opts.limit } : {})), ((opts === null || opts === void 0 ? void 0 : opts.includeSubPaths) ? { includeSubPaths: true } : {})));\n }\n async count(path) {\n var _a;\n await this.ensureConnected();\n const normalizedPath = path.startsWith('/') ? path.slice(1) : path;\n const requestId = `r_${nextRequestId++}`;\n const result = await this.sendRequest(requestId, {\n type: 'count',\n requestId,\n path: normalizedPath,\n });\n return typeof result === 'number' ? result : ((_a = result === null || result === void 0 ? void 0 : result.value) !== null && _a !== void 0 ? _a : 0);\n }\n async aggregate(path, operation, opts) {\n await this.ensureConnected();\n const normalizedPath = path.startsWith('/') ? path.slice(1) : path;\n const requestId = `r_${nextRequestId++}`;\n return this.sendRequest(requestId, Object.assign({ type: 'aggregate', requestId, path: normalizedPath, operation }, ((opts === null || opts === void 0 ? void 0 : opts.field) ? { field: opts.field } : {})));\n }\n // -----------------------------------------------------------------------\n // Helpers\n // -----------------------------------------------------------------------\n sendSubscribe(sub) {\n if (!this.ws || this.ws.readyState !== WebSocket.OPEN)\n return;\n const msg = {\n type: 'subscribe',\n subscriptionId: sub.id,\n path: sub.path,\n };\n if (sub.options.filter)\n msg.filter = sub.options.filter;\n if (sub.options.includeSubPaths)\n msg.includeSubPaths = true;\n if (sub.options.limit)\n msg.limit = sub.options.limit;\n if (sub.options.prompt)\n msg.prompt = sub.options.prompt;\n this.ws.send(JSON.stringify(msg));\n }\n sendRequest(requestId, msg) {\n return new Promise((resolve, reject) => {\n const timeout = setTimeout(() => {\n this.pendingRequests.delete(requestId);\n reject(new Error('Request timed out'));\n }, 30000);\n this.pendingRequests.set(requestId, { resolve, reject, timeout });\n if (this.ws && this.ws.readyState === WebSocket.OPEN) {\n this.ws.send(JSON.stringify(msg));\n }\n else {\n this.pendingRequests.delete(requestId);\n clearTimeout(timeout);\n reject(new Error('WebSocket not connected'));\n }\n });\n }\n notifySubscription(sub) {\n const data = this.docsToArray(sub);\n const callbacks = Array.from(sub.callbacks);\n for (const cb of callbacks) {\n try {\n cb(data);\n }\n catch ( /* swallow callback errors */_a) { /* swallow callback errors */ }\n }\n this.notifyState(sub);\n }\n notifyState(sub) {\n const state = this.getState(sub);\n const callbacks = Array.from(sub.stateCallbacks);\n for (const cb of callbacks) {\n try {\n cb(state);\n }\n catch ( /* swallow */_a) { /* swallow */ }\n }\n }\n getState(sub) {\n return {\n data: this.docsToArray(sub),\n status: sub.status,\n isStale: sub.isStale,\n error: sub.error,\n };\n }\n docsToArray(sub) {\n return Array.from(sub.docs.values());\n }\n findSubscriptionById(id) {\n for (const sub of this.subscriptions.values()) {\n if (sub.id === id)\n return sub;\n }\n return undefined;\n }\n findSubscriptionByPath(collectionPath) {\n for (const sub of this.subscriptions.values()) {\n const subPath = sub.path.startsWith('/') ? sub.path.slice(1) : sub.path;\n if (subPath === collectionPath)\n return sub;\n if (collectionPath.startsWith(subPath + '/'))\n return sub;\n }\n return undefined;\n }\n getCollectionPath(docPath) {\n const segments = docPath.split('/');\n if (segments.length % 2 === 0) {\n return segments.slice(0, -1).join('/');\n }\n return docPath;\n }\n getSubKey(path, opts) {\n const parts = [this.appId, this.authPrincipalKey, path];\n if (opts.filter)\n parts.push(JSON.stringify(opts.filter));\n if (opts.prompt)\n parts.push(opts.prompt);\n if (opts.tier)\n parts.push(opts.tier);\n return parts.join('::');\n }\n idbKey(path) {\n return `${this.appId}:${this.authPrincipalKey}:${path}`;\n }\n shouldUseIdb(tier, opts) {\n return opts.cache === true && tier !== 'ephemeral' && this.authPrincipalKey !== 'anon';\n }\n markIdbDirty(path) {\n const sub = this.findSubscriptionByPath(path);\n if (!sub || !this.shouldUseIdb(sub.tier, sub.options))\n return;\n this.idbDirtyKeys.add(path);\n if (!this.idbFlushTimer) {\n this.idbFlushTimer = setTimeout(() => {\n this.flushIdb();\n this.idbFlushTimer = null;\n }, 500);\n }\n }\n async flushIdb() {\n const keys = Array.from(this.idbDirtyKeys);\n this.idbDirtyKeys.clear();\n for (const path of keys) {\n const sub = this.findSubscriptionByPath(path);\n if (sub && this.shouldUseIdb(sub.tier, sub.options)) {\n const docs = this.docsToArray(sub);\n await idbSet(this.idbKey(path), docs);\n }\n }\n }\n createUnsubscribe(subKey, subId, onData, onState, onError) {\n return async () => {\n var _a;\n const sub = (_a = this.subscriptions.get(subKey)) !== null && _a !== void 0 ? _a : this.findSubscriptionById(subId);\n if (!sub)\n return;\n const currentSubKey = this.getSubKey(sub.path, sub.options);\n if (onData)\n sub.callbacks.delete(onData);\n if (onState)\n sub.stateCallbacks.delete(onState);\n if (onError)\n sub.errorCallbacks.delete(onError);\n // If no more callbacks, unsubscribe entirely\n if (sub.callbacks.size === 0 && sub.stateCallbacks.size === 0 && sub.errorCallbacks.size === 0) {\n this.subscriptions.delete(subKey);\n this.subscriptions.delete(currentSubKey);\n if (this.ws && this.ws.readyState === WebSocket.OPEN) {\n this.ws.send(JSON.stringify({\n type: 'unsubscribe',\n subscriptionId: sub.id,\n }));\n }\n }\n };\n }\n resolveOperations(doc, path) {\n var _a;\n if (!doc || typeof doc !== 'object')\n return doc;\n const resolved = {};\n for (const [key, value] of Object.entries(doc)) {\n if (value && typeof value === 'object' && !Array.isArray(value) && value.operation) {\n const op = value;\n if (op.operation === 'time' && op.value === 'now') {\n resolved[key] = Math.floor(Date.now() / 1000);\n }\n else if (op.operation === 'increment') {\n // For optimistic: get current value and add\n const normalizedPath = path.startsWith('/') ? path.slice(1) : path;\n const collectionPath = this.getCollectionPath(normalizedPath);\n const sub = this.findSubscriptionByPath(collectionPath);\n const existing = sub === null || sub === void 0 ? void 0 : sub.docs.get(normalizedPath);\n const current = (_a = existing === null || existing === void 0 ? void 0 : existing[key]) !== null && _a !== void 0 ? _a : 0;\n resolved[key] = (typeof current === 'number' ? current : 0) + op.value;\n }\n else {\n resolved[key] = value;\n }\n }\n else {\n resolved[key] = value;\n }\n }\n return resolved;\n }\n rejectAllPending(reason) {\n for (const [requestId, pending] of this.pendingRequests) {\n clearTimeout(pending.timeout);\n pending.reject(new Error(reason));\n }\n this.pendingRequests.clear();\n }\n setAllSubscriptionStatus(status) {\n for (const sub of this.subscriptions.values()) {\n sub.status = status;\n this.notifyState(sub);\n }\n }\n // -----------------------------------------------------------------------\n // Lifecycle\n // -----------------------------------------------------------------------\n close() {\n this.closed = true;\n if (this.reconnectTimer)\n clearTimeout(this.reconnectTimer);\n if (this.idbFlushTimer)\n clearTimeout(this.idbFlushTimer);\n if (this.tokenRefreshTimer)\n clearInterval(this.tokenRefreshTimer);\n this.flushIdb();\n if (this.ws) {\n this.ws.close(1000, 'Store closed');\n this.ws = null;\n }\n this.rejectAllPending('Store closed');\n this.subscriptions.clear();\n }\n async reconnectWithNewAuth() {\n if (this.closed)\n return;\n await this.ensureInitialized();\n await this.refreshToken();\n await this.applyAuthPrincipalChange();\n if (this.subscriptions.size > 0) {\n await this.ensureConnected().catch((error) => {\n this.setAllSubscriptionStatus('error');\n for (const sub of this.subscriptions.values()) {\n sub.error = error instanceof Error ? error : new Error(String(error));\n this.notifyState(sub);\n }\n });\n }\n }\n}\n// ---------------------------------------------------------------------------\n// Singleton instance\n// ---------------------------------------------------------------------------\nlet storeInstance = null;\nexport function getRealtimeStore() {\n if (!storeInstance) {\n storeInstance = new RealtimeStore();\n }\n return storeInstance;\n}\nexport function resetRealtimeStore() {\n if (storeInstance) {\n storeInstance.close();\n storeInstance = null;\n }\n}\nexport async function reconnectRealtimeStoreWithNewAuth() {\n if (storeInstance) {\n await storeInstance.reconnectWithNewAuth();\n }\n}\n"],"names":[],"mappings":";;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AA++BO,eAAe,iCAAiC,GAAG;AAI1D;;;;"}
@@ -158,18 +158,7 @@ async function buildSetDocumentsTransaction(connection, idl, programProvider, pa
158
158
  instructions.unshift(computeBudgetIx);
159
159
  instructions.push(...preInstructions);
160
160
  let tx = null;
161
- if (idl.address === "taro6CvKqwrYrDc16ufYgzQ2NZcyyVKStffbtudrhRu") {
162
- const program = new Program(idl, programProvider);
163
- tx = await program.methods
164
- .setDocuments(args.app_id, prepareProgramArgs(args.documents, PublicKey), args.delete_paths, simulate)
165
- .preInstructions(instructions)
166
- .accounts({
167
- payer: payerPublicKey
168
- })
169
- .remainingAccounts(remainingAccounts)
170
- .transaction();
171
- }
172
- else if (idl.address === "poof4b5pk1L9tmThvBmaABjcyjfhFGbMbQP5BXk2QZp") {
161
+ if (idl.address === "openTv7fbpYSseNHYmCZFZ1CZgj4r8D9fKNgEz1qo6F") {
173
162
  const program = new Program(idl, programProvider);
174
163
  // Single canonical set_documents wire format (ra_indices Vec<u8>).
175
164
  tx = await program.methods
@@ -240,4 +229,4 @@ async function buildSetDocumentsTransaction(connection, idl, programProvider, pa
240
229
 
241
230
  exports.buildSetDocumentsTransaction = buildSetDocumentsTransaction;
242
231
  exports.isHexString = isHexString;
243
- //# sourceMappingURL=sol-utils-oBMCMbe_.js.map
232
+ //# sourceMappingURL=sol-utils-Cwxyl2h6.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sol-utils-Cwxyl2h6.js","sources":["../.rollup-tmp/utils/sol/sol-utils.js"],"sourcesContent":["import BN from \"bn.js\";\n// ─────────────────────────────────────────────────────────────\n// Local implementation of getSimulationComputeUnits\n// (Replaces @solana-developers/helpers to avoid Wallet import issue in browser/ESM builds)\n// Source: https://github.com/solana-developers/helpers/blob/main/src/lib/transaction.ts\n// ─────────────────────────────────────────────────────────────\n/**\n * Check if a given instruction is a SetComputeUnitLimit instruction\n * See https://github.com/solana-program/compute-budget/blob/main/clients/js/src/generated/programs/computeBudget.ts#L29\n */\nfunction isSetComputeLimitInstruction(ix, computeBudgetProgram) {\n return (ix.programId.equals(computeBudgetProgram.programId) &&\n ix.data[0] === 2 // opcode for setComputeUnitLimit is 2\n );\n}\n/**\n * Get the compute units required for a set of instructions via simulation.\n * Credit https://twitter.com/stegabob, originally from https://x.com/stegaBOB/status/1766662289392889920\n */\nasync function getSimulationComputeUnits(connection, instructions, payer, lookupTables, chainSdk, commitment = \"confirmed\") {\n var _a, _b, _c;\n const simulationInstructions = [...instructions];\n // Replace or add compute limit instruction\n const computeLimitIndex = simulationInstructions.findIndex(ix => isSetComputeLimitInstruction(ix, chainSdk.ComputeBudgetProgram));\n const simulationLimitIx = chainSdk.ComputeBudgetProgram.setComputeUnitLimit({\n units: 1400000,\n });\n if (computeLimitIndex >= 0) {\n simulationInstructions[computeLimitIndex] = simulationLimitIx;\n }\n else {\n simulationInstructions.unshift(simulationLimitIx);\n }\n const testTransaction = new chainSdk.VersionedTransaction(new chainSdk.TransactionMessage({\n instructions: simulationInstructions,\n payerKey: payer,\n // RecentBlockhash can by any public key during simulation\n // since 'replaceRecentBlockhash' is set to 'true' below\n recentBlockhash: chainSdk.PublicKey.default.toString(),\n }).compileToV0Message(lookupTables));\n const rpcResponse = await connection.simulateTransaction(testTransaction, {\n replaceRecentBlockhash: true,\n sigVerify: false,\n commitment,\n });\n if ((_a = rpcResponse === null || rpcResponse === void 0 ? void 0 : rpcResponse.value) === null || _a === void 0 ? void 0 : _a.err) {\n const logs = ((_b = rpcResponse.value.logs) === null || _b === void 0 ? void 0 : _b.join(\"\\n • \")) || \"No logs available\";\n throw new Error(`Transaction simulation failed:\\n •${logs}` +\n JSON.stringify((_c = rpcResponse === null || rpcResponse === void 0 ? void 0 : rpcResponse.value) === null || _c === void 0 ? void 0 : _c.err));\n }\n return rpcResponse.value.unitsConsumed || null;\n}\n// ─────────────────────────────────────────────────────────────\n// Serialization Helpers\n// ─────────────────────────────────────────────────────────────\nexport function isHexString(value) {\n // Only strings can be hex-encoded. Numbers, BNs, and other shapes coming back\n // from the API for u64Val/i64Val should fall through to `new BN(value)` which\n // accepts those shapes natively. Without this guard, calling .startsWith on a\n // non-string blows up with TypeError.\n if (typeof value !== \"string\")\n return false;\n // Matches strings containing only 0-9, a-f, or A-F (optionally prefixed with 0x)\n let testValue = value;\n // Handle negative values\n if (value.startsWith(\"-\")) {\n testValue = value.slice(1);\n }\n const hexRegex = /^(0x)?[0-9a-fA-F]+$/;\n return hexRegex.test(testValue);\n}\nfunction prepareProgramArgs(documents, PublicKeyClass) {\n return documents.map((doc) => ({\n path: doc.path,\n operations: doc.operations.map((op) => {\n let preparedValue = null;\n if (op.value) {\n if (\"u64Val\" in op.value) {\n if (isHexString(op.value.u64Val.value)) {\n preparedValue = { u64Val: { value: new BN(op.value.u64Val.value, \"hex\") } };\n }\n else {\n preparedValue = { u64Val: { value: new BN(op.value.u64Val.value) } };\n }\n }\n else if (\"i64Val\" in op.value) {\n if (isHexString(op.value.i64Val.value)) {\n if (op.value.i64Val.value.startsWith(\"-\")) {\n preparedValue = { i64Val: { value: new BN(op.value.i64Val.value.slice(1), \"hex\").mul(new BN(-1)) } };\n }\n else {\n preparedValue = { i64Val: { value: new BN(op.value.i64Val.value, \"hex\") } };\n }\n }\n else {\n preparedValue = { i64Val: { value: new BN(op.value.i64Val.value) } };\n }\n }\n else if (op.value.boolVal) {\n preparedValue = { boolVal: { value: !!op.value.boolVal.value } };\n }\n else if (op.value.stringVal) {\n preparedValue = { stringVal: { value: String(op.value.stringVal.value) } };\n }\n else if (op.value.addressVal) {\n preparedValue = { addressVal: { value: new PublicKeyClass(op.value.addressVal.value) } };\n }\n else {\n preparedValue = op.value;\n }\n }\n return {\n key: op.key,\n value: preparedValue,\n operation: op.operation,\n };\n }),\n }));\n}\n// ─────────────────────────────────────────────────────────────\n// Updated Transaction Builder: It now accepts a PublicKey for the payer\n// ─────────────────────────────────────────────────────────────\nexport async function buildSetDocumentsTransaction(connection, idl, programProvider, payerPublicKey, args, remainingAccounts, lutKey, preInstructions, simulate, additionalLutAddresses) {\n // LITERAL dynamic import specifiers on purpose: bundlers can only code-split\n // a lazy chunk when the specifier is statically analyzable. (An earlier pass\n // obfuscated these to satisfy a bundle-grep check — that breaks Vite/webpack\n // consumers at runtime. Verify laziness with esbuild --splitting instead.)\n const [chainSdk, programSdk] = await Promise.all([\n import(\"@solana/web3.js\"),\n import(\"@coral-xyz/anchor\"),\n ]);\n const { ComputeBudgetProgram, PublicKey } = chainSdk;\n const { Program } = programSdk;\n // The sync convertRemainingAccounts facade produces PublicKey DUCK-TYPES\n // (base58/bytes/equals only) so callers can dedupe without loading web3.\n // Anything entering Anchor/web3 internals must be a REAL PublicKey —\n // instanceof checks and _bn internals are not part of the duck contract.\n // web3 is loaded here, so rehydrate at this seam.\n const asRealPublicKey = (value) => value instanceof PublicKey ? value : new PublicKey(value.toBytes());\n remainingAccounts = remainingAccounts.map((account) => ({\n pubkey: asRealPublicKey(account.pubkey),\n isWritable: account.isWritable,\n isSigner: account.isSigner,\n }));\n payerPublicKey = asRealPublicKey(payerPublicKey);\n const computeBudgetIx = ComputeBudgetProgram.setComputeUnitLimit({\n units: 1400000,\n });\n //TODO: Figure out long term solution to estimate heap frame size\n const requestHeapFrameIx = ComputeBudgetProgram.requestHeapFrame({\n bytes: 1024 * 160,\n });\n const instructions = [];\n instructions.unshift(requestHeapFrameIx);\n instructions.unshift(computeBudgetIx);\n instructions.push(...preInstructions);\n let tx = null;\n if (idl.address === \"openTv7fbpYSseNHYmCZFZ1CZgj4r8D9fKNgEz1qo6F\") {\n const program = new Program(idl, programProvider);\n // Single canonical set_documents wire format (ra_indices Vec<u8>).\n tx = await program.methods\n .setDocuments(args.app_id, prepareProgramArgs(args.documents, PublicKey), args.delete_paths, args.txData, simulate)\n .preInstructions(instructions)\n .accounts({\n payer: payerPublicKey\n })\n .remainingAccounts(remainingAccounts)\n .transaction();\n }\n else {\n throw new Error(`Invalid program id: ${idl.address}`);\n }\n tx.feePayer = payerPublicKey;\n const { blockhash, lastValidBlockHeight } = await connection.getLatestBlockhash(\"confirmed\");\n tx.recentBlockhash = blockhash;\n // Resolve LUTs: additionalLutAddresses (when present) is the authoritative set.\n // Falls back to lutKey alone for backwards compatibility.\n const lookupTables = [];\n if (additionalLutAddresses && additionalLutAddresses.length > 0) {\n const results = await Promise.all(additionalLutAddresses.map(addr => connection.getAddressLookupTable(new PublicKey(addr)).catch(() => null)));\n for (const result of results) {\n if (result === null || result === void 0 ? void 0 : result.value) {\n lookupTables.push(result.value);\n }\n }\n }\n else if (lutKey != null) {\n // The LUT may have just been created server-side and the client's RPC node\n // may not have indexed it yet (load-balancer split). Retry with exponential\n // back-off; first retry at 100 ms almost always resolves it.\n let table = null;\n for (let attempt = 0; attempt < 5; attempt++) {\n const { value } = await connection.getAddressLookupTable(new PublicKey(lutKey));\n if (value) {\n table = value;\n break;\n }\n if (attempt < 4)\n await new Promise(r => setTimeout(r, 100 * Math.pow(2, attempt)));\n }\n if (!table)\n throw new Error('LUT not found after creation/extend');\n lookupTables.push(table);\n }\n if (lookupTables.length === 0) {\n const computeUnits = await getSimulationComputeUnits(connection, tx.instructions, payerPublicKey, [], chainSdk);\n const computeBudgetIxOptimized = ComputeBudgetProgram.setComputeUnitLimit({\n units: computeUnits ? computeUnits * 1.2 : 1400000,\n }); // 20% buffer\n tx.instructions[0] = computeBudgetIxOptimized;\n return { tx, blockhash, lastValidBlockHeight };\n }\n const computeUnits = await getSimulationComputeUnits(connection, tx.instructions, payerPublicKey, lookupTables, chainSdk);\n const computeBudgetIxOptimized = ComputeBudgetProgram.setComputeUnitLimit({\n units: computeUnits ? computeUnits * 1.2 : 1400000,\n }); // 20% buffer\n tx.instructions[0] = computeBudgetIxOptimized;\n const msgV0 = new chainSdk.TransactionMessage({\n payerKey: payerPublicKey,\n recentBlockhash: blockhash,\n instructions: tx.instructions,\n }).compileToV0Message(lookupTables);\n const vTx = new chainSdk.VersionedTransaction(msgV0);\n return { tx: vTx, blockhash, lastValidBlockHeight };\n}\n"],"names":[],"mappings":";;;;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS,4BAA4B,CAAC,EAAE,EAAE,oBAAoB,EAAE;AAChE,IAAI,QAAQ,EAAE,CAAC,SAAS,CAAC,MAAM,CAAC,oBAAoB,CAAC,SAAS,CAAC;AAC/D,QAAQ,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC;AACxB;AACA;AACA;AACA;AACA;AACA;AACA,eAAe,yBAAyB,CAAC,UAAU,EAAE,YAAY,EAAE,KAAK,EAAE,YAAY,EAAE,QAAQ,EAAE,UAAU,GAAG,WAAW,EAAE;AAC5H,IAAI,IAAI,EAAE,EAAE,EAAE,EAAE,EAAE;AAClB,IAAI,MAAM,sBAAsB,GAAG,CAAC,GAAG,YAAY,CAAC;AACpD;AACA,IAAI,MAAM,iBAAiB,GAAG,sBAAsB,CAAC,SAAS,CAAC,EAAE,IAAI,4BAA4B,CAAC,EAAE,EAAE,QAAQ,CAAC,oBAAoB,CAAC,CAAC;AACrI,IAAI,MAAM,iBAAiB,GAAG,QAAQ,CAAC,oBAAoB,CAAC,mBAAmB,CAAC;AAChF,QAAQ,KAAK,EAAE,OAAO;AACtB,KAAK,CAAC;AACN,IAAI,IAAI,iBAAiB,IAAI,CAAC,EAAE;AAChC,QAAQ,sBAAsB,CAAC,iBAAiB,CAAC,GAAG,iBAAiB;AACrE,IAAI;AACJ,SAAS;AACT,QAAQ,sBAAsB,CAAC,OAAO,CAAC,iBAAiB,CAAC;AACzD,IAAI;AACJ,IAAI,MAAM,eAAe,GAAG,IAAI,QAAQ,CAAC,oBAAoB,CAAC,IAAI,QAAQ,CAAC,kBAAkB,CAAC;AAC9F,QAAQ,YAAY,EAAE,sBAAsB;AAC5C,QAAQ,QAAQ,EAAE,KAAK;AACvB;AACA;AACA,QAAQ,eAAe,EAAE,QAAQ,CAAC,SAAS,CAAC,OAAO,CAAC,QAAQ,EAAE;AAC9D,KAAK,CAAC,CAAC,kBAAkB,CAAC,YAAY,CAAC,CAAC;AACxC,IAAI,MAAM,WAAW,GAAG,MAAM,UAAU,CAAC,mBAAmB,CAAC,eAAe,EAAE;AAC9E,QAAQ,sBAAsB,EAAE,IAAI;AACpC,QAAQ,SAAS,EAAE,KAAK;AACxB,QAAQ,UAAU;AAClB,KAAK,CAAC;AACN,IAAI,IAAI,CAAC,EAAE,GAAG,WAAW,KAAK,IAAI,IAAI,WAAW,KAAK,MAAM,GAAG,MAAM,GAAG,WAAW,CAAC,KAAK,MAAM,IAAI,IAAI,EAAE,KAAK,MAAM,GAAG,MAAM,GAAG,EAAE,CAAC,GAAG,EAAE;AACxI,QAAQ,MAAM,IAAI,GAAG,CAAC,CAAC,EAAE,GAAG,WAAW,CAAC,KAAK,CAAC,IAAI,MAAM,IAAI,IAAI,EAAE,KAAK,MAAM,GAAG,MAAM,GAAG,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,mBAAmB;AAClI,QAAQ,MAAM,IAAI,KAAK,CAAC,CAAC,mCAAmC,EAAE,IAAI,CAAC,CAAC;AACpE,YAAY,IAAI,CAAC,SAAS,CAAC,CAAC,EAAE,GAAG,WAAW,KAAK,IAAI,IAAI,WAAW,KAAK,MAAM,GAAG,MAAM,GAAG,WAAW,CAAC,KAAK,MAAM,IAAI,IAAI,EAAE,KAAK,MAAM,GAAG,MAAM,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC;AAC3J,IAAI;AACJ,IAAI,OAAO,WAAW,CAAC,KAAK,CAAC,aAAa,IAAI,IAAI;AAClD;AACA;AACA;AACA;AACO,SAAS,WAAW,CAAC,KAAK,EAAE;AACnC;AACA;AACA;AACA;AACA,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ;AACjC,QAAQ,OAAO,KAAK;AACpB;AACA,IAAI,IAAI,SAAS,GAAG,KAAK;AACzB;AACA,IAAI,IAAI,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE;AAC/B,QAAQ,SAAS,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC;AAClC,IAAI;AACJ,IAAI,MAAM,QAAQ,GAAG,qBAAqB;AAC1C,IAAI,OAAO,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC;AACnC;AACA,SAAS,kBAAkB,CAAC,SAAS,EAAE,cAAc,EAAE;AACvD,IAAI,OAAO,SAAS,CAAC,GAAG,CAAC,CAAC,GAAG,MAAM;AACnC,QAAQ,IAAI,EAAE,GAAG,CAAC,IAAI;AACtB,QAAQ,UAAU,EAAE,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK;AAC/C,YAAY,IAAI,aAAa,GAAG,IAAI;AACpC,YAAY,IAAI,EAAE,CAAC,KAAK,EAAE;AAC1B,gBAAgB,IAAI,QAAQ,IAAI,EAAE,CAAC,KAAK,EAAE;AAC1C,oBAAoB,IAAI,WAAW,CAAC,EAAE,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE;AAC5D,wBAAwB,aAAa,GAAG,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,EAAE,EAAE;AACnG,oBAAoB;AACpB,yBAAyB;AACzB,wBAAwB,aAAa,GAAG,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,EAAE;AAC5F,oBAAoB;AACpB,gBAAgB;AAChB,qBAAqB,IAAI,QAAQ,IAAI,EAAE,CAAC,KAAK,EAAE;AAC/C,oBAAoB,IAAI,WAAW,CAAC,EAAE,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE;AAC5D,wBAAwB,IAAI,EAAE,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE;AACnE,4BAA4B,aAAa,GAAG,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE;AAChI,wBAAwB;AACxB,6BAA6B;AAC7B,4BAA4B,aAAa,GAAG,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,EAAE,EAAE;AACvG,wBAAwB;AACxB,oBAAoB;AACpB,yBAAyB;AACzB,wBAAwB,aAAa,GAAG,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,EAAE;AAC5F,oBAAoB;AACpB,gBAAgB;AAChB,qBAAqB,IAAI,EAAE,CAAC,KAAK,CAAC,OAAO,EAAE;AAC3C,oBAAoB,aAAa,GAAG,EAAE,OAAO,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE;AACpF,gBAAgB;AAChB,qBAAqB,IAAI,EAAE,CAAC,KAAK,CAAC,SAAS,EAAE;AAC7C,oBAAoB,aAAa,GAAG,EAAE,SAAS,EAAE,EAAE,KAAK,EAAE,MAAM,CAAC,EAAE,CAAC,KAAK,CAAC,SAAS,CAAC,KAAK,CAAC,EAAE,EAAE;AAC9F,gBAAgB;AAChB,qBAAqB,IAAI,EAAE,CAAC,KAAK,CAAC,UAAU,EAAE;AAC9C,oBAAoB,aAAa,GAAG,EAAE,UAAU,EAAE,EAAE,KAAK,EAAE,IAAI,cAAc,CAAC,EAAE,CAAC,KAAK,CAAC,UAAU,CAAC,KAAK,CAAC,EAAE,EAAE;AAC5G,gBAAgB;AAChB,qBAAqB;AACrB,oBAAoB,aAAa,GAAG,EAAE,CAAC,KAAK;AAC5C,gBAAgB;AAChB,YAAY;AACZ,YAAY,OAAO;AACnB,gBAAgB,GAAG,EAAE,EAAE,CAAC,GAAG;AAC3B,gBAAgB,KAAK,EAAE,aAAa;AACpC,gBAAgB,SAAS,EAAE,EAAE,CAAC,SAAS;AACvC,aAAa;AACb,QAAQ,CAAC,CAAC;AACV,KAAK,CAAC,CAAC;AACP;AACA;AACA;AACA;AACO,eAAe,4BAA4B,CAAC,UAAU,EAAE,GAAG,EAAE,eAAe,EAAE,cAAc,EAAE,IAAI,EAAE,iBAAiB,EAAE,MAAM,EAAE,eAAe,EAAE,QAAQ,EAAE,sBAAsB,EAAE;AACzL;AACA;AACA;AACA;AACA,IAAI,MAAM,CAAC,QAAQ,EAAE,UAAU,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;AACrD,QAAQ,OAAO,iBAAiB,CAAC;AACjC,QAAQ,OAAO,mBAAmB,CAAC;AACnC,KAAK,CAAC;AACN,IAAI,MAAM,EAAE,oBAAoB,EAAE,SAAS,EAAE,GAAG,QAAQ;AACxD,IAAI,MAAM,EAAE,OAAO,EAAE,GAAG,UAAU;AAClC;AACA;AACA;AACA;AACA;AACA,IAAI,MAAM,eAAe,GAAG,CAAC,KAAK,KAAK,KAAK,YAAY,SAAS,GAAG,KAAK,GAAG,IAAI,SAAS,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC;AAC1G,IAAI,iBAAiB,GAAG,iBAAiB,CAAC,GAAG,CAAC,CAAC,OAAO,MAAM;AAC5D,QAAQ,MAAM,EAAE,eAAe,CAAC,OAAO,CAAC,MAAM,CAAC;AAC/C,QAAQ,UAAU,EAAE,OAAO,CAAC,UAAU;AACtC,QAAQ,QAAQ,EAAE,OAAO,CAAC,QAAQ;AAClC,KAAK,CAAC,CAAC;AACP,IAAI,cAAc,GAAG,eAAe,CAAC,cAAc,CAAC;AACpD,IAAI,MAAM,eAAe,GAAG,oBAAoB,CAAC,mBAAmB,CAAC;AACrE,QAAQ,KAAK,EAAE,OAAO;AACtB,KAAK,CAAC;AACN;AACA,IAAI,MAAM,kBAAkB,GAAG,oBAAoB,CAAC,gBAAgB,CAAC;AACrE,QAAQ,KAAK,EAAE,IAAI,GAAG,GAAG;AACzB,KAAK,CAAC;AACN,IAAI,MAAM,YAAY,GAAG,EAAE;AAC3B,IAAI,YAAY,CAAC,OAAO,CAAC,kBAAkB,CAAC;AAC5C,IAAI,YAAY,CAAC,OAAO,CAAC,eAAe,CAAC;AACzC,IAAI,YAAY,CAAC,IAAI,CAAC,GAAG,eAAe,CAAC;AACzC,IAAI,IAAI,EAAE,GAAG,IAAI;AACjB,IAAI,IAAI,GAAG,CAAC,OAAO,KAAK,6CAA6C,EAAE;AACvE,QAAQ,MAAM,OAAO,GAAG,IAAI,OAAO,CAAC,GAAG,EAAE,eAAe,CAAC;AACzD;AACA,QAAQ,EAAE,GAAG,MAAM,OAAO,CAAC;AAC3B,aAAa,YAAY,CAAC,IAAI,CAAC,MAAM,EAAE,kBAAkB,CAAC,IAAI,CAAC,SAAS,EAAE,SAAS,CAAC,EAAE,IAAI,CAAC,YAAY,EAAE,IAAI,CAAC,MAAM,EAAE,QAAQ;AAC9H,aAAa,eAAe,CAAC,YAAY;AACzC,aAAa,QAAQ,CAAC;AACtB,YAAY,KAAK,EAAE;AACnB,SAAS;AACT,aAAa,iBAAiB,CAAC,iBAAiB;AAChD,aAAa,WAAW,EAAE;AAC1B,IAAI;AACJ,SAAS;AACT,QAAQ,MAAM,IAAI,KAAK,CAAC,CAAC,oBAAoB,EAAE,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC;AAC7D,IAAI;AACJ,IAAI,EAAE,CAAC,QAAQ,GAAG,cAAc;AAChC,IAAI,MAAM,EAAE,SAAS,EAAE,oBAAoB,EAAE,GAAG,MAAM,UAAU,CAAC,kBAAkB,CAAC,WAAW,CAAC;AAChG,IAAI,EAAE,CAAC,eAAe,GAAG,SAAS;AAClC;AACA;AACA,IAAI,MAAM,YAAY,GAAG,EAAE;AAC3B,IAAI,IAAI,sBAAsB,IAAI,sBAAsB,CAAC,MAAM,GAAG,CAAC,EAAE;AACrE,QAAQ,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC,sBAAsB,CAAC,GAAG,CAAC,IAAI,IAAI,UAAU,CAAC,qBAAqB,CAAC,IAAI,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,IAAI,CAAC,CAAC,CAAC;AACtJ,QAAQ,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE;AACtC,YAAY,IAAI,MAAM,KAAK,IAAI,IAAI,MAAM,KAAK,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC,KAAK,EAAE;AAC9E,gBAAgB,YAAY,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC;AAC/C,YAAY;AACZ,QAAQ;AACR,IAAI;AACJ,SAAS,IAAI,MAAM,IAAI,IAAI,EAAE;AAC7B;AACA;AACA;AACA,QAAQ,IAAI,KAAK,GAAG,IAAI;AACxB,QAAQ,KAAK,IAAI,OAAO,GAAG,CAAC,EAAE,OAAO,GAAG,CAAC,EAAE,OAAO,EAAE,EAAE;AACtD,YAAY,MAAM,EAAE,KAAK,EAAE,GAAG,MAAM,UAAU,CAAC,qBAAqB,CAAC,IAAI,SAAS,CAAC,MAAM,CAAC,CAAC;AAC3F,YAAY,IAAI,KAAK,EAAE;AACvB,gBAAgB,KAAK,GAAG,KAAK;AAC7B,gBAAgB;AAChB,YAAY;AACZ,YAAY,IAAI,OAAO,GAAG,CAAC;AAC3B,gBAAgB,MAAM,IAAI,OAAO,CAAC,CAAC,IAAI,UAAU,CAAC,CAAC,EAAE,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC;AACjF,QAAQ;AACR,QAAQ,IAAI,CAAC,KAAK;AAClB,YAAY,MAAM,IAAI,KAAK,CAAC,qCAAqC,CAAC;AAClE,QAAQ,YAAY,CAAC,IAAI,CAAC,KAAK,CAAC;AAChC,IAAI;AACJ,IAAI,IAAI,YAAY,CAAC,MAAM,KAAK,CAAC,EAAE;AACnC,QAAQ,MAAM,YAAY,GAAG,MAAM,yBAAyB,CAAC,UAAU,EAAE,EAAE,CAAC,YAAY,EAAE,cAAc,EAAE,EAAE,EAAE,QAAQ,CAAC;AACvH,QAAQ,MAAM,wBAAwB,GAAG,oBAAoB,CAAC,mBAAmB,CAAC;AAClF,YAAY,KAAK,EAAE,YAAY,GAAG,YAAY,GAAG,GAAG,GAAG,OAAO;AAC9D,SAAS,CAAC,CAAC;AACX,QAAQ,EAAE,CAAC,YAAY,CAAC,CAAC,CAAC,GAAG,wBAAwB;AACrD,QAAQ,OAAO,EAAE,EAAE,EAAE,SAAS,EAAE,oBAAoB,EAAE;AACtD,IAAI;AACJ,IAAI,MAAM,YAAY,GAAG,MAAM,yBAAyB,CAAC,UAAU,EAAE,EAAE,CAAC,YAAY,EAAE,cAAc,EAAE,YAAY,EAAE,QAAQ,CAAC;AAC7H,IAAI,MAAM,wBAAwB,GAAG,oBAAoB,CAAC,mBAAmB,CAAC;AAC9E,QAAQ,KAAK,EAAE,YAAY,GAAG,YAAY,GAAG,GAAG,GAAG,OAAO;AAC1D,KAAK,CAAC,CAAC;AACP,IAAI,EAAE,CAAC,YAAY,CAAC,CAAC,CAAC,GAAG,wBAAwB;AACjD,IAAI,MAAM,KAAK,GAAG,IAAI,QAAQ,CAAC,kBAAkB,CAAC;AAClD,QAAQ,QAAQ,EAAE,cAAc;AAChC,QAAQ,eAAe,EAAE,SAAS;AAClC,QAAQ,YAAY,EAAE,EAAE,CAAC,YAAY;AACrC,KAAK,CAAC,CAAC,kBAAkB,CAAC,YAAY,CAAC;AACvC,IAAI,MAAM,GAAG,GAAG,IAAI,QAAQ,CAAC,oBAAoB,CAAC,KAAK,CAAC;AACxD,IAAI,OAAO,EAAE,EAAE,EAAE,GAAG,EAAE,SAAS,EAAE,oBAAoB,EAAE;AACvD;;;;;"}
@@ -156,18 +156,7 @@ async function buildSetDocumentsTransaction(connection, idl, programProvider, pa
156
156
  instructions.unshift(computeBudgetIx);
157
157
  instructions.push(...preInstructions);
158
158
  let tx = null;
159
- if (idl.address === "taro6CvKqwrYrDc16ufYgzQ2NZcyyVKStffbtudrhRu") {
160
- const program = new Program(idl, programProvider);
161
- tx = await program.methods
162
- .setDocuments(args.app_id, prepareProgramArgs(args.documents, PublicKey), args.delete_paths, simulate)
163
- .preInstructions(instructions)
164
- .accounts({
165
- payer: payerPublicKey
166
- })
167
- .remainingAccounts(remainingAccounts)
168
- .transaction();
169
- }
170
- else if (idl.address === "poof4b5pk1L9tmThvBmaABjcyjfhFGbMbQP5BXk2QZp") {
159
+ if (idl.address === "openTv7fbpYSseNHYmCZFZ1CZgj4r8D9fKNgEz1qo6F") {
171
160
  const program = new Program(idl, programProvider);
172
161
  // Single canonical set_documents wire format (ra_indices Vec<u8>).
173
162
  tx = await program.methods
@@ -237,4 +226,4 @@ async function buildSetDocumentsTransaction(connection, idl, programProvider, pa
237
226
  }
238
227
 
239
228
  export { buildSetDocumentsTransaction, isHexString };
240
- //# sourceMappingURL=sol-utils-BhjxvQUj.mjs.map
229
+ //# sourceMappingURL=sol-utils-DVjPeoMy.mjs.map