@bounded-sh/core 0.0.117 → 0.0.118

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 (39) hide show
  1. package/dist/client/reconcile.d.ts +2 -2
  2. package/dist/client/solana-confirm.d.ts +49 -0
  3. package/dist/client/solana-operations.d.ts +7 -1
  4. package/dist/{index-C6ZW5Gt2.mjs → index-BXfRRAUZ.mjs} +84 -28
  5. package/dist/index-BXfRRAUZ.mjs.map +1 -0
  6. package/dist/{index-DQ0FXeI8.js → index-l9LI3y1q.js} +84 -27
  7. package/dist/index-l9LI3y1q.js.map +1 -0
  8. package/dist/index.d.ts +2 -0
  9. package/dist/index.js +2 -1
  10. package/dist/index.js.map +1 -1
  11. package/dist/index.mjs +1 -1
  12. package/dist/{mirror-sync-ChzMq7bH.js → mirror-sync-DJVm0KKu.js} +2 -2
  13. package/dist/{mirror-sync-ChzMq7bH.js.map → mirror-sync-DJVm0KKu.js.map} +1 -1
  14. package/dist/{mirror-sync-kt9PzQen.mjs → mirror-sync-DoH9ewTo.mjs} +2 -2
  15. package/dist/{mirror-sync-kt9PzQen.mjs.map → mirror-sync-DoH9ewTo.mjs.map} +1 -1
  16. package/dist/{realtime-store-FUjGL_Z8.mjs → realtime-store-BreUReSo.mjs} +2 -2
  17. package/dist/{realtime-store-FUjGL_Z8.mjs.map → realtime-store-BreUReSo.mjs.map} +1 -1
  18. package/dist/{realtime-store-CURbZ68v.js → realtime-store-kZMPgwVB.js} +2 -2
  19. package/dist/{realtime-store-CURbZ68v.js.map → realtime-store-kZMPgwVB.js.map} +1 -1
  20. package/dist/{solana-message-CEjXjnaG.mjs → solana-message-CjI0h27k.mjs} +2 -2
  21. package/dist/{solana-message-CEjXjnaG.mjs.map → solana-message-CjI0h27k.mjs.map} +1 -1
  22. package/dist/{solana-message-F1e5wPKF.js → solana-message-HkOoUz8z.js} +2 -2
  23. package/dist/{solana-message-F1e5wPKF.js.map → solana-message-HkOoUz8z.js.map} +1 -1
  24. package/dist/{solana-operations-5WjFwhnt.mjs → solana-operations-CD6pks4-.mjs} +32 -30
  25. package/dist/solana-operations-CD6pks4-.mjs.map +1 -0
  26. package/dist/{solana-operations-CI0sREvl.js → solana-operations-DFohVGz6.js} +32 -29
  27. package/dist/solana-operations-DFohVGz6.js.map +1 -0
  28. package/dist/{solana-v1-operations-oot2R63I.js → solana-v1-operations-bhDjo-5U.js} +10 -24
  29. package/dist/solana-v1-operations-bhDjo-5U.js.map +1 -0
  30. package/dist/{solana-v1-operations-DB9pih7o.mjs → solana-v1-operations-ewIzFDeY.mjs} +10 -24
  31. package/dist/solana-v1-operations-ewIzFDeY.mjs.map +1 -0
  32. package/dist/types.d.ts +8 -15
  33. package/package.json +1 -1
  34. package/dist/index-C6ZW5Gt2.mjs.map +0 -1
  35. package/dist/index-DQ0FXeI8.js.map +0 -1
  36. package/dist/solana-operations-5WjFwhnt.mjs.map +0 -1
  37. package/dist/solana-operations-CI0sREvl.js.map +0 -1
  38. package/dist/solana-v1-operations-DB9pih7o.mjs.map +0 -1
  39. package/dist/solana-v1-operations-oot2R63I.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"realtime-store-CURbZ68v.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 { RECONNECT_BASE_DELAY_MS, RECONNECT_MAX_DELAY_MS, jitteredReconnectDelayMs, reconnectDelayAfterClose } from './reconnect-backoff';\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 = RECONNECT_BASE_DELAY_MS;\n this.maxReconnectDelay = RECONNECT_MAX_DELAY_MS;\n /** When the current socket finished connecting; decides whether a close resets the backoff. */\n this.connectedAtMs = null;\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 // The delay resets only once this connection proves stable (see the\n // close handler), so an accept-then-drop loop still backs off.\n this.connectedAtMs = Date.now();\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 const connectedFor = this.connectedAtMs === null ? null : Date.now() - this.connectedAtMs;\n this.connectedAtMs = null;\n // A socket that never finished connecting is backed off by the\n // failed-connect path in scheduleReconnect instead.\n if (connectedFor !== null) {\n this.reconnectDelay = reconnectDelayAfterClose(this.reconnectDelay, connectedFor, this.maxReconnectDelay);\n }\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 }, jitteredReconnectDelayMs(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;AA2/BO,eAAe,iCAAiC,GAAG;AAI1D;;;;"}
