{"version":3,"file":"json-frame.cjs","names":[],"sources":["../../src/utils/json-frame.ts"],"sourcesContent":["import { isDevBuild } from \"./dev-mode\";\nimport { validateWithSchema, type SchemaIssue, type SchemaLike } from \"./schema-like\";\n\n/**\n * The one decoder behind `createWebSocket`, `useWebSocket` and\n * `createEventStream`.\n *\n * Internal, and imported by path rather than through the `utils` barrel: it\n * exists so the three transports share one answer to \"the frame is not JSON\",\n * not so consumers can call it.\n *\n * That question used to have three identical copies of the same wrong answer —\n * `return raw as unknown as T`, which hands the consumer a `string` announced as\n * `T`. The failure never surfaced at the parse; it surfaced later, on the first\n * `message.id`, with nothing left to say the frame had not been JSON.\n */\n\n/** Outcome of decoding one frame. */\nexport interface DecodedFrame<T> {\n    /** Whether the message should reach `onMessage`. */\n    delivered: boolean;\n    /** The decoded payload. Only meaningful when `delivered` is `true`. */\n    data: T;\n}\n\n/** How one frame should be turned into `T`, and who hears about failures. */\nexport interface DecodeFrameOptions<T> {\n    /** Caller-supplied decoder, which owns the frame completely. */\n    parser?: (raw: string) => T;\n    /** Caller-supplied handler for a frame that is not valid JSON. */\n    onParseError?: (error: unknown, raw: string) => void;\n    /** Caller-supplied schema the decoded payload must satisfy. */\n    schema?: SchemaLike<T>;\n    /** Caller-supplied handler for a payload the schema refused. */\n    onValidationError?: (issues: SchemaIssue[], raw: string) => void;\n}\n\nconst warned = new Set<string>();\n\n/**\n * Warn once per transport that a frame arrived which was not JSON.\n *\n * Once, because a stream sending text frames sends many, and a console line per\n * frame buries the one that mattered. Development builds only.\n *\n * @param transport - Label used in the message, e.g. `\"createWebSocket\"`.\n * @returns Nothing.\n */\nfunction warnOnce(transport: string): void {\n    if (!isDevBuild() || warned.has(transport)) return;\n    warned.add(transport);\n    console.warn(\n        `[tempest-react-sdk] ${transport}: a frame was not valid JSON, so the raw string is ` +\n            `being delivered as if it were your message type. Pass \\`parser\\` to decode it, or ` +\n            `\\`onParseError\\` to drop it and handle the failure. This warning appears once.`,\n    );\n}\n\n/**\n * Warn once per transport that a frame was dropped by the schema.\n *\n * A dropped frame with no `onValidationError` is otherwise completely silent —\n * the stream looks healthy and the payload simply never arrives, which is the\n * hardest shape of failure to notice. Development builds only, once, for the\n * same reason as {@link warnOnce}.\n *\n * @param transport - Label used in the message, e.g. `\"createEventStream\"`.\n * @param issues - The issues the schema reported, summarized into the message.\n * @returns Nothing.\n */\nfunction warnValidationOnce(transport: string, issues: SchemaIssue[]): void {\n    const key = `${transport}:schema`;\n    if (!isDevBuild() || warned.has(key)) return;\n    warned.add(key);\n    const summary = issues.map((issue) => `${issue.path}: ${issue.message}`).join(\"; \");\n    console.warn(\n        `[tempest-react-sdk] ${transport}: a frame did not match \\`schema\\` and was dropped ` +\n            `(${summary}). Pass \\`onValidationError\\` to handle it yourself. This warning ` +\n            `appears once.`,\n    );\n}\n\n/**\n * Decode one frame, reporting whether it should be delivered.\n *\n * A caller-supplied `parser` owns the frame completely: its result is delivered\n * as it is, or validated when a `schema` was also supplied — decoding text,\n * binary-as-base64 or a protocol of its own is the point of that option.\n *\n * Without one, the frame is parsed as JSON. When that throws:\n *\n * - with `onParseError`, the callback fires and the frame is **not** delivered,\n *   because a consumer that asked to hear about failures did not ask to also\n *   receive the broken frame;\n * - with `schema` and no `onParseError`, the raw string goes to the schema,\n *   which refuses it — a caller who asked for validation never receives an\n *   unvalidated payload, and a frame the server sent empty is exactly this case;\n * - with neither, the raw string is delivered as `T` — the behaviour every\n *   version before this one had, kept so nothing breaks — and development builds\n *   warn once that it happened.\n *\n * With a `schema`, a payload the schema refuses is not delivered, and\n * `onValidationError` hears the issues. The value delivered is the schema's\n * **output**, so a schema that coerces or defaults is honoured.\n *\n * @param raw - The frame body as text.\n * @param transport - Label used in the development warnings.\n * @param options - Caller-supplied decoder, schema and failure handlers.\n * @returns Whether to deliver, and the payload.\n */\nexport function decodeFrame<T>(\n    raw: string,\n    transport: string,\n    options: DecodeFrameOptions<T>,\n): DecodedFrame<T> {\n    const { parser, onParseError, schema, onValidationError } = options;\n\n    /**\n     * Put one decoded payload through the schema, when there is one.\n     *\n     * @param value - The payload as parsing produced it.\n     * @returns Whether to deliver, and the payload the consumer should see.\n     */\n    function gate(value: unknown): DecodedFrame<T> {\n        if (!schema) return { delivered: true, data: value as T };\n        const result = validateWithSchema(schema, value);\n        if (result.ok) return { delivered: true, data: result.data };\n        if (onValidationError) onValidationError(result.issues, raw);\n        else warnValidationOnce(transport, result.issues);\n        return { delivered: false, data: undefined as T };\n    }\n\n    if (parser) return gate(parser(raw));\n    let parsed: unknown;\n    try {\n        parsed = JSON.parse(raw);\n    } catch (error) {\n        if (onParseError) {\n            onParseError(error, raw);\n            return { delivered: false, data: undefined as T };\n        }\n        if (schema) return gate(raw);\n        warnOnce(transport);\n        return { delivered: true, data: raw as unknown as T };\n    }\n    return gate(parsed);\n}\n\n/**\n * Forget which transports have already warned.\n *\n * Exists for tests, which would otherwise see the first case swallow the\n * warning for every case after it.\n *\n * @returns Nothing.\n */\nexport function resetFrameWarnings(): void {\n    warned.clear();\n}\n"],"mappings":"iEAqCA,IAAM,EAAS,IAAI,IAWnB,SAAS,EAAS,EAAyB,CAClC,EAAA,WAAW,GAAK,GAAO,IAAI,CAAS,IACzC,EAAO,IAAI,CAAS,EACpB,QAAQ,KACJ,uBAAuB,EAAU,oNAGrC,EACJ,CAcA,SAAS,EAAmB,EAAmB,EAA6B,CACxE,IAAM,EAAM,GAAG,EAAU,SACzB,GAAI,CAAC,EAAA,WAAW,GAAK,EAAO,IAAI,CAAG,EAAG,OACtC,EAAO,IAAI,CAAG,EACd,IAAM,EAAU,EAAO,IAAK,GAAU,GAAG,EAAM,KAAK,IAAI,EAAM,SAAS,CAAC,CAAC,KAAK,IAAI,EAClF,QAAQ,KACJ,uBAAuB,EAAU,sDACzB,EAAQ,gFAEpB,CACJ,CA8BA,SAAgB,EACZ,EACA,EACA,EACe,CACf,GAAM,CAAE,SAAQ,eAAc,SAAQ,qBAAsB,EAQ5D,SAAS,EAAK,EAAiC,CAC3C,GAAI,CAAC,EAAQ,MAAO,CAAE,UAAW,GAAM,KAAM,CAAW,EACxD,IAAM,EAAS,EAAA,mBAAmB,EAAQ,CAAK,EAI/C,OAHI,EAAO,GAAW,CAAE,UAAW,GAAM,KAAM,EAAO,IAAK,GACvD,EAAmB,EAAkB,EAAO,OAAQ,CAAG,EACtD,EAAmB,EAAW,EAAO,MAAM,EACzC,CAAE,UAAW,GAAO,KAAM,IAAA,EAAe,EACpD,CAEA,GAAI,EAAQ,OAAO,EAAK,EAAO,CAAG,CAAC,EACnC,IAAI,EACJ,GAAI,CACA,EAAS,KAAK,MAAM,CAAG,CAC3B,OAAS,EAAO,CAOZ,OANI,GACA,EAAa,EAAO,CAAG,EAChB,CAAE,UAAW,GAAO,KAAM,IAAA,EAAe,GAEhD,EAAe,EAAK,CAAG,GAC3B,EAAS,CAAS,EACX,CAAE,UAAW,GAAM,KAAM,CAAoB,EACxD,CACA,OAAO,EAAK,CAAM,CACtB"}