{"version":3,"sources":["/Users/gerard/dev/gapp/client/dist/index.cjs","../src/callbackSet.ts","../src/result.ts","../src/store.ts","../src/registry.ts","../src/router.ts","../src/rpcProxy.ts","../src/rpcTransport.ts","../src/rpcError.ts","../src/preload.ts"],"names":["metadata"],"mappings":"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","file":"/Users/gerard/dev/gapp/client/dist/index.cjs","sourcesContent":[null,"export function createCallbackSet<T>() {\n  const set = new Set<(args: T) => void>();\n\n  function add(callback: (args: T) => void) {\n    const reference = (args: T) => callback(args);\n\n    set.add(reference);\n\n    return () => {\n      set.delete(reference);\n    };\n  }\n\n  function call(args: T) {\n    // We need to do a copy because if any thing adds to the set during the call, it would cause an infinite loop\n    const copy = Array.from(set);\n    copy.forEach((callback) => callback(args));\n  }\n\n  function clear() {\n    set.clear();\n  }\n\n  return { add, call, clear };\n}\n","export interface Result<T, E = Error> {\n  isOk(): this is Ok<T, E>;\n  isErr(): this is Err<T, E>;\n  map<U>(fn: (value: T) => U): Result<U, E>;\n  unwrap(): T | never;\n  unwrapOrDefault(defaultValue: T): T;\n}\n\nexport function ok<E = any>(): Result<void, E>;\nexport function ok<T, E = any>(value: T): Result<T, E>;\nexport function ok<T, E = any>(value?: T): Result<T | void, E> {\n  return new Ok<T | void, E>(value as T);\n}\n\nexport function err<T = any>(): Result<T, void>;\nexport function err<T, E = any>(error: E): Result<T, E>;\nexport function err<T, E = any>(error?: E): Result<T, E | void> {\n  return new Err<T, E | void>(error as E);\n}\n\nexport class Ok<T, E> implements Result<T, E> {\n  constructor(public value: T) {}\n\n  isOk(): this is Ok<T, E> {\n    return true;\n  }\n\n  isErr(): this is Err<T, E> {\n    return false;\n  }\n\n  map<U>(fn: (value: T) => U): Result<U, E> {\n    return new Ok<U, E>(fn(this.value));\n  }\n\n  unwrap(): T {\n    return this.value;\n  }\n\n  unwrapOrDefault(_defaultValue: T): T {\n    return this.value;\n  }\n}\n\nexport class Err<T, E> implements Result<T, E> {\n  constructor(public error: E) {}\n\n  isOk(): this is Ok<T, E> {\n    return false;\n  }\n\n  isErr(): this is Err<T, E> {\n    return true;\n  }\n\n  map<U>(_fn: (value: T) => U): Result<U, E> {\n    return new Err<U, E>(this.error);\n  }\n\n  unwrap(): T {\n    throw new Error(`Called unwrap on Err: ${this.error}`);\n  }\n\n  unwrapOrDefault(defaultValue: T): T {\n    return defaultValue;\n  }\n}\n","import { createCallbackSet } from \"./callbackSet\";\n\nexport type DecodedRpc = {\n  method: string;\n  request: unknown;\n  response: unknown;\n};\n\nexport abstract class Store<State, RpcResult = unknown, Action = never, RpcRequest = unknown> {\n  protected state: State;\n  private listeners = createCallbackSet<State>();\n\n  constructor(initialState: State) {\n    this.state = initialState;\n    this.setState = this.setState.bind(this);\n    this.getState = this.getState.bind(this);\n  }\n\n  getState(): State {\n    return this.state;\n  }\n\n  setState(newState: State) {\n    this.state = newState;\n    this.listeners.call(this.state);\n  }\n\n  subscribe(callback: (newState: State) => void): () => void {\n    callback(this.state);\n    return this.listeners.add(callback);\n  }\n\n  // Dispatch a local action (for non-RPC state changes)\n  dispatch(action: Action): void {\n    const newState = this.reduceAction(this.state, action);\n    if (newState !== this.state) {\n      this.setState(newState);\n    }\n  }\n\n  // Pure reducer for RPC outcomes (success or error) - returns new state\n  reduceRpc(state: State, _event: RpcResult): State {\n    return state;\n  }\n\n  // Pure reducer for \"about to send RPC\" - returns new state (optional)\n  reduceSendRpc(state: State, _event: RpcRequest): State {\n    return state;\n  }\n\n  // Pure reducer for local actions - returns new state (optional)\n  reduceAction(state: State, _action: Action): State {\n    return state;\n  }\n}\n","import { Store, type DecodedRpc } from \"./store\";\nimport { ok } from \"./result\";\n\nexport class StoreRegistry {\n  private stores = new Set<Store<any>>();\n\n  register<S extends Store<any>>(store: S): S {\n    this.stores.add(store);\n    return store;\n  }\n\n  dispatchRpc(event: unknown): void {\n    for (const store of this.stores) {\n      const current = store.getState();\n      const next = store.reduceRpc(current, event);\n      if (next !== current) store.setState(next);\n    }\n  }\n\n  dispatchSendRpc(event: unknown): void {\n    for (const store of this.stores) {\n      const current = store.getState();\n      const next = store.reduceSendRpc(current, event);\n      if (next !== current) store.setState(next);\n    }\n  }\n\n  hydrate(decoded: DecodedRpc[]): void {\n    for (const event of decoded) {\n      this.dispatchRpc({\n        method: event.method,\n        request: event.request,\n        result: ok(event.response),\n      });\n    }\n  }\n}\n","import { createCallbackSet } from \"./callbackSet\";\n\n// Extract params from a single path segment\ntype ExtractSegmentParams<S extends string> = S extends `:${infer Param}?`\n  ? { [k in Param]?: string }\n  : S extends `:${infer Param}`\n  ? { [k in Param]: string }\n  : {};\n\n// Extract params from route path by splitting on / and processing each segment\n// Supports optional params with :param? syntax\ntype ExtractParams<S extends string> = S extends `${infer Head}/${infer Rest}`\n  ? ExtractSegmentParams<Head> & ExtractParams<Rest>\n  : ExtractSegmentParams<S>;\n\nexport type Route<Path extends string, Metadata> = {\n  factory: (props: ExtractParams<Path>) => Metadata;\n  path: Path;\n};\n\nexport type RouteTree<Metadata> = {\n  node: Route<string, Metadata> | null;\n  static: {\n    [key: string]: RouteTree<Metadata>;\n  };\n  dynamic: {\n    key: string;\n    optional: boolean;\n    value: RouteTree<Metadata>;\n  } | null;\n};\n\ntype WindowAPI = {\n  getPathname: () => string;\n  navigate: (to: string) => void;\n  addEventListener: (\n    type: string,\n    listener: (this: Window, ev: Event) => any\n  ) => void;\n  removeEventListener: (\n    type: string,\n    listener: (this: Window, ev: Event) => any\n  ) => void;\n};\n\nconst BrowserWindowAPI: WindowAPI = {\n  getPathname: () => window.location.pathname,\n  navigate: (to: string) => {\n    window.history.pushState(null, \"\", to);\n    window.dispatchEvent(new Event(\"popstate\"));\n  },\n  addEventListener: window.addEventListener.bind(window),\n  removeEventListener: window.removeEventListener.bind(window),\n};\n\nexport class Router<Metadata> {\n  private windowAPI: WindowAPI;\n  public tree: RouteTree<Metadata>;\n  private subscribers = createCallbackSet<Metadata>();\n  private cache: Set<Route<string, Metadata>> = new Set();\n  private metadataCache: Map<string, Metadata> = new Map();\n\n  constructor(\n    routes: Route<string, Metadata>[],\n    windowAPI: WindowAPI = BrowserWindowAPI\n  ) {\n    this.windowAPI = windowAPI;\n\n    if (routes.length === 0) {\n      throw new Error(\n        \"At least one route is required to build the route tree.\"\n      );\n    }\n\n    const firstRoute = routes[0]!;\n    if (firstRoute.path !== \"/\") {\n      throw new Error(\"The first route must be the root route with path '/'\");\n    }\n\n    this.tree = {\n      node: firstRoute,\n      static: {},\n      dynamic: null,\n    };\n\n    for (const route of routes.slice(1)) {\n      this.addRouteToTree(route);\n    }\n\n    for (const route of routes) {\n      this.cache.add(route);\n    }\n\n    this.windowAPI.addEventListener(\"popstate\", this.onPopstate);\n  }\n\n  cleanup() {\n    this.windowAPI.removeEventListener(\"popstate\", this.onPopstate);\n    this.subscribers.clear();\n    this.cache.clear();\n    this.metadataCache.clear();\n    this.tree = { node: null, static: {}, dynamic: null };\n  }\n\n  private onPopstate = () => {\n    this.subscribers.call(this.current());\n  };\n\n  url<Path extends string>(\n    route: Route<Path, any>,\n    params: ExtractParams<Path>,\n    queryParams?: { [key: string]: string | number | boolean }\n  ): string {\n    if (!this.cache.has(route)) {\n      throw new Error(\"Route not registered in the router\");\n    }\n\n    const parts = route.path.split(\"/\").filter(Boolean);\n    let urlParts: string[] = [];\n\n    for (const part of parts) {\n      if (part.startsWith(\":\")) {\n        const isOptional = part.endsWith(\"?\");\n        const paramName = isOptional ? part.slice(1, -1) : part.slice(1);\n        const paramValue = params[paramName as keyof typeof params];\n\n        if (!paramValue) {\n          if (isOptional) {\n            // Stop building URL when we hit an unprovided optional param\n            break;\n          }\n          throw new Error(\n            `Missing required parameter '${paramName}' for route '${route.path}'`\n          );\n        }\n        urlParts.push(paramValue as string);\n      } else {\n        urlParts.push(part);\n      }\n    }\n\n    const baseUrl = \"/\" + urlParts.join(\"/\");\n\n    if (queryParams && Object.keys(queryParams).length > 0) {\n      const queryString = Object.entries(queryParams)\n        .map(\n          ([key, value]) =>\n            `${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`\n        )\n        .join(\"&\");\n      return `${baseUrl}?${queryString}`;\n    }\n\n    return baseUrl;\n  }\n\n  onNavigate(callback: (metadata: Metadata) => void): () => void {\n    const unsubscribe = this.subscribers.add(callback);\n    callback(this.current());\n    return unsubscribe;\n  }\n\n  current(): Metadata {\n    const pathname = this.windowAPI.getPathname();\n    const result = this.find(pathname);\n    if (!result) {\n      throw new Error(`No route found for pathname: ${pathname}`);\n    }\n    return result;\n  }\n\n  path(): string {\n    const pathname = this.windowAPI.getPathname();\n    return pathname;\n  }\n\n  navigate(path: string) {\n    this.windowAPI.navigate(path);\n    this.subscribers.call(this.current());\n  }\n\n  find(path: string): Metadata | null {\n    const cached = this.metadataCache.get(path);\n    if (cached) {\n      return cached;\n    }\n\n    if (path === \"/\") {\n      const metadata = this.tree.node!.factory({});\n      this.metadataCache.set(path, metadata);\n      return metadata;\n    }\n\n    const segments = path.split(\"/\");\n\n    let current: RouteTree<Metadata> = {\n      node: null,\n      static: {\n        \"\": this.tree,\n      },\n      dynamic: null,\n    };\n\n    for (const segment of segments) {\n      const child = current.static[segment];\n      if (child) {\n        current = child;\n      } else if (current.dynamic) {\n        current = current.dynamic.value;\n      } else {\n        return null;\n      }\n    }\n\n    if (current.node == null) {\n      return null;\n    }\n\n    const params = this.findParams(current.node.path, path);\n    const metadata = current.node.factory(params);\n    this.metadataCache.set(path, metadata);\n    return metadata;\n  }\n\n  private findParams(\n    routePath: string,\n    pathname: string\n  ): { [key: string]: string | undefined } {\n    const params: { [key: string]: string | undefined } = {};\n    const routePathSegments = routePath.split(\"/\").filter(Boolean);\n    const pathnameSegments = pathname.split(\"/\").filter(Boolean);\n\n    for (let i = 0; i < routePathSegments.length; i++) {\n      const routeSegment = routePathSegments[i]!;\n      const pathSegment = pathnameSegments[i];\n\n      if (routeSegment.startsWith(\":\")) {\n        // Remove the optional marker (?) if present\n        const isOptional = routeSegment.endsWith(\"?\");\n        const paramName = isOptional\n          ? routeSegment.slice(1, -1)\n          : routeSegment.slice(1);\n\n        // If we have a path segment, use it; otherwise it's undefined (optional param not provided)\n        params[paramName] = pathSegment;\n      }\n    }\n\n    return params;\n  }\n\n  private addRouteToTree(route: Route<string, Metadata>) {\n    const parts = route.path.split(\"/\").filter(Boolean);\n    let current: RouteTree<Metadata> = this.tree;\n\n    for (let i = 0; i < parts.length; i++) {\n      const part = parts[i]!;\n      const isOptional = part.endsWith(\"?\");\n      const isDynamic = part.startsWith(\":\");\n\n      if (isDynamic) {\n        // Normalize the key (remove the ? suffix for comparison)\n        const key = isOptional ? part.slice(0, -1) : part;\n\n        if (current.dynamic) {\n          const existingKey = current.dynamic.key;\n          if (existingKey !== key) {\n            throw new Error(\n              `Conflicting dynamic segments: '${existingKey}' and '${key}'`\n            );\n          }\n        } else {\n          current.dynamic = {\n            key,\n            optional: isOptional,\n            value: { node: null, static: {}, dynamic: null },\n          };\n        }\n\n        // If this param is optional, the current node is also a valid endpoint\n        if (isOptional && !current.node) {\n          current.node = route;\n        }\n\n        current = current.dynamic.value;\n      } else {\n        if (!current.static[part]) {\n          current.static[part] = {\n            node: null,\n            static: {},\n            dynamic: null,\n          };\n        }\n        current = current.static[part]!;\n      }\n    }\n\n    if (current.node && current.node !== route) {\n      throw new Error(`Conflicting routes for path: '${route.path}'`);\n    }\n\n    current.node = route;\n  }\n}\n","import type { StoreRegistry } from \"./registry\";\nimport { ok, err } from \"./result\";\n\n/**\n * Creates a proxy around an RPC client that auto-dispatches to stores.\n * @param client The base RPC client to wrap\n * @param options.registry The store registry to dispatch events to\n * @param options.streamingMethods Methods that should not be wrapped (streaming methods)\n */\nexport function createRpcProxy<T extends object>(\n  client: T,\n  options: { registry: StoreRegistry; streamingMethods?: Set<string> },\n): T {\n  const { registry, streamingMethods = new Set() } = options;\n\n  return new Proxy(client, {\n    get(target, prop: string) {\n      const original = target[prop as keyof T];\n      if (typeof original !== \"function\") {\n        return original;\n      }\n\n      // Don't wrap streaming methods\n      if (streamingMethods.has(prop)) {\n        return original.bind(target);\n      }\n\n      // Wrap unary methods to auto-dispatch send + result (ok/err)\n      return async (request: unknown) => {\n        registry.dispatchSendRpc({ method: prop, request });\n        try {\n          const response = await (original as Function).call(target, request);\n          registry.dispatchRpc({\n            method: prop,\n            request,\n            result: ok(response),\n          });\n          return response;\n        } catch (error) {\n          registry.dispatchRpc({\n            method: prop,\n            request,\n            result: err(error as Error),\n          });\n          throw error;\n        }\n      };\n    },\n  }) as T;\n}\n","import { Observable } from \"rxjs\";\nimport { parseRpcError } from \"./rpcError\";\n\nexport type RpcTransportConfig = {\n  url: string | (() => string);\n  credentials?: RequestCredentials; // default: \"include\"\n};\n\n/**\n * The Rpc interface expected by ts-proto generated clients.\n */\nexport interface RpcTransport {\n  request(\n    service: string,\n    method: string,\n    data: Uint8Array\n  ): Promise<Uint8Array>;\n  clientStreamingRequest(\n    service: string,\n    method: string,\n    data: Observable<Uint8Array>\n  ): Promise<Uint8Array>;\n  serverStreamingRequest(\n    service: string,\n    method: string,\n    data: Uint8Array\n  ): Observable<Uint8Array>;\n  bidirectionalStreamingRequest(\n    service: string,\n    method: string,\n    data: Observable<Uint8Array>\n  ): Observable<Uint8Array>;\n}\n\nexport function createRpcTransport(config: RpcTransportConfig): RpcTransport {\n  const getUrl = typeof config.url === \"function\" ? config.url : () => config.url as string;\n  const credentials = config.credentials ?? \"include\";\n\n  return {\n    request(_service, method, data) {\n      return fetch(getUrl(), {\n        method: \"POST\",\n        headers: {\n          \"Content-Type\": \"application/x-protobuf\",\n          \"X-Rpc-Method\": method,\n        },\n        credentials,\n        body: data as unknown as BodyInit,\n      })\n        .then(async (res) => {\n          if (!res.ok) {\n            throw await parseRpcError(res);\n          }\n          return res.arrayBuffer();\n        })\n        .then((buffer) => {\n          return new Uint8Array(buffer);\n        });\n    },\n\n    clientStreamingRequest(\n      _service: string,\n      method: string,\n      data: Observable<Uint8Array>\n    ): Promise<Uint8Array> {\n      return new Promise<Uint8Array>((resolve, reject) => {\n        const chunks: Uint8Array[] = [];\n\n        data.subscribe({\n          next(encoded) {\n            // Prepend 4-byte big-endian length prefix\n            const prefix = new Uint8Array(4);\n            const view = new DataView(prefix.buffer);\n            view.setUint32(0, encoded.length, false);\n            chunks.push(prefix, encoded);\n          },\n          error(err) {\n            reject(err);\n          },\n          complete() {\n            // Concatenate all prefixed chunks into a single body\n            let totalLength = 0;\n            for (const chunk of chunks) {\n              totalLength += chunk.length;\n            }\n            const body = new Uint8Array(totalLength);\n            let offset = 0;\n            for (const chunk of chunks) {\n              body.set(chunk, offset);\n              offset += chunk.length;\n            }\n\n            // Send as a regular POST\n            fetch(getUrl(), {\n              method: \"POST\",\n              headers: {\n                \"Content-Type\": \"application/x-protobuf\",\n                \"X-Rpc-Method\": method,\n              },\n              credentials,\n              body: body as unknown as BodyInit,\n            })\n              .then(async (res) => {\n                if (!res.ok) {\n                  throw await parseRpcError(res);\n                }\n                return res.arrayBuffer();\n              })\n              .then((buffer) => resolve(new Uint8Array(buffer)))\n              .catch(reject);\n          },\n        });\n      });\n    },\n\n    serverStreamingRequest(\n      _service: string,\n      method: string,\n      data: Uint8Array\n    ): Observable<Uint8Array> {\n      return new Observable<Uint8Array>((subscriber) => {\n        let aborted = false;\n\n        fetch(getUrl(), {\n          method: \"POST\",\n          headers: {\n            \"Content-Type\": \"application/x-protobuf\",\n            \"X-Rpc-Method\": method,\n          },\n          credentials,\n          body: data as unknown as BodyInit,\n        })\n          .then(async (response) => {\n            if (!response.ok) {\n              throw await parseRpcError(response);\n            }\n\n            const reader = response.body?.getReader();\n            if (!reader) {\n              throw new Error(\"No response body\");\n            }\n\n            let buffer = new Uint8Array(0);\n\n            try {\n              while (!aborted) {\n                const { done, value } = await reader.read();\n\n                if (done) {\n                  break;\n                }\n\n                // Append new data to buffer\n                const newBuffer = new Uint8Array(buffer.length + value.length);\n                newBuffer.set(buffer);\n                newBuffer.set(value, buffer.length);\n                buffer = newBuffer;\n\n                // Parse complete messages from buffer\n                while (buffer.length >= 4) {\n                  // Read 4-byte length prefix (big endian)\n                  const length =\n                    (buffer[0]! << 24) |\n                    (buffer[1]! << 16) |\n                    (buffer[2]! << 8) |\n                    buffer[3]!;\n\n                  // Check if we have the complete message\n                  if (buffer.length < 4 + length) {\n                    break; // Need more data\n                  }\n\n                  // Extract the message\n                  const message = buffer.slice(4, 4 + length);\n\n                  // Remove processed data from buffer\n                  buffer = buffer.slice(4 + length);\n\n                  // Emit the message\n                  subscriber.next(message);\n                }\n              }\n\n              subscriber.complete();\n            } catch (error) {\n              if (!aborted) {\n                subscriber.error(error);\n              }\n            }\n          })\n          .catch((error) => {\n            if (!aborted) {\n              subscriber.error(error);\n            }\n          });\n\n        // Cleanup function\n        return () => {\n          aborted = true;\n        };\n      });\n    },\n\n    bidirectionalStreamingRequest(\n      _service: string,\n      _method: string,\n      _data: Observable<Uint8Array>\n    ): Observable<Uint8Array> {\n      throw new Error(\"Bidirectional streaming not implemented\");\n    },\n  };\n}\n","export const RpcErrorCode = {\n  VALIDATION_ERROR: \"VALIDATION_ERROR\",\n  NOT_FOUND: \"NOT_FOUND\",\n  ALREADY_EXISTS: \"ALREADY_EXISTS\",\n  UNAUTHENTICATED: \"UNAUTHENTICATED\",\n  PERMISSION_DENIED: \"PERMISSION_DENIED\",\n  RATE_LIMITED: \"RATE_LIMITED\",\n  INTERNAL: \"INTERNAL\",\n} as const;\n\nexport type RpcErrorCodeType = (typeof RpcErrorCode)[keyof typeof RpcErrorCode];\n\nexport class RpcError extends Error {\n  code: string;\n  details: Record<string, string>;\n  httpStatus: number;\n\n  constructor(\n    code: string,\n    message: string,\n    httpStatus: number,\n    details?: Record<string, string>\n  ) {\n    super(message);\n    this.name = \"RpcError\";\n    this.code = code;\n    this.details = details ?? {};\n    this.httpStatus = httpStatus;\n  }\n\n  is(code: string): boolean {\n    return this.code === code;\n  }\n}\n\nexport async function parseRpcError(\n  res: Response\n): Promise<RpcError> {\n  const contentType = res.headers.get(\"Content-Type\") ?? \"\";\n  if (contentType.includes(\"application/json\")) {\n    try {\n      const body = await res.json();\n      return new RpcError(\n        body.code ?? \"UNKNOWN\",\n        body.message ?? res.statusText,\n        res.status,\n        body.details\n      );\n    } catch {\n      // fall through to text parsing\n    }\n  }\n  const text = await res.text();\n  return new RpcError(\n    \"UNKNOWN\",\n    text.trim() || `HTTP error! status: ${res.status}`,\n    res.status\n  );\n}\n","// Preloaded data structure embedded in HTML (gzip-compressed, base64-encoded protobuf bytes)\nexport type PreloadedData = {\n  [method: string]: {\n    requestBytes: string;\n    responseBytes: string;\n  };\n};\n\ndeclare global {\n  interface Window {\n    __PRELOADED__?: PreloadedData;\n    __PRELOAD_TIMESTAMP__?: number;\n  }\n}\n\n// RPC declaration for route preloading\nexport type RpcDeclaration = {\n  method: string;\n  params?: Record<string, string>;\n};\n\nexport type DecoderMap = Record<\n  string,\n  (bytes: Uint8Array) => unknown\n>;\n\n/**\n * Decode base64 string to Uint8Array\n */\nfunction base64ToBytes(base64: string): Uint8Array {\n  const binary = atob(base64);\n  const bytes = new Uint8Array(binary.length);\n  for (let i = 0; i < binary.length; i++) {\n    bytes[i] = binary.charCodeAt(i);\n  }\n  return bytes;\n}\n\n/**\n * Decompress gzipped bytes using DecompressionStream API\n */\nasync function decompressGzip(compressed: Uint8Array): Promise<Uint8Array> {\n  const ds = new DecompressionStream(\"gzip\");\n  const writer = ds.writable.getWriter();\n  writer.write(compressed as unknown as BufferSource);\n  writer.close();\n\n  const reader = ds.readable.getReader();\n  const chunks: Uint8Array[] = [];\n  let totalLength = 0;\n\n  while (true) {\n    const { done, value } = await reader.read();\n    if (done) break;\n    chunks.push(value);\n    totalLength += value.length;\n  }\n\n  const result = new Uint8Array(totalLength);\n  let offset = 0;\n  for (const chunk of chunks) {\n    result.set(chunk, offset);\n    offset += chunk.length;\n  }\n  return result;\n}\n\n/**\n * Decode and dispatch all preloaded RPCs.\n * Returns array of { method, request, response } for dispatching to stores.\n * Data is gzip compressed, so decompression is async.\n */\nexport async function decodeAllPreloaded(\n  requestDecoders: DecoderMap,\n  responseDecoders: DecoderMap\n): Promise<\n  Array<{\n    method: string;\n    request: unknown;\n    response: unknown;\n  }>\n> {\n  const preloaded = window.__PRELOADED__;\n  if (!preloaded) {\n    return [];\n  }\n\n  const results: Array<{\n    method: string;\n    request: unknown;\n    response: unknown;\n  }> = [];\n\n  for (const [method, { requestBytes, responseBytes }] of Object.entries(\n    preloaded\n  )) {\n    const reqDecoder = requestDecoders[method];\n    const resDecoder = responseDecoders[method];\n    if (!resDecoder) {\n      console.warn(`[Preload] No decoder for method: ${method}`);\n      continue;\n    }\n\n    try {\n      // Decode request (may be empty for some RPCs)\n      let request: unknown = {};\n      if (requestBytes && reqDecoder) {\n        const compressedReq = base64ToBytes(requestBytes);\n        const reqBytes = await decompressGzip(compressedReq);\n        request = reqDecoder(reqBytes);\n      }\n\n      // Decode response (decompress gzipped data)\n      const compressedRes = base64ToBytes(responseBytes);\n      const resBytes = await decompressGzip(compressedRes);\n      const response = resDecoder(resBytes);\n\n      results.push({ method, request, response });\n    } catch (err) {\n      console.error(`[Preload] Failed to decode ${method}:`, err);\n    }\n  }\n\n  // Clear preloaded data\n  delete window.__PRELOADED__;\n\n  return results;\n}\n"]}