1
+ {"version":3,"file":"realtime-store-kZMPgwVB.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 { RECONNECT_BASE_DELAY_MS, RECONNECT_MAX_DELAY_MS, jitteredReconnectDelayMs, reconnectDelayAfterClose } from './reconnect-backoff';\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 = RECONNECT_BASE_DELAY_MS;\n this.maxReconnectDelay = RECONNECT_MAX_DELAY_MS;\n /** When the current socket finished connecting; decides whether a close resets the backoff. */\n this.connectedAtMs = null;\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 // The delay resets only once this connection proves stable (see the\n // close handler), so an accept-then-drop loop still backs off.\n this.connectedAtMs = Date.now();\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 const connectedFor = this.connectedAtMs === null ? null : Date.now() - this.connectedAtMs;\n this.connectedAtMs = null;\n // A socket that never finished connecting is backed off by the\n // failed-connect path in scheduleReconnect instead.\n if (connectedFor !== null) {\n this.reconnectDelay = reconnectDelayAfterClose(this.reconnectDelay, connectedFor, this.maxReconnectDelay);\n }\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 }, jitteredReconnectDelayMs(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;AA2/BO,eAAe,iCAAiC,GAAG;AAI1D;;;;"}
@@ -1,4 +1,4 @@
1
- import { g as getConfig } from './index-C6ZW5Gt2.mjs';
1
+ import { g as getConfig } from './index-BXfRRAUZ.mjs';
2
2
  import 'axios';
3
3
  import 'tweetnacl';
4
4
  import 'reconnecting-websocket';
@@ -34,4 +34,4 @@ async function genSolanaMessage(address, nonce, appIdOverride) {
34
34
  }
35
35
 
36
36
  export { genSolanaMessage };
37
- //# sourceMappingURL=solana-message-CEjXjnaG.mjs.map
37
+ //# sourceMappingURL=solana-message-CjI0h27k.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"solana-message-CEjXjnaG.mjs","sources":["../.rollup-tmp/utils/sol/solana-message.js"],"sourcesContent":["import { getConfig } from \"../../client/config\";\n// NOTE: this message text must stay BYTE-IDENTICAL to what the wallet-auth\n// verifier reconstructs — any drift breaks every SIWS login.\nexport async function genSolanaMessage(address, nonce, appIdOverride) {\n var _a, _b;\n /* Domain / origin aren't available server-side – fall back to envs\n or simple placeholders. */\n const domain = (typeof process !== 'undefined' && ((_a = process.env) === null || _a === void 0 ? void 0 : _a.SESSION_DOMAIN)) || \"server\";\n const origin = (typeof process !== 'undefined' && ((_b = process.env) === null || _b === void 0 ? void 0 : _b.SESSION_ORIGIN)) || \"server\";\n // SECURITY (audit-4 CRITICAL-1): bind the signed prompt to the target app.\n // A server wallet client passes its snapshotted appId (appIdOverride) so the\n // signed \"App ID\" stays bound to the app the wallet client was created against\n // even after a later global init() (CTA-05); the default path reads the SAME\n // global config that genAuthNonce + /session use, so the signed \"App ID\" is\n // guaranteed to equal the app the session is minted for; the server verifier\n // rejects any mismatch.\n const appId = appIdOverride !== null && appIdOverride !== void 0 ? appIdOverride : (await getConfig()).appId;\n const statement = \"Sign this message to authenticate with our application.\";\n const currentDate = new Date();\n const issuedAt = currentDate.toISOString();\n const expirationTime = new Date(currentDate.getTime() + 5 * 60 * 1000).toISOString();\n return `${statement}\\n\\n` +\n `Wallet address:\\n${address}\\n\\n` +\n `App ID: ${appId}\\n` +\n `Domain: ${domain}\\n` +\n `Origin: ${origin}\\n` +\n `Nonce: ${nonce}\\n` +\n `Issued At: ${issuedAt}\\n` +\n `Expiration Time: ${expirationTime}`;\n}\n"],"names":[],"mappings":";;;;;AACA;AACA;AACO,eAAe,gBAAgB,CAAC,OAAO,EAAE,KAAK,EAAE,aAAa,EAAE;AACtE,IAAI,IAAI,EAAE,EAAE,EAAE;AACd;AACA;AACA,IAAI,MAAM,MAAM,GAAG,CAAC,OAAO,OAAO,KAAK,WAAW,KAAK,CAAC,EAAE,GAAG,OAAO,CAAC,GAAG,MAAM,IAAI,IAAI,EAAE,KAAK,MAAM,GAAG,MAAM,GAAG,EAAE,CAAC,cAAc,CAAC,KAAK,QAAQ;AAC9I,IAAI,MAAM,MAAM,GAAG,CAAC,OAAO,OAAO,KAAK,WAAW,KAAK,CAAC,EAAE,GAAG,OAAO,CAAC,GAAG,MAAM,IAAI,IAAI,EAAE,KAAK,MAAM,GAAG,MAAM,GAAG,EAAE,CAAC,cAAc,CAAC,KAAK,QAAQ;AAC9I;AACA;AACA;AACA;AACA;AACA;AACA;AACA,IAAI,MAAM,KAAK,GAAG,aAAa,KAAK,IAAI,IAAI,aAAa,KAAK,MAAM,GAAG,aAAa,GAAG,CAAC,MAAM,SAAS,EAAE,EAAE,KAAK;AAChH,IAAI,MAAM,SAAS,GAAG,yDAAyD;AAC/E,IAAI,MAAM,WAAW,GAAG,IAAI,IAAI,EAAE;AAClC,IAAI,MAAM,QAAQ,GAAG,WAAW,CAAC,WAAW,EAAE;AAC9C,IAAI,MAAM,cAAc,GAAG,IAAI,IAAI,CAAC,WAAW,CAAC,OAAO,EAAE,GAAG,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC,WAAW,EAAE;AACxF,IAAI,OAAO,CAAC,EAAE,SAAS,CAAC,IAAI,CAAC;AAC7B,QAAQ,CAAC,iBAAiB,EAAE,OAAO,CAAC,IAAI,CAAC;AACzC,QAAQ,CAAC,QAAQ,EAAE,KAAK,CAAC,EAAE,CAAC;AAC5B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC,EAAE,CAAC;AAC7B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC,EAAE,CAAC;AAC7B,QAAQ,CAAC,OAAO,EAAE,KAAK,CAAC,EAAE,CAAC;AAC3B,QAAQ,CAAC,WAAW,EAAE,QAAQ,CAAC,EAAE,CAAC;AAClC,QAAQ,CAAC,iBAAiB,EAAE,cAAc,CAAC,CAAC;AAC5C;;;;"}
1
+ {"version":3,"file":"solana-message-CjI0h27k.mjs","sources":["../.rollup-tmp/utils/sol/solana-message.js"],"sourcesContent":["import { getConfig } from \"../../client/config\";\n// NOTE: this message text must stay BYTE-IDENTICAL to what the wallet-auth\n// verifier reconstructs — any drift breaks every SIWS login.\nexport async function genSolanaMessage(address, nonce, appIdOverride) {\n var _a, _b;\n /* Domain / origin aren't available server-side – fall back to envs\n or simple placeholders. */\n const domain = (typeof process !== 'undefined' && ((_a = process.env) === null || _a === void 0 ? void 0 : _a.SESSION_DOMAIN)) || \"server\";\n const origin = (typeof process !== 'undefined' && ((_b = process.env) === null || _b === void 0 ? void 0 : _b.SESSION_ORIGIN)) || \"server\";\n // SECURITY (audit-4 CRITICAL-1): bind the signed prompt to the target app.\n // A server wallet client passes its snapshotted appId (appIdOverride) so the\n // signed \"App ID\" stays bound to the app the wallet client was created against\n // even after a later global init() (CTA-05); the default path reads the SAME\n // global config that genAuthNonce + /session use, so the signed \"App ID\" is\n // guaranteed to equal the app the session is minted for; the server verifier\n // rejects any mismatch.\n const appId = appIdOverride !== null && appIdOverride !== void 0 ? appIdOverride : (await getConfig()).appId;\n const statement = \"Sign this message to authenticate with our application.\";\n const currentDate = new Date();\n const issuedAt = currentDate.toISOString();\n const expirationTime = new Date(currentDate.getTime() + 5 * 60 * 1000).toISOString();\n return `${statement}\\n\\n` +\n `Wallet address:\\n${address}\\n\\n` +\n `App ID: ${appId}\\n` +\n `Domain: ${domain}\\n` +\n `Origin: ${origin}\\n` +\n `Nonce: ${nonce}\\n` +\n `Issued At: ${issuedAt}\\n` +\n `Expiration Time: ${expirationTime}`;\n}\n"],"names":[],"mappings":";;;;;AACA;AACA;AACO,eAAe,gBAAgB,CAAC,OAAO,EAAE,KAAK,EAAE,aAAa,EAAE;AACtE,IAAI,IAAI,EAAE,EAAE,EAAE;AACd;AACA;AACA,IAAI,MAAM,MAAM,GAAG,CAAC,OAAO,OAAO,KAAK,WAAW,KAAK,CAAC,EAAE,GAAG,OAAO,CAAC,GAAG,MAAM,IAAI,IAAI,EAAE,KAAK,MAAM,GAAG,MAAM,GAAG,EAAE,CAAC,cAAc,CAAC,KAAK,QAAQ;AAC9I,IAAI,MAAM,MAAM,GAAG,CAAC,OAAO,OAAO,KAAK,WAAW,KAAK,CAAC,EAAE,GAAG,OAAO,CAAC,GAAG,MAAM,IAAI,IAAI,EAAE,KAAK,MAAM,GAAG,MAAM,GAAG,EAAE,CAAC,cAAc,CAAC,KAAK,QAAQ;AAC9I;AACA;AACA;AACA;AACA;AACA;AACA;AACA,IAAI,MAAM,KAAK,GAAG,aAAa,KAAK,IAAI,IAAI,aAAa,KAAK,MAAM,GAAG,aAAa,GAAG,CAAC,MAAM,SAAS,EAAE,EAAE,KAAK;AAChH,IAAI,MAAM,SAAS,GAAG,yDAAyD;AAC/E,IAAI,MAAM,WAAW,GAAG,IAAI,IAAI,EAAE;AAClC,IAAI,MAAM,QAAQ,GAAG,WAAW,CAAC,WAAW,EAAE;AAC9C,IAAI,MAAM,cAAc,GAAG,IAAI,IAAI,CAAC,WAAW,CAAC,OAAO,EAAE,GAAG,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC,WAAW,EAAE;AACxF,IAAI,OAAO,CAAC,EAAE,SAAS,CAAC,IAAI,CAAC;AAC7B,QAAQ,CAAC,iBAAiB,EAAE,OAAO,CAAC,IAAI,CAAC;AACzC,QAAQ,CAAC,QAAQ,EAAE,KAAK,CAAC,EAAE,CAAC;AAC5B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC,EAAE,CAAC;AAC7B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC,EAAE,CAAC;AAC7B,QAAQ,CAAC,OAAO,EAAE,KAAK,CAAC,EAAE,CAAC;AAC3B,QAAQ,CAAC,WAAW,EAAE,QAAQ,CAAC,EAAE,CAAC;AAClC,QAAQ,CAAC,iBAAiB,EAAE,cAAc,CAAC,CAAC;AAC5C;;;;"}
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var index = require('./index-DQ0FXeI8.js');
3
+ var index = require('./index-l9LI3y1q.js');
4
4
  require('axios');
5
5
  require('tweetnacl');
6
6
  require('reconnecting-websocket');
@@ -36,4 +36,4 @@ async function genSolanaMessage(address, nonce, appIdOverride) {
36
36
  }
37
37
 
38
38
  exports.genSolanaMessage = genSolanaMessage;
39
- //# sourceMappingURL=solana-message-F1e5wPKF.js.map
39
+ //# sourceMappingURL=solana-message-HkOoUz8z.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"solana-message-F1e5wPKF.js","sources":["../.rollup-tmp/utils/sol/solana-message.js"],"sourcesContent":["import { getConfig } from \"../../client/config\";\n// NOTE: this message text must stay BYTE-IDENTICAL to what the wallet-auth\n// verifier reconstructs — any drift breaks every SIWS login.\nexport async function genSolanaMessage(address, nonce, appIdOverride) {\n var _a, _b;\n /* Domain / origin aren't available server-side – fall back to envs\n or simple placeholders. */\n const domain = (typeof process !== 'undefined' && ((_a = process.env) === null || _a === void 0 ? void 0 : _a.SESSION_DOMAIN)) || \"server\";\n const origin = (typeof process !== 'undefined' && ((_b = process.env) === null || _b === void 0 ? void 0 : _b.SESSION_ORIGIN)) || \"server\";\n // SECURITY (audit-4 CRITICAL-1): bind the signed prompt to the target app.\n // A server wallet client passes its snapshotted appId (appIdOverride) so the\n // signed \"App ID\" stays bound to the app the wallet client was created against\n // even after a later global init() (CTA-05); the default path reads the SAME\n // global config that genAuthNonce + /session use, so the signed \"App ID\" is\n // guaranteed to equal the app the session is minted for; the server verifier\n // rejects any mismatch.\n const appId = appIdOverride !== null && appIdOverride !== void 0 ? appIdOverride : (await getConfig()).appId;\n const statement = \"Sign this message to authenticate with our application.\";\n const currentDate = new Date();\n const issuedAt = currentDate.toISOString();\n const expirationTime = new Date(currentDate.getTime() + 5 * 60 * 1000).toISOString();\n return `${statement}\\n\\n` +\n `Wallet address:\\n${address}\\n\\n` +\n `App ID: ${appId}\\n` +\n `Domain: ${domain}\\n` +\n `Origin: ${origin}\\n` +\n `Nonce: ${nonce}\\n` +\n `Issued At: ${issuedAt}\\n` +\n `Expiration Time: ${expirationTime}`;\n}\n"],"names":["getConfig"],"mappings":";;;;;;;AACA;AACA;AACO,eAAe,gBAAgB,CAAC,OAAO,EAAE,KAAK,EAAE,aAAa,EAAE;AACtE,IAAI,IAAI,EAAE,EAAE,EAAE;AACd;AACA;AACA,IAAI,MAAM,MAAM,GAAG,CAAC,OAAO,OAAO,KAAK,WAAW,KAAK,CAAC,EAAE,GAAG,OAAO,CAAC,GAAG,MAAM,IAAI,IAAI,EAAE,KAAK,MAAM,GAAG,MAAM,GAAG,EAAE,CAAC,cAAc,CAAC,KAAK,QAAQ;AAC9I,IAAI,MAAM,MAAM,GAAG,CAAC,OAAO,OAAO,KAAK,WAAW,KAAK,CAAC,EAAE,GAAG,OAAO,CAAC,GAAG,MAAM,IAAI,IAAI,EAAE,KAAK,MAAM,GAAG,MAAM,GAAG,EAAE,CAAC,cAAc,CAAC,KAAK,QAAQ;AAC9I;AACA;AACA;AACA;AACA;AACA;AACA;AACA,IAAI,MAAM,KAAK,GAAG,aAAa,KAAK,IAAI,IAAI,aAAa,KAAK,MAAM,GAAG,aAAa,GAAG,CAAC,MAAMA,eAAS,EAAE,EAAE,KAAK;AAChH,IAAI,MAAM,SAAS,GAAG,yDAAyD;AAC/E,IAAI,MAAM,WAAW,GAAG,IAAI,IAAI,EAAE;AAClC,IAAI,MAAM,QAAQ,GAAG,WAAW,CAAC,WAAW,EAAE;AAC9C,IAAI,MAAM,cAAc,GAAG,IAAI,IAAI,CAAC,WAAW,CAAC,OAAO,EAAE,GAAG,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC,WAAW,EAAE;AACxF,IAAI,OAAO,CAAC,EAAE,SAAS,CAAC,IAAI,CAAC;AAC7B,QAAQ,CAAC,iBAAiB,EAAE,OAAO,CAAC,IAAI,CAAC;AACzC,QAAQ,CAAC,QAAQ,EAAE,KAAK,CAAC,EAAE,CAAC;AAC5B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC,EAAE,CAAC;AAC7B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC,EAAE,CAAC;AAC7B,QAAQ,CAAC,OAAO,EAAE,KAAK,CAAC,EAAE,CAAC;AAC3B,QAAQ,CAAC,WAAW,EAAE,QAAQ,CAAC,EAAE,CAAC;AAClC,QAAQ,CAAC,iBAAiB,EAAE,cAAc,CAAC,CAAC;AAC5C;;;;"}
1
+ {"version":3,"file":"solana-message-HkOoUz8z.js","sources":["../.rollup-tmp/utils/sol/solana-message.js"],"sourcesContent":["import { getConfig } from \"../../client/config\";\n// NOTE: this message text must stay BYTE-IDENTICAL to what the wallet-auth\n// verifier reconstructs — any drift breaks every SIWS login.\nexport async function genSolanaMessage(address, nonce, appIdOverride) {\n var _a, _b;\n /* Domain / origin aren't available server-side – fall back to envs\n or simple placeholders. */\n const domain = (typeof process !== 'undefined' && ((_a = process.env) === null || _a === void 0 ? void 0 : _a.SESSION_DOMAIN)) || \"server\";\n const origin = (typeof process !== 'undefined' && ((_b = process.env) === null || _b === void 0 ? void 0 : _b.SESSION_ORIGIN)) || \"server\";\n // SECURITY (audit-4 CRITICAL-1): bind the signed prompt to the target app.\n // A server wallet client passes its snapshotted appId (appIdOverride) so the\n // signed \"App ID\" stays bound to the app the wallet client was created against\n // even after a later global init() (CTA-05); the default path reads the SAME\n // global config that genAuthNonce + /session use, so the signed \"App ID\" is\n // guaranteed to equal the app the session is minted for; the server verifier\n // rejects any mismatch.\n const appId = appIdOverride !== null && appIdOverride !== void 0 ? appIdOverride : (await getConfig()).appId;\n const statement = \"Sign this message to authenticate with our application.\";\n const currentDate = new Date();\n const issuedAt = currentDate.toISOString();\n const expirationTime = new Date(currentDate.getTime() + 5 * 60 * 1000).toISOString();\n return `${statement}\\n\\n` +\n `Wallet address:\\n${address}\\n\\n` +\n `App ID: ${appId}\\n` +\n `Domain: ${domain}\\n` +\n `Origin: ${origin}\\n` +\n `Nonce: ${nonce}\\n` +\n `Issued At: ${issuedAt}\\n` +\n `Expiration Time: ${expirationTime}`;\n}\n"],"names":["getConfig"],"mappings":";;;;;;;AACA;AACA;AACO,eAAe,gBAAgB,CAAC,OAAO,EAAE,KAAK,EAAE,aAAa,EAAE;AACtE,IAAI,IAAI,EAAE,EAAE,EAAE;AACd;AACA;AACA,IAAI,MAAM,MAAM,GAAG,CAAC,OAAO,OAAO,KAAK,WAAW,KAAK,CAAC,EAAE,GAAG,OAAO,CAAC,GAAG,MAAM,IAAI,IAAI,EAAE,KAAK,MAAM,GAAG,MAAM,GAAG,EAAE,CAAC,cAAc,CAAC,KAAK,QAAQ;AAC9I,IAAI,MAAM,MAAM,GAAG,CAAC,OAAO,OAAO,KAAK,WAAW,KAAK,CAAC,EAAE,GAAG,OAAO,CAAC,GAAG,MAAM,IAAI,IAAI,EAAE,KAAK,MAAM,GAAG,MAAM,GAAG,EAAE,CAAC,cAAc,CAAC,KAAK,QAAQ;AAC9I;AACA;AACA;AACA;AACA;AACA;AACA;AACA,IAAI,MAAM,KAAK,GAAG,aAAa,KAAK,IAAI,IAAI,aAAa,KAAK,MAAM,GAAG,aAAa,GAAG,CAAC,MAAMA,eAAS,EAAE,EAAE,KAAK;AAChH,IAAI,MAAM,SAAS,GAAG,yDAAyD;AAC/E,IAAI,MAAM,WAAW,GAAG,IAAI,IAAI,EAAE;AAClC,IAAI,MAAM,QAAQ,GAAG,WAAW,CAAC,WAAW,EAAE;AAC9C,IAAI,MAAM,cAAc,GAAG,IAAI,IAAI,CAAC,WAAW,CAAC,OAAO,EAAE,GAAG,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC,WAAW,EAAE;AACxF,IAAI,OAAO,CAAC,EAAE,SAAS,CAAC,IAAI,CAAC;AAC7B,QAAQ,CAAC,iBAAiB,EAAE,OAAO,CAAC,IAAI,CAAC;AACzC,QAAQ,CAAC,QAAQ,EAAE,KAAK,CAAC,EAAE,CAAC;AAC5B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC,EAAE,CAAC;AAC7B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC,EAAE,CAAC;AAC7B,QAAQ,CAAC,OAAO,EAAE,KAAK,CAAC,EAAE,CAAC;AAC3B,QAAQ,CAAC,WAAW,EAAE,QAAQ,CAAC,EAAE,CAAC;AAClC,QAAQ,CAAC,iBAAiB,EAAE,cAAc,CAAC,CAAC;AAC5C;;;;"}
@@ -1,4 +1,4 @@
1
- import { g as getConfig, b as bufferExports, a as markWriteNotApplied } from './index-C6ZW5Gt2.mjs';
1
+ import { g as getConfig, b as bufferExports, a as markWriteNotApplied, c as confirmSignature } from './index-BXfRRAUZ.mjs';
2
2
  import 'axios';
3
3
  import 'tweetnacl';
4
4
  import 'reconnecting-websocket';
@@ -206,6 +206,25 @@ function errorText(error) {
206
206
  const message = error === null || error === void 0 ? void 0 : error.message;
207
207
  return typeof message === 'string' && message.length > 0 ? message : String(error);
208
208
  }
209
+ /**
210
+ * The tagged failure for a transaction that LANDED and failed on-chain: a Bounded
211
+ * rule/invariant rejection when the logs say so, otherwise the raw error, with the
212
+ * program logs attached whenever the RPC can still read them.
213
+ */
214
+ async function landedTransactionFailure(rpc, signature, err) {
215
+ var _a, _b;
216
+ let logMessages;
217
+ try {
218
+ logMessages = (_b = (_a = (await getTransactionJson(rpc, signature, 'confirmed'))) === null || _a === void 0 ? void 0 : _a.meta) === null || _b === void 0 ? void 0 : _b.logMessages;
219
+ }
220
+ catch (_c) {
221
+ // Log retrieval is diagnostic only; the on-chain error is the verdict.
222
+ }
223
+ if (isBoundedOnchainRuleRejection(err, logMessages)) {
224
+ return markWriteNotApplied(new BoundedOnchainRuleRejection(err, logMessages), 'failed_onchain');
225
+ }
226
+ return markWriteNotApplied(new Error(`Transaction failed: ${logMessages ? JSON.stringify(logMessages) : JSON.stringify(err)}`), 'failed_onchain');
227
+ }
209
228
  const BOUNDED_PROGRAM_MAINNET = ['open', 'Tv7fbpYSseNHYmCZFZ1CZgj4r8D9fKNgEz1qo6F'].join('');
210
229
  const BOUNDED_PROGRAM_DEVNET = ['open', 'Tv7fbpYSseNHYmCZFZ1CZgj4r8D9fKNgEz1qo6F'].join('');
211
230
  const COMPUTE_BUDGET_PROGRAM = 'ComputeBudget111111111111111111111111111111';
@@ -904,11 +923,11 @@ async function signPoofnetTransaction(serializedTransaction, authProvider, optio
904
923
  return bufferExports.Buffer.from(signed.serialize()).toString('base64');
905
924
  }
906
925
  async function handlePreBuiltTransaction(tx, authProvider, options, expectedIntent, beforeBroadcast) {
907
- var _a, _b, _c, _d, _e, _f, _g, _h;
926
+ var _a, _b, _c, _d, _e, _f;
908
927
  // SIMD-0385 v1 wires start with 0x81 (message first, signatures trailing);
909
928
  // web3.js's legacy/v0 classes cannot represent them, so they take their own lane.
910
929
  if (isTransactionV1Base64(tx.serializedTransaction)) {
911
- const { handlePreBuiltV1Transaction } = await import('./solana-v1-operations-DB9pih7o.mjs');
930
+ const { handlePreBuiltV1Transaction } = await import('./solana-v1-operations-ewIzFDeY.mjs');
912
931
  return handlePreBuiltV1Transaction(tx, authProvider, options, expectedIntent, beforeBroadcast);
913
932
  }
914
933
  const { Connection, VersionedTransaction, PublicKey } = await import('@solana/web3.js');
@@ -979,50 +998,33 @@ async function handlePreBuiltTransaction(tx, authProvider, options, expectedInte
979
998
  reason: `Broadcast response was lost or inconclusive: ${errorText(error)}`,
980
999
  };
981
1000
  }
982
- let confirmation;
983
- try {
984
- confirmation = await submitRpc.confirmTransaction({
985
- signature, blockhash: fence.blockhash, lastValidBlockHeight: fence.lastValidBlockHeight,
986
- }, 'confirmed');
987
- }
988
- catch (error) {
989
- // The bytes were accepted; only the confirmation outcome is unknown.
1001
+ const confirmation = await confirmSignature(submitRpc, { signature, lastValidBlockHeight: fence.lastValidBlockHeight });
1002
+ if (!confirmation.landed) {
1003
+ // The bytes were accepted; only the confirmation outcome is unknown. Whether the
1004
+ // transaction can still land is reconcileSetResult's call, from the finalized height.
990
1005
  return {
991
1006
  outcome: 'submitted',
992
1007
  lane: 'solana',
993
1008
  transactionSignature: signature,
994
1009
  signedTransaction,
995
1010
  fence,
996
- reason: `Confirmation outcome unknown: ${errorText(error)}`,
1011
+ reason: `Confirmation outcome unknown: ${confirmation.reason}`,
997
1012
  };
998
1013
  }
999
1014
  // #310: "confirmed" only means the network recorded the transaction, not that it
1000
1015
  // succeeded - a landed-but-failed transaction carries an err. Report it as a failure
1001
1016
  // (with on-chain logs) instead of a fake success, mirroring the server keypair provider.
1002
- if ((_e = confirmation.value) === null || _e === void 0 ? void 0 : _e.err) {
1003
- let logMessages;
1004
- try {
1005
- const txInfo = await getTransactionJson(submitRpc, signature, 'confirmed');
1006
- logMessages = (_f = txInfo === null || txInfo === void 0 ? void 0 : txInfo.meta) === null || _f === void 0 ? void 0 : _f.logMessages;
1007
- }
1008
- catch (_j) {
1009
- // Log retrieval is diagnostic only; the on-chain error is the verdict.
1010
- }
1011
- if (isBoundedOnchainRuleRejection(confirmation.value.err, logMessages)) {
1012
- throw markWriteNotApplied(new BoundedOnchainRuleRejection(confirmation.value.err, logMessages), 'failed_onchain');
1013
- }
1014
- const errorMessage = logMessages ? JSON.stringify(logMessages) : JSON.stringify(confirmation.value.err);
1015
- throw markWriteNotApplied(new Error(`Transaction failed: ${errorMessage}`), 'failed_onchain');
1016
- }
1017
+ if (confirmation.status.err)
1018
+ throw await landedTransactionFailure(submitRpc, signature, confirmation.status.err);
1017
1019
  return {
1018
1020
  outcome: 'confirmed',
1019
1021
  lane: 'solana',
1020
1022
  transactionSignature: signature,
1021
1023
  signedTransaction,
1022
1024
  fence,
1023
- confirmationContextSlot: (_h = (_g = confirmation.context) === null || _g === void 0 ? void 0 : _g.slot) !== null && _h !== void 0 ? _h : 0,
1025
+ confirmationContextSlot: (_f = (_e = confirmation.status.slot) !== null && _e !== void 0 ? _e : confirmation.contextSlot) !== null && _f !== void 0 ? _f : 0,
1024
1026
  };
1025
1027
  }
1026
1028
 
1027
- export { BoundedOnchainRuleRejection, POOFNET_VOID_FEE_PAYER, SOLANA_MAX_SUPPORTED_TRANSACTION_VERSION, buildExpectedWriteIntent, decodeBase58, deriveTransactionSignature, encodeBase58, errorText, getTransactionJson, handlePreBuiltTransaction, isBoundedOnchainRuleRejection, isDefinitiveSolanaSendFailure, isTransactionV1Base64, signPoofnetTransaction, validateServerTransactionSemantics };
1028
- //# sourceMappingURL=solana-operations-5WjFwhnt.mjs.map
1029
+ export { BoundedOnchainRuleRejection, POOFNET_VOID_FEE_PAYER, SOLANA_MAX_SUPPORTED_TRANSACTION_VERSION, buildExpectedWriteIntent, decodeBase58, deriveTransactionSignature, encodeBase58, errorText, getTransactionJson, handlePreBuiltTransaction, isBoundedOnchainRuleRejection, isDefinitiveSolanaSendFailure, isTransactionV1Base64, landedTransactionFailure, signPoofnetTransaction, validateServerTransactionSemantics };
1030
+ //# sourceMappingURL=solana-operations-CD6pks4-.mjs.map