@loro-dev/streams-crdt 0.15.0 → 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -8
- package/dist/flock.d.ts +1 -1
- package/dist/flock.js +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/loro.d.ts +9 -11
- package/dist/loro.js +43 -3
- package/dist/loro.js.map +1 -1
- package/dist/{stream-id-CO65-58E.d.ts → stream-id-BEEukuNT.d.ts} +68 -3
- package/dist/{stream-id-CggbAC6b.js → stream-id-DwQujw_H.js} +337 -81
- package/dist/stream-id-DwQujw_H.js.map +1 -0
- package/package.json +2 -2
- package/dist/stream-id-CggbAC6b.js.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stream-id-DwQujw_H.js","names":["createTransportJoinState","disposeTransportJoinState"],"sources":["../src/deque.ts","../src/reconnect.ts","../src/items.ts","../src/payload-protection.ts","../src/transport-internal.ts","../src/transport-client.ts","../src/ephemeral-runtime.ts","../src/checkpoint.ts","../src/transport-cursor.ts","../src/transport-snapshot.ts","../src/transport-room.ts","../src/transport-runtime.ts","../src/stream-id.ts"],"sourcesContent":["const DEFAULT_COMPACT_THRESHOLD = 64;\nconst DEFAULT_MIN_CAPACITY = 16;\n\nexport interface DequeOptions {\n readonly compactThreshold?: number;\n readonly minCapacity?: number;\n}\n\nexport class Deque<T> implements Iterable<T> {\n private buffer: Array<T | null | undefined>;\n private head = 0;\n private tail = 0;\n private readonly compactThreshold: number;\n private readonly minCapacity: number;\n\n constructor(values?: Iterable<T>, options: DequeOptions = {}) {\n this.compactThreshold = Math.max(\n 1,\n options.compactThreshold ?? DEFAULT_COMPACT_THRESHOLD\n );\n this.minCapacity = Math.max(1, options.minCapacity ?? DEFAULT_MIN_CAPACITY);\n const seed = values == null ? [] : Array.from(values);\n this.buffer = seed.slice();\n this.tail = seed.length;\n }\n\n get length(): number {\n return this.tail - this.head;\n }\n\n isEmpty(): boolean {\n return this.length === 0;\n }\n\n clear(): void {\n this.buffer = [];\n this.head = 0;\n this.tail = 0;\n }\n\n peekFront(): T | undefined {\n if (this.length === 0) {\n return undefined;\n }\n const value = this.buffer[this.head];\n return value == null ? undefined : value;\n }\n\n peekBack(): T | undefined {\n if (this.length === 0) {\n return undefined;\n }\n const value = this.buffer[this.tail - 1];\n return value == null ? undefined : value;\n }\n\n at(index: number): T | undefined {\n const length = this.length;\n if (length === 0) {\n return undefined;\n }\n let offset = index;\n if (offset < 0) {\n offset += length;\n }\n if (offset < 0 || offset >= length) {\n return undefined;\n }\n const value = this.buffer[this.head + offset];\n return value == null ? undefined : value;\n }\n\n pushBack(value: T): number {\n this.ensureBackCapacity();\n this.buffer[this.tail] = value;\n this.tail += 1;\n return this.length;\n }\n\n pushFront(value: T): number {\n if (this.head === 0) {\n this.recenter(1, 0);\n }\n this.head -= 1;\n this.buffer[this.head] = value;\n return this.length;\n }\n\n popFront(): T | undefined {\n if (this.length === 0) {\n return undefined;\n }\n const value = this.buffer[this.head];\n // Explicitly null out removed slot to release references for GC.\n this.buffer[this.head] = null;\n this.head += 1;\n this.compactIfNeeded();\n return value == null ? undefined : value;\n }\n\n popBack(): T | undefined {\n if (this.length === 0) {\n return undefined;\n }\n this.tail -= 1;\n const value = this.buffer[this.tail];\n // Explicitly null out removed slot to release references for GC.\n this.buffer[this.tail] = null;\n this.compactIfNeeded();\n return value == null ? undefined : value;\n }\n\n toArray(): T[] {\n const out: T[] = [];\n for (let i = this.head; i < this.tail; i += 1) {\n out.push(this.buffer[i] as T);\n }\n return out;\n }\n\n *[Symbol.iterator](): IterableIterator<T> {\n for (let i = this.head; i < this.tail; i += 1) {\n yield this.buffer[i] as T;\n }\n }\n\n private ensureBackCapacity(): void {\n if (this.tail < this.buffer.length) {\n return;\n }\n if (this.head > 0) {\n this.compactToFront();\n if (this.tail < this.buffer.length) {\n return;\n }\n }\n this.recenter(0, 1);\n }\n\n private recenter(extraFront: number, extraBack: number): void {\n const length = this.length;\n const needed = length + extraFront + extraBack;\n const currentCapacity = this.buffer.length;\n let nextCapacity = Math.max(currentCapacity, this.minCapacity);\n if (nextCapacity < needed) {\n nextCapacity = Math.max(nextCapacity * 2, needed, this.minCapacity);\n }\n const free = nextCapacity - length;\n const nextHead = Math.max(extraFront, Math.floor(free / 2));\n const next = new Array<T | null | undefined>(nextCapacity);\n for (let i = 0; i < length; i += 1) {\n next[nextHead + i] = this.buffer[this.head + i];\n }\n this.buffer = next;\n this.head = nextHead;\n this.tail = nextHead + length;\n }\n\n private compactIfNeeded(): void {\n const length = this.length;\n if (length === 0) {\n this.clear();\n return;\n }\n if (this.head < this.compactThreshold || this.head < length) {\n return;\n }\n this.compactToFront();\n }\n\n private compactToFront(): void {\n const length = this.length;\n this.buffer = this.buffer.slice(this.head, this.tail);\n this.head = 0;\n this.tail = length;\n }\n}\n","/**\n * Retry and timeout settings used by `StreamsCrdt`.\n */\nexport interface ReconnectConfig {\n /** Retry delay schedule in milliseconds, indexed by retry attempt and clamped at the last entry. */\n readonly delays: readonly number[];\n /** Fractional +/- jitter applied to scheduled retry delays. */\n readonly jitterFraction: number;\n /** Maximum retry attempts for background live loops. Use `Infinity` to retry forever. */\n readonly maxAttempts: number;\n /** Connect deadline for ordinary transport requests. */\n readonly connectTimeoutMs: number;\n /**\n * Soft diagnostic threshold for initial bootstrap/catch-up.\n *\n * Crossing this threshold does not abort or reject the in-flight request.\n * Set to 0 or `Infinity` to disable it.\n */\n readonly initialSyncSlowAfterMs?: number;\n /**\n * Hard cap for initial bootstrap/catch-up before aborting with\n * `initial_sync_timeout`.\n *\n * Set to 0 or `Infinity` to disable it.\n */\n readonly initialSyncHardTimeoutMs?: number;\n /** Poll deadline for ordinary long-poll/read requests. */\n readonly pollTimeoutMs: number;\n /** SSE inactivity timeout. Aborts and reconnects if no event arrives within this period. */\n readonly liveInactivityTimeoutMs?: number;\n}\n\nexport const DEFAULT_RECONNECT_CONFIG: ReconnectConfig = {\n delays: [0, 500, 1000, 2000, 4000, 8000, 15000, 30000],\n jitterFraction: 0.2,\n maxAttempts: Infinity,\n connectTimeoutMs: 10000,\n initialSyncSlowAfterMs: 30000,\n initialSyncHardTimeoutMs: 120000,\n pollTimeoutMs: 30000,\n liveInactivityTimeoutMs: 45_000,\n};\n\n/**\n * Minimum time a live SSE must stay open before it counts as a \"successful\n * session\". A read that stayed open at least this long before erroring is\n * treated as a fresh disconnect (e.g. a routine server-side stream recycle)\n * rather than a failing-to-connect retry, so its reconnect does not consume the\n * backoff budget. Shared by the durable and ephemeral read loops.\n */\nexport const MIN_SUCCESSFUL_SSE_DURATION_MS = 5_000;\n\n/**\n * Apply jitter of +/- fraction to a base delay.\n * E.g. applyJitter(1000, 0.2) returns a value in [800, 1200].\n */\nexport function applyJitter(baseMs: number, fraction: number): number {\n if (baseMs === 0) return 0;\n const jitter = baseMs * fraction * (2 * Math.random() - 1);\n return Math.max(0, Math.round(baseMs + jitter));\n}\n\n/**\n * Compute the retry delay for a given attempt number.\n * Uses the delays table from config, clamping at the last entry.\n *\n * If `minDelayMs` is provided (e.g. from a Retry-After hint), the result is\n * clamped to be at least that value before jitter, so honoring the server's\n * backoff request never falls below what we'd already wait.\n */\nexport function computeRetryDelay(\n attempt: number,\n config: ReconnectConfig = DEFAULT_RECONNECT_CONFIG,\n minDelayMs?: number,\n): number {\n const { delays, jitterFraction } = config;\n const index = Math.min(attempt, delays.length - 1);\n const scheduled = delays[index];\n const base =\n typeof minDelayMs === \"number\" && minDelayMs > scheduled\n ? minDelayMs\n : scheduled;\n return applyJitter(base, jitterFraction);\n}\n","export const BINARY_CONTENT_TYPE = \"application/octet-stream\";\n\nexport function encodeItems(items: ReadonlyArray<Uint8Array>): Uint8Array {\n const total = items.reduce((sum, item) => sum + 4 + item.byteLength, 0);\n const out = new Uint8Array(total);\n const view = new DataView(out.buffer, out.byteOffset, out.byteLength);\n let cursor = 0;\n for (const item of items) {\n view.setUint32(cursor, item.byteLength, false);\n cursor += 4;\n out.set(item, cursor);\n cursor += item.byteLength;\n }\n return out;\n}\n\nexport function decodeItems(payload: Uint8Array): Uint8Array[] {\n const out: Uint8Array[] = [];\n const view = new DataView(payload.buffer, payload.byteOffset, payload.byteLength);\n let cursor = 0;\n while (cursor < payload.byteLength) {\n if (cursor + 4 > payload.byteLength) {\n throw new Error(\"corrupted items payload: truncated length prefix\");\n }\n const len = view.getUint32(cursor, false);\n cursor += 4;\n if (cursor + len > payload.byteLength) {\n throw new Error(\"corrupted items payload: truncated item payload\");\n }\n out.push(payload.slice(cursor, cursor + len));\n cursor += len;\n }\n return out;\n}\n","import { decodeItems, encodeItems } from \"./items\";\nimport type {\n PayloadProtectionContext,\n PayloadProtectionFailureReason,\n PayloadProtectionKind,\n PayloadProtectionOptions,\n PayloadProtectionProvider,\n PayloadProtectionReadPolicy,\n PayloadProtectionWritePolicy,\n} from \"./types\";\n\ntype PayloadProtectionRuntimeOptions = {\n readonly readPolicy: PayloadProtectionReadPolicy;\n readonly writePolicy: PayloadProtectionWritePolicy;\n readonly provider?: PayloadProtectionProvider;\n};\n\nconst MAGIC = new Uint8Array([0x4c, 0x53, 0x43, 0x45]); // \"LSCE\"\nconst PROVIDER_ENVELOPE_VERSION = 2;\nconst PAYLOAD_PROTECTION_PROTOCOL = \"loro-streams-crdt-payload-protection\";\nconst UPDATE_BATCH_KIND = 1;\nconst SNAPSHOT_KIND = 2;\nconst PROVIDER_PREFIX_BYTES = 10;\nconst MAX_PROVIDER_HEADER_BYTES = 512;\nconst MAX_PROVIDER_SEAL_OVERHEAD_BYTES = 4 * 1024;\nconst textEncoder = new TextEncoder();\nconst providerAadDomain = textEncoder.encode(`${PAYLOAD_PROTECTION_PROTOCOL}/v2\\0`);\n\nexport class PayloadProtectionError extends Error {\n readonly reason: PayloadProtectionFailureReason;\n\n constructor(\n reason: PayloadProtectionFailureReason,\n message = payloadProtectionFailureMessage(reason),\n ) {\n super(message);\n this.name = \"PayloadProtectionError\";\n this.reason = reason;\n }\n}\n\nclass ProviderSealContractError extends PayloadProtectionError {\n constructor(message: string) {\n super(\"encrypt_failed\", message);\n this.name = \"ProviderSealContractError\";\n }\n}\n\nexport class PayloadProtectionRuntime {\n readonly readPolicy: PayloadProtectionReadPolicy;\n readonly writePolicy: PayloadProtectionWritePolicy;\n\n private readonly provider?: PayloadProtectionProvider;\n\n constructor(options: PayloadProtectionRuntimeOptions) {\n this.readPolicy = options.readPolicy;\n this.writePolicy = options.writePolicy;\n this.provider = options.provider;\n }\n\n async encodeUpdateItems(clearUpdates: readonly Uint8Array[]): Promise<readonly Uint8Array[]> {\n if (this.writePolicy === \"plaintext\") {\n return clearUpdates;\n }\n return [await this.sealPayload(\"update_batch\", encodeItems(clearUpdates))];\n }\n\n measureEncodedUpdateItemsByteLength(clearUpdates: readonly Uint8Array[]): number {\n if (this.writePolicy === \"plaintext\") {\n return encodedItemsByteLength(clearUpdates);\n }\n\n if (this.provider == null) {\n throw new PayloadProtectionError(\n \"encrypt_failed\",\n \"provider-backed E2EE writes require a provider\",\n );\n }\n return (\n 4 +\n PROVIDER_PREFIX_BYTES +\n encodedItemsByteLength(clearUpdates) +\n this.provider.maxSealOverheadBytes\n );\n }\n\n async decodeUpdateItems(wireItems: readonly Uint8Array[]): Promise<Uint8Array[]> {\n const out: Uint8Array[] = [];\n for (const item of wireItems) {\n if (isEncryptedEnvelope(item)) {\n const clearBatch = await this.openPayload(\"update_batch\", item);\n out.push(...decodeItems(clearBatch));\n continue;\n }\n this.assertPlaintextAllowed(\"update batch\");\n out.push(item);\n }\n return out;\n }\n\n async encodeSnapshot(\n snapshot: Uint8Array,\n continuationOffset: string,\n ): Promise<Uint8Array> {\n checkContinuationOffset(continuationOffset);\n if (this.writePolicy === \"plaintext\") {\n return snapshot;\n }\n return await this.sealPayload(\"snapshot\", snapshot, continuationOffset);\n }\n\n async decodeSnapshot(\n snapshot: Uint8Array,\n continuationOffset: string,\n ): Promise<Uint8Array> {\n checkContinuationOffset(continuationOffset);\n if (isEncryptedEnvelope(snapshot)) {\n return await this.openPayload(\"snapshot\", snapshot, continuationOffset);\n }\n this.assertPlaintextAllowed(\"snapshot\");\n return snapshot;\n }\n\n private async sealPayload(\n kind: PayloadProtectionKind,\n plaintext: Uint8Array,\n continuationOffset?: string,\n ): Promise<Uint8Array> {\n return await this.sealProviderPayload(kind, plaintext, continuationOffset);\n }\n\n private async openPayload(\n expectedKind: PayloadProtectionKind,\n envelope: Uint8Array,\n continuationOffset?: string,\n ): Promise<Uint8Array> {\n const version = envelope[4];\n if (version === PROVIDER_ENVELOPE_VERSION) {\n return await this.openProviderPayload(expectedKind, envelope, continuationOffset);\n }\n throw new PayloadProtectionError(\n \"invalid_envelope\",\n `unsupported streams-crdt protected payload envelope version '${String(version)}'`,\n );\n }\n\n private async sealProviderPayload(\n kind: PayloadProtectionKind,\n plaintext: Uint8Array,\n continuationOffset?: string,\n ): Promise<Uint8Array> {\n const provider = this.provider;\n if (provider == null) {\n throw new PayloadProtectionError(\n \"encrypt_failed\",\n \"provider-backed E2EE writes require a provider\",\n );\n }\n\n const context = createProviderContext(kind, continuationOffset);\n let authenticatedHeader: Uint8Array | undefined;\n let additionalDataCalls = 0;\n let result: Awaited<ReturnType<PayloadProtectionProvider[\"seal\"]>>;\n try {\n result = await provider.seal({\n plaintext,\n context,\n additionalData: (header) => {\n additionalDataCalls += 1;\n if (additionalDataCalls !== 1) {\n throw new ProviderSealContractError(\n \"payload protection provider must request additionalData exactly once\",\n );\n }\n authenticatedHeader = normalizeProviderHeader(header).slice();\n return createProviderAad(createProviderEnvelopeHeader(kind, authenticatedHeader));\n },\n });\n } catch (error) {\n if (error instanceof ProviderSealContractError) {\n throw error;\n }\n const reason = error instanceof PayloadProtectionError ? error.reason : \"encrypt_failed\";\n throw new PayloadProtectionError(reason, payloadProtectionFailureMessage(reason));\n }\n\n try {\n const returnedHeader = normalizeProviderHeader(result.header);\n if (additionalDataCalls !== 1) {\n throw new ProviderSealContractError(\n \"payload protection provider must request additionalData exactly once\",\n );\n }\n if (authenticatedHeader == null || !equalBytes(authenticatedHeader, returnedHeader)) {\n throw new ProviderSealContractError(\n \"payload protection provider must authenticate the exact header it returns\",\n );\n }\n if (!(result.sealed instanceof Uint8Array) || result.sealed.byteLength === 0) {\n throw new ProviderSealContractError(\n \"payload protection provider returned empty or invalid sealed bytes\",\n );\n }\n const actualOverhead =\n returnedHeader.byteLength + result.sealed.byteLength - plaintext.byteLength;\n if (actualOverhead > provider.maxSealOverheadBytes) {\n throw new ProviderSealContractError(\n \"payload protection provider exceeded maxSealOverheadBytes\",\n );\n }\n return concatBytes(createProviderEnvelopeHeader(kind, returnedHeader), result.sealed);\n } catch (error) {\n if (error instanceof ProviderSealContractError) {\n throw error;\n }\n const reason = error instanceof PayloadProtectionError ? error.reason : \"encrypt_failed\";\n throw new PayloadProtectionError(reason, payloadProtectionFailureMessage(reason));\n }\n }\n\n private async openProviderPayload(\n expectedKind: PayloadProtectionKind,\n envelope: Uint8Array,\n continuationOffset?: string,\n ): Promise<Uint8Array> {\n const parsed = parseProviderEnvelope(envelope);\n if (parsed.kind !== expectedKind) {\n throw new PayloadProtectionError(\n \"wrong_payload_kind\",\n `expected ${expectedKind} payload, got ${parsed.kind}`,\n );\n }\n\n const provider = this.provider;\n if (provider == null) {\n throw new PayloadProtectionError(\n \"missing_read_key\",\n \"provider-backed E2EE reads require a provider\",\n );\n }\n\n const context = createProviderContext(parsed.kind, continuationOffset);\n try {\n const plaintext = await provider.open({\n sealed: parsed.sealed,\n header: parsed.header,\n context,\n additionalData: createProviderAad(parsed.authenticatedHeader),\n });\n if (!(plaintext instanceof Uint8Array)) {\n throw new PayloadProtectionError(\n \"decrypt_failed\",\n \"payload protection provider returned invalid plaintext bytes\",\n );\n }\n return plaintext;\n } catch (error) {\n const reason = error instanceof PayloadProtectionError ? error.reason : \"decrypt_failed\";\n throw new PayloadProtectionError(reason, payloadProtectionFailureMessage(reason));\n }\n }\n\n private assertPlaintextAllowed(label: string): void {\n if (this.readPolicy === \"allow-plaintext\") {\n return;\n }\n throw new PayloadProtectionError(\n \"plaintext_forbidden\",\n `received plaintext streams-crdt ${label} while readPolicy is encrypted-only`,\n );\n }\n}\n\nexport function normalizePayloadProtection(\n options: PayloadProtectionOptions | undefined,\n label = \"payloadProtection\",\n): PayloadProtectionRuntime | undefined {\n if (options == null) {\n return undefined;\n }\n\n const readPolicy = options.readPolicy ?? \"encrypted-only\";\n const writePolicy = options.writePolicy ?? \"encrypt\";\n if (readPolicy !== \"encrypted-only\" && readPolicy !== \"allow-plaintext\") {\n throw new Error(`${label}.readPolicy must be encrypted-only or allow-plaintext`);\n }\n if (writePolicy !== \"encrypt\" && writePolicy !== \"plaintext\") {\n throw new Error(`${label}.writePolicy must be encrypt or plaintext`);\n }\n\n const provider = options.provider;\n if (\n provider != null &&\n (typeof provider.seal !== \"function\" ||\n typeof provider.open !== \"function\" ||\n !Number.isSafeInteger(provider.maxSealOverheadBytes) ||\n provider.maxSealOverheadBytes < 0 ||\n provider.maxSealOverheadBytes > MAX_PROVIDER_SEAL_OVERHEAD_BYTES)\n ) {\n throw new Error(\n `${label}.provider must implement seal()/open() and declare maxSealOverheadBytes ` +\n `as a safe integer from 0 through ${MAX_PROVIDER_SEAL_OVERHEAD_BYTES}`,\n );\n }\n if (writePolicy === \"encrypt\" && provider == null) {\n throw new Error(`${label}.provider is required when writePolicy is encrypt`);\n }\n return new PayloadProtectionRuntime({\n readPolicy,\n writePolicy,\n provider,\n });\n}\n\nexport function isPayloadProtectionError(error: unknown): error is PayloadProtectionError {\n return error instanceof PayloadProtectionError;\n}\n\nexport function payloadProtectionFailureMessage(reason: PayloadProtectionFailureReason): string {\n switch (reason) {\n case \"plaintext_forbidden\":\n return \"plaintext payload rejected by protected-read policy\";\n case \"missing_read_key\":\n return \"protected payload key is unavailable\";\n case \"decrypt_failed\":\n return \"protected payload authentication failed\";\n case \"invalid_envelope\":\n return \"invalid protected payload envelope\";\n case \"wrong_payload_kind\":\n return \"protected payload kind mismatch\";\n case \"encrypt_failed\":\n return \"protected payload sealing failed\";\n }\n}\n\nfunction isEncryptedEnvelope(payload: Uint8Array): boolean {\n return (\n payload.byteLength >= MAGIC.byteLength && MAGIC.every((byte, index) => payload[index] === byte)\n );\n}\n\nfunction createProviderContext(\n kind: PayloadProtectionKind,\n continuationOffset?: string,\n): PayloadProtectionContext {\n if (kind === \"snapshot\") {\n checkContinuationOffset(continuationOffset);\n return {\n protocol: PAYLOAD_PROTECTION_PROTOCOL,\n version: PROVIDER_ENVELOPE_VERSION,\n kind,\n continuationOffset,\n };\n }\n return {\n protocol: PAYLOAD_PROTECTION_PROTOCOL,\n version: PROVIDER_ENVELOPE_VERSION,\n kind,\n };\n}\n\n/** Opaque offset token only. Not a timestamp, Org, bucket, or role. */\nfunction checkContinuationOffset(value: unknown): asserts value is string {\n if (\n typeof value !== \"string\" ||\n value.length === 0 ||\n value.length > 1024 ||\n value === \"now\"\n ) {\n throw new PayloadProtectionError(\"invalid_envelope\", \"invalid continuation offset\");\n }\n}\n\nfunction createProviderEnvelopeHeader(\n kind: PayloadProtectionKind,\n providerHeader: Uint8Array,\n): Uint8Array {\n const normalizedHeader = normalizeProviderHeader(providerHeader);\n const header = new Uint8Array(PROVIDER_PREFIX_BYTES + normalizedHeader.byteLength);\n const view = new DataView(header.buffer);\n header.set(MAGIC, 0);\n header[4] = PROVIDER_ENVELOPE_VERSION;\n header[5] = encodeKind(kind);\n view.setUint16(6, normalizedHeader.byteLength, false);\n header[8] = 0;\n header[9] = 0;\n header.set(normalizedHeader, PROVIDER_PREFIX_BYTES);\n return header;\n}\n\nfunction parseProviderEnvelope(payload: Uint8Array): {\n readonly kind: PayloadProtectionKind;\n readonly header: Uint8Array;\n readonly authenticatedHeader: Uint8Array;\n readonly sealed: Uint8Array;\n} {\n if (!isEncryptedEnvelope(payload) || payload.byteLength < PROVIDER_PREFIX_BYTES) {\n throw new PayloadProtectionError(\n \"invalid_envelope\",\n \"invalid streams-crdt provider payload envelope\",\n );\n }\n const view = new DataView(payload.buffer, payload.byteOffset, payload.byteLength);\n const providerHeaderLength = view.getUint16(6, false);\n if (\n payload[4] !== PROVIDER_ENVELOPE_VERSION ||\n providerHeaderLength === 0 ||\n providerHeaderLength > MAX_PROVIDER_HEADER_BYTES ||\n payload[8] !== 0 ||\n payload[9] !== 0\n ) {\n throw new PayloadProtectionError(\n \"invalid_envelope\",\n \"unsupported streams-crdt provider payload envelope\",\n );\n }\n\n const headerEnd = PROVIDER_PREFIX_BYTES + providerHeaderLength;\n if (payload.byteLength <= headerEnd) {\n throw new PayloadProtectionError(\n \"invalid_envelope\",\n \"truncated streams-crdt provider payload envelope\",\n );\n }\n return {\n kind: decodeKind(payload[5]),\n header: payload.slice(PROVIDER_PREFIX_BYTES, headerEnd),\n authenticatedHeader: payload.slice(0, headerEnd),\n sealed: payload.slice(headerEnd),\n };\n}\n\nfunction normalizeProviderHeader(header: unknown): Uint8Array {\n if (\n !(header instanceof Uint8Array) ||\n header.byteLength === 0 ||\n header.byteLength > MAX_PROVIDER_HEADER_BYTES\n ) {\n throw new ProviderSealContractError(\n `payload protection provider header must be 1..${MAX_PROVIDER_HEADER_BYTES} bytes`,\n );\n }\n return header;\n}\n\nfunction equalBytes(left: Uint8Array, right: Uint8Array): boolean {\n return left.byteLength === right.byteLength && left.every((byte, index) => byte === right[index]);\n}\n\nfunction createProviderAad(authenticatedHeader: Uint8Array): Uint8Array {\n return concatBytes(providerAadDomain, authenticatedHeader);\n}\n\nfunction encodeKind(kind: PayloadProtectionKind): number {\n return kind === \"update_batch\" ? UPDATE_BATCH_KIND : SNAPSHOT_KIND;\n}\n\nfunction decodeKind(encoded: number | undefined): PayloadProtectionKind {\n if (encoded === UPDATE_BATCH_KIND) {\n return \"update_batch\";\n }\n if (encoded === SNAPSHOT_KIND) {\n return \"snapshot\";\n }\n throw new PayloadProtectionError(\n \"invalid_envelope\",\n \"unsupported streams-crdt encrypted payload kind\",\n );\n}\n\nfunction concatBytes(...parts: readonly Uint8Array[]): Uint8Array {\n const out = new Uint8Array(parts.reduce((length, part) => length + part.byteLength, 0));\n let cursor = 0;\n for (const part of parts) {\n out.set(part, cursor);\n cursor += part.byteLength;\n }\n return out;\n}\n\nfunction encodedItemsByteLength(items: readonly Uint8Array[]): number {\n return items.reduce((sum, item) => sum + 4 + item.byteLength, 0);\n}\n","import type { RemoteCursor } from \"./checkpoint\";\nimport { Deque } from \"./deque\";\nimport { decodeItems } from \"./items\";\nimport {\n isPayloadProtectionError,\n payloadProtectionFailureMessage,\n} from \"./payload-protection\";\nimport type { TransportSnapshotState } from \"./transport-snapshot\";\nimport type {\n CrdtUpdateBatch,\n JsonObject,\n JsonValue,\n MaybePromise,\n SnapshotCodec,\n SnapshotUploadOptions,\n StreamsAuthContext,\n StreamsAuthProvider,\n TransportError,\n TransportRoomStatus,\n} from \"./types\";\n\nexport type FetchLike = (\n input: string,\n init?: RequestInit,\n) => Promise<Response>;\n\nexport type ReadResult = {\n readonly nextOffset: string;\n readonly upToDate: boolean;\n readonly updates: Uint8Array[];\n readonly streamCursor?: string;\n};\n\nexport type BootstrapResult = {\n readonly nextOffset: string;\n readonly upToDate: boolean;\n readonly snapshot: Uint8Array;\n /** Opaque host-admitted snapshot continuation offset, or \"-1\" when absent. */\n readonly snapshotOffset: string;\n readonly updates: Uint8Array[];\n readonly streamCursor?: string;\n};\n\nexport type SseControlData = {\n readonly nextOffset: string;\n readonly upToDate: boolean;\n readonly streamCursor?: string;\n};\n\nexport type SseControlHandlerResult = void | \"pause\" | \"stop\";\n\nexport type SseItemsContinueResult = {\n readonly kind: \"ok\";\n readonly latestOffset: string;\n readonly latestCursor?: string;\n readonly paused: boolean;\n};\n\nexport type SseItemsReadResult =\n | {\n readonly kind: \"ok\";\n readonly latestOffset: string;\n readonly latestCursor?: string;\n }\n | { readonly kind: \"gone\" }\n | { readonly kind: \"unsupported\" }\n | { readonly kind: \"not-found\" };\n\nexport type SseItemsSession = {\n continue: (\n onControl: (\n updates: Uint8Array[],\n nextOffset: string,\n upToDate: boolean,\n streamCursor?: string,\n ) => MaybePromise<SseControlHandlerResult>,\n ) => Promise<SseItemsContinueResult>;\n cancel: () => Promise<void>;\n};\n\nexport type SseItemsOpenResult =\n | { readonly kind: \"ok\"; readonly session: SseItemsSession }\n | { readonly kind: \"gone\" }\n | { readonly kind: \"unsupported\" }\n | { readonly kind: \"not-found\" };\n\nexport type AppendResult =\n | {\n readonly kind: \"ok\";\n readonly ackEpoch: number;\n readonly ackSeq: number;\n readonly nextOffset: string;\n }\n | {\n readonly kind: \"cas-mismatch\";\n readonly expectedOffset: string;\n readonly currentOffset: string;\n }\n | {\n readonly kind: \"stale-epoch\";\n readonly serverEpoch: number;\n }\n | {\n readonly kind: \"seq-gap\";\n readonly expectedSeq: number;\n };\n\nexport type ProducerSession = {\n readonly producerId: string;\n readonly producerEpoch: number;\n readonly nextProducerSeq: number;\n};\n\nexport type LiveReadMode = \"sse\" | \"long-poll\";\n\nexport type ReadSubStatus = \"ok\" | \"connecting\" | \"reconnecting\" | \"error\" | \"disconnected\";\nexport type WriteSubStatus = \"ok\" | \"reconnecting\" | \"error\";\n\nexport type CursorSyncResult<TVersion extends JsonObject> = {\n readonly cursor: RemoteCursor<TVersion>;\n readonly streamCursor?: string;\n};\n\nexport type LocalExportResult<TVersion extends JsonObject> = {\n readonly batch?: CrdtUpdateBatch<TVersion>;\n readonly nextRefVersion: TVersion;\n};\n\nexport type SnapshotUploadRuntimeConfig = {\n readonly canUpload: () => Promise<boolean>;\n readonly debounceMs: number;\n readonly minBytesSinceRemoteSnapshot: bigint;\n};\n\nexport type SnapshotCodecRuntimeConfig = {\n readonly compress: (snapshot: Uint8Array) => Promise<Uint8Array>;\n readonly decompress: (snapshot: Uint8Array) => Promise<Uint8Array>;\n};\n\n/**\n * A pending waiter created by `waitUntilSynced()`.\n *\n * `remaining` tracks how many batches still need to be flushed before\n * this waiter resolves. It is decremented each time a batch is\n * successfully appended.\n */\nexport type SyncWaiter = {\n remaining: number;\n resolve: () => void;\n reject: (error: Error) => void;\n};\n\nexport type JoinState<TVersion extends JsonObject> = {\n refCount: number;\n closed: boolean;\n status: \"connecting\" | TransportRoomStatus;\n readSubStatus: ReadSubStatus;\n writeSubStatus: WriteSubStatus;\n readonly statusListeners: Set<\n (status: \"connecting\" | TransportRoomStatus) => void\n >;\n /**\n * Queued local update batches awaiting durable append.\n *\n * Ordering invariant (load-bearing — do NOT \"optimize\" into a VV-diff\n * upload): this queue is a strict per-author FIFO. Batches are appended in\n * exactly the order the adapter's author-scoped `subscribeLocalUpdates`\n * emitted them, and the head is never skipped or dropped on failure — a\n * failing head blocks the queue until it durably appends (see\n * `flushPendingLocal`).\n *\n * Why not compute the upload as a version-vector diff (`local − server`)?\n * For adapters like Flock that computation is expensive per flush, and a\n * version vector cannot express holes: if this author emitted updates\n * 1, 2, 3 and 2 was lost, the VV still reads \"saw up to 3\" and the gap is\n * undetectable forever. The author-scoped live drain plus this strict FIFO\n * is the structure that guarantees hole-free upload per author, so failure\n * handling must retry the head in place — never reorder, skip, or rebuild\n * the queue from a version diff.\n */\n readonly pendingLocal: Deque<CrdtUpdateBatch<TVersion>>;\n flushingLocal: boolean;\n retryAttempt: number;\n /**\n * Consecutive failed incomplete-apply recoveries in the current wedge\n * episode. `retryAttempt` alone cannot pace this loop: each recovery cycle\n * reopens the SSE (which is healthy — only the CRDT apply fails), and the\n * healthy-SSE paths reset `retryAttempt`, so a wedged stream used to\n * re-bootstrap every few seconds indefinitely. This counter is folded into\n * `retryAttempt` before each recovery backoff so consecutive failures keep\n * escalating toward the delay cap regardless of SSE health.\n *\n * Cleared on every durable cursor save (`saveRemoteCursor`) — the single\n * point every heal passes through, including heals WITHOUT a recovery\n * success (a later live read or catch-up delivers the missing deps) — so\n * the counter is scoped to one wedge episode and never leaks an\n * accumulated backoff into a later, unrelated one.\n */\n incompleteRecoveryAttempt: number;\n retrySleepController: AbortController;\n /** True while `runReadLoop` is executing; prevents concurrent read loops. */\n readLoopRunning: boolean;\n /**\n * Consecutive terminal-class (\"error\" sub-status) read-loop exits since the\n * last healthy read. Drives the capped self-restart backoff schedule.\n */\n readErrorRetryAttempt: number;\n /**\n * Consecutive terminal-class flush failures since the last successful\n * append. Drives the capped self-driven write retry backoff schedule.\n */\n writeErrorRetryAttempt: number;\n /** Pending self-restart timer for the read loop after a terminal-class error. */\n readRestartTimer?: ReturnType<typeof setTimeout>;\n /** Pending self-driven retry timer for the local write queue. */\n writeRetryTimer?: ReturnType<typeof setTimeout>;\n /**\n * When true, the next pending drain takes only the head batch instead of\n * merging up to the byte cap. Set after a 413 so an over-limit merged body\n * is retried as its smallest unit before being declared deterministic.\n */\n flushHeadBatchOnly: boolean;\n /**\n * Last error observed by the local write path. Unlike `lastError` (which\n * either loop may overwrite), this is only set by flush failures and only\n * cleared by a successful append, so \"N pending updates\" UIs can always\n * show why uploads are stuck.\n */\n lastWriteError?: TransportError;\n requestController?: AbortController;\n unsubscribeLocal?: () => void;\n cursor: RemoteCursor<TVersion>;\n liveMode: LiveReadMode;\n streamCursor?: string;\n snapshot: TransportSnapshotState;\n started: Promise<void>;\n readonly syncWaiters: SyncWaiter[];\n /**\n * Set to `true` when the initial `startJoin()` fails with a retryable error.\n * The join state is kept alive so `rejoin()` can restart the initial sync.\n */\n initialSyncFailed: boolean;\n /** The last transport error observed during initial sync or live loops. */\n lastError?: TransportError;\n};\n\nexport type JoinSyncResult<TVersion extends JsonObject> =\n CursorSyncResult<TVersion> & {\n readonly liveMode: LiveReadMode;\n readonly localExportRefVersion: TVersion;\n };\n\nexport type LastError = {\n code:\n | \"network\"\n | \"timeout\"\n | \"initial_sync_timeout\"\n | \"auth_failed\"\n | \"auth_provider\"\n | \"gone_410\"\n | \"protocol\"\n | \"payload_too_large\"\n | \"apply_incomplete\"\n | \"server\"\n | \"internal\";\n message: string;\n retriable: boolean;\n atMs: number;\n};\n\nexport function createProducerId(): string {\n if (typeof globalThis.crypto?.randomUUID === \"function\") {\n return globalThis.crypto.randomUUID();\n }\n return `${Math.random().toString(36).slice(2)}${Math.random().toString(36).slice(2)}`;\n}\n\nexport class HttpError extends Error {\n readonly status: number;\n readonly requestId?: string;\n readonly retryAfterMs?: number;\n /** Raw response body text, when readable. Empty bodies are stored as undefined. */\n readonly body?: string;\n\n constructor(\n message: string,\n status: number,\n options?: {\n requestId?: string;\n retryAfterMs?: number;\n body?: string;\n },\n ) {\n super(message);\n this.status = status;\n if (options?.requestId !== undefined) {\n this.requestId = options.requestId;\n }\n if (options?.retryAfterMs !== undefined) {\n this.retryAfterMs = options.retryAfterMs;\n }\n if (options?.body !== undefined && options.body.length > 0) {\n this.body = options.body;\n }\n }\n}\n\nexport class TimeoutError extends Error {\n readonly timeoutType: \"connect\" | \"poll\";\n readonly timeoutMs: number;\n\n constructor(timeoutType: \"connect\" | \"poll\", timeoutMs: number) {\n super(`TIMEOUT: ${timeoutType} request exceeded ${timeoutMs}ms`);\n this.timeoutType = timeoutType;\n this.timeoutMs = timeoutMs;\n }\n}\n\nexport class InitialSyncTimeoutError extends Error {\n readonly operation: string;\n readonly timeoutMs: number;\n readonly elapsedMs: number;\n\n constructor(operation: string, timeoutMs: number, elapsedMs: number) {\n super(\n `initial ${operation} did not complete within ${timeoutMs}ms; the in-flight bootstrap/catch-up request was aborted at the hard timeout`,\n );\n this.operation = operation;\n this.timeoutMs = timeoutMs;\n this.elapsedMs = elapsedMs;\n }\n}\n\nexport class ProtocolError extends Error {\n readonly status?: number;\n\n constructor(message: string, status?: number) {\n super(message);\n this.status = status;\n }\n}\n\n/**\n * Wraps any error thrown by a user-supplied auth provider callback.\n *\n * The runtime treats these as retryable so transient credential-fetch failures\n * (e.g. a hiccupping auth backend) do not terminate the live read/write loops.\n * For permanent \"user is logged out\" cases, the auth provider should return\n * `undefined` instead of throwing — that lets the request go out without an\n * `Authorization` header so the server returns a real 401 (`auth_failed`).\n */\nexport class AuthProviderError extends Error {\n readonly cause?: unknown;\n\n constructor(message: string, options?: { cause?: unknown }) {\n super(message);\n if (options?.cause !== undefined) {\n this.cause = options.cause;\n }\n }\n}\n\n/**\n * Rejection value used for `waitUntilSynced()` when the local append pipeline\n * fails. Carries the structured {@link TransportError} so downstream callers\n * (e.g. CLIs surfacing \"N updates not yet uploaded\") can branch on\n * `transportError.code` instead of parsing the message.\n */\nexport class LocalAppendFailedError extends Error {\n readonly transportError: TransportError;\n\n constructor(transportError: TransportError) {\n super(`local append failed: ${transportError.code}`);\n this.name = \"LocalAppendFailedError\";\n this.transportError = transportError;\n }\n}\n\n/**\n * True for HTTP 413 responses. The server (or an intermediary proxy) rejected\n * the request body as too large — a deterministic failure for a given payload:\n * retrying the identical bytes cannot succeed while the limit is unchanged.\n */\nexport function isPayloadTooLargeError(error: unknown): error is HttpError {\n return error instanceof HttpError && error.status === 413;\n}\n\nexport function getHeader(\n headers: Headers,\n lowerCaseName: string,\n titleCaseName: string,\n): string | null {\n return headers.get(lowerCaseName) ?? headers.get(titleCaseName);\n}\n\nfunction visibleHeaderNames(headers: Headers): string {\n const names: string[] = [];\n headers.forEach((_value, name) => {\n names.push(name);\n });\n if (names.length === 0) {\n return \"(none)\";\n }\n names.sort();\n return names.join(\", \");\n}\n\nexport function missingRequiredHeaderError(\n context: \"read\" | \"bootstrap\" | \"append\",\n requiredHeader: string,\n headers: Headers,\n): ProtocolError {\n return new ProtocolError(\n `${context} response missing ${requiredHeader}. ` +\n `If this is cross-origin, expose it via Access-Control-Expose-Headers (${requiredHeader.toLowerCase()}). ` +\n `Visible headers: ${visibleHeaderNames(headers)}`,\n );\n}\n\nfunction isObject(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value != null;\n}\n\nexport function parseHeaderU64(value: string | null): number | null {\n if (value == null) {\n return null;\n }\n const parsed = Number.parseInt(value, 10);\n if (!Number.isFinite(parsed) || parsed < 0) {\n return null;\n }\n return parsed;\n}\n\nfunction decodeBase64Bytes(encoded: string): Uint8Array {\n // specs/ds-protocol.md allows servers to split base64 payloads across\n // multiple SSE data lines, and clients must remove the inserted CR/LF\n // characters before decoding.\n const normalized = encoded.replace(/[\\r\\n]/g, \"\");\n if (typeof Buffer !== \"undefined\") {\n return new Uint8Array(Buffer.from(normalized, \"base64\"));\n }\n if (typeof atob === \"function\") {\n const binary = atob(normalized);\n const out = new Uint8Array(binary.length);\n for (let i = 0; i < binary.length; i += 1) {\n out[i] = binary.charCodeAt(i);\n }\n return out;\n }\n throw new Error(\"base64 decoding is unavailable\");\n}\n\nfunction normalizeSseDataEncoding(\n dataEncoding: string | null | undefined,\n): \"base64\" | undefined {\n const normalized = dataEncoding?.trim().toLowerCase();\n if (normalized == null || normalized.length === 0) {\n return undefined;\n }\n if (normalized === \"base64\") {\n return \"base64\";\n }\n throw new ProtocolError(\n `unsupported stream-sse-data-encoding '${dataEncoding}'`,\n );\n}\n\nfunction decodeSseRawPayload(\n data: string,\n dataEncoding: \"base64\" | undefined,\n): Uint8Array {\n if (dataEncoding === \"base64\") {\n return decodeBase64Bytes(data);\n }\n return new TextEncoder().encode(data);\n}\n\nexport function decodeSseItemPayloads(\n data: string,\n dataEncoding?: string | null,\n): Uint8Array[] {\n const payload = decodeSseRawPayload(\n data,\n normalizeSseDataEncoding(dataEncoding),\n );\n if (payload.byteLength === 0) {\n return [payload];\n }\n try {\n return decodeItems(payload);\n } catch {\n return [payload];\n }\n}\n\n/**\n * Decode a single multipart part body. If the payload contains\n * length-prefixed binary framed items, decode them. Otherwise\n * return the raw bytes as a single-element array. This mirrors the\n * try/fallback approach used by {@link decodeSseItemPayloads}.\n */\nexport function decodeMultipartPartItems(payload: Uint8Array): Uint8Array[] {\n if (payload.byteLength === 0) {\n return [payload];\n }\n try {\n return decodeItems(payload);\n } catch {\n return [payload];\n }\n}\n\nexport function parseSseControlData(data: string): SseControlData {\n let parsed: unknown;\n try {\n parsed = JSON.parse(data);\n } catch {\n throw new ProtocolError(\"invalid sse control event JSON\");\n }\n if (!isObject(parsed) || typeof parsed.streamNextOffset !== \"string\") {\n throw new ProtocolError(\"sse control event missing streamNextOffset\");\n }\n const streamCursor =\n typeof parsed.streamCursor === \"string\" ? parsed.streamCursor : undefined;\n return {\n nextOffset: parsed.streamNextOffset,\n upToDate: parsed.upToDate === true,\n streamCursor,\n };\n}\n\nexport function isSseUnsupportedErrorText(message: string): boolean {\n const normalized = message.trim().toLowerCase();\n if (normalized.length === 0) {\n return false;\n }\n const mentionsSse =\n normalized.includes(\"sse\") || normalized.includes(\"text/event-stream\");\n const mentionsCompatibility =\n normalized.includes(\"unsupported\") ||\n normalized.includes(\"not support\") ||\n normalized.includes(\"incompatible\") ||\n normalized.includes(\"content type\");\n return mentionsSse && mentionsCompatibility;\n}\n\nexport function withCursorQuery(url: string, cursor?: string): string {\n if (cursor == null || cursor.length === 0) {\n return url;\n }\n const target = new URL(url);\n target.searchParams.set(\"cursor\", cursor);\n return target.toString();\n}\n\nexport function bytesToArrayBuffer(bytes: Uint8Array): ArrayBuffer {\n return bytes.buffer.slice(\n bytes.byteOffset,\n bytes.byteOffset + bytes.byteLength,\n ) as ArrayBuffer;\n}\n\nexport function sleep(ms: number, signal?: AbortSignal): Promise<void> {\n if (signal?.aborted) {\n return Promise.resolve();\n }\n return new Promise((resolve) => {\n const timer = setTimeout(resolve, ms);\n signal?.addEventListener(\n \"abort\",\n () => {\n clearTimeout(timer);\n resolve();\n },\n { once: true },\n );\n });\n}\n\nexport function mergeAbortSignals(\n ...signals: ReadonlyArray<AbortSignal | null | undefined>\n): AbortSignal | undefined {\n const active = signals.filter(\n (signal): signal is AbortSignal => signal != null,\n );\n if (active.length === 0) {\n return undefined;\n }\n if (active.length === 1) {\n return active[0];\n }\n if (typeof AbortSignal.any === \"function\") {\n return AbortSignal.any(active);\n }\n\n const controller = new AbortController();\n for (const signal of active) {\n if (signal.aborted) {\n controller.abort(signal.reason);\n break;\n }\n signal.addEventListener(\n \"abort\",\n () => {\n controller.abort(signal.reason);\n },\n { once: true },\n );\n }\n return controller.signal;\n}\n\nexport function classifyFetchError(err: unknown): LastError {\n const message = err instanceof Error ? err.message : String(err);\n const status = err instanceof HttpError ? err.status : undefined;\n if (isCrdtApplyIncompleteError(err)) {\n return {\n code: \"apply_incomplete\",\n message,\n retriable: true,\n atMs: Date.now(),\n };\n }\n if (err instanceof AuthProviderError) {\n return {\n code: \"auth_provider\",\n message,\n retriable: true,\n atMs: Date.now(),\n };\n }\n if (status === 401 || status === 403) {\n return { code: \"auth_failed\", message, retriable: false, atMs: Date.now() };\n }\n if (status === 410) {\n return { code: \"gone_410\", message, retriable: true, atMs: Date.now() };\n }\n if (status === 400 || status === 405 || status === 409) {\n return { code: \"protocol\", message, retriable: false, atMs: Date.now() };\n }\n if (status === 413) {\n return {\n code: \"payload_too_large\",\n message,\n retriable: false,\n atMs: Date.now(),\n };\n }\n if (status === 429) {\n // Rate limiting is transient by definition; the retry paths honor the\n // server's Retry-After hint via `HttpError.retryAfterMs`.\n return { code: \"server\", message, retriable: true, atMs: Date.now() };\n }\n if (typeof status === \"number\" && status >= 500 && status < 600) {\n return { code: \"server\", message, retriable: true, atMs: Date.now() };\n }\n if (err instanceof ProtocolError) {\n return { code: \"protocol\", message, retriable: false, atMs: Date.now() };\n }\n if (err instanceof TimeoutError) {\n return { code: \"timeout\", message, retriable: true, atMs: Date.now() };\n }\n if (err instanceof InitialSyncTimeoutError) {\n return {\n code: \"initial_sync_timeout\",\n message,\n retriable: true,\n atMs: Date.now(),\n };\n }\n if (err instanceof TypeError) {\n return { code: \"network\", message, retriable: true, atMs: Date.now() };\n }\n if (isPayloadProtectionError(err)) {\n return {\n code: \"protocol\",\n message: err.message,\n retriable: false,\n atMs: Date.now(),\n };\n }\n return { code: \"internal\", message, retriable: false, atMs: Date.now() };\n}\n\nexport function isStreamNotFoundError(error: unknown): boolean {\n return error instanceof HttpError && error.status === 404;\n}\n\nexport function toTransportError(error: unknown): TransportError {\n if (isCrdtApplyIncompleteError(error)) {\n return {\n code: \"apply_incomplete\",\n retryable: true,\n reason: \"loro_pending_updates\",\n message: error instanceof Error ? error.message : String(error),\n };\n }\n if (error instanceof AuthProviderError) {\n return {\n code: \"auth_provider_error\",\n retryable: true,\n message: error.message,\n ...(error.cause !== undefined && { cause: error.cause }),\n };\n }\n if (isStreamNotFoundError(error)) {\n const httpError = error as HttpError;\n return {\n code: \"stream_not_found\",\n retryable: false,\n status: 404,\n message: httpError.message,\n ...(httpError.body !== undefined && { body: httpError.body }),\n };\n }\n if (\n error instanceof HttpError &&\n (error.status === 401 || error.status === 403)\n ) {\n return {\n code: \"auth_failed\",\n retryable: false,\n status: error.status as 401 | 403,\n message: error.message,\n ...(error.body !== undefined && { body: error.body }),\n };\n }\n if (error instanceof HttpError && [400, 405, 409].includes(error.status)) {\n return {\n code: \"protocol_error\",\n retryable: false,\n status: error.status,\n message: error.message,\n ...(error.body !== undefined && { body: error.body }),\n };\n }\n if (isPayloadTooLargeError(error)) {\n return {\n code: \"payload_too_large\",\n retryable: false,\n status: 413,\n message: error.message,\n ...(error.body !== undefined && { body: error.body }),\n };\n }\n if (error instanceof ProtocolError) {\n return {\n code: \"protocol_error\",\n retryable: false,\n status: error.status,\n message: error.message,\n };\n }\n if (error instanceof TimeoutError) {\n return {\n code: \"timeout\",\n retryable: true,\n phase: error.timeoutType,\n message: error.message,\n };\n }\n if (error instanceof InitialSyncTimeoutError) {\n return {\n code: \"initial_sync_timeout\",\n retryable: true,\n operation: error.operation,\n timeoutMs: error.timeoutMs,\n elapsedMs: error.elapsedMs,\n message: error.message,\n };\n }\n if (error instanceof HttpError && error.status === 410) {\n return {\n code: \"gone\",\n retryable: true,\n status: 410,\n message: error.message,\n ...(error.body !== undefined && { body: error.body }),\n };\n }\n if (\n error instanceof HttpError &&\n (error.status === 429 || (error.status >= 500 && error.status < 600))\n ) {\n return {\n code: \"server_error\",\n retryable: true,\n status: error.status,\n message: error.message,\n ...(error.body !== undefined && { body: error.body }),\n ...(error.requestId !== undefined && { requestId: error.requestId }),\n ...(error.retryAfterMs !== undefined && {\n retryAfterMs: error.retryAfterMs,\n }),\n };\n }\n if (isPayloadProtectionError(error)) {\n return {\n code: \"payload_protection_error\",\n retryable: false,\n reason: error.reason,\n message: payloadProtectionFailureMessage(error.reason),\n };\n }\n if (error instanceof TypeError) {\n return {\n code: \"network_error\",\n retryable: true,\n message: error.message,\n };\n }\n if (error instanceof Error) {\n return {\n code: \"internal_error\",\n retryable: false,\n message: error.message,\n };\n }\n return {\n code: \"unknown\",\n retryable: false,\n message: String(error),\n };\n}\n\nfunction isCrdtApplyIncompleteError(error: unknown): boolean {\n return (\n error instanceof Error &&\n Array.isArray(\n (error as { readonly unresolved?: unknown }).unresolved,\n ) &&\n error.message.includes(\"CRDT apply incomplete\")\n );\n}\n\nexport function isReadNoProgress(\n requestOffset: string,\n result: ReadResult,\n): boolean {\n return (\n !result.upToDate &&\n result.updates.length === 0 &&\n result.nextOffset === requestOffset\n );\n}\n\nexport function isAuthOrProtocolError(error: unknown): boolean {\n return (\n error instanceof ProtocolError ||\n (error instanceof HttpError &&\n [400, 401, 403, 405, 409].includes(error.status))\n );\n}\n\nexport function normalizeAuthProvider(\n auth: StreamsAuthProvider | undefined,\n): ((context?: StreamsAuthContext) => Promise<string | undefined>) | undefined {\n if (auth == null) {\n return undefined;\n }\n if (typeof auth === \"string\") {\n const token = auth.trim();\n return async () => (token.length === 0 ? undefined : token);\n }\n return async (context) => {\n let value: string | null | undefined;\n try {\n value = await auth(context);\n } catch (cause) {\n const message =\n cause instanceof Error\n ? cause.message\n : `auth provider threw: ${String(cause)}`;\n throw new AuthProviderError(message, { cause });\n }\n const token = value?.trim() ?? \"\";\n return token.length === 0 ? undefined : token;\n };\n}\n\nexport function normalizeSnapshotUploadConfig(\n config: SnapshotUploadOptions | undefined,\n): SnapshotUploadRuntimeConfig | undefined {\n if (config == null || config.enabled === false) {\n return undefined;\n }\n if (!Number.isFinite(config.debounceMs ?? 10_000)) {\n throw new Error(\"snapshotUpload.debounceMs must be a finite number\");\n }\n if (!Number.isFinite(config.minBytesSinceRemoteSnapshot ?? 100 * 1024)) {\n throw new Error(\n \"snapshotUpload.minBytesSinceRemoteSnapshot must be a finite number\",\n );\n }\n const debounceMs = Math.max(0, Math.floor(config.debounceMs ?? 10_000));\n const minBytes = config.minBytesSinceRemoteSnapshot ?? 100 * 1024;\n if (minBytes < 0) {\n throw new Error(\n \"snapshotUpload.minBytesSinceRemoteSnapshot must be non-negative\",\n );\n }\n return {\n canUpload: async () => Boolean(await config.canUpload()),\n debounceMs,\n minBytesSinceRemoteSnapshot: BigInt(Math.floor(minBytes)),\n };\n}\n\nfunction assertSnapshotBytes(\n value: unknown,\n label: \"snapshotCodec.compress\" | \"snapshotCodec.decompress\",\n): Uint8Array {\n if (value instanceof Uint8Array) {\n return value;\n }\n throw new Error(`${label} must return Uint8Array`);\n}\n\nexport function normalizeSnapshotCodec(\n codec: SnapshotCodec | undefined,\n): SnapshotCodecRuntimeConfig | undefined {\n if (codec == null) {\n return undefined;\n }\n if (\n typeof codec.compress !== \"function\" ||\n typeof codec.decompress !== \"function\"\n ) {\n throw new Error(\n \"snapshotCodec.compress and snapshotCodec.decompress must be provided together\",\n );\n }\n return {\n compress: async (snapshot) =>\n assertSnapshotBytes(\n await codec.compress(snapshot),\n \"snapshotCodec.compress\",\n ),\n decompress: async (snapshot) =>\n assertSnapshotBytes(\n await codec.decompress(snapshot),\n \"snapshotCodec.decompress\",\n ),\n };\n}\n\nfunction jsonValueEquals(a: JsonValue, b: JsonValue): boolean {\n if (a === b) {\n return true;\n }\n if (typeof a !== typeof b || a == null || b == null) {\n return false;\n }\n if (Array.isArray(a) || Array.isArray(b)) {\n if (!Array.isArray(a) || !Array.isArray(b) || a.length !== b.length) {\n return false;\n }\n for (let i = 0; i < a.length; i += 1) {\n if (!jsonValueEquals(a[i] as JsonValue, b[i] as JsonValue)) {\n return false;\n }\n }\n return true;\n }\n if (typeof a !== \"object\" || typeof b !== \"object\") {\n return false;\n }\n const aEntries = Object.entries(a).sort(([left], [right]) =>\n left.localeCompare(right),\n );\n const bEntries = Object.entries(b).sort(([left], [right]) =>\n left.localeCompare(right),\n );\n if (aEntries.length !== bEntries.length) {\n return false;\n }\n for (let i = 0; i < aEntries.length; i += 1) {\n const [aKey, aValue] = aEntries[i];\n const [bKey, bValue] = bEntries[i];\n if (aKey !== bKey || !jsonValueEquals(aValue as JsonValue, bValue as JsonValue)) {\n return false;\n }\n }\n return true;\n}\n\nexport function jsonObjectEquals(a: JsonObject, b: JsonObject): boolean {\n return jsonValueEquals(a, b);\n}\n\nexport function parseSnapshotByteOffset(\n offset: string | null | undefined,\n): bigint | null {\n if (offset == null || offset.length === 0 || offset === \"-1\") {\n return 0n;\n }\n if (!/^\\d+$/.test(offset)) {\n return null;\n }\n return BigInt(offset);\n}\n\nexport function resolveNextOffsetAfterConcurrentAdvance(\n requestOffset: string,\n currentOffset: string,\n responseNextOffset: string,\n): string {\n if (currentOffset === requestOffset) {\n return responseNextOffset;\n }\n if (currentOffset === responseNextOffset) {\n return currentOffset;\n }\n const current = parseSnapshotByteOffset(currentOffset);\n const incoming = parseSnapshotByteOffset(responseNextOffset);\n if (current != null && incoming != null) {\n return incoming > current ? responseNextOffset : currentOffset;\n }\n return currentOffset;\n}\n","import {\n decodeSsePayload,\n readSseEvents,\n StreamsClient,\n type StreamError,\n} from \"@loro-dev/streams-client\";\n\nimport { BINARY_CONTENT_TYPE, decodeItems, encodeItems } from \"./items\";\nimport type { ReconnectConfig } from \"./reconnect\";\nimport {\n AppendResult,\n BootstrapResult,\n decodeMultipartPartItems,\n decodeSseItemPayloads,\n FetchLike,\n HttpError,\n isSseUnsupportedErrorText,\n mergeAbortSignals,\n ProtocolError,\n parseSseControlData,\n type ProducerSession,\n ReadResult,\n type SseControlHandlerResult,\n type SseItemsOpenResult,\n type SseItemsReadResult,\n TimeoutError,\n} from \"./transport-internal\";\nimport type {\n CrdtAdapter,\n JsonObject,\n MaybePromise,\n StreamsCrdtShardUrlsOptions,\n StreamsAuthContext,\n} from \"./types\";\n\ntype StreamsTransportClientOptions<TVersion extends JsonObject> = {\n readonly streamUrl: string;\n readonly adapter: CrdtAdapter<TVersion>;\n readonly streamTtlSeconds?: number;\n readonly authProvider?: (context?: StreamsAuthContext) => Promise<string | undefined>;\n readonly fetchImpl: FetchLike;\n readonly reconnectConfig: ReconnectConfig;\n readonly shardUrls?: StreamsCrdtShardUrlsOptions;\n};\n\ntype ShardOperation = \"bootstrap\" | \"catchup\" | \"large-post\";\ntype RequestTimeoutMode = \"default\" | \"initial\";\ntype ShardRouteKey = ShardOperation | \"other\";\n\ntype NormalizedShardUrlsOptions = {\n readonly bootstrap?: readonly string[];\n readonly catchup?: readonly string[];\n readonly largePost?: readonly string[];\n readonly other?: readonly string[];\n readonly largePostMinBytes: number;\n};\n\nconst bodySizeEncoder = new TextEncoder();\n\nfunction normalizeShardOrigins(\n urls: readonly string[] | undefined,\n label: string,\n): readonly string[] | undefined {\n if (urls == null) {\n return undefined;\n }\n const out: string[] = [];\n for (const raw of urls) {\n const trimmed = raw.trim();\n if (trimmed.length === 0) {\n continue;\n }\n const url = new URL(trimmed);\n if (url.protocol !== \"http:\" && url.protocol !== \"https:\") {\n throw new Error(`${label} shard URL must use http or https`);\n }\n if (url.pathname !== \"/\" || url.search !== \"\" || url.hash !== \"\") {\n throw new Error(`${label} shard URL must be an origin URL`);\n }\n out.push(url.origin);\n }\n return out.length === 0 ? undefined : out;\n}\n\nfunction normalizeShardUrlsOptions(\n options: StreamsCrdtShardUrlsOptions | undefined,\n): NormalizedShardUrlsOptions | undefined {\n if (options == null) {\n return undefined;\n }\n const largePostMinBytes = options.largePostMinBytes ?? 0;\n if (!Number.isSafeInteger(largePostMinBytes) || largePostMinBytes < 0) {\n throw new Error(\"shardUrls.largePostMinBytes must be a non-negative safe integer\");\n }\n const normalized: NormalizedShardUrlsOptions = {\n bootstrap: normalizeShardOrigins(options.bootstrap, \"shardUrls.bootstrap\"),\n catchup: normalizeShardOrigins(options.catchup, \"shardUrls.catchup\"),\n largePost: normalizeShardOrigins(options.largePost, \"shardUrls.largePost\"),\n other: normalizeShardOrigins(options.other, \"shardUrls.other\"),\n largePostMinBytes,\n };\n if (\n normalized.bootstrap == null &&\n normalized.catchup == null &&\n normalized.largePost == null &&\n normalized.other == null\n ) {\n return undefined;\n }\n return normalized;\n}\n\nfunction requestBodyByteLength(body: BodyInit | null | undefined): number | undefined {\n if (body == null) {\n return 0;\n }\n if (typeof body === \"string\") {\n return bodySizeEncoder.encode(body).byteLength;\n }\n if (body instanceof ArrayBuffer) {\n return body.byteLength;\n }\n if (ArrayBuffer.isView(body)) {\n return body.byteLength;\n }\n if (typeof Blob !== \"undefined\" && body instanceof Blob) {\n return body.size;\n }\n if (typeof URLSearchParams !== \"undefined\" && body instanceof URLSearchParams) {\n return bodySizeEncoder.encode(body.toString()).byteLength;\n }\n return undefined;\n}\n\nfunction createRandomShardStart(originCount: number): number {\n if (originCount <= 1) {\n return 0;\n }\n try {\n const crypto = globalThis.crypto;\n if (crypto != null) {\n const values = new Uint32Array(1);\n crypto.getRandomValues(values);\n return (values[0] ?? 0) % originCount;\n }\n } catch {\n // Ignore missing or restricted crypto and fall back to Math.random below.\n }\n return Math.floor(Math.random() * originCount);\n}\n\nfunction normalizedStreamPath(streamUrl: URL): string {\n return streamUrl.pathname.endsWith(\"/\")\n ? streamUrl.pathname.slice(0, -1)\n : streamUrl.pathname;\n}\n\n/**\n * Low-level HTTP and SSE client for one stream.\n *\n * This layer owns request headers, timeouts, and response parsing. Higher-level\n * join/sync state transitions stay in `transport-runtime.ts`.\n */\nexport class StreamsTransportClient<TVersion extends JsonObject> {\n readonly streamUrl: string;\n\n private readonly adapter: CrdtAdapter<TVersion>;\n private readonly streamTtlSeconds?: number;\n private readonly client: StreamsClient;\n private readonly initialSyncClient: StreamsClient;\n private readonly fetchImpl: FetchLike;\n private readonly authProvider?: (\n context?: StreamsAuthContext,\n ) => Promise<string | undefined>;\n private readonly reconnectConfig: ReconnectConfig;\n private readonly shardUrls?: NormalizedShardUrlsOptions;\n private readonly shardCursorIndexes: Partial<Record<ShardRouteKey, number>> = {};\n private authRefreshInFlight?: Promise<string | undefined>;\n\n constructor(options: StreamsTransportClientOptions<TVersion>) {\n this.streamUrl = options.streamUrl;\n this.adapter = options.adapter;\n this.streamTtlSeconds = normalizeStreamTtlSeconds(options.streamTtlSeconds);\n this.fetchImpl = options.fetchImpl;\n this.shardUrls = normalizeShardUrlsOptions(options.shardUrls);\n this.client = new StreamsClient({\n url: options.streamUrl,\n auth: options.authProvider,\n fetch: this.fetchRouted.bind(this) as typeof globalThis.fetch,\n retry: {\n maxAttempts: 0,\n },\n timeout: {\n connectTimeoutMs: options.reconnectConfig.connectTimeoutMs,\n pollTimeoutMs: options.reconnectConfig.pollTimeoutMs,\n },\n });\n this.initialSyncClient = new StreamsClient({\n url: options.streamUrl,\n auth: options.authProvider,\n fetch: this.fetchRouted.bind(this) as typeof globalThis.fetch,\n retry: {\n maxAttempts: 0,\n },\n timeout: {\n connectTimeoutMs: 0,\n pollTimeoutMs: options.reconnectConfig.pollTimeoutMs,\n },\n });\n this.authProvider = options.authProvider;\n this.reconnectConfig = options.reconnectConfig;\n }\n\n private fetchRouted(input: string, init?: RequestInit): Promise<Response> {\n return this.fetchImpl(this.routeRequestUrl(input, init), init);\n }\n\n private routeRequestUrl(input: string, init?: RequestInit): string {\n const shardUrls = this.shardUrls;\n if (shardUrls == null) {\n return input;\n }\n const operation = this.classifyShardOperation(input, init, shardUrls);\n const origins = this.originsForShardOperation(operation, shardUrls);\n if (origins == null || origins.length === 0) {\n return input;\n }\n const target = new URL(input);\n const shardOrigin = new URL(\n this.nextShardOrigin(operation ?? \"other\", origins),\n );\n target.protocol = shardOrigin.protocol;\n target.host = shardOrigin.host;\n target.username = \"\";\n target.password = \"\";\n return target.toString();\n }\n\n private originsForShardOperation(\n operation: ShardOperation | undefined,\n shardUrls: NormalizedShardUrlsOptions,\n ): readonly string[] | undefined {\n if (operation == null) {\n return shardUrls.other;\n }\n const operationOrigins =\n operation === \"bootstrap\"\n ? shardUrls.bootstrap\n : operation === \"catchup\"\n ? shardUrls.catchup\n : shardUrls.largePost;\n return operationOrigins ?? shardUrls.other;\n }\n\n private nextShardOrigin(\n operation: ShardRouteKey,\n origins: readonly string[],\n ): string {\n const currentIndex =\n this.shardCursorIndexes[operation] ?? createRandomShardStart(origins.length);\n this.shardCursorIndexes[operation] = (currentIndex + 1) % origins.length;\n return origins[currentIndex % origins.length]!;\n }\n\n private classifyShardOperation(\n input: string,\n init: RequestInit | undefined,\n shardUrls: NormalizedShardUrlsOptions,\n ): ShardOperation | undefined {\n const method = (init?.method ?? \"GET\").toUpperCase();\n const requestUrl = new URL(input);\n const streamUrl = new URL(this.streamUrl);\n const streamPath = normalizedStreamPath(streamUrl);\n\n if (\n method === \"GET\" &&\n requestUrl.pathname === `${streamPath}/bootstrap`\n ) {\n return \"bootstrap\";\n }\n\n if (\n method === \"GET\" &&\n requestUrl.pathname === streamUrl.pathname &&\n requestUrl.searchParams.has(\"offset\") &&\n requestUrl.searchParams.get(\"live\") !== \"sse\"\n ) {\n return \"catchup\";\n }\n\n if (method !== \"POST\" || requestUrl.pathname !== streamUrl.pathname) {\n return undefined;\n }\n\n const bodyLength = requestBodyByteLength(init?.body);\n if (\n bodyLength == null ||\n bodyLength === 0 ||\n bodyLength < shardUrls.largePostMinBytes\n ) {\n return undefined;\n }\n return \"large-post\";\n }\n\n async createStreamRequest(): Promise<boolean> {\n const adapterHeaders = this.adapter.getCreateStreamHeaders\n ? await this.adapter.getCreateStreamHeaders()\n : undefined;\n const result = await this.client.create({\n headers: adapterHeaders,\n ttlSeconds: this.streamTtlSeconds,\n });\n if (result.ok) {\n return result.result.created;\n }\n throw this.toTransportFailure(result.result);\n }\n\n async deleteStreamRequest(): Promise<boolean> {\n const result = await this.client.delete();\n if (result.ok) {\n return result.result.deleted;\n }\n throw this.toTransportFailure(result.result);\n }\n\n async readFromOffsetSseItems(\n offset: string,\n cursor: string | undefined,\n onControl: (\n updates: Uint8Array[],\n nextOffset: string,\n upToDate: boolean,\n streamCursor?: string,\n ) => MaybePromise<SseControlHandlerResult>,\n onOpen?: () => void,\n signal?: AbortSignal,\n onActivity?: () => void,\n ): Promise<SseItemsReadResult> {\n return this.readItemsSse(\n offset,\n cursor,\n onControl,\n onOpen,\n signal,\n onActivity,\n );\n }\n\n async readFromOffset(\n offset: string,\n cursor?: string,\n live?: \"long-poll\",\n signal?: AbortSignal,\n timeoutMode: RequestTimeoutMode = \"default\",\n ): Promise<{ kind: \"gone\" } | { kind: \"ok\"; value: ReadResult }> {\n const client =\n live == null && timeoutMode === \"initial\"\n ? this.initialSyncClient\n : this.client;\n const result = await client.readOnce({\n offset,\n cursor,\n live,\n signal,\n });\n if (!result.ok) {\n if (result.result.code === \"gone\") {\n return { kind: \"gone\" };\n }\n throw this.toTransportFailure(result.result);\n }\n\n const payload = result.result.payload?.body ?? new Uint8Array();\n return {\n kind: \"ok\",\n value: {\n nextOffset: result.result.nextOffset,\n upToDate: result.result.upToDate,\n updates: payload.byteLength === 0 ? [] : decodeItems(payload),\n streamCursor: result.result.cursor,\n },\n };\n }\n\n async bootstrap(\n signal?: AbortSignal,\n timeoutMode: RequestTimeoutMode = \"default\",\n ): Promise<BootstrapResult> {\n const client =\n timeoutMode === \"initial\" ? this.initialSyncClient : this.client;\n const result = await client.bootstrap({ signal });\n if (!result.ok) {\n throw this.toTransportFailure(result.result);\n }\n\n // Stream-Snapshot-Offset: -1 is the bootstrap protocol sentinel for\n // \"no snapshot\". Drop the snapshot slot regardless of its bytes so a\n // padded empty part never reaches payload protection's encrypted-only\n // readPolicy as a \"plaintext snapshot\".\n const snapshotOffset = result.result.snapshotOffset;\n const snapshot =\n snapshotOffset === \"-1\"\n ? new Uint8Array()\n : result.result.snapshot?.body ?? new Uint8Array();\n return {\n nextOffset: result.result.nextOffset,\n upToDate: result.result.upToDate,\n snapshot,\n snapshotOffset,\n updates: result.result.updates.flatMap((part) =>\n decodeMultipartPartItems(part.body)\n ),\n streamCursor: result.result.cursor,\n };\n }\n\n async readSnapshotOffsetValue(): Promise<string | null> {\n const result = await this.client.head();\n if (!result.ok) {\n if (result.result.code === \"not_found\") {\n return null;\n }\n throw this.toTransportFailure(result.result);\n }\n\n const snapshotOffset = result.result.snapshotOffset;\n return snapshotOffset == null || snapshotOffset.length === 0\n ? null\n : snapshotOffset;\n }\n\n async streamSupportsAppendCas(): Promise<boolean> {\n const result = await this.client.streamSupportsAppendCas();\n if (result.ok) {\n return result.result;\n }\n throw this.toTransportFailure(result.result);\n }\n\n async putSnapshotAt(\n snapshotOffset: string,\n snapshot: Uint8Array,\n ): Promise<void> {\n const result = await this.client.putSnapshot({\n offset: snapshotOffset,\n part: {\n contentType: \"application/octet-stream\",\n body: snapshot,\n },\n });\n if (!result.ok) {\n throw this.toTransportFailure(result.result);\n }\n }\n\n async appendUpdates(\n updates: readonly Uint8Array[],\n producerSession: ProducerSession,\n ): Promise<AppendResult> {\n const result = await this.client.append({\n part: {\n contentType: BINARY_CONTENT_TYPE,\n body: encodeItems(updates),\n },\n producer: {\n producerId: producerSession.producerId,\n epoch: producerSession.producerEpoch,\n seq: producerSession.nextProducerSeq,\n },\n });\n if (result.ok) {\n return {\n kind: \"ok\",\n ackEpoch: result.result.producer?.epoch ?? producerSession.producerEpoch,\n ackSeq: result.result.producer?.seq ?? producerSession.nextProducerSeq,\n nextOffset: result.result.nextOffset,\n };\n }\n\n const error = result.result;\n if (\n error.code === \"conflict\" &&\n error.detail?.kind === \"producer_seq_gap\"\n ) {\n return { kind: \"seq-gap\", expectedSeq: error.detail.expectedSeq };\n }\n if (\n error.code === \"forbidden\" &&\n error.detail?.kind === \"stale_producer_epoch\"\n ) {\n return {\n kind: \"stale-epoch\",\n serverEpoch: error.detail.serverEpoch,\n };\n }\n throw this.toTransportFailure(error);\n }\n\n async appendUpdatesCas(\n expectedOffset: string,\n updates: readonly Uint8Array[],\n producerSession: ProducerSession,\n ): Promise<AppendResult> {\n const result = await this.client.appendCas({\n expectedOffset,\n part: {\n contentType: BINARY_CONTENT_TYPE,\n body: encodeItems(updates),\n },\n producer: {\n producerId: producerSession.producerId,\n epoch: producerSession.producerEpoch,\n seq: producerSession.nextProducerSeq,\n },\n });\n if (result.ok) {\n if (result.result.kind === \"mismatch\") {\n return {\n kind: \"cas-mismatch\",\n expectedOffset: result.result.expectedOffset,\n currentOffset: result.result.currentOffset,\n };\n }\n return {\n kind: \"ok\",\n ackEpoch:\n result.result.value.producer?.epoch ?? producerSession.producerEpoch,\n ackSeq:\n result.result.value.producer?.seq ?? producerSession.nextProducerSeq,\n nextOffset: result.result.value.nextOffset,\n };\n }\n\n const error = result.result;\n if (\n error.code === \"conflict\" &&\n error.detail?.kind === \"producer_seq_gap\"\n ) {\n return { kind: \"seq-gap\", expectedSeq: error.detail.expectedSeq };\n }\n if (\n error.code === \"forbidden\" &&\n error.detail?.kind === \"stale_producer_epoch\"\n ) {\n return {\n kind: \"stale-epoch\",\n serverEpoch: error.detail.serverEpoch,\n };\n }\n throw this.toTransportFailure(error);\n }\n\n async appendItems(updates: readonly Uint8Array[]): Promise<string> {\n const result = await this.client.append({\n part: {\n contentType: BINARY_CONTENT_TYPE,\n body: encodeItems(updates),\n },\n });\n if (result.ok) {\n return result.result.nextOffset;\n }\n throw this.toTransportFailure(result.result);\n }\n\n async appendEphemeralUpdate(\n update: Uint8Array,\n signal?: AbortSignal,\n ): Promise<void> {\n if (update.byteLength === 0) {\n return;\n }\n const response = await this.fetchAuthorized(\n this.streamUrl,\n {\n method: \"POST\",\n headers: {\n \"Content-Type\": BINARY_CONTENT_TYPE,\n },\n body: update as unknown as BodyInit,\n signal,\n },\n this.reconnectConfig.connectTimeoutMs,\n \"connect\",\n );\n if (response.status === 200 || response.status === 204) {\n return;\n }\n throw await this.responseToEphemeralFailure(response, \"ephemeral append\");\n }\n\n /**\n * Opens one bootstrap-first ephemeral SSE session.\n *\n * Ephemeral channels intentionally do not expose offsets or control events:\n * the first event must be `bootstrap`, and all following events must be live\n * `data` updates.\n */\n async readEphemeralSse(\n onBootstrap: (bootstrap: Uint8Array) => MaybePromise<void>,\n onData: (update: Uint8Array) => MaybePromise<void>,\n signal?: AbortSignal,\n onActivity?: () => void,\n ): Promise<\n | { kind: \"ok\" }\n | { kind: \"unsupported\" }\n | { kind: \"not-found\" }\n > {\n const requestUrl = this.ephemeralReadEndpoint();\n const response = await this.fetchAuthorized(\n requestUrl,\n {\n method: \"GET\",\n headers: {\n Accept: \"text/event-stream\",\n },\n signal,\n },\n this.reconnectConfig.connectTimeoutMs,\n \"connect\",\n );\n if (response.status === 404) {\n return { kind: \"not-found\" };\n }\n if (!response.ok) {\n if (response.status === 400) {\n const message = await response.text();\n if (isSseUnsupportedErrorText(message)) {\n return { kind: \"unsupported\" };\n }\n throw new HttpError(\n message.length > 0\n ? `ephemeral live read failed: ${message}`\n : \"ephemeral live read failed\",\n response.status,\n );\n }\n throw await this.responseToEphemeralFailure(response, \"ephemeral live read\");\n }\n if (response.body == null) {\n throw new ProtocolError(\"ephemeral sse response missing body\");\n }\n const responseContentType = response.headers.get(\"Content-Type\") ?? \"\";\n if (!responseContentType.toLowerCase().includes(\"text/event-stream\")) {\n throw new ProtocolError(\n \"ephemeral sse response must use text/event-stream content type\",\n );\n }\n\n const dataEncoding =\n response.headers.get(\"Stream-SSE-Data-Encoding\") ?? undefined;\n const consume = async (): Promise<{ kind: \"ok\" }> => {\n let sawBootstrap = false;\n // onActivity fires on every non-empty chunk (including SSE `: keepalive`\n // comment frames the server emits every ~15s), not just data events, so\n // the caller's inactivity timer counts server keepalives as liveness.\n for await (const event of readSseEvents(response.body!, { onActivity })) {\n if (!sawBootstrap) {\n if (event.event !== \"bootstrap\") {\n throw new ProtocolError(\n `ephemeral sse must start with bootstrap, got '${event.event}'`,\n );\n }\n await onBootstrap(decodeSsePayload(event.data, dataEncoding));\n sawBootstrap = true;\n continue;\n }\n if (event.event === \"data\") {\n await onData(decodeSsePayload(event.data, dataEncoding));\n continue;\n }\n throw new ProtocolError(\n `unexpected ephemeral sse event '${event.event}'`,\n );\n }\n if (!sawBootstrap) {\n throw new ProtocolError(\"ephemeral sse ended before bootstrap\");\n }\n return { kind: \"ok\" };\n };\n\n let abortHandler: (() => void) | undefined;\n const abortRace =\n signal != null && !signal.aborted\n ? new Promise<never>((_, reject) => {\n abortHandler = () => {\n void response.body?.cancel().catch(() => {});\n reject(\n signal.reason ??\n new DOMException(\"The operation was aborted.\", \"AbortError\"),\n );\n };\n signal.addEventListener(\"abort\", abortHandler, { once: true });\n })\n : null;\n try {\n if (signal?.aborted) {\n await response.body.cancel().catch(() => {});\n throw (\n signal.reason ??\n new DOMException(\"The operation was aborted.\", \"AbortError\")\n );\n }\n return abortRace == null\n ? await consume()\n : await Promise.race([consume(), abortRace]);\n } finally {\n if (abortHandler != null && signal != null) {\n signal.removeEventListener(\"abort\", abortHandler);\n }\n }\n }\n\n private async fetchAuthorized(\n input: string,\n init: RequestInit,\n timeoutMs: number,\n phase: \"connect\" | \"poll\",\n ): Promise<Response> {\n const requestUrl = this.routeRequestUrl(input, init);\n // The auth callback runs before `fetchWithTimeout` starts its timer, so it\n // shares the same per-phase budget; otherwise a stalled token request\n // leaves the ephemeral room `reconnecting` with no timeout at all.\n const firstToken =\n this.authProvider == null\n ? undefined\n : await withTokenTimeout(\n this.resolveToken({ reason: \"request\" }),\n timeoutMs,\n phase,\n );\n const firstResponse = await this.fetchWithTimeout(\n requestUrl,\n {\n ...init,\n headers: this.attachAuthorization(init.headers, firstToken),\n },\n timeoutMs,\n phase,\n );\n if (\n this.authProvider == null ||\n (firstResponse.status !== 401 && firstResponse.status !== 403)\n ) {\n return firstResponse;\n }\n await firstResponse.body?.cancel().catch(() => {});\n const refreshedToken = await this.refreshTokenAfterFailure(\n firstResponse.status as 401 | 403,\n firstToken,\n timeoutMs,\n phase,\n );\n return this.fetchWithTimeout(\n requestUrl,\n {\n ...init,\n headers: this.attachAuthorization(init.headers, refreshedToken),\n },\n timeoutMs,\n phase,\n );\n }\n\n private async fetchWithTimeout(\n input: string,\n init: RequestInit,\n timeoutMs: number,\n phase: \"connect\" | \"poll\",\n ): Promise<Response> {\n const controller = new AbortController();\n const signal = mergeAbortSignals(init.signal, controller.signal);\n const timer = setTimeout(() => {\n controller.abort(new TimeoutError(phase, timeoutMs));\n }, timeoutMs);\n try {\n return await this.fetchImpl(input, {\n ...init,\n signal,\n });\n } catch (error) {\n if (\n controller.signal.aborted &&\n controller.signal.reason instanceof TimeoutError\n ) {\n throw controller.signal.reason;\n }\n throw error;\n } finally {\n clearTimeout(timer);\n }\n }\n\n private async resolveToken(\n context?: StreamsAuthContext,\n ): Promise<string | undefined> {\n return await this.authProvider?.(context);\n }\n\n private async refreshTokenAfterFailure(\n status: 401 | 403,\n previousToken: string | undefined,\n timeoutMs: number,\n phase: \"connect\" | \"poll\",\n ): Promise<string | undefined> {\n if (this.authProvider == null) {\n return undefined;\n }\n if (this.authRefreshInFlight == null) {\n const inFlight: Promise<string | undefined> = this.resolveToken({\n reason: \"unauthorized\",\n status,\n previousToken,\n }).finally(() => {\n if (this.authRefreshInFlight === inFlight) {\n this.authRefreshInFlight = undefined;\n }\n });\n this.authRefreshInFlight = inFlight;\n }\n const inFlight = this.authRefreshInFlight;\n try {\n return await withTokenTimeout(inFlight, timeoutMs, phase);\n } catch (error) {\n // Do not let a stuck refresh pin every later 401 retry to it.\n if (\n error instanceof TimeoutError &&\n this.authRefreshInFlight === inFlight\n ) {\n this.authRefreshInFlight = undefined;\n }\n throw error;\n }\n }\n\n private attachAuthorization(\n base: HeadersInit | undefined,\n token: string | undefined,\n ): Headers {\n const headers = new Headers(base);\n if (token == null || token.length === 0) {\n headers.delete(\"Authorization\");\n return headers;\n }\n headers.set(\"Authorization\", `Bearer ${token}`);\n return headers;\n }\n\n private async responseToEphemeralFailure(\n response: Response,\n context: string,\n ): Promise<Error> {\n const bodyText = await response.text().catch(() => \"\");\n return new HttpError(\n bodyText.length > 0\n ? `${context} failed: ${bodyText}`\n : `${context} failed with status ${response.status}`,\n response.status,\n {\n requestId: extractRequestIdFromResponseHeaders(response.headers),\n retryAfterMs: parseRetryAfterMs(\n response.headers.get(\"retry-after\") ?? undefined,\n ),\n body: bodyText.length > 0 ? bodyText : undefined,\n },\n );\n }\n\n private ephemeralReadEndpoint(): string {\n const target = new URL(this.streamUrl);\n target.searchParams.set(\"live\", \"sse\");\n return target.toString();\n }\n\n private toTransportFailure(error: StreamError): Error {\n switch (error.code) {\n case \"bad_request\":\n case \"unauthorized\":\n case \"forbidden\":\n case \"not_found\":\n case \"conflict\":\n case \"gone\":\n case \"rate_limited\":\n return new HttpError(error.message, error.status, {\n body: error.bodyText,\n });\n case \"timeout\":\n return new TimeoutError(\n error.phase,\n error.phase === \"poll\"\n ? this.reconnectConfig.pollTimeoutMs\n : this.reconnectConfig.connectTimeoutMs,\n );\n case \"network_error\":\n return error.cause instanceof TypeError\n ? error.cause\n : new TypeError(error.message);\n case \"protocol_error\":\n return new ProtocolError(error.message, error.status);\n case \"unknown\":\n if (typeof error.status === \"number\") {\n return new HttpError(error.message, error.status, {\n requestId: extractRequestIdFromHeaders(error.headers),\n retryAfterMs: parseRetryAfterMs(error.headers?.[\"retry-after\"]),\n body: error.bodyText,\n });\n }\n if (error.cause instanceof TypeError) {\n return error.cause;\n }\n if (error.cause instanceof Error) {\n return error.cause;\n }\n return new Error(error.message);\n default: {\n const exhaustive: never = error;\n return new Error(String(exhaustive));\n }\n }\n }\n\n private async readItemsSse(\n initialOffset: string,\n cursor: string | undefined,\n onControl: (\n updates: Uint8Array[],\n nextOffset: string,\n upToDate: boolean,\n streamCursor?: string,\n ) => MaybePromise<SseControlHandlerResult>,\n onOpen?: () => void,\n signal?: AbortSignal,\n onActivity?: () => void,\n ): Promise<SseItemsReadResult> {\n const opened = await this.openItemsSseSession(\n initialOffset,\n cursor,\n onOpen,\n signal,\n onActivity,\n );\n if (opened.kind !== \"ok\") {\n return opened;\n }\n // Race continue() against the abort signal so that callers (e.g.\n // the inactivity timeout) can interrupt an in-progress SSE read\n // even when the fetch implementation does not propagate abort to\n // the response body stream.\n let abortHandler: (() => void) | undefined;\n const abortRace =\n signal != null && !signal.aborted\n ? new Promise<never>((_, reject) => {\n abortHandler = () => {\n void opened.session.cancel();\n reject(\n signal.reason ??\n new DOMException(\"The operation was aborted.\", \"AbortError\"),\n );\n };\n signal.addEventListener(\"abort\", abortHandler, { once: true });\n })\n : null;\n try {\n if (signal?.aborted) {\n await opened.session.cancel();\n throw (\n signal.reason ??\n new DOMException(\"The operation was aborted.\", \"AbortError\")\n );\n }\n const result =\n abortRace != null\n ? await Promise.race([\n opened.session.continue(onControl),\n abortRace,\n ])\n : await opened.session.continue(onControl);\n if (result.paused) {\n await opened.session.cancel();\n }\n return {\n kind: \"ok\",\n latestOffset: result.latestOffset,\n latestCursor: result.latestCursor,\n };\n } finally {\n if (abortHandler != null && signal != null) {\n signal.removeEventListener(\"abort\", abortHandler);\n }\n }\n }\n\n private async openItemsSseSession(\n initialOffset: string,\n cursor: string | undefined,\n onOpen?: () => void,\n signal?: AbortSignal,\n onActivity?: () => void,\n ): Promise<SseItemsOpenResult> {\n const result = await this.client.openSseSession({\n offset: initialOffset,\n cursor,\n signal,\n onActivity,\n });\n if (!result.ok) {\n if (result.result.code === \"gone\") {\n return { kind: \"gone\" };\n }\n if (result.result.code === \"not_found\") {\n return { kind: \"not-found\" };\n }\n if (\n result.result.code === \"bad_request\" &&\n isSseUnsupportedErrorText(result.result.message)\n ) {\n return { kind: \"unsupported\" };\n }\n throw this.toTransportFailure(result.result);\n }\n\n const session = result.result;\n onOpen?.();\n let pendingDataEvents: string[] = [];\n let latestOffset = session.nextOffset ?? initialOffset;\n let latestCursor = session.cursor;\n let exhausted = false;\n\n return {\n kind: \"ok\",\n session: {\n continue: async (nextOnControl) => {\n if (exhausted) {\n return {\n kind: \"ok\",\n latestOffset,\n latestCursor,\n paused: false,\n };\n }\n for await (const event of session.events) {\n if (event.event === \"data\") {\n pendingDataEvents.push(event.data);\n continue;\n }\n if (event.event !== \"control\") {\n continue;\n }\n const control = parseSseControlData(event.data);\n const updates: Uint8Array[] = [];\n for (const eventData of pendingDataEvents) {\n updates.push(\n ...decodeSseItemPayloads(eventData, session.dataEncoding),\n );\n }\n pendingDataEvents = [];\n const handlerResult = await nextOnControl(\n updates,\n control.nextOffset,\n control.upToDate,\n control.streamCursor,\n );\n latestOffset = control.nextOffset;\n if (control.streamCursor != null) {\n latestCursor = control.streamCursor;\n }\n if (handlerResult === \"stop\") {\n exhausted = true;\n await session.cancel();\n return {\n kind: \"ok\",\n latestOffset,\n latestCursor,\n paused: false,\n };\n }\n if (handlerResult === \"pause\") {\n return {\n kind: \"ok\",\n latestOffset,\n latestCursor,\n paused: true,\n };\n }\n }\n exhausted = true;\n return {\n kind: \"ok\",\n latestOffset,\n latestCursor,\n paused: false,\n };\n },\n cancel: async () => {\n if (exhausted) {\n return;\n }\n exhausted = true;\n await session.cancel();\n },\n },\n };\n }\n}\n\nfunction normalizeStreamTtlSeconds(\n value: number | undefined,\n): number | undefined {\n if (value == null) {\n return undefined;\n }\n if (!Number.isSafeInteger(value) || value < 0) {\n throw new Error(\"streamTtlSeconds must be a non-negative safe integer\");\n }\n return value;\n}\n\nconst REQUEST_ID_HEADER_NAMES = [\n \"x-request-id\",\n \"x-amzn-trace-id\",\n \"cf-ray\",\n \"x-trace-id\",\n];\n\nfunction extractRequestIdFromHeaders(\n headers: Readonly<Record<string, string>> | undefined,\n): string | undefined {\n if (headers == null) {\n return undefined;\n }\n const lowerCased: Record<string, string> = {};\n for (const [key, value] of Object.entries(headers)) {\n lowerCased[key.toLowerCase()] = value;\n }\n for (const name of REQUEST_ID_HEADER_NAMES) {\n const value = lowerCased[name];\n if (typeof value === \"string\" && value.length > 0) {\n return value;\n }\n }\n return undefined;\n}\n\nfunction extractRequestIdFromResponseHeaders(\n headers: Headers,\n): string | undefined {\n for (const name of REQUEST_ID_HEADER_NAMES) {\n const value = headers.get(name);\n if (typeof value === \"string\" && value.length > 0) {\n return value;\n }\n }\n return undefined;\n}\n\n// Parses an HTTP `Retry-After` header value into milliseconds. Accepts either\n// a delta-seconds integer or an HTTP-date. Returns undefined for invalid or\n// past-dated values so callers fall back to the default backoff schedule.\nfunction parseRetryAfterMs(value: string | undefined): number | undefined {\n if (value == null) {\n return undefined;\n }\n const trimmed = value.trim();\n if (trimmed.length === 0) {\n return undefined;\n }\n const numeric = Number(trimmed);\n if (Number.isFinite(numeric)) {\n if (numeric < 0) {\n return undefined;\n }\n return Math.round(numeric * 1000);\n }\n const parsed = Date.parse(trimmed);\n if (Number.isNaN(parsed)) {\n return undefined;\n }\n const delta = parsed - Date.now();\n return delta > 0 ? delta : undefined;\n}\n\nasync function withTokenTimeout<T>(\n promise: Promise<T>,\n timeoutMs: number,\n phase: \"connect\" | \"poll\",\n): Promise<T> {\n if (!(timeoutMs > 0)) {\n return await promise;\n }\n let timer: ReturnType<typeof setTimeout> | undefined;\n try {\n return await Promise.race([\n promise,\n new Promise<never>((_, reject) => {\n timer = setTimeout(() => {\n reject(new TimeoutError(phase, timeoutMs));\n }, timeoutMs);\n }),\n ]);\n } finally {\n clearTimeout(timer);\n }\n}\n","import { Deque } from \"./deque\";\nimport {\n computeRetryDelay,\n DEFAULT_RECONNECT_CONFIG,\n MIN_SUCCESSFUL_SSE_DURATION_MS,\n type ReconnectConfig,\n} from \"./reconnect\";\nimport { StreamsTransportClient } from \"./transport-client\";\nimport {\n classifyFetchError,\n HttpError,\n isAuthOrProtocolError,\n normalizeAuthProvider,\n sleep,\n toTransportError,\n} from \"./transport-internal\";\nimport type {\n CrdtAdapter,\n EphemeralStreamAdaptor,\n EphemeralStreamCrdtOptions,\n EphemeralStreamJoinParams,\n EphemeralStreamSubscription,\n JsonObject,\n TransportError,\n TransportRoomStatus,\n} from \"./types\";\n\n/**\n * Superseded entries are skipped lazily; once they outnumber live entries\n * (and exceed this floor) the queue is rebuilt so it stays O(live entries).\n */\nconst PENDING_LOCAL_REBUILD_MIN_SUPERSEDED = 64;\n\n/**\n * One queued local update. Every POST body is an original update emitted by\n * the adaptor; the queue never merges bodies, so it cannot create a request\n * larger than one the application produced.\n */\ntype PendingLocalUpdate = {\n /** Original update bytes; released once the entry is superseded. */\n update: Uint8Array | null;\n /** Single key the update writes, from `adaptor.localUpdateKey`. */\n readonly key: string | undefined;\n /**\n * Local updates this entry accounts for in `pendingLocalCount`. A\n * superseded entry hands its count to the entry that replaced it. This is\n * reporting only; `waitUntilSynced` tracks `chainId` instead.\n */\n count: number;\n /**\n * Identity of the update chain this entry carries. A fresh entry opens a\n * new chain; an entry that supersedes a queued one inherits its chain, so\n * the chain ends only when the latest value for it is acknowledged.\n */\n readonly chainId: number;\n /** Set when a newer update for the same key replaced this queued entry. */\n superseded: boolean;\n /** Set once the flush loop has started sending this entry. */\n inFlight: boolean;\n};\n\ntype EphemeralWaiter = {\n /**\n * Highest chain id live when `waitUntilSynced()` was called. Chains opened\n * later (other keys, or writes after the tracked value went in flight)\n * never count toward this waiter.\n */\n readonly lastChainId: number;\n /** Tracked chains (id <= `lastChainId`) not yet acknowledged. */\n remaining: number;\n /** Resolves the caller's `waitUntilSynced()` once the tracked updates flush. */\n resolve: () => void;\n /** Rejects the caller's `waitUntilSynced()` on close or non-recoverable write failure. */\n reject: (error: Error) => void;\n};\n\n/**\n * Shared lifecycle state for all active subscriptions on one ephemeral stream.\n *\n * A single `EphemeralStreamCrdt` instance keeps one SSE read loop and one local\n * write queue. Multiple callers can hold lightweight subscription handles that\n * reference this shared state through `refCount`.\n */\ntype EphemeralJoinState = {\n /** Number of active public subscription handles referencing this join state. */\n refCount: number;\n /** Set after the shared read/write loops have been disposed. */\n closed: boolean;\n /** Public aggregate status derived from read and write sides. */\n status: \"connecting\" | TransportRoomStatus;\n /** Status of the bootstrap-first SSE read loop. */\n readStatus: \"connecting\" | \"ok\" | \"reconnecting\" | \"disconnected\" | \"error\";\n /** Status of the local POST write queue. */\n writeStatus: \"connecting\" | \"ok\" | \"reconnecting\" | \"error\";\n /** Registered public status callbacks. */\n readonly statusListeners: Set<\n (status: \"connecting\" | TransportRoomStatus) => void\n >;\n /**\n * Local ephemeral updates waiting for POST acknowledgement, in FIFO order.\n * Entries replaced by a newer same-key update stay in place as superseded\n * markers until they reach the head (see `enqueueLocal`).\n */\n readonly pendingLocal: Deque<PendingLocalUpdate>;\n /** Latest queued, not yet in-flight entry per key, for superseding. */\n readonly pendingLocalByKey: Map<string, PendingLocalUpdate>;\n /** Superseded entries still sitting in `pendingLocal`. */\n pendingLocalSuperseded: number;\n /** Number of local updates covered by `pendingLocal`. */\n pendingLocalCount: number;\n /** Last assigned `PendingLocalUpdate.chainId`. */\n lastChainId: number;\n /** Chains with a queued or in-flight entry that is not acknowledged yet. */\n liveChains: number;\n /** `waitUntilSynced()` waiters bound to the chains live at call time. */\n readonly syncWaiters: EphemeralWaiter[];\n /** Guards against concurrent flush loops for `pendingLocal`. */\n flushingLocal: boolean;\n /** Current reconnect attempt for the read loop. */\n retryAttempt: number;\n /** Abort controller for pending reconnect sleeps. */\n retrySleepController: AbortController;\n /** Abort controller for the active SSE request. */\n requestController?: AbortController;\n /** Unsubscribe callback for local ephemeral store updates. */\n unsubscribeLocal?: () => void;\n /** Resolver for the initial bootstrap event. */\n firstOpenResolve?: () => void;\n /** Rejecter for failures before the initial bootstrap event. */\n firstOpenReject?: (error: Error) => void;\n /** Prevents the initial bootstrap promise from settling twice. */\n firstOpenSettled: boolean;\n /** Join startup promise shared by concurrent `join()` calls. */\n started: Promise<void>;\n};\n\nfunction createNoopAdapter(): CrdtAdapter<JsonObject> {\n return {\n emptyVersion: () => ({}),\n mergeVersions: (base) => base,\n exportSnapshot: () => new Uint8Array(),\n applySnapshot: () => ({}),\n exportUpdates: () => undefined,\n applyRemoteUpdates: () => ({}),\n subscribeLocalUpdates: () => () => undefined,\n };\n}\n\nfunction normalizeEphemeralUpdate(update: Uint8Array | null | undefined) {\n if (update == null || update.byteLength === 0) {\n return null;\n }\n return update;\n}\n\nfunction toError(error: unknown): Error {\n return error instanceof Error ? error : new Error(String(error));\n}\n\nfunction closedSubscriptionError(): Error {\n return new Error(\"subscription closed\");\n}\n\n/**\n * Realtime transport for one ephemeral CRDT stream.\n *\n * This is intentionally separate from `StreamsCrdt`: it bootstraps through\n * the first SSE event, then tails live deltas, and never stores a replay\n * offset, snapshot checkpoint, or remote cursor.\n */\nexport class EphemeralStreamCrdt {\n /** Full ephemeral stream URL, including `?ephemeral=` or `?awareness=`. */\n readonly streamUrl: string;\n /** Store-specific adapter used to encode local state and apply remote bytes. */\n readonly adaptor: EphemeralStreamAdaptor;\n\n /** Low-level HTTP/SSE transport. It is reused only for auth, fetch, and parsing helpers. */\n private readonly client: StreamsTransportClient<JsonObject>;\n /** Retry policy shared by the read loop and local write queue. */\n private readonly reconnectConfig: ReconnectConfig;\n /** Enables diagnostic console errors for failed read/write loops. */\n private readonly debug: boolean;\n /** Current shared subscription state, if the stream is joined. */\n private joinState?: EphemeralJoinState;\n\n constructor(options: EphemeralStreamCrdtOptions) {\n this.streamUrl = options.streamUrl;\n this.adaptor = options.adaptor;\n this.reconnectConfig = {\n ...DEFAULT_RECONNECT_CONFIG,\n ...options.reconnectConfig,\n };\n this.debug = options.debug ?? false;\n this.client = new StreamsTransportClient({\n streamUrl: options.streamUrl,\n adapter: createNoopAdapter(),\n authProvider: normalizeAuthProvider(options.auth),\n fetchImpl: options.fetch ?? globalThis.fetch.bind(globalThis),\n reconnectConfig: this.reconnectConfig,\n shardUrls: options.shardUrls,\n });\n }\n\n async join(\n params?: EphemeralStreamJoinParams,\n ): Promise<\n | { readonly ok: true; readonly value: EphemeralStreamSubscription }\n | { readonly ok: false; readonly error: TransportError }\n > {\n const existing = this.joinState;\n if (existing != null) {\n try {\n await existing.started;\n return { ok: true, value: this.createSubscription(existing, params) };\n } catch (error) {\n return { ok: false, error: toTransportError(error) };\n }\n }\n\n const state = this.createJoinState();\n state.started = this.startJoin(state);\n this.joinState = state;\n try {\n await state.started;\n return { ok: true, value: this.createSubscription(state, params) };\n } catch (error) {\n await this.disposeJoinState(state);\n return { ok: false, error: toTransportError(error) };\n }\n }\n\n rejoin(): void {\n const state = this.joinState;\n if (state == null || state.closed) {\n return;\n }\n state.retryAttempt = 0;\n state.retrySleepController.abort();\n state.retrySleepController = new AbortController();\n\n const previousController = state.requestController;\n state.requestController = new AbortController();\n previousController?.abort();\n\n if (state.readStatus === \"disconnected\" || state.readStatus === \"error\") {\n this.setReadStatus(state, \"reconnecting\");\n void this.runReadLoop(state);\n }\n if (state.writeStatus === \"error\") {\n this.setWriteStatus(state, \"reconnecting\");\n void this.flushPendingLocal(state);\n }\n }\n\n async close(): Promise<void> {\n const state = this.joinState;\n if (state != null) {\n await this.disposeJoinState(state);\n }\n }\n\n private createJoinState(): EphemeralJoinState {\n return {\n refCount: 0,\n closed: false,\n status: \"connecting\",\n readStatus: \"connecting\",\n writeStatus: \"connecting\",\n statusListeners: new Set(),\n pendingLocal: new Deque(),\n pendingLocalByKey: new Map(),\n pendingLocalSuperseded: 0,\n pendingLocalCount: 0,\n lastChainId: 0,\n liveChains: 0,\n syncWaiters: [],\n flushingLocal: false,\n retryAttempt: 0,\n retrySleepController: new AbortController(),\n firstOpenSettled: false,\n started: Promise.resolve(),\n };\n }\n\n private async startJoin(state: EphemeralJoinState): Promise<void> {\n state.unsubscribeLocal = this.adaptor.subscribeLocalUpdates((update) => {\n this.enqueueLocal(state, normalizeEphemeralUpdate(update));\n });\n state.requestController = new AbortController();\n const firstOpen = this.waitForFirstOpen(state);\n void this.runReadLoop(state);\n await firstOpen;\n\n if (state.closed) {\n return;\n }\n this.setWriteStatus(state, \"ok\");\n await this.flushPendingLocal(state);\n if (state.status === \"error\" || state.status === \"disconnected\") {\n throw new Error(`ephemeral stream failed to join: ${state.status}`);\n }\n }\n\n private waitForFirstOpen(state: EphemeralJoinState): Promise<void> {\n return new Promise((resolve, reject) => {\n state.firstOpenResolve = resolve;\n state.firstOpenReject = reject;\n });\n }\n\n private createSubscription(\n state: EphemeralJoinState,\n params?: EphemeralStreamJoinParams,\n ): EphemeralStreamSubscription {\n state.refCount += 1;\n const statusListener = params?.onStatusChange;\n if (statusListener != null) {\n state.statusListeners.add(statusListener);\n statusListener(state.status);\n }\n let unsubscribed = false;\n return {\n unsubscribe: () => {\n if (unsubscribed) {\n return;\n }\n unsubscribed = true;\n if (statusListener != null) {\n state.statusListeners.delete(statusListener);\n }\n state.refCount = Math.max(0, state.refCount - 1);\n if (state.refCount === 0 && !state.closed) {\n void this.disposeJoinState(state);\n }\n },\n get connected() {\n return state.status === \"joined\";\n },\n get status() {\n return state.status as TransportRoomStatus;\n },\n get pendingLocalCount() {\n return state.pendingLocalCount;\n },\n onStatusChange: (listener) => this.onStatusChange(state, listener),\n waitUntilSynced: async () => {\n if (state.closed) {\n throw closedSubscriptionError();\n }\n const remaining = state.liveChains;\n const lastChainId = state.lastChainId;\n if (remaining === 0) {\n return;\n }\n return new Promise<void>((resolve, reject) => {\n state.syncWaiters.push({ lastChainId, remaining, resolve, reject });\n });\n },\n };\n }\n\n private onStatusChange(\n state: EphemeralJoinState,\n listener: (status: \"connecting\" | TransportRoomStatus) => void,\n ): () => void {\n state.statusListeners.add(listener);\n listener(state.status);\n return () => {\n state.statusListeners.delete(listener);\n };\n }\n\n private async disposeJoinState(state: EphemeralJoinState): Promise<void> {\n if (state.closed) {\n return;\n }\n state.closed = true;\n state.unsubscribeLocal?.();\n state.requestController?.abort();\n state.retrySleepController.abort();\n this.rejectFirstOpen(state, closedSubscriptionError());\n this.rejectSyncWaiters(state, closedSubscriptionError());\n if (this.joinState === state) {\n this.joinState = undefined;\n }\n }\n\n private resolveFirstOpen(state: EphemeralJoinState): void {\n if (state.firstOpenSettled) {\n return;\n }\n state.firstOpenSettled = true;\n state.firstOpenResolve?.();\n }\n\n private rejectFirstOpen(\n state: EphemeralJoinState,\n error: Error,\n ): void {\n if (state.firstOpenSettled) {\n return;\n }\n state.firstOpenSettled = true;\n state.firstOpenReject?.(error);\n }\n\n private async runReadLoop(state: EphemeralJoinState): Promise<void> {\n while (!state.closed) {\n state.requestController ??= new AbortController();\n const capturedController = state.requestController;\n // Wall-clock time the ephemeral SSE actually opened (bootstrapped); 0\n // until then. Read in the catch handler to tell a healthy-then-recycled\n // stream apart from a failing-to-connect retry chain.\n let sseOpenedAtMs = 0;\n // Live-inactivity watchdog. Unlike the connect timeout (which only covers\n // establishing the response), this fires when an already-open SSE goes\n // silent — a zombie connection after sleep/NAT drop that never surfaces a\n // FIN or error would otherwise hang the read forever. Reset on every\n // server frame (bootstrap, data, and `: keepalive` comments via\n // onActivity), so a healthy-but-quiet stream never trips it.\n let inactivityTimer: ReturnType<typeof setTimeout> | null = null;\n let inactivityTriggered = false;\n const clearInactivity = (): void => {\n if (inactivityTimer != null) {\n clearTimeout(inactivityTimer);\n inactivityTimer = null;\n }\n };\n const resetInactivity = (): void => {\n clearInactivity();\n const timeoutMs = this.reconnectConfig.liveInactivityTimeoutMs;\n if (timeoutMs != null && timeoutMs > 0 && !state.closed) {\n inactivityTimer = setTimeout(() => {\n inactivityTriggered = true;\n capturedController.abort();\n }, timeoutMs);\n }\n };\n try {\n resetInactivity();\n const result = await this.client.readEphemeralSse(\n async (bootstrap) => {\n resetInactivity();\n if (bootstrap.byteLength > 0) {\n await this.adaptor.applyRemoteUpdates([bootstrap]);\n }\n sseOpenedAtMs = Date.now();\n this.setReadStatus(state, \"ok\");\n this.resolveFirstOpen(state);\n },\n async (update) => {\n resetInactivity();\n if (update.byteLength > 0) {\n await this.adaptor.applyRemoteUpdates([update]);\n }\n },\n capturedController.signal,\n resetInactivity,\n );\n clearInactivity();\n if (state.closed) {\n return;\n }\n if (result.kind === \"ok\") {\n state.retryAttempt = 0;\n this.setReadStatus(state, \"reconnecting\");\n await sleep(0);\n continue;\n }\n if (result.kind === \"unsupported\") {\n throw new Error(\"ephemeral stream requires SSE support\");\n }\n if (result.kind === \"not-found\") {\n throw new HttpError(\"ephemeral stream not found\", 404);\n }\n } catch (error) {\n clearInactivity();\n if (state.closed) {\n return;\n }\n if (capturedController.signal.aborted) {\n state.requestController = new AbortController();\n this.setReadStatus(state, \"reconnecting\");\n if (!inactivityTriggered) {\n // rejoin()/external abort: reconnect immediately, no backoff.\n continue;\n }\n // Inactivity timeout: treat like a retriable disconnect so a\n // persistently-silent endpoint backs off (and, with finite\n // maxAttempts, eventually reaches \"disconnected\"). A stream that was\n // healthy past the successful-session threshold resets the budget so\n // one-off zombies recover on the immediate first retry.\n if (\n sseOpenedAtMs > 0 &&\n Date.now() - sseOpenedAtMs >= MIN_SUCCESSFUL_SSE_DURATION_MS\n ) {\n state.retryAttempt = 0;\n }\n if (state.retryAttempt >= this.reconnectConfig.maxAttempts) {\n this.logError(\"ephemeral live reconnect exhausted (inactivity)\", error, {\n streamUrl: this.streamUrl,\n attempts: state.retryAttempt,\n });\n this.setReadStatus(state, \"disconnected\");\n this.rejectFirstOpen(state, toError(error));\n return;\n }\n const delay = computeRetryDelay(\n state.retryAttempt,\n this.reconnectConfig,\n );\n state.retryAttempt += 1;\n await sleep(delay, state.retrySleepController.signal);\n continue;\n }\n const classified = classifyFetchError(error);\n if (!classified.retriable || isAuthOrProtocolError(error)) {\n this.logError(\"ephemeral live read failed\", error, {\n streamUrl: this.streamUrl,\n code: classified.code,\n });\n this.setReadStatus(state, \"error\");\n this.rejectFirstOpen(state, toError(error));\n return;\n }\n this.setReadStatus(state, \"reconnecting\");\n // An ephemeral SSE that stayed open past the \"successful session\"\n // threshold before erroring is a fresh disconnect (e.g. an expected\n // periodic stream recycle), not a failing-to-connect retry chain. Reset\n // the backoff budget so routine recycles over the life of a long-lived\n // presence connection don't ratchet `retryAttempt` up to the delay cap.\n // Connections that never opened or flapped sub-threshold keep their\n // `retryAttempt`, so real failures keep backing off and honour\n // maxAttempts.\n if (\n sseOpenedAtMs > 0 &&\n Date.now() - sseOpenedAtMs >= MIN_SUCCESSFUL_SSE_DURATION_MS\n ) {\n state.retryAttempt = 0;\n }\n if (state.retryAttempt >= this.reconnectConfig.maxAttempts) {\n this.logError(\"ephemeral live reconnect exhausted\", error, {\n streamUrl: this.streamUrl,\n attempts: state.retryAttempt,\n });\n this.setReadStatus(state, \"disconnected\");\n this.rejectFirstOpen(state, toError(error));\n return;\n }\n const delay = computeRetryDelay(\n state.retryAttempt,\n this.reconnectConfig,\n );\n state.retryAttempt += 1;\n await sleep(delay, state.retrySleepController.signal);\n }\n }\n }\n\n private enqueueLocal(\n state: EphemeralJoinState,\n update: Uint8Array | null,\n ): void {\n if (state.closed || update == null || update.byteLength === 0) {\n return;\n }\n const key = this.localUpdateKey(update);\n // A newer update for the same key replaces a queued one that has not\n // been sent yet, so a burst on one key (e.g. streaming status detail)\n // cannot queue stale values ahead of other keys such as a heartbeat.\n // The in-flight head is never replaced; it is dropped from this map\n // when sending starts.\n const previous =\n key == null ? undefined : state.pendingLocalByKey.get(key);\n let chainId: number;\n if (previous == null) {\n state.lastChainId += 1;\n state.liveChains += 1;\n chainId = state.lastChainId;\n } else {\n chainId = previous.chainId;\n }\n const entry: PendingLocalUpdate = {\n update,\n key,\n count: 1,\n chainId,\n superseded: false,\n inFlight: false,\n };\n if (previous != null) {\n previous.superseded = true;\n previous.update = null;\n entry.count += previous.count;\n previous.count = 0;\n state.pendingLocalSuperseded += 1;\n }\n if (key != null) {\n state.pendingLocalByKey.set(key, entry);\n }\n state.pendingLocal.pushBack(entry);\n state.pendingLocalCount += 1;\n this.maybeRebuildPendingLocal(state);\n if (state.writeStatus !== \"connecting\") {\n void this.flushPendingLocal(state);\n }\n }\n\n private async flushPendingLocal(state: EphemeralJoinState): Promise<void> {\n if (state.flushingLocal || state.closed) {\n return;\n }\n state.flushingLocal = true;\n try {\n while (!state.closed) {\n let attempt = 0;\n while (!state.closed) {\n const entry = this.nextPendingLocal(state);\n if (entry == null) {\n return;\n }\n try {\n await this.client.appendEphemeralUpdate(entry.update!);\n // Only this loop pops or rebuilds past the head, and enqueue\n // appends at the back, so `entry` is still the head.\n state.pendingLocal.popFront();\n state.pendingLocalCount -= entry.count;\n state.liveChains -= 1;\n this.notifyChainAcknowledged(state, entry.chainId);\n this.setWriteStatus(state, \"ok\");\n break;\n } catch (error) {\n const classified = classifyFetchError(error);\n if (!classified.retriable || isAuthOrProtocolError(error)) {\n this.rejectSyncWaiters(\n state,\n new Error(`ephemeral append failed: ${classified.code}`),\n );\n this.setWriteStatus(state, \"error\");\n return;\n }\n if (attempt >= this.reconnectConfig.maxAttempts) {\n this.rejectSyncWaiters(\n state,\n new Error(\"ephemeral append retry budget exhausted\"),\n );\n this.setWriteStatus(state, \"error\");\n return;\n }\n this.setWriteStatus(state, \"reconnecting\");\n const delay = computeRetryDelay(attempt, this.reconnectConfig);\n attempt += 1;\n await sleep(delay, state.retrySleepController.signal);\n }\n }\n }\n } finally {\n state.flushingLocal = false;\n }\n }\n\n private localUpdateKey(update: Uint8Array): string | undefined {\n const adaptor = this.adaptor;\n if (adaptor.localUpdateKey == null) {\n return undefined;\n }\n try {\n return adaptor.localUpdateKey(update) ?? undefined;\n } catch (error) {\n this.logError(\"ephemeral local update key lookup failed\", error);\n return undefined;\n }\n }\n\n /**\n * Pops superseded entries off the head and returns the next entry to send,\n * marking it in-flight so later same-key updates queue behind it.\n */\n private nextPendingLocal(\n state: EphemeralJoinState,\n ): PendingLocalUpdate | undefined {\n for (;;) {\n const head = state.pendingLocal.peekFront();\n if (head == null) {\n return undefined;\n }\n if (head.superseded) {\n state.pendingLocal.popFront();\n state.pendingLocalSuperseded -= 1;\n continue;\n }\n if (!head.inFlight) {\n head.inFlight = true;\n if (\n head.key != null &&\n state.pendingLocalByKey.get(head.key) === head\n ) {\n state.pendingLocalByKey.delete(head.key);\n }\n }\n return head;\n }\n }\n\n /** Drops superseded markers once they dominate the queue (amortized O(1)). */\n private maybeRebuildPendingLocal(state: EphemeralJoinState): void {\n if (\n state.pendingLocalSuperseded < PENDING_LOCAL_REBUILD_MIN_SUPERSEDED ||\n state.pendingLocalSuperseded * 2 <= state.pendingLocal.length\n ) {\n return;\n }\n const live = state.pendingLocal\n .toArray()\n .filter((entry) => !entry.superseded);\n state.pendingLocal.clear();\n for (const entry of live) {\n state.pendingLocal.pushBack(entry);\n }\n state.pendingLocalSuperseded = 0;\n }\n\n /**\n * Completes waiters that tracked `chainId`. Each chain is acknowledged\n * exactly once (its entry is never superseded after it goes in flight), so\n * a waiter resolves only after every chain live at call time has had its\n * latest value acknowledged.\n */\n private notifyChainAcknowledged(\n state: EphemeralJoinState,\n chainId: number,\n ): void {\n let i = 0;\n while (i < state.syncWaiters.length) {\n const waiter = state.syncWaiters[i]!;\n if (chainId > waiter.lastChainId) {\n i += 1;\n continue;\n }\n waiter.remaining -= 1;\n if (waiter.remaining <= 0) {\n state.syncWaiters.splice(i, 1);\n waiter.resolve();\n } else {\n i += 1;\n }\n }\n }\n\n private rejectSyncWaiters(state: EphemeralJoinState, error: Error): void {\n for (const waiter of state.syncWaiters.splice(0)) {\n waiter.reject(error);\n }\n }\n\n private setReadStatus(\n state: EphemeralJoinState,\n readStatus: EphemeralJoinState[\"readStatus\"],\n ): void {\n if (state.readStatus === readStatus) {\n return;\n }\n state.readStatus = readStatus;\n this.recomputeStatus(state);\n }\n\n private setWriteStatus(\n state: EphemeralJoinState,\n writeStatus: EphemeralJoinState[\"writeStatus\"],\n ): void {\n if (state.writeStatus === writeStatus) {\n return;\n }\n state.writeStatus = writeStatus;\n this.recomputeStatus(state);\n }\n\n private recomputeStatus(state: EphemeralJoinState): void {\n const status = this.computeStatus(state);\n if (state.status === status) {\n return;\n }\n state.status = status;\n for (const listener of state.statusListeners) {\n listener(status);\n }\n }\n\n private computeStatus(\n state: EphemeralJoinState,\n ): \"connecting\" | TransportRoomStatus {\n if (state.readStatus === \"error\" || state.writeStatus === \"error\") {\n return \"error\";\n }\n if (state.readStatus === \"disconnected\") {\n return \"disconnected\";\n }\n if (\n state.readStatus === \"connecting\" ||\n state.writeStatus === \"connecting\"\n ) {\n return \"connecting\";\n }\n if (\n state.readStatus === \"reconnecting\" ||\n state.writeStatus === \"reconnecting\"\n ) {\n return \"reconnecting\";\n }\n return \"joined\";\n }\n\n private logError(\n message: string,\n error: unknown,\n detail?: Record<string, unknown>,\n ): void {\n if (!this.debug) {\n return;\n }\n console.error(`[streams-crdt] ${message}`, error, detail);\n }\n}\n","import type { JsonObject } from \"./types\";\n\n/**\n * Persisted replay progress for one stream.\n *\n * This is metadata only — it does **not** contain any CRDT update data.\n * A persisted cursor is only valid when the caller also restores the matching\n * local CRDT state before the next `sync()` or `join()`.\n */\nexport interface RemoteCursor<TVersion extends JsonObject = JsonObject> {\n /** Identity of the stream this cursor belongs to. */\n readonly streamUrl: string;\n /** The offset to resume reading from on the next sync/join. */\n readonly nextOffset: string;\n /** The CRDT version that the server data up to `nextOffset` represents. */\n readonly serverLowerBoundVersion: TVersion;\n /** Timestamp (ms) when this cursor was last persisted. */\n readonly updatedAtMs: number;\n}\n\n/**\n * Storage for replay progress metadata.\n *\n * A persisted cursor is only valid when the caller also restores the matching\n * local CRDT state before the next `sync()` or `join()`. Storing only the\n * cursor can make transport resume from `nextOffset` against an empty local\n * document, which is not sufficient for delta-only catchup.\n */\nexport interface RemoteCursorStore<TVersion extends JsonObject = JsonObject> {\n load(streamUrl: string): Promise<RemoteCursor<TVersion> | null>;\n save(cursor: RemoteCursor<TVersion>): Promise<void>;\n delete?(streamUrl: string): Promise<void>;\n}\n\n/**\n * Why the transport is advancing the remote cursor.\n */\nexport type RemoteCursorSaveSource = \"local\" | \"remote\" | \"bootstrap\";\n\n/**\n * Context passed to durability hooks before a remote cursor is persisted.\n *\n * Hooks should make local durable state correspond to `cursor` before this\n * cursor is stored, so a future process can restore the same CRDT state before\n * transport resumes from `cursor.nextOffset`.\n */\nexport type BeforeRemoteCursorSaveContext<\n TVersion extends JsonObject = JsonObject,\n> = {\n readonly cursor: RemoteCursor<TVersion>;\n readonly source: RemoteCursorSaveSource;\n};\n\n/**\n * Hook invoked before the transport persists replay progress.\n *\n * This is the place to persist local CRDT payloads or snapshots. If that local\n * durability step does not happen here, the remote cursor should not advance.\n */\nexport type BeforeRemoteCursorSaveHook<TVersion extends JsonObject = JsonObject> =\n (context: BeforeRemoteCursorSaveContext<TVersion>) => void | Promise<void>;\n\ntype BeforeRemoteCursorSavePredicate<\n TVersion extends JsonObject = JsonObject,\n> = (context: BeforeRemoteCursorSaveContext<TVersion>) => boolean;\n\nexport function composeBeforeRemoteCursorSaveHooks<\n TVersion extends JsonObject = JsonObject,\n>(\n ...hooks: Array<BeforeRemoteCursorSaveHook<TVersion> | null | undefined>\n): BeforeRemoteCursorSaveHook<TVersion> | undefined {\n const active = hooks.filter(\n (hook): hook is BeforeRemoteCursorSaveHook<TVersion> => hook != null,\n );\n if (active.length === 0) {\n return undefined;\n }\n return async (context) => {\n for (const hook of active) {\n await hook(context);\n }\n };\n}\n\nfunction withRemoteCursorSavePredicate<\n TVersion extends JsonObject = JsonObject,\n>(\n predicate: BeforeRemoteCursorSavePredicate<TVersion>,\n hook: BeforeRemoteCursorSaveHook<TVersion>,\n): BeforeRemoteCursorSaveHook<TVersion> {\n return async (context) => {\n if (!predicate(context)) {\n return;\n }\n await hook(context);\n };\n}\n\nexport async function persistRemoteCursor<\n TVersion extends JsonObject = JsonObject,\n>(input: {\n remoteCursorStore: RemoteCursorStore<TVersion>;\n cursor: RemoteCursor<TVersion>;\n source: RemoteCursorSaveSource;\n beforeRemoteCursorSave?: BeforeRemoteCursorSaveHook<TVersion>;\n now?: () => number;\n}): Promise<RemoteCursor<TVersion>> {\n const next: RemoteCursor<TVersion> = {\n ...input.cursor,\n updatedAtMs: (input.now ?? Date.now)(),\n };\n if (input.beforeRemoteCursorSave != null) {\n await input.beforeRemoteCursorSave({\n cursor: next,\n source: input.source,\n });\n }\n await input.remoteCursorStore.save(next);\n return next;\n}\n\n/**\n * Construction options for `IndexedDbRemoteCursorStore`.\n */\nexport interface IndexedDbRemoteCursorStoreOptions {\n readonly dbName?: string;\n readonly storeName?: string;\n readonly dbVersion?: number;\n readonly indexedDb?: IDBFactory;\n}\n\nfunction key(streamUrl: string): string {\n return streamUrl;\n}\n\nfunction isJsonObject(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value != null && !Array.isArray(value);\n}\n\nfunction cloneJsonValue<T>(value: T): T {\n return structuredClone(value);\n}\n\nfunction cloneRemoteCursor<TVersion extends JsonObject>(\n cursor: RemoteCursor<TVersion>,\n): RemoteCursor<TVersion> {\n return {\n ...cursor,\n serverLowerBoundVersion: cloneJsonValue(cursor.serverLowerBoundVersion),\n };\n}\n\ntype StoredRemoteCursor = {\n readonly streamUrl: string;\n readonly nextOffset: string;\n readonly serverLowerBoundVersion: JsonObject;\n readonly updatedAtMs: number;\n};\n\nfunction fromStoredRemoteCursor<TVersion extends JsonObject>(\n record: StoredRemoteCursor,\n): RemoteCursor<TVersion> {\n if (!isJsonObject(record.serverLowerBoundVersion)) {\n throw new Error(\n \"invalid remote cursor record: serverLowerBoundVersion is not a JSON object\",\n );\n }\n return {\n streamUrl: record.streamUrl,\n nextOffset: record.nextOffset,\n serverLowerBoundVersion: cloneJsonValue(\n record.serverLowerBoundVersion as TVersion,\n ),\n updatedAtMs: record.updatedAtMs,\n };\n}\n\nfunction toStoredRemoteCursor<TVersion extends JsonObject>(\n cursor: RemoteCursor<TVersion>,\n): StoredRemoteCursor {\n return {\n streamUrl: cursor.streamUrl,\n nextOffset: cursor.nextOffset,\n serverLowerBoundVersion: cloneJsonValue(cursor.serverLowerBoundVersion),\n updatedAtMs: cursor.updatedAtMs,\n };\n}\n\n/**\n * In-memory cursor store useful for tests or ephemeral runtimes.\n */\nexport class InMemoryRemoteCursorStore<\n TVersion extends JsonObject = JsonObject,\n> implements RemoteCursorStore<TVersion> {\n private readonly data = new Map<string, RemoteCursor<TVersion>>();\n\n async load(\n streamUrl: string,\n ): Promise<RemoteCursor<TVersion> | null> {\n const value = this.data.get(key(streamUrl));\n return value == null ? null : cloneRemoteCursor(value);\n }\n\n async save(cursor: RemoteCursor<TVersion>): Promise<void> {\n this.data.set(key(cursor.streamUrl), cloneRemoteCursor(cursor));\n }\n\n async delete(streamUrl: string): Promise<void> {\n this.data.delete(key(streamUrl));\n }\n}\n\nconst DEFAULT_DB_NAME = \"streams-crdt\";\nconst DEFAULT_STORE_NAME = \"remote-cursors\";\nconst DEFAULT_DB_VERSION = 1;\n\n/**\n * IndexedDB-backed cursor store for browser clients that need replay progress persistence.\n *\n * This store only persists `RemoteCursor` metadata. It does not persist CRDT\n * payloads. Use it only when the application also persists the corresponding\n * local document state and restores that state before calling `sync()` or\n * `join()` after a reload.\n */\nexport class IndexedDbRemoteCursorStore<\n TVersion extends JsonObject = JsonObject,\n> implements RemoteCursorStore<TVersion> {\n private readonly dbName: string;\n private readonly storeName: string;\n private readonly dbVersion: number;\n private readonly indexedDb: IDBFactory | undefined;\n\n private db: IDBDatabase | null = null;\n private initPromise: Promise<void> | null = null;\n\n constructor(options?: IndexedDbRemoteCursorStoreOptions) {\n this.dbName = options?.dbName ?? DEFAULT_DB_NAME;\n this.storeName = options?.storeName ?? DEFAULT_STORE_NAME;\n this.dbVersion = options?.dbVersion ?? DEFAULT_DB_VERSION;\n this.indexedDb = options?.indexedDb ?? globalThis.indexedDB;\n }\n\n async load(\n streamUrl: string,\n ): Promise<RemoteCursor<TVersion> | null> {\n const db = await this.ensureDb();\n const record = await new Promise<StoredRemoteCursor | null>((resolve, reject) => {\n const tx = db.transaction([this.storeName], \"readonly\");\n const store = tx.objectStore(this.storeName);\n const request = store.get(key(streamUrl));\n request.onsuccess = () => {\n resolve((request.result as StoredRemoteCursor | undefined) ?? null);\n };\n request.onerror = () => {\n reject(request.error ?? new Error(\"indexeddb load failed\"));\n };\n });\n return record == null ? null : fromStoredRemoteCursor<TVersion>(record);\n }\n\n async save(cursor: RemoteCursor<TVersion>): Promise<void> {\n const db = await this.ensureDb();\n const record = toStoredRemoteCursor(cursor);\n await new Promise<void>((resolve, reject) => {\n const tx = db.transaction([this.storeName], \"readwrite\");\n const store = tx.objectStore(this.storeName);\n const request = store.put(record, key(cursor.streamUrl));\n tx.oncomplete = () => resolve();\n tx.onerror = () =>\n reject(tx.error ?? request.error ?? new Error(\"indexeddb save failed\"));\n tx.onabort = () =>\n reject(\n tx.error ??\n request.error ??\n new Error(\"indexeddb save transaction aborted\"),\n );\n request.onerror = () =>\n reject(request.error ?? new Error(\"indexeddb save failed\"));\n });\n }\n\n async delete(streamUrl: string): Promise<void> {\n const db = await this.ensureDb();\n await new Promise<void>((resolve, reject) => {\n const tx = db.transaction([this.storeName], \"readwrite\");\n const store = tx.objectStore(this.storeName);\n const request = store.delete(key(streamUrl));\n tx.oncomplete = () => resolve();\n tx.onerror = () =>\n reject(\n tx.error ?? request.error ?? new Error(\"indexeddb delete failed\"),\n );\n tx.onabort = () =>\n reject(\n tx.error ??\n request.error ??\n new Error(\"indexeddb delete transaction aborted\"),\n );\n request.onerror = () =>\n reject(request.error ?? new Error(\"indexeddb delete failed\"));\n });\n }\n\n private async ensureDb(): Promise<IDBDatabase> {\n if (this.db != null) {\n return this.db;\n }\n if (this.initPromise != null) {\n await this.initPromise;\n if (this.db == null) {\n throw new Error(\"indexeddb is not available\");\n }\n return this.db;\n }\n if (this.indexedDb == null) {\n throw new Error(\"indexeddb is not available in this environment\");\n }\n\n this.initPromise = new Promise<void>((resolve, reject) => {\n const request = this.indexedDb!.open(this.dbName, this.dbVersion);\n\n request.onupgradeneeded = () => {\n const db = request.result;\n if (!db.objectStoreNames.contains(this.storeName)) {\n db.createObjectStore(this.storeName);\n }\n };\n request.onerror = () =>\n reject(request.error ?? new Error(\"indexeddb open failed\"));\n request.onsuccess = () => {\n this.db = request.result;\n this.db.onversionchange = () => {\n this.db?.close();\n this.db = null;\n };\n resolve();\n };\n }).finally(() => {\n this.initPromise = null;\n });\n\n await this.initPromise;\n if (this.db == null) {\n throw new Error(\"indexeddb open succeeded without a database handle\");\n }\n return this.db;\n }\n}\n\n/**\n * Creates a cursor value suitable for seeding a `RemoteCursorStore`.\n */\nexport function createInitialRemoteCursor<\n TVersion extends JsonObject = JsonObject,\n>(input: {\n streamUrl: string;\n nextOffset: string;\n serverLowerBoundVersion: TVersion;\n}): RemoteCursor<TVersion> {\n return {\n streamUrl: input.streamUrl,\n nextOffset: input.nextOffset,\n serverLowerBoundVersion: cloneJsonValue(input.serverLowerBoundVersion),\n updatedAtMs: Date.now(),\n };\n}\n","import type {\n RemoteCursor,\n RemoteCursorSaveSource,\n} from \"./checkpoint\";\nimport {\n resolveNextOffsetAfterConcurrentAdvance,\n type ReadResult,\n} from \"./transport-internal\";\nimport type {\n CrdtAdapter,\n CrdtApplyOutcome,\n CrdtApplyReturn,\n CrdtUnresolvedSpan,\n JsonObject,\n MaybePromise,\n} from \"./types\";\n\nconst MAX_SPANS_IN_MESSAGE = 8;\n\n/**\n * Renders a bounded summary of unresolved spans for the error message, so a\n * caller's error log names the author(s) whose ops are missing from the\n * stream without the caller having to unwrap `error.unresolved` itself.\n */\nfunction formatUnresolvedSpans(\n unresolved: readonly CrdtUnresolvedSpan[],\n): string {\n if (unresolved.length === 0) {\n return \"\";\n }\n const shown = unresolved\n .slice(0, MAX_SPANS_IN_MESSAGE)\n .map((span) => `${span.peer}@[${span.start}..${span.end})`)\n .join(\", \");\n const suffix =\n unresolved.length > MAX_SPANS_IN_MESSAGE\n ? `, +${unresolved.length - MAX_SPANS_IN_MESSAGE} more`\n : \"\";\n return ` (missing deps for ${unresolved.length} span(s): ${shown}${suffix})`;\n}\n\nexport class CrdtApplyIncompleteError extends Error {\n readonly unresolved: readonly CrdtUnresolvedSpan[];\n /**\n * The volatile server version at the moment apply failed: everything the\n * server delivered that the local doc actually applied (success spans\n * only — pending spans never advance it). `exportUpdates(appliedVersion)`\n * is therefore exactly \"the ops this replica holds that the server does\n * not\", which is what the join-time salvage append publishes.\n */\n readonly appliedVersion?: JsonObject;\n\n constructor(\n unresolved: readonly CrdtUnresolvedSpan[],\n appliedVersion?: JsonObject,\n ) {\n // The \"CRDT apply incomplete\" prefix is load-bearing:\n // `isCrdtApplyIncompleteError` (transport-internal.ts) matches on it.\n super(\n `CRDT apply incomplete: unresolved remote updates remain${formatUnresolvedSpans(unresolved)}`,\n );\n this.unresolved = unresolved;\n this.appliedVersion = appliedVersion;\n }\n}\n\nfunction isApplyOutcome<TVersion extends JsonObject>(\n value: CrdtApplyReturn<TVersion>,\n): value is CrdtApplyOutcome<TVersion> {\n return (\n typeof value === \"object\" &&\n value != null &&\n \"version\" in value &&\n \"complete\" in value &&\n typeof (value as { readonly complete?: unknown }).complete === \"boolean\"\n );\n}\n\nfunction normalizeApplyReturn<TVersion extends JsonObject>(\n value: CrdtApplyReturn<TVersion>,\n): CrdtApplyOutcome<TVersion> {\n if (isApplyOutcome(value)) {\n return value;\n }\n return {\n version: value,\n complete: true,\n };\n}\n\ntype TransportCursorManagerOptions<TVersion extends JsonObject> = {\n readonly adapter: Pick<\n CrdtAdapter<TVersion>,\n \"applySnapshot\" | \"applyRemoteUpdates\"\n >;\n readonly decodeSnapshot?: (\n snapshot: Uint8Array,\n continuationOffset: string,\n ) => MaybePromise<Uint8Array>;\n readonly decodeUpdateItems?: (\n items: readonly Uint8Array[],\n ) => MaybePromise<Uint8Array[]>;\n readonly saveRemoteCursor: (\n cursor: RemoteCursor<TVersion>,\n source: RemoteCursorSaveSource,\n ) => Promise<RemoteCursor<TVersion>>;\n};\n\nexport class TransportCursorManager<TVersion extends JsonObject> {\n private readonly adapter: Pick<\n CrdtAdapter<TVersion>,\n \"applySnapshot\" | \"applyRemoteUpdates\"\n >;\n private readonly decodeSnapshot?: (\n snapshot: Uint8Array,\n continuationOffset: string,\n ) => MaybePromise<Uint8Array>;\n private readonly decodeUpdateItems?: (\n items: readonly Uint8Array[],\n ) => MaybePromise<Uint8Array[]>;\n private readonly saveRemoteCursor: (\n cursor: RemoteCursor<TVersion>,\n source: RemoteCursorSaveSource,\n ) => Promise<RemoteCursor<TVersion>>;\n private readonly incompleteCursors = new WeakMap<\n RemoteCursor<TVersion>,\n readonly CrdtUnresolvedSpan[]\n >();\n\n constructor(options: TransportCursorManagerOptions<TVersion>) {\n this.adapter = options.adapter;\n this.decodeSnapshot = options.decodeSnapshot;\n this.decodeUpdateItems = options.decodeUpdateItems;\n this.saveRemoteCursor = options.saveRemoteCursor;\n }\n\n isCursorComplete(cursor: RemoteCursor<TVersion>): boolean {\n return !this.incompleteCursors.has(cursor);\n }\n\n /**\n * Marks `cursor` incomplete without an apply.\n *\n * The `CrdtApplyIncompleteError` throw path discards its candidate cursor\n * BEFORE the usual WeakMap marking, so a caller's retained cursor keeps\n * whatever completeness it had. For a caught-up live room the first\n * poisoned event typically arrives with `up_to_date=true`, so the retained\n * cursor still reads \"complete\" for the whole wedge — leaving the degraded\n * local flush unengaged and the snapshot-upload clean gate open, exactly\n * the two consumers keyed on this marking. The read loop calls this when\n * it catches the error so the room's incomplete state is visible on the\n * cursor it actually holds.\n */\n markCursorIncomplete(\n cursor: RemoteCursor<TVersion>,\n unresolved: readonly CrdtUnresolvedSpan[],\n ): void {\n this.incompleteCursors.set(cursor, unresolved);\n }\n\n async applyBootstrapPayload(\n cursor: RemoteCursor<TVersion>,\n snapshot: Uint8Array,\n updates: readonly Uint8Array[],\n nextOffset: string,\n upToDate = false,\n snapshotOffset = \"-1\",\n ): Promise<RemoteCursor<TVersion>> {\n let nextVersion = cursor.serverLowerBoundVersion;\n let complete = true;\n let unresolved: readonly CrdtUnresolvedSpan[] = [];\n if (snapshot.byteLength > 0) {\n if (\n typeof snapshotOffset !== \"string\" ||\n snapshotOffset.length === 0 ||\n snapshotOffset.length > 1024 ||\n snapshotOffset === \"now\" ||\n snapshotOffset === \"-1\"\n ) {\n throw new Error(\"bootstrap snapshot offset does not match an admitted continuation\");\n }\n const outcome = normalizeApplyReturn(\n await this.adapter.applySnapshot(\n this.decodeSnapshot == null\n ? snapshot\n : await this.decodeSnapshot(snapshot, snapshotOffset),\n ),\n );\n nextVersion = outcome.version;\n complete = outcome.complete;\n unresolved = outcome.unresolved ?? [];\n }\n const clearUpdates = await this.decodeRemoteUpdateItems(updates);\n if (clearUpdates.length > 0) {\n const outcome = normalizeApplyReturn(\n await this.adapter.applyRemoteUpdates(\n clearUpdates,\n nextVersion,\n ),\n );\n nextVersion = outcome.version;\n complete = outcome.complete;\n unresolved = outcome.unresolved ?? [];\n }\n return await this.finalizeCursor(\n {\n ...cursor,\n nextOffset,\n serverLowerBoundVersion: nextVersion,\n },\n \"bootstrap\",\n complete,\n unresolved,\n upToDate,\n );\n }\n\n async applyBootstrapItems(\n cursor: RemoteCursor<TVersion>,\n items: readonly Uint8Array[],\n nextOffset: string,\n upToDate = false,\n snapshotOffset = \"-1\",\n ): Promise<RemoteCursor<TVersion>> {\n const snapshot = items[0] ?? new Uint8Array();\n return await this.applyBootstrapPayload(\n cursor,\n snapshot,\n items.slice(1),\n nextOffset,\n upToDate,\n snapshotOffset,\n );\n }\n\n async applyRemoteItems(\n cursor: RemoteCursor<TVersion>,\n items: readonly Uint8Array[],\n nextOffset: string,\n ): Promise<RemoteCursor<TVersion>> {\n return await this.applyRemotePayload(\n cursor,\n [...items],\n nextOffset,\n \"remote\",\n undefined,\n );\n }\n\n async applyCatchupResult(\n cursor: RemoteCursor<TVersion>,\n result: ReadResult,\n onRemoteVersionApplied?: (\n previousVersion: TVersion,\n nextVersion: TVersion,\n ) => MaybePromise<void>,\n ): Promise<RemoteCursor<TVersion>> {\n return await this.applyRemotePayload(\n cursor,\n result.updates,\n result.nextOffset,\n \"remote\",\n onRemoteVersionApplied,\n result.upToDate,\n );\n }\n\n async applyLiveResult(\n cursor: RemoteCursor<TVersion>,\n requestOffset: string,\n result: {\n readonly nextOffset: string;\n readonly updates: readonly Uint8Array[];\n readonly upToDate?: boolean;\n },\n onRemoteVersionApplied?: (\n previousVersion: TVersion,\n nextVersion: TVersion,\n ) => MaybePromise<void>,\n ): Promise<RemoteCursor<TVersion>> {\n const previousVersion = cursor.serverLowerBoundVersion;\n let nextVersion = previousVersion;\n let complete = this.isCursorComplete(cursor);\n let unresolved = this.incompleteCursors.get(cursor) ?? [];\n const clearUpdates = await this.decodeRemoteUpdateItems(result.updates);\n if (clearUpdates.length > 0) {\n const outcome = normalizeApplyReturn(\n await this.adapter.applyRemoteUpdates(\n clearUpdates,\n nextVersion,\n ),\n );\n nextVersion = outcome.version;\n complete = outcome.complete;\n unresolved = outcome.unresolved ?? [];\n await onRemoteVersionApplied?.(previousVersion, nextVersion);\n }\n return await this.finalizeCursor(\n {\n ...cursor,\n nextOffset: resolveNextOffsetAfterConcurrentAdvance(\n requestOffset,\n cursor.nextOffset,\n result.nextOffset,\n ),\n serverLowerBoundVersion: nextVersion,\n },\n \"remote\",\n complete,\n unresolved,\n result.upToDate === true,\n );\n }\n\n private async applyRemotePayload(\n cursor: RemoteCursor<TVersion>,\n updates: readonly Uint8Array[],\n nextOffset: string,\n source: RemoteCursorSaveSource,\n onRemoteVersionApplied?: (\n previousVersion: TVersion,\n nextVersion: TVersion,\n ) => MaybePromise<void>,\n upToDate = false,\n ): Promise<RemoteCursor<TVersion>> {\n const previousVersion = cursor.serverLowerBoundVersion;\n let nextVersion = previousVersion;\n let complete = this.isCursorComplete(cursor);\n let unresolved = this.incompleteCursors.get(cursor) ?? [];\n const clearUpdates = await this.decodeRemoteUpdateItems(updates);\n if (clearUpdates.length > 0) {\n const outcome = normalizeApplyReturn(\n await this.adapter.applyRemoteUpdates(\n clearUpdates,\n nextVersion,\n ),\n );\n nextVersion = outcome.version;\n complete = outcome.complete;\n unresolved = outcome.unresolved ?? [];\n await onRemoteVersionApplied?.(previousVersion, nextVersion);\n }\n return await this.finalizeCursor(\n {\n ...cursor,\n nextOffset,\n serverLowerBoundVersion: nextVersion,\n },\n source,\n complete,\n unresolved,\n upToDate,\n );\n }\n\n private async finalizeCursor(\n cursor: RemoteCursor<TVersion>,\n source: RemoteCursorSaveSource,\n complete: boolean,\n unresolved: readonly CrdtUnresolvedSpan[],\n upToDate: boolean,\n ): Promise<RemoteCursor<TVersion>> {\n if (!complete) {\n if (upToDate) {\n throw new CrdtApplyIncompleteError(\n unresolved,\n cursor.serverLowerBoundVersion,\n );\n }\n this.incompleteCursors.set(cursor, unresolved);\n return cursor;\n }\n return await this.saveRemoteCursor(cursor, source);\n }\n\n private async decodeRemoteUpdateItems(\n items: readonly Uint8Array[],\n ): Promise<Uint8Array[]> {\n if (items.length === 0) {\n return [];\n }\n return this.decodeUpdateItems == null\n ? [...items]\n : await this.decodeUpdateItems(items);\n }\n}\n","import {\n jsonObjectEquals,\n parseSnapshotByteOffset,\n type JoinState,\n type SnapshotUploadRuntimeConfig,\n} from \"./transport-internal\";\nimport type { RemoteCursor } from \"./checkpoint\";\nimport type { CrdtAdapter, JsonObject, MaybePromise } from \"./types\";\n\nexport type TransportSnapshotState = {\n observedForeignWriter: boolean;\n taskScheduled: boolean;\n taskNeedsRerun: boolean;\n debounceTimer?: ReturnType<typeof setTimeout>;\n};\n\nexport function createTransportSnapshotState(): TransportSnapshotState {\n return {\n observedForeignWriter: false,\n taskScheduled: false,\n taskNeedsRerun: false,\n };\n}\n\ntype TransportSnapshotManagerOptions<TVersion extends JsonObject> = {\n readonly adapter: Pick<CrdtAdapter<TVersion>, \"exportSnapshot\">;\n readonly config?: SnapshotUploadRuntimeConfig;\n readonly encodeSnapshot?: (\n snapshot: Uint8Array,\n continuationOffset: string,\n ) => MaybePromise<Uint8Array>;\n readonly readSnapshotOffsetValue: () => Promise<string | null>;\n readonly putSnapshotAt: (\n snapshotOffset: string,\n snapshot: Uint8Array,\n ) => Promise<void>;\n readonly enqueueExclusive: <T>(task: () => MaybePromise<T>) => Promise<T>;\n readonly logDebug: (\n message: string,\n detail?: Record<string, unknown>,\n ) => void;\n readonly logError: (\n message: string,\n error?: unknown,\n detail?: Record<string, unknown>,\n ) => void;\n readonly isCurrentState: (state: JoinState<TVersion>) => boolean;\n readonly isCursorComplete: (cursor: RemoteCursor<TVersion>) => boolean;\n};\n\n/**\n * Manages debounce and upload gating for remote snapshots.\n *\n * All \"is this state still current / closed / reconnecting / blocked by a\n * foreign writer?\" checks live here so the runtime does not duplicate them.\n */\nexport class TransportSnapshotManager<TVersion extends JsonObject> {\n private readonly adapter: Pick<CrdtAdapter<TVersion>, \"exportSnapshot\">;\n private readonly config?: SnapshotUploadRuntimeConfig;\n private readonly encodeSnapshot?: (\n snapshot: Uint8Array,\n continuationOffset: string,\n ) => MaybePromise<Uint8Array>;\n private readonly readSnapshotOffsetValue: () => Promise<string | null>;\n private readonly putSnapshotAt: (\n snapshotOffset: string,\n snapshot: Uint8Array,\n ) => Promise<void>;\n private readonly enqueueExclusive: <T>(\n task: () => MaybePromise<T>,\n ) => Promise<T>;\n private readonly logDebug: (\n message: string,\n detail?: Record<string, unknown>,\n ) => void;\n private readonly logError: (\n message: string,\n error?: unknown,\n detail?: Record<string, unknown>,\n ) => void;\n private readonly isCurrentState: (state: JoinState<TVersion>) => boolean;\n private readonly isCursorComplete: (\n cursor: RemoteCursor<TVersion>,\n ) => boolean;\n\n constructor(options: TransportSnapshotManagerOptions<TVersion>) {\n this.adapter = options.adapter;\n this.config = options.config;\n this.encodeSnapshot = options.encodeSnapshot;\n this.readSnapshotOffsetValue = options.readSnapshotOffsetValue;\n this.putSnapshotAt = options.putSnapshotAt;\n this.enqueueExclusive = options.enqueueExclusive;\n this.logDebug = options.logDebug;\n this.logError = options.logError;\n this.isCurrentState = options.isCurrentState;\n this.isCursorComplete = options.isCursorComplete;\n }\n\n dispose(state: JoinState<TVersion>): void {\n this.clearDebounceTimer(state.snapshot);\n }\n\n observeRemoteVersion(\n state: JoinState<TVersion>,\n previousVersion: TVersion,\n nextVersion: TVersion,\n ): void {\n const snapshot = state.snapshot;\n if (snapshot.debounceTimer == null) {\n return;\n }\n if (!jsonObjectEquals(previousVersion, nextVersion)) {\n snapshot.observedForeignWriter = true;\n }\n }\n\n schedule(state: JoinState<TVersion>): void {\n const config = this.config;\n if (config == null || state.closed) {\n return;\n }\n const snapshot = state.snapshot;\n this.clearDebounceTimer(snapshot);\n snapshot.observedForeignWriter = false;\n snapshot.debounceTimer = setTimeout(() => {\n void this.handleDebounceExpiry(state);\n }, config.debounceMs);\n }\n\n private clearDebounceTimer(snapshot: TransportSnapshotState): void {\n if (snapshot.debounceTimer != null) {\n clearTimeout(snapshot.debounceTimer);\n snapshot.debounceTimer = undefined;\n }\n }\n\n private async handleDebounceExpiry(state: JoinState<TVersion>): Promise<void> {\n const snapshot = state.snapshot;\n snapshot.debounceTimer = undefined;\n if (this.getRunnableConfig(state) == null) {\n return;\n }\n if (snapshot.taskScheduled) {\n snapshot.taskNeedsRerun = true;\n return;\n }\n snapshot.taskScheduled = true;\n void this.enqueueExclusive(async () => {\n await this.maybeUpload(state);\n })\n .catch((error) => {\n this.logError(\"snapshot upload task failed\", error);\n })\n .finally(() => {\n snapshot.taskScheduled = false;\n if (snapshot.taskNeedsRerun && !state.closed) {\n snapshot.taskNeedsRerun = false;\n void this.handleDebounceExpiry(state);\n }\n });\n }\n\n private async maybeUpload(state: JoinState<TVersion>): Promise<void> {\n const config = await this.resolveUploadConfig(state);\n if (config == null) {\n return;\n }\n if (!this.isCursorComplete(state.cursor)) {\n this.logDebug(\"skip snapshot upload: current cursor is incomplete\", {\n offset: state.cursor.nextOffset,\n });\n return;\n }\n const tailBytes = parseSnapshotByteOffset(state.cursor.nextOffset);\n if (tailBytes == null) {\n this.logDebug(\"skip snapshot upload: non-numeric offset token\", {\n offset: state.cursor.nextOffset,\n });\n return;\n }\n try {\n const remoteSnapshotOffset = await this.readSnapshotOffsetValue();\n const snapshotBytes = parseSnapshotByteOffset(remoteSnapshotOffset);\n if (snapshotBytes == null) {\n this.logDebug(\"skip snapshot upload: non-numeric snapshot offset\", {\n offset: remoteSnapshotOffset,\n });\n return;\n }\n if (tailBytes <= snapshotBytes) {\n return;\n }\n if (tailBytes - snapshotBytes < config.minBytesSinceRemoteSnapshot) {\n return;\n }\n const snapshot = this.adapter.exportSnapshot();\n await this.putSnapshotAt(\n state.cursor.nextOffset,\n this.encodeSnapshot == null\n ? snapshot\n : await this.encodeSnapshot(snapshot, state.cursor.nextOffset),\n );\n } catch (error) {\n this.logError(\"snapshot upload failed\", error);\n }\n }\n\n private getRunnableConfig(\n state: JoinState<TVersion>,\n ): SnapshotUploadRuntimeConfig | undefined {\n const config = this.config;\n if (config == null || state.closed || !this.isCurrentState(state)) {\n return undefined;\n }\n if (\n state.snapshot.observedForeignWriter ||\n state.status === \"reconnecting\" ||\n state.status === \"disconnected\" ||\n state.status === \"error\"\n ) {\n return undefined;\n }\n return config;\n }\n\n private async resolveUploadConfig(\n state: JoinState<TVersion>,\n ): Promise<SnapshotUploadRuntimeConfig | undefined> {\n const config = this.getRunnableConfig(state);\n if (config == null) {\n return undefined;\n }\n if (!(await this.canUpload(config))) {\n return undefined;\n }\n return config;\n }\n\n private async canUpload(\n config: SnapshotUploadRuntimeConfig,\n ): Promise<boolean> {\n try {\n return Boolean(await config.canUpload());\n } catch (error) {\n this.logError(\"snapshot upload permission check failed\", error);\n return false;\n }\n }\n}\n","import { Deque } from \"./deque\";\nimport type { RemoteCursor } from \"./checkpoint\";\nimport type { JoinState, LiveReadMode, ReadSubStatus, WriteSubStatus } from \"./transport-internal\";\nimport {\n createTransportSnapshotState,\n type TransportSnapshotManager,\n} from \"./transport-snapshot\";\nimport type { JsonObject } from \"./types\";\n\nexport function createJoinState<TVersion extends JsonObject>(\n cursor: RemoteCursor<TVersion>,\n liveMode: LiveReadMode = \"sse\",\n): JoinState<TVersion> {\n return {\n refCount: 0,\n closed: false,\n status: \"connecting\",\n readSubStatus: \"connecting\" as ReadSubStatus,\n writeSubStatus: \"ok\" as WriteSubStatus,\n statusListeners: new Set(),\n pendingLocal: new Deque(),\n flushingLocal: false,\n retryAttempt: 0,\n incompleteRecoveryAttempt: 0,\n retrySleepController: new AbortController(),\n readLoopRunning: false,\n readErrorRetryAttempt: 0,\n writeErrorRetryAttempt: 0,\n flushHeadBatchOnly: false,\n cursor,\n liveMode,\n snapshot: createTransportSnapshotState(),\n started: Promise.resolve(),\n syncWaiters: [],\n initialSyncFailed: false,\n };\n}\n\ntype DisposeJoinStateOptions<TVersion extends JsonObject> = {\n readonly snapshotManager: Pick<TransportSnapshotManager<TVersion>, \"dispose\">;\n readonly onDisposed?: () => void;\n};\n\nexport async function disposeJoinState<TVersion extends JsonObject>(\n state: JoinState<TVersion>,\n options: DisposeJoinStateOptions<TVersion>,\n): Promise<void> {\n state.closed = true;\n options.snapshotManager.dispose(state);\n if (state.readRestartTimer != null) {\n clearTimeout(state.readRestartTimer);\n state.readRestartTimer = undefined;\n }\n if (state.writeRetryTimer != null) {\n clearTimeout(state.writeRetryTimer);\n state.writeRetryTimer = undefined;\n }\n const requestController = state.requestController;\n state.requestController = undefined;\n const unsubscribeLocal = state.unsubscribeLocal;\n state.unsubscribeLocal = undefined;\n requestController?.abort();\n state.retrySleepController.abort();\n unsubscribeLocal?.();\n const disposedError = new Error(\"subscription closed\");\n for (const waiter of state.syncWaiters.splice(0)) {\n waiter.reject(disposedError);\n }\n options.onDisposed?.();\n}\n","import {\n composeBeforeRemoteCursorSaveHooks,\n createInitialRemoteCursor,\n InMemoryRemoteCursorStore,\n persistRemoteCursor,\n type BeforeRemoteCursorSaveHook,\n type RemoteCursor,\n type RemoteCursorSaveSource,\n type RemoteCursorStore,\n} from \"./checkpoint\";\nimport {\n computeRetryDelay,\n DEFAULT_RECONNECT_CONFIG,\n MIN_SUCCESSFUL_SSE_DURATION_MS,\n type ReconnectConfig,\n} from \"./reconnect\";\nimport { Deque } from \"./deque\";\nimport {\n normalizePayloadProtection,\n type PayloadProtectionRuntime,\n} from \"./payload-protection\";\nimport { StreamsTransportClient } from \"./transport-client\";\nimport {\n CrdtApplyIncompleteError,\n TransportCursorManager,\n} from \"./transport-cursor\";\nimport {\n createJoinState as createTransportJoinState,\n disposeJoinState as disposeTransportJoinState,\n} from \"./transport-room\";\nimport { TransportSnapshotManager } from \"./transport-snapshot\";\nimport {\n classifyFetchError,\n createProducerId,\n HttpError,\n InitialSyncTimeoutError,\n isAuthOrProtocolError,\n isPayloadTooLargeError,\n isReadNoProgress,\n isStreamNotFoundError,\n LocalAppendFailedError,\n mergeAbortSignals,\n normalizeAuthProvider,\n normalizeSnapshotCodec,\n normalizeSnapshotUploadConfig,\n sleep,\n toTransportError,\n type CursorSyncResult,\n type JoinState,\n type JoinSyncResult,\n type LiveReadMode,\n type LocalExportResult,\n type ProducerSession,\n type ReadSubStatus,\n type WriteSubStatus,\n} from \"./transport-internal\";\nimport type {\n CrdtAdapter,\n CrdtUpdateBatch,\n JsonObject,\n LocalSyncStats,\n MaybePromise,\n Result,\n StreamsCrdtLike,\n StreamsCrdtOptions,\n TransportCatchupParams,\n TransportCatchupSuccess,\n TransportCreateStreamSuccess,\n TransportDeleteStreamSuccess,\n TransportError,\n TransportJoinParams,\n TransportRoomStatus,\n TransportSnapshotUploadSuccess,\n TransportSubscription,\n TransportSyncSuccess,\n WriteOnlyAppendResult,\n} from \"./types\";\n\nconst MIN_RECONNECT_DELAY_MS = 1_000;\n\ntype ProducerAck = {\n readonly epoch: number;\n readonly seq: number;\n};\n\ntype TentativeLocalAppend<TVersion extends JsonObject> = {\n readonly cursor: RemoteCursor<TVersion>;\n readonly producerAck: ProducerAck;\n};\n\ntype FrozenLocalAppendSource = \"direct\" | \"pending\" | \"write-only\";\n\ntype FrozenLocalAppend<TVersion extends JsonObject> = {\n readonly source: FrozenLocalAppendSource;\n readonly baseCursor: RemoteCursor<TVersion>;\n readonly batch: CrdtUpdateBatch<TVersion>;\n readonly pendingCount?: number;\n readonly wireUpdates: readonly Uint8Array[];\n producerSession: ProducerSession;\n acknowledged?: TentativeLocalAppend<TVersion>;\n};\n\ntype DurableLocalAppend<TVersion extends JsonObject> = {\n readonly cursor: RemoteCursor<TVersion>;\n readonly pendingCount?: number;\n readonly streamCursor?: string;\n};\n\nfunction reconnectDelayWithFloor(\n attempt: number,\n config: ReconnectConfig,\n): number {\n return Math.max(computeRetryDelay(attempt, config), MIN_RECONNECT_DELAY_MS);\n}\n\nfunction fastSseCloseDelay(startedAtMs: number): number {\n const elapsedMs = Math.max(0, Date.now() - startedAtMs);\n if (elapsedMs >= MIN_SUCCESSFUL_SSE_DURATION_MS) {\n return 0;\n }\n return Math.max(\n MIN_RECONNECT_DELAY_MS,\n MIN_SUCCESSFUL_SSE_DURATION_MS - elapsedMs,\n );\n}\n\n/**\n * Durable Streams transport runtime bound to one stream URL and one local CRDT adapter.\n */\nexport class StreamsCrdt<\n TVersion extends JsonObject,\n> implements StreamsCrdtLike<TVersion> {\n readonly streamUrl: string;\n readonly adapter: CrdtAdapter<TVersion>;\n readonly remoteCursorStore: RemoteCursorStore<TVersion>;\n\n private readonly createStreamIfMissing: boolean;\n private readonly client: StreamsTransportClient<TVersion>;\n private readonly cursorManager: TransportCursorManager<TVersion>;\n private readonly volatileCursorManager: TransportCursorManager<TVersion>;\n private readonly decodeSnapshot?: (\n snapshot: Uint8Array,\n continuationOffset: string,\n ) => MaybePromise<Uint8Array>;\n private readonly encodeSnapshot?: (\n snapshot: Uint8Array,\n continuationOffset: string,\n ) => MaybePromise<Uint8Array>;\n private readonly decodeUpdateItems?: (\n items: readonly Uint8Array[],\n ) => MaybePromise<Uint8Array[]>;\n private readonly encodeUpdateItems?: (\n updates: readonly Uint8Array[],\n ) => Promise<readonly Uint8Array[]>;\n private readonly measureUpdateItemsByteLength?: (\n updates: readonly Uint8Array[],\n ) => MaybePromise<number>;\n private readonly reconnectConfig: ReconnectConfig;\n private readonly debug: boolean;\n private readonly beforeRemoteCursorSave?: BeforeRemoteCursorSaveHook<TVersion>;\n private readonly snapshotManager: TransportSnapshotManager<TVersion>;\n private readonly producerId = createProducerId();\n private producerEpoch = 0;\n private nextProducerSeq = 0;\n // Local append invariant:\n // - Before an append request can leave the client, freeze the exact logical\n // batch, encoded wire items, pending queue count, and producer session.\n // - Retry that frozen batch until the server explicitly rejects the session\n // as not appended (seq-gap/stale-epoch), or until the append is locally\n // finalized. Never merge newer pending updates into the same producer seq.\n // - Advance producer seq only after the local durability boundary has\n // completed. In read-capable mode that boundary includes catching up from\n // the pre-append offset and saving that cursor, not saving the append tail.\n // An acknowledged-but-not-finalized append is replayed by finalizing this\n // frozen record, not by sending a new batch.\n // - If a write-only append is abandoned while entering read-capable mode,\n // fence that maybe-sent producer session by moving to the next epoch.\n private frozenLocalAppend?: FrozenLocalAppend<TVersion>;\n private committedPendingCount?: number;\n private committedLocalStreamCursor?: string;\n private readonly pendingWriteOnly = new Deque<CrdtUpdateBatch<TVersion>>();\n private writeOnlyCursor: RemoteCursor<TVersion>;\n private unsubscribeWriteOnlyLocal?: () => void;\n private writeOnlyDisabledReason?: string;\n private joinState?: JoinState<TVersion>;\n private opQueue: Promise<void> = Promise.resolve();\n /** Wall-clock ms of the last durably committed local append, if any. */\n private lastLocalAppendAtMs?: number;\n\n /**\n * Internal catch-up result used when callers need to handle 410 by\n * preserving local state before rebuilding through bootstrap.\n */\n private static readonly GONE_CATCHUP = { kind: \"gone\" as const };\n\n /**\n * Creates a transport for one stream URL and one local adapter instance.\n */\n constructor(options: StreamsCrdtOptions<TVersion>) {\n this.streamUrl = options.streamUrl;\n this.adapter = options.adapter;\n const authProvider = normalizeAuthProvider(options.auth);\n this.createStreamIfMissing = options.createStreamIfMissing ?? false;\n const fetchImpl = options.fetch ?? globalThis.fetch.bind(globalThis);\n this.remoteCursorStore =\n options.remoteCursorStore ?? new InMemoryRemoteCursorStore<TVersion>();\n this.reconnectConfig = {\n ...DEFAULT_RECONNECT_CONFIG,\n ...options.reconnectConfig,\n };\n this.debug = options.debug ?? false;\n this.beforeRemoteCursorSave = composeBeforeRemoteCursorSaveHooks(\n options.beforeRemoteCursorSave,\n ...(options.beforeRemoteCursorSaveHooks ?? []),\n );\n const snapshotCodec = normalizeSnapshotCodec(options.snapshotCodec);\n if (options.payloadProtectionRequired === true && options.e2ee == null) {\n throw new Error(\n \"payloadProtectionRequired is true but this room has no e2ee config\",\n );\n }\n const payloadProtection = normalizePayloadProtection(\n options.e2ee,\n \"e2ee\",\n );\n if (\n payloadProtection?.writePolicy === \"plaintext\" &&\n options.snapshotUpload != null &&\n options.snapshotUpload.enabled !== false\n ) {\n throw new Error(\n \"snapshotUpload is not supported when e2ee.writePolicy is plaintext\",\n );\n }\n const snapshotUploadConfig = normalizeSnapshotUploadConfig(\n options.snapshotUpload,\n );\n this.decodeSnapshot = this.composeSnapshotDecoder(\n payloadProtection,\n snapshotCodec?.decompress,\n );\n this.encodeSnapshot = this.composeSnapshotEncoder(\n payloadProtection,\n snapshotCodec?.compress,\n );\n this.decodeUpdateItems =\n payloadProtection == null\n ? undefined\n : (items) => payloadProtection.decodeUpdateItems(items);\n this.encodeUpdateItems =\n payloadProtection == null\n ? undefined\n : (updates) => payloadProtection.encodeUpdateItems(updates);\n this.measureUpdateItemsByteLength =\n payloadProtection == null\n ? undefined\n : (updates) => payloadProtection.measureEncodedUpdateItemsByteLength(updates);\n this.client = new StreamsTransportClient({\n streamUrl: this.streamUrl,\n adapter: this.adapter,\n streamTtlSeconds: options.streamTtlSeconds,\n authProvider,\n fetchImpl,\n reconnectConfig: this.reconnectConfig,\n shardUrls: options.shardUrls,\n });\n this.cursorManager = this.createCursorManager(\n this.adapter,\n (cursor, source) => this.saveRemoteCursor(cursor, source),\n );\n this.volatileCursorManager = this.createCursorManager(\n this.adapter,\n async (cursor) => cursor,\n );\n this.snapshotManager = new TransportSnapshotManager({\n adapter: this.adapter,\n config: snapshotUploadConfig,\n encodeSnapshot: this.encodeSnapshot,\n readSnapshotOffsetValue: () => this.client.readSnapshotOffsetValue(),\n putSnapshotAt: (snapshotOffset, snapshot) =>\n this.client.putSnapshotAt(snapshotOffset, snapshot),\n enqueueExclusive: (task) => this.enqueueExclusive(task),\n logDebug: (message, detail) => this.logDebug(message, detail),\n logError: (message, error, detail) =>\n this.logError(message, error, detail),\n isCurrentState: (state) => this.joinState === state,\n isCursorComplete: (cursor) => this.cursorManager.isCursorComplete(cursor),\n });\n this.writeOnlyCursor = this.createInitialCursor();\n this.unsubscribeWriteOnlyLocal = this.adapter.subscribeLocalUpdates((batch) => {\n if (this.writeOnlyDisabledReason != null) {\n return;\n }\n this.pendingWriteOnly.pushBack(batch);\n });\n }\n\n private composeSnapshotDecoder(\n payloadProtection: PayloadProtectionRuntime | undefined,\n decompressSnapshot: ((snapshot: Uint8Array) => MaybePromise<Uint8Array>) | undefined,\n ):\n | ((snapshot: Uint8Array, continuationOffset: string) => MaybePromise<Uint8Array>)\n | undefined {\n if (payloadProtection == null && decompressSnapshot == null) {\n return undefined;\n }\n return async (snapshot, continuationOffset) => {\n const clearSnapshot =\n payloadProtection == null\n ? snapshot\n : await payloadProtection.decodeSnapshot(snapshot, continuationOffset);\n return decompressSnapshot == null\n ? clearSnapshot\n : await decompressSnapshot(clearSnapshot);\n };\n }\n\n private composeSnapshotEncoder(\n payloadProtection: PayloadProtectionRuntime | undefined,\n compressSnapshot: ((snapshot: Uint8Array) => MaybePromise<Uint8Array>) | undefined,\n ):\n | ((snapshot: Uint8Array, continuationOffset: string) => MaybePromise<Uint8Array>)\n | undefined {\n if (payloadProtection == null && compressSnapshot == null) {\n return undefined;\n }\n return async (snapshot, continuationOffset) => {\n const compressedSnapshot =\n compressSnapshot == null\n ? snapshot\n : await compressSnapshot(snapshot);\n return payloadProtection == null\n ? compressedSnapshot\n : await payloadProtection.encodeSnapshot(compressedSnapshot, continuationOffset);\n };\n }\n\n /**\n * Stops any active live join and releases transport-owned state.\n */\n async close(): Promise<void> {\n this.disableWriteOnlyMode(\"transport closed\");\n const state = this.joinState;\n if (state != null) {\n await this.disposeJoinState(state);\n }\n }\n\n /**\n * Resets the retry counter and immediately attempts to reconnect.\n *\n * Use this after device wake or network recovery to skip any pending\n * backoff delay. No-op when the room is already fully healthy (both\n * read and write sub-statuses are \"ok\") or no join is active.\n *\n * If the room reached `\"disconnected\"` or `\"error\"` status, this\n * restarts the affected loop(s).\n */\n rejoin(): void {\n const state = this.joinState;\n if (state == null || state.closed) {\n return;\n }\n\n // Handle failed initial sync recovery: restart the entire startJoin flow.\n if (state.initialSyncFailed) {\n state.initialSyncFailed = false;\n state.lastError = undefined;\n state.retryAttempt = 0;\n state.retrySleepController.abort();\n state.retrySleepController = new AbortController();\n state.readSubStatus = \"connecting\";\n state.writeSubStatus = \"ok\";\n this.recomputeJoinStatus(state);\n state.started = this.startJoin(state);\n // If startJoin fails again, decide based on retryability:\n // retryable → mark for another rejoin(); non-retryable → dispose.\n state.started.catch((error) => {\n if (state.closed) {\n return;\n }\n const transportError = toTransportError(error);\n if (transportError.retryable) {\n state.initialSyncFailed = true;\n state.lastError = transportError;\n } else {\n void this.disposeJoinState(state);\n }\n });\n return;\n }\n\n // Both loops are healthy — nothing to do.\n // Also no-op when initial sync is in progress (\"connecting\") to avoid\n // aborting an in-flight startJoin() from a prior rejoin() call.\n if (\n (state.readSubStatus === \"ok\" || state.readSubStatus === \"connecting\") &&\n state.writeSubStatus === \"ok\"\n ) {\n return;\n }\n state.retryAttempt = 0;\n state.readErrorRetryAttempt = 0;\n state.writeErrorRetryAttempt = 0;\n this.clearReadRestartTimer(state);\n this.clearWriteRetryTimer(state);\n state.retrySleepController.abort();\n state.retrySleepController = new AbortController();\n\n // Abort the current read request so the loop restarts immediately.\n const previousController = state.requestController;\n state.requestController = new AbortController();\n previousController?.abort();\n\n const needsReadRestart =\n state.readSubStatus === \"disconnected\" ||\n state.readSubStatus === \"error\";\n if (needsReadRestart) {\n this.setReadSubStatus(state, \"reconnecting\");\n void this.runReadLoop(state);\n }\n // For \"ok\" and \"reconnecting\", aborting the request controller\n // causes the current read loop iteration to restart.\n\n if (state.writeSubStatus === \"error\") {\n state.writeSubStatus =\n state.pendingLocal.length > 0 ? \"reconnecting\" : \"ok\";\n this.recomputeJoinStatus(state);\n if (state.pendingLocal.length > 0) {\n void this.flushPendingLocal(state);\n }\n }\n }\n\n /**\n * Reports current local upload health for this transport.\n *\n * In join mode this reflects the live room's pending queue; in write-only\n * mode it reflects the write-only queue. Intended for downstream tooling\n * (e.g. a CLI showing \"N updates not yet uploaded\" plus the blocking\n * error) without needing a `TransportSubscription` reference.\n */\n getLocalSyncStats(): LocalSyncStats {\n const state = this.joinState;\n const pendingLocalCount =\n state != null && !state.closed\n ? state.pendingLocal.length\n : this.writeOnlyDisabledReason == null\n ? this.pendingWriteOnly.length\n : 0;\n return {\n pendingLocalCount,\n ...(this.lastLocalAppendAtMs !== undefined && {\n lastLocalAppendAtMs: this.lastLocalAppendAtMs,\n }),\n ...(state?.lastError !== undefined && { lastError: state.lastError }),\n ...(state?.lastWriteError !== undefined && {\n lastWriteError: state.lastWriteError,\n }),\n };\n }\n\n /**\n * Reads the latest remote snapshot offset, if the server exposes one.\n */\n async getSnapshotOffset(): Promise<Result<string | null, TransportError>> {\n try {\n return {\n ok: true,\n value: await this.client.readSnapshotOffsetValue(),\n };\n } catch (error) {\n return { ok: false, error: toTransportError(error) };\n }\n }\n\n /**\n * Uploads a full adapter snapshot at the current remote tail.\n *\n * @internal Intended for end-to-end tests that must deterministically create\n * a remote snapshot without waiting for the best-effort join() debounce.\n */\n async uploadSnapshotForTesting(): Promise<\n Result<TransportSnapshotUploadSuccess, TransportError>\n > {\n try {\n await this.adapter.flushLocalExports?.();\n const cursor = await this.syncOnce(\"sync\");\n const rawSnapshot = this.adapter.exportSnapshot();\n const snapshot =\n this.encodeSnapshot == null\n ? rawSnapshot\n : await this.encodeSnapshot(rawSnapshot, cursor.nextOffset);\n await this.client.putSnapshotAt(cursor.nextOffset, snapshot);\n return {\n ok: true,\n value: {\n snapshotOffset: cursor.nextOffset,\n snapshotByteLength: snapshot.byteLength,\n },\n };\n } catch (error) {\n return { ok: false, error: toTransportError(error) };\n }\n }\n\n /**\n * Explicitly creates the target stream.\n */\n async createStream(): Promise<\n Result<TransportCreateStreamSuccess, TransportError>\n > {\n try {\n const created = await this.client.createStreamRequest();\n return { ok: true, value: { created } };\n } catch (error) {\n return { ok: false, error: toTransportError(error) };\n }\n }\n\n /**\n * Deletes the target stream and clears persisted replay state when supported by the cursor store.\n */\n async deleteStream(): Promise<\n Result<TransportDeleteStreamSuccess, TransportError>\n > {\n try {\n await this.close();\n const deleted = await this.enqueueExclusive(async () => {\n const deleted = await this.client.deleteStreamRequest();\n await this.remoteCursorStore.delete?.(this.streamUrl);\n this.producerEpoch = 0;\n this.nextProducerSeq = 0;\n this.frozenLocalAppend = undefined;\n this.committedPendingCount = undefined;\n this.committedLocalStreamCursor = undefined;\n return deleted;\n });\n return { ok: true, value: { deleted } };\n } catch (error) {\n return { ok: false, error: toTransportError(error) };\n }\n }\n\n /**\n * Runs one bootstrap/catch-up cycle without entering live mode.\n */\n async sync(): Promise<\n Result<TransportSyncSuccess<TVersion>, TransportError>\n > {\n try {\n this.disableWriteOnlyMode(\n \"appendWriteOnly cannot be used after sync() on the same transport\",\n );\n const cursor = await this.syncOnce(\"sync\");\n return { ok: true, value: { cursor } };\n } catch (error) {\n return { ok: false, error: toTransportError(error) };\n }\n }\n\n /**\n * Pulls remote updates into the local CRDT without intentionally appending\n * new local updates.\n *\n * Healthy live reads already catch up continuously, so an active `join()`\n * returns immediately unless `force: true` is passed. Reconnecting or\n * terminal live reads perform one immediate non-SSE offset read and then\n * nudge the room to reconnect.\n */\n async catchup(\n params: TransportCatchupParams = {},\n ): Promise<Result<TransportCatchupSuccess<TVersion>, TransportError>> {\n try {\n this.disableWriteOnlyMode(\n \"appendWriteOnly cannot be used after catchup() on the same transport\",\n );\n\n const state = this.joinState;\n if (state != null && !state.closed) {\n const cursor = await this.catchupJoinState(\n state,\n params,\n state.requestController?.signal,\n );\n return { ok: true, value: { cursor } };\n }\n\n const caughtUp = await this.resolvePullOnlyRemoteState(\"catchup\");\n return { ok: true, value: { cursor: caughtUp.cursor } };\n } catch (error) {\n return { ok: false, error: toTransportError(error) };\n }\n }\n\n /**\n * Appends local CRDT updates without reading the stream first.\n *\n * Use this for write-only actors whose auth token may POST to the stream but\n * may not bootstrap, catch up, or join live reads. The method does not save a\n * RemoteCursorStore cursor and does not apply remote updates.\n */\n async appendWriteOnly(): Promise<\n Result<WriteOnlyAppendResult<TVersion>, TransportError>\n > {\n return await this.enqueueExclusive(async () => {\n try {\n if (this.writeOnlyDisabledReason != null) {\n throw new Error(this.writeOnlyDisabledReason);\n }\n await this.adapter.flushLocalExports?.();\n const drained = await this.drainPendingWriteOnlyBatch();\n if (drained == null) {\n return {\n ok: true,\n value: {\n appended: false,\n localVersion: this.writeOnlyCursor.serverLowerBoundVersion,\n },\n };\n }\n\n const appended = await this.appendLocalBatchRemote(\n this.writeOnlyCursor,\n drained.batch,\n drained.count,\n \"write-only\",\n );\n const committedCount = appended.pendingCount ?? drained.count;\n for (let i = 0; i < committedCount; i += 1) {\n this.pendingWriteOnly.popFront();\n }\n this.writeOnlyCursor = appended.cursor;\n this.commitProducerAck(appended.producerAck);\n return {\n ok: true,\n value: {\n appended: true,\n nextOffset: appended.cursor.nextOffset,\n localVersion: appended.cursor.serverLowerBoundVersion,\n },\n };\n } catch (error) {\n return { ok: false, error: toTransportError(error) };\n }\n });\n }\n\n /**\n * Replays remote state, flushes queued local updates, then enters live read mode.\n */\n async join(\n params?: TransportJoinParams,\n ): Promise<Result<TransportSubscription, TransportError>> {\n this.disableWriteOnlyMode(\n \"appendWriteOnly cannot be used after join() on the same transport\",\n );\n const existing = this.joinState;\n if (existing != null) {\n // If initial sync failed but state was kept alive, return a subscription\n // immediately so callers can observe the error status and use rejoin().\n if (existing.initialSyncFailed && !existing.closed) {\n return { ok: true, value: this.createSubscription(existing, params) };\n }\n try {\n await existing.started;\n return { ok: true, value: this.createSubscription(existing, params) };\n } catch (error) {\n return { ok: false, error: toTransportError(error) };\n }\n }\n\n const initialCursor = createInitialRemoteCursor<TVersion>({\n streamUrl: this.streamUrl,\n nextOffset: \"-1\",\n serverLowerBoundVersion: this.adapter.emptyVersion(),\n });\n const state = createTransportJoinState(initialCursor);\n // Register the status listener early so it observes the \"connecting\" →\n // \"error\" transition if initial sync fails.\n const statusListener = params?.onStatusChange;\n if (statusListener != null) {\n state.statusListeners.add(statusListener);\n }\n state.started = this.startJoin(state);\n this.joinState = state;\n try {\n await state.started;\n return { ok: true, value: this.createSubscription(state, params) };\n } catch (error) {\n const transportError = toTransportError(error);\n // Keep state alive on retryable failure so rejoin() can recover.\n if (transportError.retryable && !state.closed) {\n state.initialSyncFailed = true;\n state.lastError = transportError;\n // Clean up the early status listener since no subscription is\n // returned. Without this, repeated failed join() calls would\n // accumulate stale callbacks.\n if (statusListener != null) {\n state.statusListeners.delete(statusListener);\n }\n return { ok: false, error: transportError };\n }\n await this.disposeJoinState(state);\n return { ok: false, error: transportError };\n }\n }\n\n private createSubscription(\n state: JoinState<TVersion>,\n params?: TransportJoinParams,\n ): TransportSubscription {\n // Captured for object-literal getters below, whose `this` is the\n // subscription object rather than the transport.\n const self = this;\n state.refCount += 1;\n const statusListener = params?.onStatusChange;\n if (statusListener != null) {\n // The listener may already have been registered in join() before\n // startJoin() was awaited. Only emit the initial status if this\n // is a fresh registration to avoid a duplicate \"joined\" callback.\n const alreadyRegistered = state.statusListeners.has(statusListener);\n if (!alreadyRegistered) {\n state.statusListeners.add(statusListener);\n statusListener(state.status as TransportRoomStatus);\n }\n }\n let unsubscribed = false;\n return {\n unsubscribe: () => {\n if (unsubscribed) {\n return;\n }\n unsubscribed = true;\n if (statusListener != null) {\n state.statusListeners.delete(statusListener);\n }\n state.refCount = Math.max(0, state.refCount - 1);\n if (state.refCount !== 0 || state.closed) {\n return;\n }\n void this.disposeJoinState(state);\n },\n get connected() {\n return state.status === \"joined\";\n },\n get status(): TransportRoomStatus {\n // Map internal \"connecting\" to \"reconnecting\" for the public API.\n // The internal distinction exists for computeOverallStatus, but\n // callers should see a valid TransportRoomStatus value.\n return state.status === \"connecting\"\n ? \"reconnecting\"\n : state.status;\n },\n get lastError() {\n return state.lastError;\n },\n get pendingLocalCount() {\n // Batches are only popped after a durable append, so this includes\n // any batch currently in flight.\n return state.pendingLocal.length;\n },\n get lastLocalAppendAtMs() {\n return self.lastLocalAppendAtMs;\n },\n get lastWriteError() {\n return state.lastWriteError;\n },\n onStatusChange: (listener) => {\n state.statusListeners.add(listener);\n listener(state.status as TransportRoomStatus);\n return () => {\n state.statusListeners.delete(listener);\n };\n },\n waitUntilSynced: async () => {\n // Reject immediately if initial sync failed and has not recovered.\n if (state.initialSyncFailed) {\n const msg = state.lastError?.code\n ? `initial sync failed: ${state.lastError.code}`\n : \"initial sync failed\";\n throw new Error(msg);\n }\n // If initial sync is still in progress (e.g. rejoin() just\n // restarted startJoin()), wait for it before checking the\n // pending queue. Local edits are not moved into pendingLocal\n // until startJoin() exports them. Wrap to produce a structured\n // error consistent with the initialSyncFailed path above.\n if (state.readSubStatus === \"connecting\") {\n try {\n await state.started;\n } catch (error) {\n const te = toTransportError(error);\n throw new Error(\n te.code\n ? `initial sync failed: ${te.code}`\n : \"initial sync failed\",\n );\n }\n }\n // Reject immediately while the write path is in \"error\": the queue\n // keeps retrying in the background (self-driven backoff), but callers\n // get the blocking error now instead of waiting an unbounded time.\n if (state.writeSubStatus === \"error\") {\n if (state.lastWriteError != null) {\n throw new LocalAppendFailedError(state.lastWriteError);\n }\n const msg = state.lastError?.code\n ? `local append failed: ${state.lastError.code}`\n : \"local append failed\";\n throw new Error(msg);\n }\n // Let adapters with async export queues (e.g. Flock) drain\n // before we snapshot the pending queue length.\n await this.adapter.flushLocalExports?.();\n if (state.closed) {\n throw new Error(\"subscription closed\");\n }\n // drainPendingBatch reads from pendingLocal without popping;\n // items are only removed after a successful append. So\n // pendingLocal.length includes any in-flight batch.\n const remaining = state.pendingLocal.length;\n if (remaining === 0) {\n return;\n }\n return new Promise<void>((resolve, reject) => {\n state.syncWaiters.push({ remaining, resolve, reject });\n });\n },\n };\n }\n\n private async disposeJoinState(state: JoinState<TVersion>): Promise<void> {\n await disposeTransportJoinState(state, {\n snapshotManager: this.snapshotManager,\n onDisposed: () => {\n if (this.joinState === state) {\n this.joinState = undefined;\n }\n },\n });\n }\n\n private async runInitialSyncWithDeadline<T>(\n operation: string,\n task: (signal?: AbortSignal) => Promise<T>,\n parentSignal?: AbortSignal,\n ): Promise<T> {\n const startedAtMs = Date.now();\n const hardController = new AbortController();\n const signal = mergeAbortSignals(parentSignal, hardController.signal);\n const slowAfterMs = this.reconnectConfig.initialSyncSlowAfterMs;\n const hardTimeoutMs = this.reconnectConfig.initialSyncHardTimeoutMs;\n let slowTimer: ReturnType<typeof setTimeout> | undefined;\n let hardTimer: ReturnType<typeof setTimeout> | undefined;\n let hardTimeoutError: InitialSyncTimeoutError | undefined;\n let hardTimeoutPromise: Promise<never> | undefined;\n\n if (\n typeof slowAfterMs === \"number\" &&\n Number.isFinite(slowAfterMs) &&\n slowAfterMs > 0\n ) {\n slowTimer = setTimeout(() => {\n this.logDebug(\"initial sync still pending\", {\n streamUrl: this.streamUrl,\n operation,\n elapsedMs: Date.now() - startedAtMs,\n slowAfterMs,\n });\n }, slowAfterMs);\n }\n\n if (\n typeof hardTimeoutMs === \"number\" &&\n Number.isFinite(hardTimeoutMs) &&\n hardTimeoutMs > 0\n ) {\n hardTimeoutPromise = new Promise((_, reject) => {\n hardTimer = setTimeout(() => {\n hardTimeoutError = new InitialSyncTimeoutError(\n operation,\n hardTimeoutMs,\n Date.now() - startedAtMs,\n );\n hardController.abort(hardTimeoutError);\n reject(hardTimeoutError);\n }, hardTimeoutMs);\n });\n }\n\n try {\n const taskPromise = task(signal);\n return hardTimeoutPromise == null\n ? await taskPromise\n : await Promise.race([taskPromise, hardTimeoutPromise]);\n } catch (error) {\n if (hardTimeoutError != null) {\n throw hardTimeoutError;\n }\n throw error;\n } finally {\n if (slowTimer != null) {\n clearTimeout(slowTimer);\n }\n if (hardTimer != null) {\n clearTimeout(hardTimer);\n }\n }\n }\n\n private async startJoin(state: JoinState<TVersion>): Promise<void> {\n try {\n state.requestController ??= new AbortController();\n let joined: JoinSyncResult<TVersion>;\n try {\n joined = await this.performInitialJoinSync(\n state.liveMode,\n state.requestController?.signal,\n );\n } catch (error) {\n if (\n !(error instanceof CrdtApplyIncompleteError) ||\n state.closed ||\n !(await this.salvageLocalBacklogOnIncompleteInitialSync(\n state,\n error,\n ))\n ) {\n throw error;\n }\n // The salvage durably appended ops the server was missing. If they\n // were the unresolved deps (the usual dual-author wedge), this retry\n // converges and the join completes normally; if not, it fails\n // exactly like the first attempt — with the backlog now published.\n joined = await this.performInitialJoinSync(\n state.liveMode,\n state.requestController?.signal,\n );\n }\n state.cursor = joined.cursor;\n state.streamCursor = joined.streamCursor;\n state.liveMode = joined.liveMode;\n const pendingLocal = this.exportUpdates(\n joined.localExportRefVersion,\n );\n const hasQueuedPreSubscribeBatch = pendingLocal.batch != null;\n if (pendingLocal.batch != null) {\n state.pendingLocal.pushBack(pendingLocal.batch);\n }\n if (state.closed) {\n return;\n }\n state.unsubscribeLocal = this.adapter.subscribeLocalUpdates((batch) => {\n state.pendingLocal.pushBack(batch);\n void this.flushPendingLocal(state);\n });\n if (hasQueuedPreSubscribeBatch) {\n await this.flushPendingLocal(state);\n if (state.status === \"error\" || state.status === \"disconnected\") {\n throw new Error(\"join local sync failed before subscription became ready\");\n }\n }\n if (state.closed) {\n return;\n }\n state.lastError = undefined;\n this.setReadSubStatus(state, \"ok\");\n this.setWriteSubStatus(state, \"ok\");\n void this.runReadLoop(state);\n } catch (error) {\n if (state.closed) {\n return;\n }\n this.snapshotManager.dispose(state);\n state.lastError = toTransportError(error);\n this.logError(\"join first sync failed\", error, {\n streamUrl: this.streamUrl,\n });\n state.readSubStatus = \"error\";\n state.writeSubStatus = \"error\";\n this.recomputeJoinStatus(state);\n throw error;\n }\n }\n\n /**\n * Join-time salvage for a stream whose head cannot be fully applied\n * (apply_incomplete at `up_to_date`, even after bootstrap): before the\n * join surfaces the error, durably append everything this replica holds\n * that the server does not.\n *\n * Why this exists: the dual-author wedge is usually two-sided — the stream\n * is missing some author's ops, and a replica that HOLDS those ops (e.g.\n * received out-of-band over a local data plane) may itself be unable to\n * complete the join because of a second gap. The regular join-time export\n * runs only AFTER a successful initial sync, so without this salvage such\n * a replica can never publish the healing ops, and its own backlog from a\n * previous process run stays local for as long as the stream stays\n * poisoned.\n *\n * `error.appliedVersion` advances only from success spans, so\n * `exportUpdates(appliedVersion)` is exactly \"ops this replica holds that\n * the server does not\" — the failed initial sync already imported\n * everything the server DOES have, so the delta cannot re-append\n * server-known data. Cursor safety is untouched: the append is write-only\n * style (producer-acked, no read catch-up, no durable-cursor save), and\n * the retried initial sync re-derives its cursor from the server.\n *\n * Returns `true` when anything was durably appended; the caller then\n * retries the initial sync once. Best-effort: a salvage failure logs and\n * returns `false` so the original apply-incomplete error is what callers\n * observe.\n */\n private async salvageLocalBacklogOnIncompleteInitialSync(\n state: JoinState<TVersion>,\n error: CrdtApplyIncompleteError,\n ): Promise<boolean> {\n const appliedVersion = error.appliedVersion as TVersion | undefined;\n if (appliedVersion == null) {\n return false;\n }\n try {\n // One exclusive section for the whole salvage: every other\n // `frozenLocalAppend` mutation (the pending flush, which a stale\n // write-retry timer can fire even mid-join) runs inside\n // `enqueueExclusive` too, so serializing here is what makes the frozen\n // handoff race-free.\n const appended = await this.enqueueExclusive(async () => {\n let didAppend = false;\n // Finalize a leftover frozen append first (e.g. a durable append\n // whose post-append catch-up hit the wedge): its ops are part of the\n // backlog, and an unfinalized frozen blocks any new append via the\n // source-mismatch guard. The producer session makes re-appending it\n // idempotent server-side. Deliberately do NOT pop the corresponding\n // `pendingLocal` entries: a flush that is mid-retry on this frozen\n // still holds its own drained reference, and popping here would make\n // that flush's eventual commit pop LATER (never-appended) entries —\n // a per-author hole. Leaving the entries queued only costs one\n // idempotent duplicate append when the flush retries.\n const frozen = this.frozenLocalAppend;\n if (frozen != null) {\n const ack = await this.appendFrozenLocalBatchRemote(frozen);\n this.commitProducerAck(ack.producerAck);\n didAppend = true;\n }\n await this.adapter.flushLocalExports?.();\n const localBatch = this.exportUpdates(appliedVersion);\n if (localBatch.batch != null) {\n const appendedBatch = await this.appendLocalBatchRemote(\n { ...state.cursor, serverLowerBoundVersion: appliedVersion },\n localBatch.batch,\n undefined,\n \"direct\",\n );\n this.commitProducerAck(appendedBatch.producerAck);\n didAppend = true;\n }\n return didAppend;\n });\n if (appended) {\n this.logDebug(\"join salvage appended local backlog\", {\n streamUrl: this.streamUrl,\n });\n }\n return appended;\n } catch (salvageError) {\n // A partially appended salvage is safe: the producer session dedupes a\n // retried frozen batch, and re-applying duplicated update bytes is\n // idempotent for the CRDT.\n this.logError(\"join salvage append failed\", salvageError, {\n streamUrl: this.streamUrl,\n });\n return false;\n }\n }\n\n private async syncOnce(\n operation: string,\n parentSignal?: AbortSignal,\n ): Promise<RemoteCursor<TVersion>> {\n let cursor = (await this.finalizeDirectLocalAppendIfNeeded(\n await this.resolveInitialRemoteState(operation, parentSignal),\n )).cursor;\n const localBatch = this.exportUpdates(\n cursor.serverLowerBoundVersion,\n );\n if (localBatch.batch != null) {\n cursor = (await this.appendLocalBatchDurably(\n cursor,\n localBatch.batch,\n )).cursor;\n }\n return cursor;\n }\n\n private async performInitialJoinSync(\n preferredLiveMode: LiveReadMode,\n parentSignal?: AbortSignal,\n ): Promise<JoinSyncResult<TVersion>> {\n const initial = await this.finalizeDirectLocalAppendIfNeeded(\n await this.resolveInitialRemoteState(\"join\", parentSignal),\n );\n let cursor = initial.cursor;\n let localExportRefVersion = cursor.serverLowerBoundVersion;\n let streamCursor = initial.streamCursor;\n let liveMode = preferredLiveMode;\n\n const exportedLocal = this.exportUpdates(localExportRefVersion);\n localExportRefVersion = exportedLocal.nextRefVersion;\n const exportedBatch = exportedLocal.batch;\n if (exportedBatch != null) {\n // Exclusive for the same reason as the pending flush and the join\n // salvage: a stale write-retry timer can fire a flush mid-join, and\n // every `frozenLocalAppend` mutation must be serialized through\n // `enqueueExclusive` to keep the frozen handoff race-free.\n const appendCursor = cursor;\n const appendStreamCursor = streamCursor;\n const committed = await this.enqueueExclusive(() =>\n this.appendLocalBatchDurably(\n appendCursor,\n exportedBatch,\n undefined,\n \"direct\",\n appendStreamCursor,\n )\n );\n cursor = committed.cursor;\n streamCursor = committed.streamCursor;\n }\n localExportRefVersion = cursor.serverLowerBoundVersion;\n\n return {\n cursor,\n streamCursor,\n liveMode,\n localExportRefVersion,\n };\n }\n\n private createInitialCursor(): RemoteCursor<TVersion> {\n return createInitialRemoteCursor<TVersion>({\n streamUrl: this.streamUrl,\n nextOffset: \"-1\",\n serverLowerBoundVersion: this.adapter.emptyVersion(),\n });\n }\n\n private async resolveInitialRemoteState(\n operation: string,\n parentSignal?: AbortSignal,\n ): Promise<CursorSyncResult<TVersion>> {\n const loaded = await this.remoteCursorStore.load(this.streamUrl);\n const cursor = loaded ?? this.createInitialCursor();\n const runInitial = <T>(task: (signal?: AbortSignal) => Promise<T>) =>\n this.runInitialSyncWithDeadline(operation, task, parentSignal);\n\n try {\n // When a cursor already exists, transport resumes from `nextOffset`\n // instead of bootstrapping. That requires the caller to have already\n // restored the matching local CRDT state for `serverLowerBoundVersion`.\n if (loaded == null) {\n return await runInitial((signal) =>\n this.bootstrapState(cursor, {\n signal,\n timeoutMode: \"initial\",\n }),\n );\n }\n const caughtUp = await runInitial((signal) =>\n this.catchupOrGone(cursor, {\n goneStrategy: \"return-gone\",\n signal,\n timeoutMode: \"initial\",\n }),\n );\n if (\"kind\" in caughtUp) {\n return await this.recoverFromGoneWithLocalBootstrap(\n cursor,\n operation,\n parentSignal,\n );\n }\n return caughtUp;\n } catch (error) {\n if (!isStreamNotFoundError(error) || !this.createStreamIfMissing) {\n throw error;\n }\n await this.ensureStreamExists();\n return await runInitial((signal) =>\n this.bootstrapState(this.createInitialCursor(), {\n signal,\n timeoutMode: \"initial\",\n }),\n );\n }\n }\n\n private async resolvePullOnlyRemoteState(\n operation: string,\n parentSignal?: AbortSignal,\n ): Promise<CursorSyncResult<TVersion>> {\n const loaded = await this.remoteCursorStore.load(this.streamUrl);\n const cursor = loaded ?? this.createInitialCursor();\n const runInitial = <T>(task: (signal?: AbortSignal) => Promise<T>) =>\n this.runInitialSyncWithDeadline(operation, task, parentSignal);\n\n try {\n if (loaded == null) {\n return await runInitial((signal) =>\n this.bootstrapState(cursor, {\n signal,\n timeoutMode: \"initial\",\n }),\n );\n }\n return await runInitial((signal) =>\n this.catchupFromCursor(\n cursor,\n undefined,\n undefined,\n undefined,\n signal,\n \"initial\",\n ),\n );\n } catch (error) {\n if (!isStreamNotFoundError(error) || !this.createStreamIfMissing) {\n throw error;\n }\n await this.ensureStreamExists();\n return await runInitial((signal) =>\n this.bootstrapState(this.createInitialCursor(), {\n signal,\n timeoutMode: \"initial\",\n }),\n );\n }\n }\n\n private async catchupJoinState(\n state: JoinState<TVersion>,\n params: TransportCatchupParams,\n parentSignal?: AbortSignal,\n ): Promise<RemoteCursor<TVersion>> {\n if (state.readSubStatus === \"connecting\") {\n await state.started;\n return state.cursor;\n }\n\n if (state.readSubStatus === \"ok\" && params.force !== true) {\n return state.cursor;\n }\n\n const cursor = await this.enqueueExclusive(async () => {\n if (state.closed) {\n throw new Error(\"subscription closed\");\n }\n const caughtUp = await this.runInitialSyncWithDeadline(\n \"catchup\",\n (signal) =>\n this.catchupFromCursor(\n state.cursor,\n undefined,\n state.streamCursor,\n (previousVersion, nextVersion) => {\n this.snapshotManager.observeRemoteVersion(\n state,\n previousVersion,\n nextVersion,\n );\n },\n signal,\n \"initial\",\n ),\n parentSignal,\n );\n state.cursor = caughtUp.cursor;\n state.streamCursor = caughtUp.streamCursor;\n return state.cursor;\n });\n\n if (state.closed) {\n return cursor;\n }\n state.retryAttempt = 0;\n state.lastError = undefined;\n if (state.readSubStatus !== \"ok\" || state.writeSubStatus === \"error\") {\n this.rejoin();\n } else if (state.pendingLocal.length > 0) {\n void this.flushPendingLocal(state);\n }\n return cursor;\n }\n\n private async bootstrapState(\n cursor: RemoteCursor<TVersion>,\n options: {\n readonly cursorManager?: TransportCursorManager<TVersion>;\n readonly signal?: AbortSignal;\n readonly timeoutMode?: \"default\" | \"initial\";\n } = {},\n ): Promise<CursorSyncResult<TVersion>> {\n const cursorManager = options.cursorManager ?? this.cursorManager;\n const result = await this.client.bootstrap(\n options.signal,\n options.timeoutMode,\n );\n let saved = await cursorManager.applyBootstrapPayload(\n cursor,\n result.snapshot,\n result.updates,\n result.nextOffset,\n result.upToDate,\n result.snapshotOffset,\n );\n let bootstrapCursor = result.streamCursor;\n if (!result.upToDate) {\n const caughtUp = await this.catchupOrGone(saved, {\n streamCursor: bootstrapCursor,\n cursorManager,\n signal: options.signal,\n timeoutMode: options.timeoutMode,\n });\n if (\"kind\" in caughtUp) {\n throw new Error(\"bootstrap catch-up unexpectedly returned gone\");\n }\n saved = caughtUp.cursor;\n bootstrapCursor = caughtUp.streamCursor;\n }\n return { cursor: saved, streamCursor: bootstrapCursor };\n }\n\n private async catchupFromCursor(\n cursor: RemoteCursor<TVersion>,\n live?: \"long-poll\",\n streamCursor?: string,\n onRemoteVersionApplied?: (\n previousVersion: TVersion,\n nextVersion: TVersion,\n ) => MaybePromise<void>,\n signal?: AbortSignal,\n timeoutMode?: \"default\" | \"initial\",\n ): Promise<CursorSyncResult<TVersion>> {\n const result = await this.catchupOrGone(cursor, {\n live,\n streamCursor,\n onRemoteVersionApplied,\n signal,\n timeoutMode,\n });\n if (\"kind\" in result) {\n throw new Error(\"catchup unexpectedly returned gone\");\n }\n return result;\n }\n\n private async catchupOrGone(\n cursor: RemoteCursor<TVersion>,\n options: {\n readonly live?: \"long-poll\";\n readonly streamCursor?: string;\n readonly onRemoteVersionApplied?: (\n previousVersion: TVersion,\n nextVersion: TVersion,\n ) => MaybePromise<void>;\n readonly goneStrategy?: \"bootstrap\" | \"return-gone\";\n readonly cursorManager?: TransportCursorManager<TVersion>;\n readonly signal?: AbortSignal;\n readonly timeoutMode?: \"default\" | \"initial\";\n } = {},\n ): Promise<CursorSyncResult<TVersion> | typeof StreamsCrdt.GONE_CATCHUP> {\n let current = cursor;\n let currentStreamCursor = options.streamCursor;\n const goneStrategy = options.goneStrategy ?? \"bootstrap\";\n const cursorManager = options.cursorManager ?? this.cursorManager;\n while (true) {\n const requestOffset = current.nextOffset;\n const response = await this.client.readFromOffset(\n requestOffset,\n currentStreamCursor,\n options.live,\n options.signal,\n options.timeoutMode,\n );\n if (response.kind === \"gone\") {\n if (goneStrategy === \"return-gone\") {\n return StreamsCrdt.GONE_CATCHUP;\n }\n return await this.bootstrapState(\n {\n ...current,\n nextOffset: \"-1\",\n serverLowerBoundVersion: this.adapter.emptyVersion(),\n },\n {\n cursorManager,\n signal: options.signal,\n timeoutMode: options.timeoutMode,\n },\n );\n }\n const result = response.value;\n if (isReadNoProgress(requestOffset, result)) {\n throw new Error(\n `catch-up made no progress at offset ${requestOffset}; server returned empty updates and unchanged next offset with up_to_date=false`,\n );\n }\n try {\n current = await cursorManager.applyCatchupResult(\n current,\n result,\n options.onRemoteVersionApplied,\n );\n } catch (error) {\n if (error instanceof CrdtApplyIncompleteError && result.upToDate) {\n return await this.bootstrapState(\n {\n ...current,\n nextOffset: \"-1\",\n serverLowerBoundVersion: this.adapter.emptyVersion(),\n },\n {\n cursorManager,\n signal: options.signal,\n timeoutMode: options.timeoutMode,\n },\n );\n }\n throw error;\n }\n currentStreamCursor = result.streamCursor;\n if (result.upToDate) {\n return { cursor: current, streamCursor: currentStreamCursor };\n }\n }\n }\n\n private async recoverLiveIncompleteRemoteApply(\n state: JoinState<TVersion>,\n error: CrdtApplyIncompleteError,\n ): Promise<\"recovered\" | \"retry\" | \"terminal\"> {\n // The throw discarded the candidate cursor before finalizeCursor's usual\n // WeakMap marking, so `state.cursor` may still read \"complete\" (typical\n // for a caught-up live room whose first poisoned event arrives with\n // up_to_date=true). Mark it so the degraded local flush engages and the\n // snapshot clean gate closes for the whole wedge; a successful recovery\n // replaces `state.cursor` with a fresh complete one below.\n this.cursorManager.markCursorIncomplete(state.cursor, error.unresolved);\n try {\n const bootstrapped = await this.bootstrapState(\n {\n ...state.cursor,\n nextOffset: \"-1\",\n serverLowerBoundVersion: this.adapter.emptyVersion(),\n },\n {\n signal: state.requestController?.signal,\n },\n );\n state.cursor = bootstrapped.cursor;\n state.streamCursor = bootstrapped.streamCursor;\n state.retryAttempt = 0;\n state.incompleteRecoveryAttempt = 0;\n state.lastError = undefined;\n this.setReadSubStatus(state, \"ok\");\n if (state.pendingLocal.length > 0) {\n void this.flushPendingLocal(state);\n }\n return \"recovered\";\n } catch (error) {\n const classified = classifyFetchError(error);\n this.logError(\"live incomplete apply recovery failed\", error, {\n streamUrl: this.streamUrl,\n code: classified.code,\n });\n state.lastError = toTransportError(error);\n // Uploads are read-independent: queued local batches still drain\n // through the degraded append-only flush while remote apply is wedged.\n // Without this kick an idle session's tail (queued before the wedge,\n // with no new local op to re-trigger the flush) would stay local until\n // the wedge cleared.\n if (state.pendingLocal.length > 0) {\n void this.flushPendingLocal(state);\n }\n if (classified.retriable && !isAuthOrProtocolError(error)) {\n // Transient recovery failure (e.g. the bootstrap fetch hit a network\n // blip). Ask the read loop to back off and try again — giving up here\n // would permanently stop remote reads over a retryable error.\n //\n // Pace consecutive failures with the dedicated counter: the SSE\n // itself stays healthy during an apply_incomplete wedge, and the\n // healthy-SSE paths keep resetting `retryAttempt`, so without this a\n // permanently poisoned stream re-bootstraps (a full snapshot\n // download) every few seconds indefinitely. Folding the counter into\n // `retryAttempt` right before the backoff keeps consecutive failed\n // recoveries escalating toward the delay cap.\n state.incompleteRecoveryAttempt += 1;\n state.retryAttempt = Math.max(\n state.retryAttempt,\n state.incompleteRecoveryAttempt,\n );\n this.setReadSubStatus(state, \"reconnecting\");\n return \"retry\";\n }\n // Terminal-class failure: surface \"error\" status. The read loop exits\n // and its self-restart timer keeps retrying in the background.\n this.setReadSubStatus(state, \"error\");\n return \"terminal\";\n }\n }\n\n private async appendLocalBatch(\n cursor: RemoteCursor<TVersion>,\n batch: CrdtUpdateBatch<TVersion>,\n pendingCount?: number,\n streamCursor?: string,\n ): Promise<RemoteCursor<TVersion>> {\n const source: FrozenLocalAppendSource =\n pendingCount == null ? \"direct\" : \"pending\";\n const committed = await this.appendLocalBatchDurably(\n cursor,\n batch,\n pendingCount,\n source,\n streamCursor,\n );\n this.committedPendingCount = committed.pendingCount;\n this.committedLocalStreamCursor = committed.streamCursor;\n return committed.cursor;\n }\n\n private async appendLocalBatchDurably(\n cursor: RemoteCursor<TVersion>,\n batch: CrdtUpdateBatch<TVersion>,\n pendingCount?: number,\n source: FrozenLocalAppendSource = pendingCount == null ? \"direct\" : \"pending\",\n streamCursor?: string,\n ): Promise<DurableLocalAppend<TVersion>> {\n const frozen = await this.getOrCreateFrozenLocalAppend(\n cursor,\n batch,\n pendingCount,\n source,\n );\n const appended = await this.appendFrozenLocalBatchRemote(frozen);\n const caughtUp = await this.catchupLocalAppend(frozen, streamCursor);\n this.commitProducerAck(appended.producerAck);\n return {\n cursor: caughtUp.cursor,\n pendingCount: frozen.pendingCount,\n streamCursor: caughtUp.streamCursor,\n };\n }\n\n private localAppendReadCursor(\n frozen: FrozenLocalAppend<TVersion>,\n ): RemoteCursor<TVersion> {\n return {\n ...frozen.baseCursor,\n serverLowerBoundVersion: this.adapter.mergeVersions(\n frozen.baseCursor.serverLowerBoundVersion,\n frozen.batch.version,\n ),\n };\n }\n\n private async catchupLocalAppend(\n frozen: FrozenLocalAppend<TVersion>,\n streamCursor?: string,\n ): Promise<CursorSyncResult<TVersion>> {\n const caughtUp = await this.catchupOrGone(\n this.localAppendReadCursor(frozen),\n {\n streamCursor,\n cursorManager: this.volatileCursorManager,\n },\n );\n if (\"kind\" in caughtUp) {\n throw new Error(\"local append catch-up unexpectedly returned gone\");\n }\n const saved = await this.saveRemoteCursor(caughtUp.cursor, \"local\");\n return {\n cursor: saved,\n streamCursor: caughtUp.streamCursor,\n };\n }\n\n private async appendLocalBatchRemote(\n cursor: RemoteCursor<TVersion>,\n batch: CrdtUpdateBatch<TVersion>,\n pendingCount?: number,\n source: FrozenLocalAppendSource = pendingCount == null ? \"direct\" : \"pending\",\n ): Promise<TentativeLocalAppend<TVersion> & { readonly pendingCount?: number }> {\n const frozen = await this.getOrCreateFrozenLocalAppend(\n cursor,\n batch,\n pendingCount,\n source,\n );\n const appended = await this.appendFrozenLocalBatchRemote(frozen);\n return {\n ...appended,\n pendingCount: frozen.pendingCount,\n };\n }\n\n private async getOrCreateFrozenLocalAppend(\n cursor: RemoteCursor<TVersion>,\n batch: CrdtUpdateBatch<TVersion>,\n pendingCount?: number,\n source: FrozenLocalAppendSource = pendingCount == null ? \"direct\" : \"pending\",\n ): Promise<FrozenLocalAppend<TVersion>> {\n const existing = this.frozenLocalAppend;\n if (existing != null) {\n if (existing.source !== source) {\n throw new Error(\n `${existing.source} local append must be finalized before ${source} append`,\n );\n }\n return existing;\n }\n\n const wireUpdates =\n this.encodeUpdateItems == null\n ? batch.updates\n : await this.encodeUpdateItems(batch.updates);\n const frozen: FrozenLocalAppend<TVersion> = {\n source,\n baseCursor: cursor,\n batch,\n pendingCount,\n wireUpdates,\n producerSession: this.createCurrentProducerSession(),\n };\n this.frozenLocalAppend = frozen;\n return frozen;\n }\n\n private async appendFrozenLocalBatchRemote(\n frozen: FrozenLocalAppend<TVersion>,\n ): Promise<TentativeLocalAppend<TVersion>> {\n if (frozen.acknowledged != null) {\n return frozen.acknowledged;\n }\n\n for (let attempt = 0; attempt < 3; attempt += 1) {\n let result: {\n readonly kind: \"ok\";\n readonly ackEpoch: number;\n readonly ackSeq: number;\n readonly nextOffset: string;\n } | {\n readonly kind: \"cas-mismatch\";\n readonly expectedOffset: string;\n readonly currentOffset: string;\n } | {\n readonly kind: \"stale-epoch\";\n readonly serverEpoch: number;\n } | {\n readonly kind: \"seq-gap\";\n readonly expectedSeq: number;\n };\n try {\n result = await this.client.appendUpdates(\n frozen.wireUpdates,\n frozen.producerSession,\n );\n } catch (error) {\n // specs/ds-protocol.md: In a multi-node cluster the stream creation\n // may not have replicated to the node that handles this append yet.\n // When `createStreamIfMissing` is enabled, re-create the stream\n // (tolerating 409 Conflict) and retry the same frozen append.\n if (isStreamNotFoundError(error) && this.createStreamIfMissing) {\n await this.ensureStreamExists();\n continue;\n }\n throw error;\n }\n if (result.kind === \"ok\") {\n // This cursor is only a producer-ack fact. Read-capable paths must not\n // persist it because there may be unread remote updates between\n // `frozen.baseCursor.nextOffset` and the server append tail.\n const next: RemoteCursor<TVersion> = {\n ...frozen.baseCursor,\n nextOffset: result.nextOffset,\n serverLowerBoundVersion: this.adapter.mergeVersions(\n frozen.baseCursor.serverLowerBoundVersion,\n frozen.batch.version,\n ),\n };\n const appended: TentativeLocalAppend<TVersion> = {\n cursor: next,\n producerAck: {\n epoch: result.ackEpoch,\n seq: result.ackSeq,\n },\n };\n frozen.acknowledged = appended;\n return appended;\n }\n if (result.kind === \"seq-gap\") {\n frozen.producerSession = {\n ...frozen.producerSession,\n nextProducerSeq: result.expectedSeq,\n };\n continue;\n }\n if (result.kind === \"cas-mismatch\") {\n throw new Error(\"unexpected CAS mismatch from unconditional append\");\n }\n frozen.producerSession = {\n ...frozen.producerSession,\n producerEpoch: result.serverEpoch + 1,\n nextProducerSeq: 0,\n };\n }\n throw new Error(\"append retry budget exhausted\");\n }\n\n private async finalizeDirectLocalAppendIfNeeded(\n state: CursorSyncResult<TVersion>,\n ): Promise<CursorSyncResult<TVersion>> {\n const frozen = this.frozenLocalAppend;\n if (frozen == null || frozen.pendingCount != null) {\n return state;\n }\n const appended = await this.appendFrozenLocalBatchRemote(frozen);\n const caughtUp = await this.catchupLocalAppend(frozen, state.streamCursor);\n this.commitProducerAck(appended.producerAck);\n return caughtUp;\n }\n\n private createCurrentProducerSession(): ProducerSession {\n return {\n producerId: this.producerId,\n producerEpoch: this.producerEpoch,\n nextProducerSeq: this.nextProducerSeq,\n };\n }\n\n private commitProducerAck(ack: ProducerAck): void {\n this.producerEpoch = ack.epoch;\n this.nextProducerSeq = ack.seq + 1;\n this.frozenLocalAppend = undefined;\n // Every durably committed local append (direct, pending flush, write-only,\n // and 410 recovery) funnels through this ack, so this is the single\n // source of truth for \"when did local data last reach the server\".\n this.lastLocalAppendAtMs = Date.now();\n }\n\n private abandonFrozenLocalAppend(\n frozen: FrozenLocalAppend<TVersion>,\n ): void {\n if (this.frozenLocalAppend !== frozen) {\n return;\n }\n this.frozenLocalAppend = undefined;\n this.producerEpoch = Math.max(\n this.producerEpoch,\n frozen.producerSession.producerEpoch + 1,\n );\n this.nextProducerSeq = 0;\n }\n\n private consumeCommittedPendingCount(): number | undefined {\n const count = this.committedPendingCount;\n this.committedPendingCount = undefined;\n return count;\n }\n\n private consumeCommittedLocalStreamCursor(): string | undefined {\n const streamCursor = this.committedLocalStreamCursor;\n this.committedLocalStreamCursor = undefined;\n return streamCursor;\n }\n\n /**\n * Ensures the target stream exists, creating it if necessary.\n * Tolerates 409 Conflict (stream already created concurrently).\n */\n private async ensureStreamExists(): Promise<void> {\n try {\n await this.client.createStreamRequest();\n } catch (error) {\n if (!(error instanceof HttpError && error.status === 409)) {\n throw error;\n }\n }\n }\n\n private exportUpdates(fromVersion: TVersion): LocalExportResult<TVersion> {\n const exported = this.adapter.exportUpdates(fromVersion);\n if (exported == null) {\n return { nextRefVersion: fromVersion };\n }\n const nextRefVersion = this.adapter.mergeVersions(\n fromVersion,\n exported.version,\n );\n if (exported.updates.length === 0) {\n return { nextRefVersion };\n }\n return {\n batch: exported,\n nextRefVersion,\n };\n }\n\n private async applyBootstrapItems(\n cursor: RemoteCursor<TVersion>,\n items: readonly Uint8Array[],\n nextOffset: string,\n ): Promise<RemoteCursor<TVersion>> {\n return await this.cursorManager.applyBootstrapItems(\n cursor,\n items,\n nextOffset,\n );\n }\n\n private async applyRemoteItems(\n cursor: RemoteCursor<TVersion>,\n items: readonly Uint8Array[],\n nextOffset: string,\n ): Promise<RemoteCursor<TVersion>> {\n return await this.cursorManager.applyRemoteItems(cursor, items, nextOffset);\n }\n\n private createCursorManager(\n adapter: Pick<CrdtAdapter<TVersion>, \"applySnapshot\" | \"applyRemoteUpdates\">,\n saveRemoteCursor: (\n cursor: RemoteCursor<TVersion>,\n source: RemoteCursorSaveSource,\n ) => Promise<RemoteCursor<TVersion>>,\n ): TransportCursorManager<TVersion> {\n return new TransportCursorManager({\n adapter,\n decodeSnapshot: this.decodeSnapshot,\n decodeUpdateItems: this.decodeUpdateItems,\n saveRemoteCursor,\n });\n }\n\n private async createIsolatedCursorManager(): Promise<\n TransportCursorManager<TVersion> | null\n > {\n const isolated = await this.adapter.createIsolatedAdapter?.();\n if (isolated == null) {\n return null;\n }\n return this.createCursorManager(isolated, async (cursor) => cursor);\n }\n\n /**\n * Infers remote state via bootstrap with the given cursor manager,\n * exports and appends any local delta, then re-bootstraps to converge\n * persisted cursor and local CRDT state on server truth.\n */\n private async inferRemoteAndAppendLocal(\n bootstrapCursor: RemoteCursor<TVersion>,\n cursorManager: TransportCursorManager<TVersion>,\n operation: string,\n parentSignal?: AbortSignal,\n ): Promise<CursorSyncResult<TVersion>> {\n const inferredRemote = await this.runInitialSyncWithDeadline(\n operation,\n (signal) =>\n this.bootstrapState(bootstrapCursor, {\n cursorManager,\n signal,\n timeoutMode: \"initial\",\n }),\n parentSignal,\n );\n let pendingAppend: TentativeLocalAppend<TVersion> | undefined;\n if (this.frozenLocalAppend != null) {\n if (this.frozenLocalAppend.pendingCount != null) {\n throw new Error(\"pending local append must be finalized by pending flush\");\n }\n pendingAppend = await this.appendFrozenLocalBatchRemote(\n this.frozenLocalAppend,\n );\n } else {\n const localBatch = this.exportUpdates(\n inferredRemote.cursor.serverLowerBoundVersion,\n );\n if (localBatch.batch != null) {\n pendingAppend = await this.appendLocalBatchRemote(\n inferredRemote.cursor,\n localBatch.batch,\n );\n }\n }\n // The recovery append is not durable until the second bootstrap has saved\n // the cursor and restored local state to server truth. Keep the producer ack\n // tentative until then, otherwise a failed final bootstrap can replay the\n // same local recovery delta under a changed producer seq.\n const result = await this.runInitialSyncWithDeadline(\n operation,\n (signal) =>\n this.bootstrapState(bootstrapCursor, {\n signal,\n timeoutMode: \"initial\",\n }),\n parentSignal,\n );\n if (pendingAppend != null) {\n this.commitProducerAck(pendingAppend.producerAck);\n }\n return result;\n }\n\n private async recoverFromGoneWithLocalBootstrap(\n cursor: RemoteCursor<TVersion>,\n operation: string,\n parentSignal?: AbortSignal,\n ): Promise<CursorSyncResult<TVersion>> {\n const isolatedCursorManager = await this.createIsolatedCursorManager();\n const bootstrapCursor = {\n ...cursor,\n nextOffset: \"-1\",\n serverLowerBoundVersion: this.adapter.emptyVersion(),\n };\n\n if (isolatedCursorManager != null) {\n return await this.inferRemoteAndAppendLocal(\n bootstrapCursor,\n isolatedCursorManager,\n operation,\n parentSignal,\n );\n }\n\n const preservedLocalSnapshot = this.adapter.exportSnapshot();\n let restoredLocalSnapshot = false;\n\n try {\n // specs/ds-client-state.md §4.2 / §6 and specs/ds-extensions.md\n // require 410 recovery to rebuild through bootstrap. Preserve local\n // cached state long enough to export a first-sync delta against the\n // inferred remote version, then bootstrap again so persisted cursor and\n // visible local CRDT state converge on the same server truth.\n const inferredRemote = await this.runInitialSyncWithDeadline(\n operation,\n (signal) =>\n this.bootstrapState(bootstrapCursor, {\n cursorManager: this.volatileCursorManager,\n signal,\n timeoutMode: \"initial\",\n }),\n parentSignal,\n );\n await this.adapter.applySnapshot(preservedLocalSnapshot);\n restoredLocalSnapshot = true;\n\n let pendingAppend: TentativeLocalAppend<TVersion> | undefined;\n if (this.frozenLocalAppend != null) {\n if (this.frozenLocalAppend.pendingCount != null) {\n throw new Error(\"pending local append must be finalized by pending flush\");\n }\n pendingAppend = await this.appendFrozenLocalBatchRemote(\n this.frozenLocalAppend,\n );\n } else {\n const localBatch = this.exportUpdates(\n inferredRemote.cursor.serverLowerBoundVersion,\n );\n if (localBatch.batch != null) {\n pendingAppend = await this.appendLocalBatchRemote(\n inferredRemote.cursor,\n localBatch.batch,\n );\n }\n }\n // Same durability boundary as the isolated-adapter path above: the append\n // only becomes locally committed after the final bootstrap converges and\n // saves the cursor.\n const result = await this.runInitialSyncWithDeadline(\n operation,\n (signal) =>\n this.bootstrapState(bootstrapCursor, {\n signal,\n timeoutMode: \"initial\",\n }),\n parentSignal,\n );\n if (pendingAppend != null) {\n this.commitProducerAck(pendingAppend.producerAck);\n }\n return result;\n } catch (error) {\n if (!restoredLocalSnapshot) {\n await this.adapter.applySnapshot(preservedLocalSnapshot);\n }\n throw error;\n }\n }\n\n private async runReadLoop(state: JoinState<TVersion>): Promise<void> {\n if (state.readLoopRunning) {\n return;\n }\n state.readLoopRunning = true;\n this.clearReadRestartTimer(state);\n try {\n await this.runReadLoopIterations(state);\n } finally {\n state.readLoopRunning = false;\n }\n // A terminal-class exit does not permanently stop remote reads: schedule\n // a background self-restart (see maybeScheduleReadLoopRestart).\n this.maybeScheduleReadLoopRestart(state);\n }\n\n /**\n * Schedules a background self-restart of the read loop after it exits with\n * a terminal-class (\"error\") failure.\n *\n * Non-retryable errors (auth failure, protocol error, stream 404) used to\n * stop the live read loop permanently — the room's remote cursor silently\n * froze until a process restart. Instead, the room stays in \"error\" status\n * (callers still see the failure via `status`/`lastError`) while a capped\n * exponential backoff (floor 1s, cap = last reconnect-table delay, with\n * jitter) keeps retrying in the background. Each retry issues fresh\n * requests, and every request re-runs the auth callback — on a 401/403 the\n * HTTP layer additionally forces a token refresh (`reason: \"unauthorized\"`)\n * and retries that request once — so refreshed credentials are picked up\n * without any caller intervention.\n *\n * \"disconnected\" (a finite `maxAttempts` budget exhausted on retryable\n * errors) intentionally stays terminal-until-`rejoin()`: a finite budget is\n * an explicit opt-in to giving up.\n */\n private maybeScheduleReadLoopRestart(state: JoinState<TVersion>): void {\n if (\n state.closed ||\n this.joinState !== state ||\n !this.readLoopNeedsRestart(state) ||\n state.readRestartTimer != null ||\n state.readLoopRunning\n ) {\n return;\n }\n const delay = reconnectDelayWithFloor(\n state.readErrorRetryAttempt,\n this.reconnectConfig,\n );\n state.readErrorRetryAttempt += 1;\n state.readRestartTimer = setTimeout(() => {\n state.readRestartTimer = undefined;\n if (\n state.closed ||\n this.joinState !== state ||\n !this.readLoopNeedsRestart(state)\n ) {\n return;\n }\n void this.runReadLoop(state);\n }, delay);\n }\n\n /**\n * Whether an exited read loop still needs a background restart.\n *\n * Only ever evaluated after the loop has unwound (from\n * `maybeScheduleReadLoopRestart`, called at `runReadLoop`'s tail). \"error\" is\n * the ordinary terminal-class exit. \"reconnecting\" here is the anomalous case\n * where `rejoin()` ran re-entrantly from a status listener *during* the loop's\n * terminal `setReadSubStatus(\"error\")`: it flipped the sub-status to\n * \"reconnecting\" and fire-and-forgot `runReadLoop`, but that no-oped because\n * the loop was still on the stack — so without this, the loop would exit and\n * nothing would ever restart it (a permanent stall, the very failure mode this\n * file removes). \"disconnected\" (a finite `maxAttempts` budget) stays terminal\n * by design, and \"ok\"/\"connecting\" don't reach this path via an exit.\n */\n private readLoopNeedsRestart(state: JoinState<TVersion>): boolean {\n return (\n state.readSubStatus === \"error\" ||\n state.readSubStatus === \"reconnecting\"\n );\n }\n\n private clearReadRestartTimer(state: JoinState<TVersion>): void {\n if (state.readRestartTimer != null) {\n clearTimeout(state.readRestartTimer);\n state.readRestartTimer = undefined;\n }\n }\n\n private clearWriteRetryTimer(state: JoinState<TVersion>): void {\n if (state.writeRetryTimer != null) {\n clearTimeout(state.writeRetryTimer);\n state.writeRetryTimer = undefined;\n }\n }\n\n /**\n * Applies the budgeted backoff for a retryable read-loop failure.\n *\n * Returns `true` when the loop should continue with the next attempt, or\n * `false` (after surfacing \"disconnected\") when a finite retry budget is\n * exhausted.\n */\n private async awaitReadRetryBackoff(\n state: JoinState<TVersion>,\n error: unknown,\n ): Promise<boolean> {\n const maxAttempts = this.reconnectConfig.maxAttempts;\n if (state.retryAttempt >= maxAttempts) {\n this.logError(\"live read reconnect exhausted\", error, {\n streamUrl: this.streamUrl,\n attempts: state.retryAttempt,\n });\n state.lastError = toTransportError(error);\n this.setReadSubStatus(state, \"disconnected\");\n return false;\n }\n const delay = computeRetryDelay(\n state.retryAttempt,\n this.reconnectConfig,\n error instanceof HttpError ? error.retryAfterMs : undefined,\n );\n state.retryAttempt += 1;\n await sleep(delay, state.retrySleepController.signal);\n return true;\n }\n\n private async runReadLoopIterations(\n state: JoinState<TVersion>,\n ): Promise<void> {\n state.requestController ??= new AbortController();\n while (!state.closed) {\n const capturedController: AbortController | undefined = state.requestController;\n let inactivityTimer: ReturnType<typeof setTimeout> | null = null;\n let inactivityTriggered = false;\n // Wall-clock time the current live SSE actually opened (0 until onOpen\n // fires — i.e. while connecting, in long-poll mode, or if the open never\n // succeeds). Read by the catch handler to tell a healthy-then-recycled\n // stream apart from a failing-to-connect retry chain.\n let sseOpenedAtMs = 0;\n\n const clearInactivity = (): void => {\n if (inactivityTimer != null) {\n clearTimeout(inactivityTimer);\n inactivityTimer = null;\n }\n };\n\n const resetInactivity = (): void => {\n clearInactivity();\n const timeoutMs = this.reconnectConfig.liveInactivityTimeoutMs;\n if (timeoutMs != null && timeoutMs > 0 && !state.closed) {\n inactivityTimer = setTimeout(() => {\n inactivityTriggered = true;\n capturedController?.abort();\n }, timeoutMs);\n }\n };\n\n try {\n if (state.liveMode === \"long-poll\") {\n const requestOffset = state.cursor.nextOffset;\n const response = await this.client.readFromOffset(\n requestOffset,\n state.streamCursor,\n \"long-poll\",\n capturedController?.signal,\n );\n if (response.kind === \"gone\") {\n const bootstrapped = await this.bootstrapState(\n {\n ...state.cursor,\n nextOffset: \"-1\",\n serverLowerBoundVersion: this.adapter.emptyVersion(),\n },\n {\n signal: capturedController?.signal,\n },\n );\n state.cursor = bootstrapped.cursor;\n state.streamCursor = bootstrapped.streamCursor;\n state.retryAttempt = 0;\n continue;\n }\n\n const result = response.value;\n if (isReadNoProgress(requestOffset, result)) {\n throw new Error(\n `catch-up made no progress at offset ${requestOffset}; server returned empty updates and unchanged next offset with up_to_date=false`,\n );\n }\n\n await this.enqueueExclusive(async () => {\n if (state.closed) {\n return;\n }\n state.cursor = await this.cursorManager.applyLiveResult(\n state.cursor,\n requestOffset,\n result,\n (previousVersion, nextVersion) => {\n this.snapshotManager.observeRemoteVersion(\n state,\n previousVersion,\n nextVersion,\n );\n },\n );\n if (result.streamCursor != null) {\n state.streamCursor = result.streamCursor;\n }\n });\n if (state.closed) {\n return;\n }\n state.retryAttempt = 0;\n this.setReadSubStatus(state, \"ok\");\n if (state.pendingLocal.length > 0) {\n void this.flushPendingLocal(state);\n }\n await sleep(0);\n continue;\n }\n\n const sseRequestOffset = state.cursor.nextOffset;\n const pendingRemote: Array<{\n updates: Uint8Array[];\n nextOffset: string;\n upToDate: boolean;\n streamCursor?: string;\n }> = [];\n let flushChain = Promise.resolve();\n\n resetInactivity();\n\n const sseStartedAtMs = Date.now();\n const sseResult = await this.client.readFromOffsetSseItems(\n sseRequestOffset,\n state.streamCursor,\n (updates, nextOffset, upToDate, streamCursor) => {\n if (capturedController?.signal.aborted) {\n return \"stop\";\n }\n resetInactivity();\n pendingRemote.push({ updates, nextOffset, upToDate, streamCursor });\n flushChain = flushChain.then(async () => {\n const events = pendingRemote.splice(0);\n if (\n events.length === 0 ||\n state.closed ||\n capturedController?.signal.aborted\n ) {\n return;\n }\n const allUpdates = events.flatMap((e) => e.updates);\n const lastEvent = events[events.length - 1]!;\n await this.enqueueExclusive(async () => {\n if (state.closed) {\n return;\n }\n state.cursor = await this.cursorManager.applyLiveResult(\n state.cursor,\n sseRequestOffset,\n {\n nextOffset: lastEvent.nextOffset,\n updates: allUpdates,\n upToDate: lastEvent.upToDate,\n },\n (previousVersion, nextVersion) => {\n this.snapshotManager.observeRemoteVersion(\n state,\n previousVersion,\n nextVersion,\n );\n },\n );\n for (let i = events.length - 1; i >= 0; i--) {\n if (events[i]!.streamCursor != null) {\n state.streamCursor = events[i]!.streamCursor!;\n break;\n }\n }\n });\n this.setReadSubStatus(state, \"ok\");\n if (state.pendingLocal.length > 0) {\n void this.flushPendingLocal(state);\n }\n }).catch((flushError) => {\n // If applyLiveResult or cursor save throws (e.g. CRDT import\n // error, E2EE decryption failure), abort the SSE connection so\n // the read loop can reconnect cleanly. Without this, the chain\n // stays rejected and all subsequent SSE events are silently\n // dropped until the connection closes naturally.\n //\n // Pass the original error as the abort reason so the outer\n // catch path can classify it properly (auth/protocol → \"error\",\n // network/timeout → reconnect) instead of treating it as a\n // benign rejoin/inactivity abort.\n this.logError(\"sse flush chain error\", flushError, {\n streamUrl: this.streamUrl,\n });\n capturedController?.abort(flushError);\n });\n },\n () => {\n resetInactivity();\n sseOpenedAtMs = Date.now();\n this.setReadSubStatus(state, \"ok\");\n },\n capturedController?.signal,\n resetInactivity,\n );\n clearInactivity();\n await flushChain;\n if (state.closed) {\n return;\n }\n if (sseResult.kind === \"ok\") {\n state.retryAttempt = 0;\n state.streamCursor = sseResult.latestCursor;\n const delay = fastSseCloseDelay(sseStartedAtMs);\n await sleep(delay, state.retrySleepController.signal);\n continue;\n }\n if (sseResult.kind === \"unsupported\") {\n state.liveMode = \"long-poll\";\n await sleep(0);\n continue;\n }\n if (sseResult.kind === \"not-found\") {\n const notFoundError = new HttpError(\"stream not found\", 404);\n this.logError(\n \"live read failed\",\n notFoundError,\n {\n streamUrl: this.streamUrl,\n code: \"stream_not_found\",\n },\n );\n state.lastError = toTransportError(notFoundError);\n this.setReadSubStatus(state, \"error\");\n // Terminal-class exit — the runReadLoop wrapper schedules a\n // background self-restart, so reads resume if the stream reappears.\n return;\n }\n const bootstrapped = await this.bootstrapState(\n {\n ...state.cursor,\n nextOffset: \"-1\",\n serverLowerBoundVersion: this.adapter.emptyVersion(),\n },\n {\n signal: capturedController?.signal,\n },\n );\n state.cursor = bootstrapped.cursor;\n state.streamCursor = bootstrapped.streamCursor;\n state.retryAttempt = 0;\n } catch (error) {\n clearInactivity();\n if (state.closed) {\n return;\n }\n if (error instanceof CrdtApplyIncompleteError) {\n if (\n capturedController?.signal.aborted &&\n state.requestController === capturedController\n ) {\n state.requestController = new AbortController();\n }\n const recovery = await this.recoverLiveIncompleteRemoteApply(\n state,\n error,\n );\n if (recovery === \"recovered\") {\n continue;\n }\n if (recovery === \"terminal\") {\n return;\n }\n // Transient recovery failure — back off and re-enter the loop; the\n // next read re-triggers incomplete-apply recovery.\n if (await this.awaitReadRetryBackoff(state, error)) {\n continue;\n }\n return;\n }\n\n // Deliberate abort: rejoin(), SSE inactivity timeout, or\n // flush-chain processing error.\n if (capturedController?.signal.aborted) {\n if (inactivityTriggered) {\n // Inactivity timeout consumes the retry budget so that a\n // persistently-silent SSE endpoint eventually reaches\n // \"disconnected\" when maxAttempts is finite.\n if (state.requestController === capturedController) {\n state.requestController = new AbortController();\n }\n this.logDebug(\"sse inactivity timeout\", {\n streamUrl: this.streamUrl,\n });\n this.setReadSubStatus(state, \"reconnecting\");\n const maxAttempts = this.reconnectConfig.maxAttempts;\n if (state.retryAttempt >= maxAttempts) {\n this.logError(\"live read reconnect exhausted (inactivity)\", error, {\n streamUrl: this.streamUrl,\n attempts: state.retryAttempt,\n });\n state.lastError = {\n code: \"timeout\",\n retryable: true,\n phase: \"poll\",\n message: \"SSE inactivity timeout exhausted retry budget\",\n };\n this.setReadSubStatus(state, \"disconnected\");\n return;\n }\n state.retryAttempt += 1;\n const delay = reconnectDelayWithFloor(\n state.retryAttempt - 1,\n this.reconnectConfig,\n );\n await sleep(delay, state.retrySleepController.signal);\n continue;\n }\n // Flush-chain error: signal.reason carries the original error\n // from applyLiveResult / cursor save / E2EE decrypt. The abort\n // race in transport-client resolves with signal.reason, so\n // `error` and `abortReason` are often the same object. Classify\n // the reason (or fall back to error) so auth/protocol failures\n // surface as terminal \"error\" status instead of a silent\n // reconnect loop.\n const abortReason = capturedController.signal.reason;\n const flushError =\n abortReason instanceof Error ? abortReason : error;\n if (flushError instanceof CrdtApplyIncompleteError) {\n if (state.requestController === capturedController) {\n state.requestController = new AbortController();\n }\n const recovery = await this.recoverLiveIncompleteRemoteApply(\n state,\n flushError,\n );\n if (recovery === \"recovered\") {\n continue;\n }\n if (recovery === \"terminal\") {\n return;\n }\n if (await this.awaitReadRetryBackoff(state, flushError)) {\n continue;\n }\n return;\n }\n if (\n flushError instanceof Error &&\n !(flushError instanceof DOMException &&\n flushError.name === \"AbortError\")\n ) {\n const flushClassified = classifyFetchError(flushError);\n if (\n !flushClassified.retriable ||\n isAuthOrProtocolError(flushError)\n ) {\n this.logError(\"live read failed (flush chain)\", flushError, {\n streamUrl: this.streamUrl,\n code: flushClassified.code,\n });\n state.lastError = toTransportError(flushError);\n this.setReadSubStatus(state, \"error\");\n // Terminal-class exit — the runReadLoop wrapper schedules a\n // background self-restart with capped backoff.\n return;\n }\n // Retriable flush error — fall through to reconnect.\n }\n\n // rejoin() abort or retriable flush — reset budget and restart.\n state.retryAttempt = 0;\n this.setReadSubStatus(state, \"reconnecting\");\n continue;\n }\n\n const classified = classifyFetchError(error);\n if (!classified.retriable || isAuthOrProtocolError(error)) {\n this.logError(\"live read failed\", error, {\n streamUrl: this.streamUrl,\n code: classified.code,\n });\n state.lastError = toTransportError(error);\n this.setReadSubStatus(state, \"error\");\n // Terminal-class exit — the runReadLoop wrapper schedules a\n // background self-restart with capped backoff. Auth failures are\n // included: each restart re-runs the auth callback (with a forced\n // refresh on 401/403), so a refreshed token recovers the room.\n return;\n }\n this.setReadSubStatus(state, \"reconnecting\");\n // An SSE that stayed open past the \"successful session\" threshold\n // before erroring is a fresh disconnect (e.g. an expected periodic\n // stream recycle or load-balancer connection cycling), not a\n // failing-to-connect retry chain. Reset the backoff budget so routine\n // recycles over the life of a long-lived connection don't ratchet\n // `retryAttempt` up to the delay cap (which would leave the room stuck\n // \"reconnecting\" for the full cap delay on every later recycle).\n // Connections that never opened or flapped sub-threshold keep their\n // `retryAttempt`, so genuine failures keep backing off and honour\n // maxAttempts.\n if (\n sseOpenedAtMs > 0 &&\n Date.now() - sseOpenedAtMs >= MIN_SUCCESSFUL_SSE_DURATION_MS\n ) {\n state.retryAttempt = 0;\n }\n if (!(await this.awaitReadRetryBackoff(state, error))) {\n return;\n }\n }\n }\n }\n\n /** Maximum encoded size (bytes) for a single batched append. */\n private static readonly MAX_BATCH_BYTES = 256 * 1024;\n\n /**\n * Drain up to `MAX_BATCH_BYTES` worth of queued batches from the front of\n * `pendingLocal` and merge them into a single `CrdtUpdateBatch`.\n *\n * `maxCount` optionally caps how many queued batches may be merged (used to\n * isolate the head batch after a 413 rejection).\n *\n * Returns `undefined` when the queue is empty.\n */\n private async drainQueuedBatch(\n pendingLocal:\n Pick<Deque<CrdtUpdateBatch<TVersion>>, \"peekFront\" | \"at\"> &\n { readonly length: number },\n maxCount?: number,\n ): Promise<{ batch: CrdtUpdateBatch<TVersion>; count: number } | undefined> {\n const first = pendingLocal.peekFront();\n if (first == null) {\n return undefined;\n }\n\n const maxBytes = StreamsCrdt.MAX_BATCH_BYTES;\n let mergedVersion = first.version;\n const allUpdates: Uint8Array[] = Array.from(first.updates);\n let count = 1;\n\n while (\n count < pendingLocal.length &&\n (maxCount == null || count < maxCount)\n ) {\n const next = pendingLocal.at(count)!;\n const candidateUpdates = [...allUpdates, ...next.updates];\n const candidateBytes = await this.measureAppendBodyByteLength(\n candidateUpdates,\n );\n if (candidateBytes > maxBytes) {\n break;\n }\n allUpdates.push(...next.updates);\n mergedVersion = this.adapter.mergeVersions(mergedVersion, next.version);\n count += 1;\n }\n\n if (count === 1) {\n return { batch: first, count: 1 };\n }\n return { batch: { updates: allUpdates, version: mergedVersion }, count };\n }\n\n private drainPendingBatch(\n state: JoinState<TVersion>,\n ): Promise<{ batch: CrdtUpdateBatch<TVersion>; count: number } | undefined> {\n return this.drainQueuedBatch(\n state.pendingLocal,\n // After a 413, isolate the head batch so an over-limit merged body is\n // retried at its smallest unit (see flushPendingLocal).\n state.flushHeadBatchOnly ? 1 : undefined,\n );\n }\n\n private drainPendingWriteOnlyBatch(): Promise<\n { batch: CrdtUpdateBatch<TVersion>; count: number } | undefined\n > {\n return this.drainQueuedBatch(this.pendingWriteOnly);\n }\n\n /**\n * Drains `pendingLocal` to the server in strict FIFO order.\n *\n * FIFO invariant: batches are only popped after a durable append, and a\n * failing head batch is retried in place — never skipped, dropped, or\n * reordered. See the `JoinState.pendingLocal` doc for why this strictness\n * (rather than a version-vector diff upload) is what prevents undetectable\n * per-author holes.\n *\n * Failure handling is self-healing: a terminal-class failure surfaces\n * `\"error\"` write status (with `lastWriteError` set) and then retries via\n * this flush's own backoff timer (`maybeScheduleWriteRetry`) — it does not\n * depend on a future local op to re-trigger the flush.\n *\n * While the cursor is incomplete (unresolved remote spans), the flush\n * degrades to an append-only mode instead of pausing: batches still reach\n * the server, but read progress, durable-cursor saves, and snapshot\n * scheduling stay untouched. See the inline comment in the incomplete\n * branch and specs/loro-pending.md \"Local Append Rules\".\n */\n private async flushPendingLocal(state: JoinState<TVersion>): Promise<void> {\n if (state.flushingLocal || state.closed) {\n return;\n }\n this.clearWriteRetryTimer(state);\n state.flushingLocal = true;\n try {\n outer: while (!state.closed) {\n const drained = await this.drainPendingBatch(state);\n if (drained == null) {\n return;\n }\n let attempts = 0;\n while (!state.closed) {\n try {\n let committedCount: number;\n if (this.cursorManager.isCursorComplete(state.cursor)) {\n state.cursor = await this.enqueueExclusive(async () =>\n this.appendLocalBatch(\n state.cursor,\n drained.batch,\n drained.count,\n state.streamCursor,\n )\n );\n const committedStreamCursor =\n this.consumeCommittedLocalStreamCursor();\n if (committedStreamCursor != null) {\n state.streamCursor = committedStreamCursor;\n }\n committedCount =\n this.consumeCommittedPendingCount() ?? drained.count;\n this.snapshotManager.schedule(state);\n } else {\n // Degraded append-only flush while the pending-import invariant\n // holds the read side (unresolved remote spans). Appending is\n // cursor-safety-neutral: a local commit can only depend on ops\n // this doc has APPLIED (pending imports are unreachable), so the\n // batch introduces no new stream gap — while pausing it would\n // deadlock the wedge: this author's un-uploaded ops are often\n // exactly the deps every other replica is missing. So the batch\n // is appended write-only style — no read catch-up, no\n // `state.cursor`/`streamCursor` advance, no snapshot schedule —\n // and the durable-cursor + snapshot gates stay fully enforced\n // (specs/loro-pending.md \"Local Append Rules\").\n const appended = await this.enqueueExclusive(async () =>\n this.appendLocalBatchRemote(\n state.cursor,\n drained.batch,\n drained.count,\n \"pending\",\n )\n );\n this.commitProducerAck(appended.producerAck);\n committedCount = appended.pendingCount ?? drained.count;\n }\n for (let i = 0; i < committedCount; i += 1) {\n state.pendingLocal.popFront();\n }\n this.notifySyncWaiters(state, committedCount);\n state.flushHeadBatchOnly = false;\n state.lastWriteError = undefined;\n this.setWriteSubStatus(state, \"ok\");\n break;\n } catch (error) {\n if (\n isPayloadTooLargeError(error) &&\n drained.count > 1 &&\n this.abandonPendingFrozenLocalAppend()\n ) {\n // The server rejected the merged body outright (413 means\n // nothing was appended), so the frozen producer batch can be\n // abandoned safely. Re-drain with only the head batch: FIFO is\n // preserved (the head is still first, nothing is skipped) and\n // only a genuinely unsplittable single batch can reach the\n // deterministic payload_too_large failure below.\n state.flushHeadBatchOnly = true;\n continue outer;\n }\n const classified = classifyFetchError(error);\n const transportError = toTransportError(error);\n if (!classified.retriable || isAuthOrProtocolError(error)) {\n if (isPayloadTooLargeError(error)) {\n // Deterministic for this head batch. Keep future drains\n // head-only so retries do not re-merge newer batches into an\n // already-over-limit body.\n state.flushHeadBatchOnly = true;\n }\n this.logError(\"local append failed\", error, {\n streamUrl: this.streamUrl,\n code: classified.code,\n });\n state.lastError = transportError;\n state.lastWriteError = transportError;\n this.rejectSyncWaiters(\n state,\n new LocalAppendFailedError(transportError),\n );\n this.setWriteSubStatus(state, \"error\");\n // Not a permanent stop: the head batch stays queued (strict\n // FIFO — never skipped or dropped) and maybeScheduleWriteRetry\n // keeps retrying on a capped backoff without waiting for a new\n // local op. Auth errors recover once the auth callback yields a\n // working token (every attempt forces a refresh after 401/403);\n // payload_too_large only recovers if the server limit changes,\n // but stays loudly observable via lastWriteError /\n // getLocalSyncStats() instead of failing silently.\n return;\n }\n const maxAttempts = this.reconnectConfig.maxAttempts;\n if (attempts >= maxAttempts) {\n state.lastError = transportError;\n state.lastWriteError = transportError;\n this.rejectSyncWaiters(\n state,\n new Error(\"local append retry budget exhausted\"),\n );\n this.setWriteSubStatus(state, \"error\");\n return;\n }\n this.setWriteSubStatus(state, \"reconnecting\");\n const delay = computeRetryDelay(\n attempts,\n this.reconnectConfig,\n error instanceof HttpError ? error.retryAfterMs : undefined,\n );\n attempts += 1;\n await sleep(delay, state.retrySleepController.signal);\n }\n }\n }\n } finally {\n state.flushingLocal = false;\n this.maybeScheduleWriteRetry(state);\n }\n }\n\n /**\n * Abandons the current frozen \"pending\" append, fencing its producer\n * session. Only safe when the server definitively did not append the frozen\n * body (e.g. a 413 rejection happens before any append). Returns `false`\n * when there is no matching frozen pending append to abandon.\n */\n private abandonPendingFrozenLocalAppend(): boolean {\n const frozen = this.frozenLocalAppend;\n if (frozen == null || frozen.source !== \"pending\") {\n return false;\n }\n this.abandonFrozenLocalAppend(frozen);\n return true;\n }\n\n /**\n * Schedules a self-driven retry of the pending-local flush after a\n * terminal-class failure.\n *\n * Historically a non-retryable append error parked the queue until the next\n * local op happened to trigger a flush — an idle session with stranded\n * updates would never upload them (data stayed local until process\n * restart). The write queue now owns its retry timer: capped exponential\n * backoff (floor 1s, cap = last reconnect-table delay, jittered),\n * independent of new local ops. `rejoin()` and fresh local ops still retry\n * immediately.\n */\n private maybeScheduleWriteRetry(state: JoinState<TVersion>): void {\n if (\n state.closed ||\n this.joinState !== state ||\n state.writeSubStatus !== \"error\" ||\n state.pendingLocal.length === 0 ||\n state.writeRetryTimer != null ||\n state.flushingLocal\n ) {\n return;\n }\n const delay = reconnectDelayWithFloor(\n state.writeErrorRetryAttempt,\n this.reconnectConfig,\n );\n state.writeErrorRetryAttempt += 1;\n state.writeRetryTimer = setTimeout(() => {\n state.writeRetryTimer = undefined;\n if (state.closed || this.joinState !== state) {\n return;\n }\n void this.flushPendingLocal(state);\n }, delay);\n }\n\n /**\n * Decrements all sync waiters by the number of flushed batches and\n * resolves any that have reached zero.\n */\n private notifySyncWaiters(\n state: JoinState<TVersion>,\n flushedCount: number,\n ): void {\n let i = 0;\n while (i < state.syncWaiters.length) {\n const waiter = state.syncWaiters[i]!;\n waiter.remaining -= flushedCount;\n if (waiter.remaining <= 0) {\n state.syncWaiters.splice(i, 1);\n waiter.resolve();\n } else {\n i += 1;\n }\n }\n }\n\n /** Rejects and removes all sync waiters. */\n private rejectSyncWaiters(\n state: JoinState<TVersion>,\n error: Error,\n ): void {\n for (const waiter of state.syncWaiters.splice(0)) {\n waiter.reject(error);\n }\n }\n\n private async saveRemoteCursor(\n cursor: RemoteCursor<TVersion>,\n source: RemoteCursorSaveSource,\n ): Promise<RemoteCursor<TVersion>> {\n const saved = await persistRemoteCursor({\n remoteCursorStore: this.remoteCursorStore,\n cursor,\n source,\n beforeRemoteCursorSave: this.beforeRemoteCursorSave,\n });\n // A durable cursor save happens only on a clean/complete apply\n // (specs/loro-pending.md invariant 1), so this is the single point every\n // heal passes through — including heals WITHOUT a recovery success,\n // where a later live read or catch-up delivers the missing deps.\n // Clearing the incomplete-recovery backoff counter here keeps it scoped\n // to one wedge episode: left stale, the FIRST failure of a later,\n // unrelated apply-incomplete episode would start at the accumulated\n // backoff (and, with a finite `maxAttempts`, could surface\n // \"disconnected\" immediately).\n const state = this.joinState;\n if (state != null && !state.closed) {\n state.incompleteRecoveryAttempt = 0;\n }\n return saved;\n }\n\n private setReadSubStatus(\n state: JoinState<TVersion>,\n sub: ReadSubStatus,\n ): void {\n if (sub === \"ok\") {\n // A healthy read resets the terminal-error self-restart backoff.\n state.readErrorRetryAttempt = 0;\n }\n if (state.readSubStatus === sub) {\n return;\n }\n state.readSubStatus = sub;\n this.recomputeJoinStatus(state);\n }\n\n private setWriteSubStatus(\n state: JoinState<TVersion>,\n sub: WriteSubStatus,\n ): void {\n if (sub === \"ok\") {\n // A healthy append resets the self-driven write retry backoff.\n state.writeErrorRetryAttempt = 0;\n }\n if (state.writeSubStatus === sub) {\n return;\n }\n state.writeSubStatus = sub;\n this.recomputeJoinStatus(state);\n }\n\n private recomputeJoinStatus(state: JoinState<TVersion>): void {\n const status = this.computeOverallStatus(state);\n if (state.status === status) {\n return;\n }\n state.status = status;\n // Clear stale error when the room fully recovers.\n if (status === \"joined\") {\n state.lastError = undefined;\n }\n for (const listener of state.statusListeners) {\n listener(status as TransportRoomStatus);\n }\n }\n\n private computeOverallStatus(\n state: JoinState<TVersion>,\n ): \"connecting\" | TransportRoomStatus {\n const { readSubStatus, writeSubStatus } = state;\n if (readSubStatus === \"error\" || writeSubStatus === \"error\") {\n return \"error\";\n }\n if (readSubStatus === \"disconnected\") {\n return \"disconnected\";\n }\n if (readSubStatus === \"connecting\") {\n return \"connecting\";\n }\n if (\n readSubStatus === \"reconnecting\" ||\n writeSubStatus === \"reconnecting\"\n ) {\n return \"reconnecting\";\n }\n return \"joined\";\n }\n\n private enqueueExclusive<T>(task: () => MaybePromise<T>): Promise<T> {\n const run = this.opQueue.then(task, task);\n this.opQueue = run.then(\n () => undefined,\n () => undefined,\n );\n return run;\n }\n\n private async measureAppendBodyByteLength(\n updates: readonly Uint8Array[],\n ): Promise<number> {\n if (this.measureUpdateItemsByteLength != null) {\n return await this.measureUpdateItemsByteLength(updates);\n }\n return updates.reduce((sum, update) => sum + 4 + update.byteLength, 0);\n }\n\n private disableWriteOnlyMode(reason: string): void {\n const frozen = this.frozenLocalAppend;\n if (frozen?.source === \"write-only\") {\n this.abandonFrozenLocalAppend(frozen);\n }\n if (this.writeOnlyDisabledReason != null) {\n return;\n }\n this.writeOnlyDisabledReason = reason;\n this.pendingWriteOnly.clear();\n this.unsubscribeWriteOnlyLocal?.();\n this.unsubscribeWriteOnlyLocal = undefined;\n }\n\n private logDebug(message: string, detail?: Record<string, unknown>): void {\n if (!this.debug) {\n return;\n }\n if (detail == null) {\n console.debug(`[streams-crdt] ${message}`);\n return;\n }\n console.debug(`[streams-crdt] ${message}`, detail);\n }\n\n private logError(\n message: string,\n error?: unknown,\n detail?: Record<string, unknown>,\n ): void {\n const payload =\n error == null\n ? detail\n : {\n ...detail,\n error: error instanceof Error ? error.message : String(error),\n };\n if (payload == null) {\n console.error(`[streams-crdt] ${message}`);\n return;\n }\n console.error(`[streams-crdt] ${message}`, payload);\n }\n}\n","const MAX_STREAM_ID_BYTES = 512;\nconst textEncoder = new TextEncoder();\n\nfunction utf8ByteLength(value: string): number {\n return textEncoder.encode(value).byteLength;\n}\n\n/**\n * Validates a Durable Streams stream id.\n */\nexport function isValidRillId(id: string): boolean {\n return (\n id.length > 0 &&\n utf8ByteLength(id) <= MAX_STREAM_ID_BYTES &&\n id !== \".\" &&\n !id.includes(\"/\") &&\n !id.includes(\"\\0\") &&\n !id.includes(\"..\")\n );\n}\n"],"mappings":";;AAAA,MAAM,4BAA4B;AAClC,MAAM,uBAAuB;AAO7B,IAAa,QAAb,MAA6C;CAC3C;CACA,OAAe;CACf,OAAe;CACf;CACA;CAEA,YAAY,QAAsB,UAAwB,EAAE,EAAE;AAC5D,OAAK,mBAAmB,KAAK,IAC3B,GACA,QAAQ,oBAAoB,0BAC7B;AACD,OAAK,cAAc,KAAK,IAAI,GAAG,QAAQ,eAAe,qBAAqB;EAC3E,MAAM,OAAO,UAAU,OAAO,EAAE,GAAG,MAAM,KAAK,OAAO;AACrD,OAAK,SAAS,KAAK,OAAO;AAC1B,OAAK,OAAO,KAAK;;CAGnB,IAAI,SAAiB;AACnB,SAAO,KAAK,OAAO,KAAK;;CAG1B,UAAmB;AACjB,SAAO,KAAK,WAAW;;CAGzB,QAAc;AACZ,OAAK,SAAS,EAAE;AAChB,OAAK,OAAO;AACZ,OAAK,OAAO;;CAGd,YAA2B;AACzB,MAAI,KAAK,WAAW,EAClB;EAEF,MAAM,QAAQ,KAAK,OAAO,KAAK;AAC/B,SAAO,SAAS,OAAO,KAAA,IAAY;;CAGrC,WAA0B;AACxB,MAAI,KAAK,WAAW,EAClB;EAEF,MAAM,QAAQ,KAAK,OAAO,KAAK,OAAO;AACtC,SAAO,SAAS,OAAO,KAAA,IAAY;;CAGrC,GAAG,OAA8B;EAC/B,MAAM,SAAS,KAAK;AACpB,MAAI,WAAW,EACb;EAEF,IAAI,SAAS;AACb,MAAI,SAAS,EACX,WAAU;AAEZ,MAAI,SAAS,KAAK,UAAU,OAC1B;EAEF,MAAM,QAAQ,KAAK,OAAO,KAAK,OAAO;AACtC,SAAO,SAAS,OAAO,KAAA,IAAY;;CAGrC,SAAS,OAAkB;AACzB,OAAK,oBAAoB;AACzB,OAAK,OAAO,KAAK,QAAQ;AACzB,OAAK,QAAQ;AACb,SAAO,KAAK;;CAGd,UAAU,OAAkB;AAC1B,MAAI,KAAK,SAAS,EAChB,MAAK,SAAS,GAAG,EAAE;AAErB,OAAK,QAAQ;AACb,OAAK,OAAO,KAAK,QAAQ;AACzB,SAAO,KAAK;;CAGd,WAA0B;AACxB,MAAI,KAAK,WAAW,EAClB;EAEF,MAAM,QAAQ,KAAK,OAAO,KAAK;AAE/B,OAAK,OAAO,KAAK,QAAQ;AACzB,OAAK,QAAQ;AACb,OAAK,iBAAiB;AACtB,SAAO,SAAS,OAAO,KAAA,IAAY;;CAGrC,UAAyB;AACvB,MAAI,KAAK,WAAW,EAClB;AAEF,OAAK,QAAQ;EACb,MAAM,QAAQ,KAAK,OAAO,KAAK;AAE/B,OAAK,OAAO,KAAK,QAAQ;AACzB,OAAK,iBAAiB;AACtB,SAAO,SAAS,OAAO,KAAA,IAAY;;CAGrC,UAAe;EACb,MAAM,MAAW,EAAE;AACnB,OAAK,IAAI,IAAI,KAAK,MAAM,IAAI,KAAK,MAAM,KAAK,EAC1C,KAAI,KAAK,KAAK,OAAO,GAAQ;AAE/B,SAAO;;CAGT,EAAE,OAAO,YAAiC;AACxC,OAAK,IAAI,IAAI,KAAK,MAAM,IAAI,KAAK,MAAM,KAAK,EAC1C,OAAM,KAAK,OAAO;;CAItB,qBAAmC;AACjC,MAAI,KAAK,OAAO,KAAK,OAAO,OAC1B;AAEF,MAAI,KAAK,OAAO,GAAG;AACjB,QAAK,gBAAgB;AACrB,OAAI,KAAK,OAAO,KAAK,OAAO,OAC1B;;AAGJ,OAAK,SAAS,GAAG,EAAE;;CAGrB,SAAiB,YAAoB,WAAyB;EAC5D,MAAM,SAAS,KAAK;EACpB,MAAM,SAAS,SAAS,aAAa;EACrC,MAAM,kBAAkB,KAAK,OAAO;EACpC,IAAI,eAAe,KAAK,IAAI,iBAAiB,KAAK,YAAY;AAC9D,MAAI,eAAe,OACjB,gBAAe,KAAK,IAAI,eAAe,GAAG,QAAQ,KAAK,YAAY;EAErE,MAAM,OAAO,eAAe;EAC5B,MAAM,WAAW,KAAK,IAAI,YAAY,KAAK,MAAM,OAAO,EAAE,CAAC;EAC3D,MAAM,OAAO,IAAI,MAA4B,aAAa;AAC1D,OAAK,IAAI,IAAI,GAAG,IAAI,QAAQ,KAAK,EAC/B,MAAK,WAAW,KAAK,KAAK,OAAO,KAAK,OAAO;AAE/C,OAAK,SAAS;AACd,OAAK,OAAO;AACZ,OAAK,OAAO,WAAW;;CAGzB,kBAAgC;EAC9B,MAAM,SAAS,KAAK;AACpB,MAAI,WAAW,GAAG;AAChB,QAAK,OAAO;AACZ;;AAEF,MAAI,KAAK,OAAO,KAAK,oBAAoB,KAAK,OAAO,OACnD;AAEF,OAAK,gBAAgB;;CAGvB,iBAA+B;EAC7B,MAAM,SAAS,KAAK;AACpB,OAAK,SAAS,KAAK,OAAO,MAAM,KAAK,MAAM,KAAK,KAAK;AACrD,OAAK,OAAO;AACZ,OAAK,OAAO;;;;;AC9IhB,MAAa,2BAA4C;CACrD,QAAQ;EAAC;EAAG;EAAK;EAAM;EAAM;EAAM;EAAM;EAAO;EAAM;CACtD,gBAAgB;CAChB,aAAa;CACb,kBAAkB;CAClB,wBAAwB;CACxB,0BAA0B;CAC1B,eAAe;CACf,yBAAyB;CAC5B;;;;;;;;AASD,MAAa,iCAAiC;;;;;AAM9C,SAAgB,YAAY,QAAgB,UAA0B;AAClE,KAAI,WAAW,EAAG,QAAO;CACzB,MAAM,SAAS,SAAS,YAAY,IAAI,KAAK,QAAQ,GAAG;AACxD,QAAO,KAAK,IAAI,GAAG,KAAK,MAAM,SAAS,OAAO,CAAC;;;;;;;;;;AAWnD,SAAgB,kBACZ,SACA,SAA0B,0BAC1B,YACM;CACN,MAAM,EAAE,QAAQ,mBAAmB;CAEnC,MAAM,YAAY,OADJ,KAAK,IAAI,SAAS,OAAO,SAAS,EAAE;AAMlD,QAAO,YAHH,OAAO,eAAe,YAAY,aAAa,YACzC,aACA,WACe,eAAe;;;;AClF5C,MAAa,sBAAsB;AAEnC,SAAgB,YAAY,OAA8C;CACxE,MAAM,QAAQ,MAAM,QAAQ,KAAK,SAAS,MAAM,IAAI,KAAK,YAAY,EAAE;CACvE,MAAM,MAAM,IAAI,WAAW,MAAM;CACjC,MAAM,OAAO,IAAI,SAAS,IAAI,QAAQ,IAAI,YAAY,IAAI,WAAW;CACrE,IAAI,SAAS;AACb,MAAK,MAAM,QAAQ,OAAO;AACxB,OAAK,UAAU,QAAQ,KAAK,YAAY,MAAM;AAC9C,YAAU;AACV,MAAI,IAAI,MAAM,OAAO;AACrB,YAAU,KAAK;;AAEjB,QAAO;;AAGT,SAAgB,YAAY,SAAmC;CAC7D,MAAM,MAAoB,EAAE;CAC5B,MAAM,OAAO,IAAI,SAAS,QAAQ,QAAQ,QAAQ,YAAY,QAAQ,WAAW;CACjF,IAAI,SAAS;AACb,QAAO,SAAS,QAAQ,YAAY;AAClC,MAAI,SAAS,IAAI,QAAQ,WACvB,OAAM,IAAI,MAAM,mDAAmD;EAErE,MAAM,MAAM,KAAK,UAAU,QAAQ,MAAM;AACzC,YAAU;AACV,MAAI,SAAS,MAAM,QAAQ,WACzB,OAAM,IAAI,MAAM,kDAAkD;AAEpE,MAAI,KAAK,QAAQ,MAAM,QAAQ,SAAS,IAAI,CAAC;AAC7C,YAAU;;AAEZ,QAAO;;;;ACfT,MAAM,QAAQ,IAAI,WAAW;CAAC;CAAM;CAAM;CAAM;CAAK,CAAC;AACtD,MAAM,4BAA4B;AAClC,MAAM,8BAA8B;AACpC,MAAM,oBAAoB;AAC1B,MAAM,gBAAgB;AACtB,MAAM,wBAAwB;AAC9B,MAAM,4BAA4B;AAClC,MAAM,mCAAmC,IAAI;AAE7C,MAAM,oBADc,IAAI,aAAa,CACC,OAAO,GAAG,4BAA4B,OAAO;AAEnF,IAAa,yBAAb,cAA4C,MAAM;CAChD;CAEA,YACE,QACA,UAAU,gCAAgC,OAAO,EACjD;AACA,QAAM,QAAQ;AACd,OAAK,OAAO;AACZ,OAAK,SAAS;;;AAIlB,IAAM,4BAAN,cAAwC,uBAAuB;CAC7D,YAAY,SAAiB;AAC3B,QAAM,kBAAkB,QAAQ;AAChC,OAAK,OAAO;;;AAIhB,IAAa,2BAAb,MAAsC;CACpC;CACA;CAEA;CAEA,YAAY,SAA0C;AACpD,OAAK,aAAa,QAAQ;AAC1B,OAAK,cAAc,QAAQ;AAC3B,OAAK,WAAW,QAAQ;;CAG1B,MAAM,kBAAkB,cAAqE;AAC3F,MAAI,KAAK,gBAAgB,YACvB,QAAO;AAET,SAAO,CAAC,MAAM,KAAK,YAAY,gBAAgB,YAAY,aAAa,CAAC,CAAC;;CAG5E,oCAAoC,cAA6C;AAC/E,MAAI,KAAK,gBAAgB,YACvB,QAAO,uBAAuB,aAAa;AAG7C,MAAI,KAAK,YAAY,KACnB,OAAM,IAAI,uBACR,kBACA,iDACD;AAEH,SACE,IACA,wBACA,uBAAuB,aAAa,GACpC,KAAK,SAAS;;CAIlB,MAAM,kBAAkB,WAAyD;EAC/E,MAAM,MAAoB,EAAE;AAC5B,OAAK,MAAM,QAAQ,WAAW;AAC5B,OAAI,oBAAoB,KAAK,EAAE;IAC7B,MAAM,aAAa,MAAM,KAAK,YAAY,gBAAgB,KAAK;AAC/D,QAAI,KAAK,GAAG,YAAY,WAAW,CAAC;AACpC;;AAEF,QAAK,uBAAuB,eAAe;AAC3C,OAAI,KAAK,KAAK;;AAEhB,SAAO;;CAGT,MAAM,eACJ,UACA,oBACqB;AACrB,0BAAwB,mBAAmB;AAC3C,MAAI,KAAK,gBAAgB,YACvB,QAAO;AAET,SAAO,MAAM,KAAK,YAAY,YAAY,UAAU,mBAAmB;;CAGzE,MAAM,eACJ,UACA,oBACqB;AACrB,0BAAwB,mBAAmB;AAC3C,MAAI,oBAAoB,SAAS,CAC/B,QAAO,MAAM,KAAK,YAAY,YAAY,UAAU,mBAAmB;AAEzE,OAAK,uBAAuB,WAAW;AACvC,SAAO;;CAGT,MAAc,YACZ,MACA,WACA,oBACqB;AACrB,SAAO,MAAM,KAAK,oBAAoB,MAAM,WAAW,mBAAmB;;CAG5E,MAAc,YACZ,cACA,UACA,oBACqB;EACrB,MAAM,UAAU,SAAS;AACzB,MAAI,YAAY,0BACd,QAAO,MAAM,KAAK,oBAAoB,cAAc,UAAU,mBAAmB;AAEnF,QAAM,IAAI,uBACR,oBACA,gEAAgE,OAAO,QAAQ,CAAC,GACjF;;CAGH,MAAc,oBACZ,MACA,WACA,oBACqB;EACrB,MAAM,WAAW,KAAK;AACtB,MAAI,YAAY,KACd,OAAM,IAAI,uBACR,kBACA,iDACD;EAGH,MAAM,UAAU,sBAAsB,MAAM,mBAAmB;EAC/D,IAAI;EACJ,IAAI,sBAAsB;EAC1B,IAAI;AACJ,MAAI;AACF,YAAS,MAAM,SAAS,KAAK;IAC3B;IACA;IACA,iBAAiB,WAAW;AAC1B,4BAAuB;AACvB,SAAI,wBAAwB,EAC1B,OAAM,IAAI,0BACR,uEACD;AAEH,2BAAsB,wBAAwB,OAAO,CAAC,OAAO;AAC7D,YAAO,kBAAkB,6BAA6B,MAAM,oBAAoB,CAAC;;IAEpF,CAAC;WACK,OAAO;AACd,OAAI,iBAAiB,0BACnB,OAAM;GAER,MAAM,SAAS,iBAAiB,yBAAyB,MAAM,SAAS;AACxE,SAAM,IAAI,uBAAuB,QAAQ,gCAAgC,OAAO,CAAC;;AAGnF,MAAI;GACF,MAAM,iBAAiB,wBAAwB,OAAO,OAAO;AAC7D,OAAI,wBAAwB,EAC1B,OAAM,IAAI,0BACR,uEACD;AAEH,OAAI,uBAAuB,QAAQ,CAAC,WAAW,qBAAqB,eAAe,CACjF,OAAM,IAAI,0BACR,4EACD;AAEH,OAAI,EAAE,OAAO,kBAAkB,eAAe,OAAO,OAAO,eAAe,EACzE,OAAM,IAAI,0BACR,qEACD;AAIH,OADE,eAAe,aAAa,OAAO,OAAO,aAAa,UAAU,aAC9C,SAAS,qBAC5B,OAAM,IAAI,0BACR,4DACD;AAEH,UAAO,YAAY,6BAA6B,MAAM,eAAe,EAAE,OAAO,OAAO;WAC9E,OAAO;AACd,OAAI,iBAAiB,0BACnB,OAAM;GAER,MAAM,SAAS,iBAAiB,yBAAyB,MAAM,SAAS;AACxE,SAAM,IAAI,uBAAuB,QAAQ,gCAAgC,OAAO,CAAC;;;CAIrF,MAAc,oBACZ,cACA,UACA,oBACqB;EACrB,MAAM,SAAS,sBAAsB,SAAS;AAC9C,MAAI,OAAO,SAAS,aAClB,OAAM,IAAI,uBACR,sBACA,YAAY,aAAa,gBAAgB,OAAO,OACjD;EAGH,MAAM,WAAW,KAAK;AACtB,MAAI,YAAY,KACd,OAAM,IAAI,uBACR,oBACA,gDACD;EAGH,MAAM,UAAU,sBAAsB,OAAO,MAAM,mBAAmB;AACtE,MAAI;GACF,MAAM,YAAY,MAAM,SAAS,KAAK;IACpC,QAAQ,OAAO;IACf,QAAQ,OAAO;IACf;IACA,gBAAgB,kBAAkB,OAAO,oBAAoB;IAC9D,CAAC;AACF,OAAI,EAAE,qBAAqB,YACzB,OAAM,IAAI,uBACR,kBACA,+DACD;AAEH,UAAO;WACA,OAAO;GACd,MAAM,SAAS,iBAAiB,yBAAyB,MAAM,SAAS;AACxE,SAAM,IAAI,uBAAuB,QAAQ,gCAAgC,OAAO,CAAC;;;CAIrF,uBAA+B,OAAqB;AAClD,MAAI,KAAK,eAAe,kBACtB;AAEF,QAAM,IAAI,uBACR,uBACA,mCAAmC,MAAM,qCAC1C;;;AAIL,SAAgB,2BACd,SACA,QAAQ,qBAC8B;AACtC,KAAI,WAAW,KACb;CAGF,MAAM,aAAa,QAAQ,cAAc;CACzC,MAAM,cAAc,QAAQ,eAAe;AAC3C,KAAI,eAAe,oBAAoB,eAAe,kBACpD,OAAM,IAAI,MAAM,GAAG,MAAM,uDAAuD;AAElF,KAAI,gBAAgB,aAAa,gBAAgB,YAC/C,OAAM,IAAI,MAAM,GAAG,MAAM,2CAA2C;CAGtE,MAAM,WAAW,QAAQ;AACzB,KACE,YAAY,SACX,OAAO,SAAS,SAAS,cACxB,OAAO,SAAS,SAAS,cACzB,CAAC,OAAO,cAAc,SAAS,qBAAqB,IACpD,SAAS,uBAAuB,KAChC,SAAS,uBAAuB,kCAElC,OAAM,IAAI,MACR,GAAG,MAAM,2GAC6B,mCACvC;AAEH,KAAI,gBAAgB,aAAa,YAAY,KAC3C,OAAM,IAAI,MAAM,GAAG,MAAM,mDAAmD;AAE9E,QAAO,IAAI,yBAAyB;EAClC;EACA;EACA;EACD,CAAC;;AAGJ,SAAgB,yBAAyB,OAAiD;AACxF,QAAO,iBAAiB;;AAG1B,SAAgB,gCAAgC,QAAgD;AAC9F,SAAQ,QAAR;EACE,KAAK,sBACH,QAAO;EACT,KAAK,mBACH,QAAO;EACT,KAAK,iBACH,QAAO;EACT,KAAK,mBACH,QAAO;EACT,KAAK,qBACH,QAAO;EACT,KAAK,iBACH,QAAO;;;AAIb,SAAS,oBAAoB,SAA8B;AACzD,QACE,QAAQ,cAAc,MAAM,cAAc,MAAM,OAAO,MAAM,UAAU,QAAQ,WAAW,KAAK;;AAInG,SAAS,sBACP,MACA,oBAC0B;AAC1B,KAAI,SAAS,YAAY;AACvB,0BAAwB,mBAAmB;AAC3C,SAAO;GACL,UAAU;GACV,SAAS;GACT;GACA;GACD;;AAEH,QAAO;EACL,UAAU;EACV,SAAS;EACT;EACD;;;AAIH,SAAS,wBAAwB,OAAyC;AACxE,KACE,OAAO,UAAU,YACjB,MAAM,WAAW,KACjB,MAAM,SAAS,QACf,UAAU,MAEV,OAAM,IAAI,uBAAuB,oBAAoB,8BAA8B;;AAIvF,SAAS,6BACP,MACA,gBACY;CACZ,MAAM,mBAAmB,wBAAwB,eAAe;CAChE,MAAM,SAAS,IAAI,WAAW,wBAAwB,iBAAiB,WAAW;CAClF,MAAM,OAAO,IAAI,SAAS,OAAO,OAAO;AACxC,QAAO,IAAI,OAAO,EAAE;AACpB,QAAO,KAAK;AACZ,QAAO,KAAK,WAAW,KAAK;AAC5B,MAAK,UAAU,GAAG,iBAAiB,YAAY,MAAM;AACrD,QAAO,KAAK;AACZ,QAAO,KAAK;AACZ,QAAO,IAAI,kBAAkB,sBAAsB;AACnD,QAAO;;AAGT,SAAS,sBAAsB,SAK7B;AACA,KAAI,CAAC,oBAAoB,QAAQ,IAAI,QAAQ,aAAa,sBACxD,OAAM,IAAI,uBACR,oBACA,iDACD;CAGH,MAAM,uBADO,IAAI,SAAS,QAAQ,QAAQ,QAAQ,YAAY,QAAQ,WAAW,CAC/C,UAAU,GAAG,MAAM;AACrD,KACE,QAAQ,OAAO,6BACf,yBAAyB,KACzB,uBAAuB,6BACvB,QAAQ,OAAO,KACf,QAAQ,OAAO,EAEf,OAAM,IAAI,uBACR,oBACA,qDACD;CAGH,MAAM,YAAY,wBAAwB;AAC1C,KAAI,QAAQ,cAAc,UACxB,OAAM,IAAI,uBACR,oBACA,mDACD;AAEH,QAAO;EACL,MAAM,WAAW,QAAQ,GAAG;EAC5B,QAAQ,QAAQ,MAAM,uBAAuB,UAAU;EACvD,qBAAqB,QAAQ,MAAM,GAAG,UAAU;EAChD,QAAQ,QAAQ,MAAM,UAAU;EACjC;;AAGH,SAAS,wBAAwB,QAA6B;AAC5D,KACE,EAAE,kBAAkB,eACpB,OAAO,eAAe,KACtB,OAAO,aAAa,0BAEpB,OAAM,IAAI,0BACR,iDAAiD,0BAA0B,QAC5E;AAEH,QAAO;;AAGT,SAAS,WAAW,MAAkB,OAA4B;AAChE,QAAO,KAAK,eAAe,MAAM,cAAc,KAAK,OAAO,MAAM,UAAU,SAAS,MAAM,OAAO;;AAGnG,SAAS,kBAAkB,qBAA6C;AACtE,QAAO,YAAY,mBAAmB,oBAAoB;;AAG5D,SAAS,WAAW,MAAqC;AACvD,QAAO,SAAS,iBAAiB,oBAAoB;;AAGvD,SAAS,WAAW,SAAoD;AACtE,KAAI,YAAY,kBACd,QAAO;AAET,KAAI,YAAY,cACd,QAAO;AAET,OAAM,IAAI,uBACR,oBACA,kDACD;;AAGH,SAAS,YAAY,GAAG,OAA0C;CAChE,MAAM,MAAM,IAAI,WAAW,MAAM,QAAQ,QAAQ,SAAS,SAAS,KAAK,YAAY,EAAE,CAAC;CACvF,IAAI,SAAS;AACb,MAAK,MAAM,QAAQ,OAAO;AACxB,MAAI,IAAI,MAAM,OAAO;AACrB,YAAU,KAAK;;AAEjB,QAAO;;AAGT,SAAS,uBAAuB,OAAsC;AACpE,QAAO,MAAM,QAAQ,KAAK,SAAS,MAAM,IAAI,KAAK,YAAY,EAAE;;;;ACnNlE,SAAgB,mBAA2B;AACzC,KAAI,OAAO,WAAW,QAAQ,eAAe,WAC3C,QAAO,WAAW,OAAO,YAAY;AAEvC,QAAO,GAAG,KAAK,QAAQ,CAAC,SAAS,GAAG,CAAC,MAAM,EAAE,GAAG,KAAK,QAAQ,CAAC,SAAS,GAAG,CAAC,MAAM,EAAE;;AAGrF,IAAa,YAAb,cAA+B,MAAM;CACnC;CACA;CACA;;CAEA;CAEA,YACE,SACA,QACA,SAKA;AACA,QAAM,QAAQ;AACd,OAAK,SAAS;AACd,MAAI,SAAS,cAAc,KAAA,EACzB,MAAK,YAAY,QAAQ;AAE3B,MAAI,SAAS,iBAAiB,KAAA,EAC5B,MAAK,eAAe,QAAQ;AAE9B,MAAI,SAAS,SAAS,KAAA,KAAa,QAAQ,KAAK,SAAS,EACvD,MAAK,OAAO,QAAQ;;;AAK1B,IAAa,eAAb,cAAkC,MAAM;CACtC;CACA;CAEA,YAAY,aAAiC,WAAmB;AAC9D,QAAM,YAAY,YAAY,oBAAoB,UAAU,IAAI;AAChE,OAAK,cAAc;AACnB,OAAK,YAAY;;;AAIrB,IAAa,0BAAb,cAA6C,MAAM;CACjD;CACA;CACA;CAEA,YAAY,WAAmB,WAAmB,WAAmB;AACnE,QACE,WAAW,UAAU,2BAA2B,UAAU,8EAC3D;AACD,OAAK,YAAY;AACjB,OAAK,YAAY;AACjB,OAAK,YAAY;;;AAIrB,IAAa,gBAAb,cAAmC,MAAM;CACvC;CAEA,YAAY,SAAiB,QAAiB;AAC5C,QAAM,QAAQ;AACd,OAAK,SAAS;;;;;;;;;;;;AAalB,IAAa,oBAAb,cAAuC,MAAM;CAC3C;CAEA,YAAY,SAAiB,SAA+B;AAC1D,QAAM,QAAQ;AACd,MAAI,SAAS,UAAU,KAAA,EACrB,MAAK,QAAQ,QAAQ;;;;;;;;;AAW3B,IAAa,yBAAb,cAA4C,MAAM;CAChD;CAEA,YAAY,gBAAgC;AAC1C,QAAM,wBAAwB,eAAe,OAAO;AACpD,OAAK,OAAO;AACZ,OAAK,iBAAiB;;;;;;;;AAS1B,SAAgB,uBAAuB,OAAoC;AACzE,QAAO,iBAAiB,aAAa,MAAM,WAAW;;AAmCxD,SAAS,SAAS,OAAkD;AAClE,QAAO,OAAO,UAAU,YAAY,SAAS;;AAc/C,SAAS,kBAAkB,SAA6B;CAItD,MAAM,aAAa,QAAQ,QAAQ,WAAW,GAAG;AACjD,KAAI,OAAO,WAAW,YACpB,QAAO,IAAI,WAAW,OAAO,KAAK,YAAY,SAAS,CAAC;AAE1D,KAAI,OAAO,SAAS,YAAY;EAC9B,MAAM,SAAS,KAAK,WAAW;EAC/B,MAAM,MAAM,IAAI,WAAW,OAAO,OAAO;AACzC,OAAK,IAAI,IAAI,GAAG,IAAI,OAAO,QAAQ,KAAK,EACtC,KAAI,KAAK,OAAO,WAAW,EAAE;AAE/B,SAAO;;AAET,OAAM,IAAI,MAAM,iCAAiC;;AAGnD,SAAS,yBACP,cACsB;CACtB,MAAM,aAAa,cAAc,MAAM,CAAC,aAAa;AACrD,KAAI,cAAc,QAAQ,WAAW,WAAW,EAC9C;AAEF,KAAI,eAAe,SACjB,QAAO;AAET,OAAM,IAAI,cACR,yCAAyC,aAAa,GACvD;;AAGH,SAAS,oBACP,MACA,cACY;AACZ,KAAI,iBAAiB,SACnB,QAAO,kBAAkB,KAAK;AAEhC,QAAO,IAAI,aAAa,CAAC,OAAO,KAAK;;AAGvC,SAAgB,sBACd,MACA,cACc;CACd,MAAM,UAAU,oBACd,MACA,yBAAyB,aAAa,CACvC;AACD,KAAI,QAAQ,eAAe,EACzB,QAAO,CAAC,QAAQ;AAElB,KAAI;AACF,SAAO,YAAY,QAAQ;SACrB;AACN,SAAO,CAAC,QAAQ;;;;;;;;;AAUpB,SAAgB,yBAAyB,SAAmC;AAC1E,KAAI,QAAQ,eAAe,EACzB,QAAO,CAAC,QAAQ;AAElB,KAAI;AACF,SAAO,YAAY,QAAQ;SACrB;AACN,SAAO,CAAC,QAAQ;;;AAIpB,SAAgB,oBAAoB,MAA8B;CAChE,IAAI;AACJ,KAAI;AACF,WAAS,KAAK,MAAM,KAAK;SACnB;AACN,QAAM,IAAI,cAAc,iCAAiC;;AAE3D,KAAI,CAAC,SAAS,OAAO,IAAI,OAAO,OAAO,qBAAqB,SAC1D,OAAM,IAAI,cAAc,6CAA6C;CAEvE,MAAM,eACJ,OAAO,OAAO,iBAAiB,WAAW,OAAO,eAAe,KAAA;AAClE,QAAO;EACL,YAAY,OAAO;EACnB,UAAU,OAAO,aAAa;EAC9B;EACD;;AAGH,SAAgB,0BAA0B,SAA0B;CAClE,MAAM,aAAa,QAAQ,MAAM,CAAC,aAAa;AAC/C,KAAI,WAAW,WAAW,EACxB,QAAO;CAET,MAAM,cACJ,WAAW,SAAS,MAAM,IAAI,WAAW,SAAS,oBAAoB;CACxE,MAAM,wBACJ,WAAW,SAAS,cAAc,IAClC,WAAW,SAAS,cAAc,IAClC,WAAW,SAAS,eAAe,IACnC,WAAW,SAAS,eAAe;AACrC,QAAO,eAAe;;AAmBxB,SAAgB,MAAM,IAAY,QAAqC;AACrE,KAAI,QAAQ,QACV,QAAO,QAAQ,SAAS;AAE1B,QAAO,IAAI,SAAS,YAAY;EAC9B,MAAM,QAAQ,WAAW,SAAS,GAAG;AACrC,UAAQ,iBACN,eACM;AACJ,gBAAa,MAAM;AACnB,YAAS;KAEX,EAAE,MAAM,MAAM,CACf;GACD;;AAGJ,SAAgB,kBACd,GAAG,SACsB;CACzB,MAAM,SAAS,QAAQ,QACpB,WAAkC,UAAU,KAC9C;AACD,KAAI,OAAO,WAAW,EACpB;AAEF,KAAI,OAAO,WAAW,EACpB,QAAO,OAAO;AAEhB,KAAI,OAAO,YAAY,QAAQ,WAC7B,QAAO,YAAY,IAAI,OAAO;CAGhC,MAAM,aAAa,IAAI,iBAAiB;AACxC,MAAK,MAAM,UAAU,QAAQ;AAC3B,MAAI,OAAO,SAAS;AAClB,cAAW,MAAM,OAAO,OAAO;AAC/B;;AAEF,SAAO,iBACL,eACM;AACJ,cAAW,MAAM,OAAO,OAAO;KAEjC,EAAE,MAAM,MAAM,CACf;;AAEH,QAAO,WAAW;;AAGpB,SAAgB,mBAAmB,KAAyB;CAC1D,MAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;CAChE,MAAM,SAAS,eAAe,YAAY,IAAI,SAAS,KAAA;AACvD,KAAI,2BAA2B,IAAI,CACjC,QAAO;EACL,MAAM;EACN;EACA,WAAW;EACX,MAAM,KAAK,KAAK;EACjB;AAEH,KAAI,eAAe,kBACjB,QAAO;EACL,MAAM;EACN;EACA,WAAW;EACX,MAAM,KAAK,KAAK;EACjB;AAEH,KAAI,WAAW,OAAO,WAAW,IAC/B,QAAO;EAAE,MAAM;EAAe;EAAS,WAAW;EAAO,MAAM,KAAK,KAAK;EAAE;AAE7E,KAAI,WAAW,IACb,QAAO;EAAE,MAAM;EAAY;EAAS,WAAW;EAAM,MAAM,KAAK,KAAK;EAAE;AAEzE,KAAI,WAAW,OAAO,WAAW,OAAO,WAAW,IACjD,QAAO;EAAE,MAAM;EAAY;EAAS,WAAW;EAAO,MAAM,KAAK,KAAK;EAAE;AAE1E,KAAI,WAAW,IACb,QAAO;EACL,MAAM;EACN;EACA,WAAW;EACX,MAAM,KAAK,KAAK;EACjB;AAEH,KAAI,WAAW,IAGb,QAAO;EAAE,MAAM;EAAU;EAAS,WAAW;EAAM,MAAM,KAAK,KAAK;EAAE;AAEvE,KAAI,OAAO,WAAW,YAAY,UAAU,OAAO,SAAS,IAC1D,QAAO;EAAE,MAAM;EAAU;EAAS,WAAW;EAAM,MAAM,KAAK,KAAK;EAAE;AAEvE,KAAI,eAAe,cACjB,QAAO;EAAE,MAAM;EAAY;EAAS,WAAW;EAAO,MAAM,KAAK,KAAK;EAAE;AAE1E,KAAI,eAAe,aACjB,QAAO;EAAE,MAAM;EAAW;EAAS,WAAW;EAAM,MAAM,KAAK,KAAK;EAAE;AAExE,KAAI,eAAe,wBACjB,QAAO;EACL,MAAM;EACN;EACA,WAAW;EACX,MAAM,KAAK,KAAK;EACjB;AAEH,KAAI,eAAe,UACjB,QAAO;EAAE,MAAM;EAAW;EAAS,WAAW;EAAM,MAAM,KAAK,KAAK;EAAE;AAExE,KAAI,yBAAyB,IAAI,CAC/B,QAAO;EACL,MAAM;EACN,SAAS,IAAI;EACb,WAAW;EACX,MAAM,KAAK,KAAK;EACjB;AAEH,QAAO;EAAE,MAAM;EAAY;EAAS,WAAW;EAAO,MAAM,KAAK,KAAK;EAAE;;AAG1E,SAAgB,sBAAsB,OAAyB;AAC7D,QAAO,iBAAiB,aAAa,MAAM,WAAW;;AAGxD,SAAgB,iBAAiB,OAAgC;AAC/D,KAAI,2BAA2B,MAAM,CACnC,QAAO;EACL,MAAM;EACN,WAAW;EACX,QAAQ;EACR,SAAS,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM;EAChE;AAEH,KAAI,iBAAiB,kBACnB,QAAO;EACL,MAAM;EACN,WAAW;EACX,SAAS,MAAM;EACf,GAAI,MAAM,UAAU,KAAA,KAAa,EAAE,OAAO,MAAM,OAAO;EACxD;AAEH,KAAI,sBAAsB,MAAM,EAAE;EAChC,MAAM,YAAY;AAClB,SAAO;GACL,MAAM;GACN,WAAW;GACX,QAAQ;GACR,SAAS,UAAU;GACnB,GAAI,UAAU,SAAS,KAAA,KAAa,EAAE,MAAM,UAAU,MAAM;GAC7D;;AAEH,KACE,iBAAiB,cAChB,MAAM,WAAW,OAAO,MAAM,WAAW,KAE1C,QAAO;EACL,MAAM;EACN,WAAW;EACX,QAAQ,MAAM;EACd,SAAS,MAAM;EACf,GAAI,MAAM,SAAS,KAAA,KAAa,EAAE,MAAM,MAAM,MAAM;EACrD;AAEH,KAAI,iBAAiB,aAAa;EAAC;EAAK;EAAK;EAAI,CAAC,SAAS,MAAM,OAAO,CACtE,QAAO;EACL,MAAM;EACN,WAAW;EACX,QAAQ,MAAM;EACd,SAAS,MAAM;EACf,GAAI,MAAM,SAAS,KAAA,KAAa,EAAE,MAAM,MAAM,MAAM;EACrD;AAEH,KAAI,uBAAuB,MAAM,CAC/B,QAAO;EACL,MAAM;EACN,WAAW;EACX,QAAQ;EACR,SAAS,MAAM;EACf,GAAI,MAAM,SAAS,KAAA,KAAa,EAAE,MAAM,MAAM,MAAM;EACrD;AAEH,KAAI,iBAAiB,cACnB,QAAO;EACL,MAAM;EACN,WAAW;EACX,QAAQ,MAAM;EACd,SAAS,MAAM;EAChB;AAEH,KAAI,iBAAiB,aACnB,QAAO;EACL,MAAM;EACN,WAAW;EACX,OAAO,MAAM;EACb,SAAS,MAAM;EAChB;AAEH,KAAI,iBAAiB,wBACnB,QAAO;EACL,MAAM;EACN,WAAW;EACX,WAAW,MAAM;EACjB,WAAW,MAAM;EACjB,WAAW,MAAM;EACjB,SAAS,MAAM;EAChB;AAEH,KAAI,iBAAiB,aAAa,MAAM,WAAW,IACjD,QAAO;EACL,MAAM;EACN,WAAW;EACX,QAAQ;EACR,SAAS,MAAM;EACf,GAAI,MAAM,SAAS,KAAA,KAAa,EAAE,MAAM,MAAM,MAAM;EACrD;AAEH,KACE,iBAAiB,cAChB,MAAM,WAAW,OAAQ,MAAM,UAAU,OAAO,MAAM,SAAS,KAEhE,QAAO;EACL,MAAM;EACN,WAAW;EACX,QAAQ,MAAM;EACd,SAAS,MAAM;EACf,GAAI,MAAM,SAAS,KAAA,KAAa,EAAE,MAAM,MAAM,MAAM;EACpD,GAAI,MAAM,cAAc,KAAA,KAAa,EAAE,WAAW,MAAM,WAAW;EACnE,GAAI,MAAM,iBAAiB,KAAA,KAAa,EACtC,cAAc,MAAM,cACrB;EACF;AAEH,KAAI,yBAAyB,MAAM,CACjC,QAAO;EACL,MAAM;EACN,WAAW;EACX,QAAQ,MAAM;EACd,SAAS,gCAAgC,MAAM,OAAO;EACvD;AAEH,KAAI,iBAAiB,UACnB,QAAO;EACL,MAAM;EACN,WAAW;EACX,SAAS,MAAM;EAChB;AAEH,KAAI,iBAAiB,MACnB,QAAO;EACL,MAAM;EACN,WAAW;EACX,SAAS,MAAM;EAChB;AAEH,QAAO;EACL,MAAM;EACN,WAAW;EACX,SAAS,OAAO,MAAM;EACvB;;AAGH,SAAS,2BAA2B,OAAyB;AAC3D,QACE,iBAAiB,SACjB,MAAM,QACH,MAA4C,WAC9C,IACD,MAAM,QAAQ,SAAS,wBAAwB;;AAInD,SAAgB,iBACd,eACA,QACS;AACT,QACE,CAAC,OAAO,YACR,OAAO,QAAQ,WAAW,KAC1B,OAAO,eAAe;;AAI1B,SAAgB,sBAAsB,OAAyB;AAC7D,QACE,iBAAiB,iBAChB,iBAAiB,aAChB;EAAC;EAAK;EAAK;EAAK;EAAK;EAAI,CAAC,SAAS,MAAM,OAAO;;AAItD,SAAgB,sBACd,MAC6E;AAC7E,KAAI,QAAQ,KACV;AAEF,KAAI,OAAO,SAAS,UAAU;EAC5B,MAAM,QAAQ,KAAK,MAAM;AACzB,SAAO,YAAa,MAAM,WAAW,IAAI,KAAA,IAAY;;AAEvD,QAAO,OAAO,YAAY;EACxB,IAAI;AACJ,MAAI;AACF,WAAQ,MAAM,KAAK,QAAQ;WACpB,OAAO;AAKd,SAAM,IAAI,kBAHR,iBAAiB,QACb,MAAM,UACN,wBAAwB,OAAO,MAAM,IACN,EAAE,OAAO,CAAC;;EAEjD,MAAM,QAAQ,OAAO,MAAM,IAAI;AAC/B,SAAO,MAAM,WAAW,IAAI,KAAA,IAAY;;;AAI5C,SAAgB,8BACd,QACyC;AACzC,KAAI,UAAU,QAAQ,OAAO,YAAY,MACvC;AAEF,KAAI,CAAC,OAAO,SAAS,OAAO,cAAc,IAAO,CAC/C,OAAM,IAAI,MAAM,oDAAoD;AAEtE,KAAI,CAAC,OAAO,SAAS,OAAO,+BAA+B,MAAM,KAAK,CACpE,OAAM,IAAI,MACR,qEACD;CAEH,MAAM,aAAa,KAAK,IAAI,GAAG,KAAK,MAAM,OAAO,cAAc,IAAO,CAAC;CACvE,MAAM,WAAW,OAAO,+BAA+B,MAAM;AAC7D,KAAI,WAAW,EACb,OAAM,IAAI,MACR,kEACD;AAEH,QAAO;EACL,WAAW,YAAY,QAAQ,MAAM,OAAO,WAAW,CAAC;EACxD;EACA,6BAA6B,OAAO,KAAK,MAAM,SAAS,CAAC;EAC1D;;AAGH,SAAS,oBACP,OACA,OACY;AACZ,KAAI,iBAAiB,WACnB,QAAO;AAET,OAAM,IAAI,MAAM,GAAG,MAAM,yBAAyB;;AAGpD,SAAgB,uBACd,OACwC;AACxC,KAAI,SAAS,KACX;AAEF,KACE,OAAO,MAAM,aAAa,cAC1B,OAAO,MAAM,eAAe,WAE5B,OAAM,IAAI,MACR,gFACD;AAEH,QAAO;EACL,UAAU,OAAO,aACf,oBACE,MAAM,MAAM,SAAS,SAAS,EAC9B,yBACD;EACH,YAAY,OAAO,aACjB,oBACE,MAAM,MAAM,WAAW,SAAS,EAChC,2BACD;EACJ;;AAGH,SAAS,gBAAgB,GAAc,GAAuB;AAC5D,KAAI,MAAM,EACR,QAAO;AAET,KAAI,OAAO,MAAM,OAAO,KAAK,KAAK,QAAQ,KAAK,KAC7C,QAAO;AAET,KAAI,MAAM,QAAQ,EAAE,IAAI,MAAM,QAAQ,EAAE,EAAE;AACxC,MAAI,CAAC,MAAM,QAAQ,EAAE,IAAI,CAAC,MAAM,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,OAC3D,QAAO;AAET,OAAK,IAAI,IAAI,GAAG,IAAI,EAAE,QAAQ,KAAK,EACjC,KAAI,CAAC,gBAAgB,EAAE,IAAiB,EAAE,GAAgB,CACxD,QAAO;AAGX,SAAO;;AAET,KAAI,OAAO,MAAM,YAAY,OAAO,MAAM,SACxC,QAAO;CAET,MAAM,WAAW,OAAO,QAAQ,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,WAChD,KAAK,cAAc,MAAM,CAC1B;CACD,MAAM,WAAW,OAAO,QAAQ,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,WAChD,KAAK,cAAc,MAAM,CAC1B;AACD,KAAI,SAAS,WAAW,SAAS,OAC/B,QAAO;AAET,MAAK,IAAI,IAAI,GAAG,IAAI,SAAS,QAAQ,KAAK,GAAG;EAC3C,MAAM,CAAC,MAAM,UAAU,SAAS;EAChC,MAAM,CAAC,MAAM,UAAU,SAAS;AAChC,MAAI,SAAS,QAAQ,CAAC,gBAAgB,QAAqB,OAAoB,CAC7E,QAAO;;AAGX,QAAO;;AAGT,SAAgB,iBAAiB,GAAe,GAAwB;AACtE,QAAO,gBAAgB,GAAG,EAAE;;AAG9B,SAAgB,wBACd,QACe;AACf,KAAI,UAAU,QAAQ,OAAO,WAAW,KAAK,WAAW,KACtD,QAAO;AAET,KAAI,CAAC,QAAQ,KAAK,OAAO,CACvB,QAAO;AAET,QAAO,OAAO,OAAO;;AAGvB,SAAgB,wCACd,eACA,eACA,oBACQ;AACR,KAAI,kBAAkB,cACpB,QAAO;AAET,KAAI,kBAAkB,mBACpB,QAAO;CAET,MAAM,UAAU,wBAAwB,cAAc;CACtD,MAAM,WAAW,wBAAwB,mBAAmB;AAC5D,KAAI,WAAW,QAAQ,YAAY,KACjC,QAAO,WAAW,UAAU,qBAAqB;AAEnD,QAAO;;;;ACl8BT,MAAM,kBAAkB,IAAI,aAAa;AAEzC,SAAS,sBACP,MACA,OAC+B;AAC/B,KAAI,QAAQ,KACV;CAEF,MAAM,MAAgB,EAAE;AACxB,MAAK,MAAM,OAAO,MAAM;EACtB,MAAM,UAAU,IAAI,MAAM;AAC1B,MAAI,QAAQ,WAAW,EACrB;EAEF,MAAM,MAAM,IAAI,IAAI,QAAQ;AAC5B,MAAI,IAAI,aAAa,WAAW,IAAI,aAAa,SAC/C,OAAM,IAAI,MAAM,GAAG,MAAM,mCAAmC;AAE9D,MAAI,IAAI,aAAa,OAAO,IAAI,WAAW,MAAM,IAAI,SAAS,GAC5D,OAAM,IAAI,MAAM,GAAG,MAAM,kCAAkC;AAE7D,MAAI,KAAK,IAAI,OAAO;;AAEtB,QAAO,IAAI,WAAW,IAAI,KAAA,IAAY;;AAGxC,SAAS,0BACP,SACwC;AACxC,KAAI,WAAW,KACb;CAEF,MAAM,oBAAoB,QAAQ,qBAAqB;AACvD,KAAI,CAAC,OAAO,cAAc,kBAAkB,IAAI,oBAAoB,EAClE,OAAM,IAAI,MAAM,kEAAkE;CAEpF,MAAM,aAAyC;EAC7C,WAAW,sBAAsB,QAAQ,WAAW,sBAAsB;EAC1E,SAAS,sBAAsB,QAAQ,SAAS,oBAAoB;EACpE,WAAW,sBAAsB,QAAQ,WAAW,sBAAsB;EAC1E,OAAO,sBAAsB,QAAQ,OAAO,kBAAkB;EAC9D;EACD;AACD,KACE,WAAW,aAAa,QACxB,WAAW,WAAW,QACtB,WAAW,aAAa,QACxB,WAAW,SAAS,KAEpB;AAEF,QAAO;;AAGT,SAAS,sBAAsB,MAAuD;AACpF,KAAI,QAAQ,KACV,QAAO;AAET,KAAI,OAAO,SAAS,SAClB,QAAO,gBAAgB,OAAO,KAAK,CAAC;AAEtC,KAAI,gBAAgB,YAClB,QAAO,KAAK;AAEd,KAAI,YAAY,OAAO,KAAK,CAC1B,QAAO,KAAK;AAEd,KAAI,OAAO,SAAS,eAAe,gBAAgB,KACjD,QAAO,KAAK;AAEd,KAAI,OAAO,oBAAoB,eAAe,gBAAgB,gBAC5D,QAAO,gBAAgB,OAAO,KAAK,UAAU,CAAC,CAAC;;AAKnD,SAAS,uBAAuB,aAA6B;AAC3D,KAAI,eAAe,EACjB,QAAO;AAET,KAAI;EACF,MAAM,SAAS,WAAW;AAC1B,MAAI,UAAU,MAAM;GAClB,MAAM,SAAS,IAAI,YAAY,EAAE;AACjC,UAAO,gBAAgB,OAAO;AAC9B,WAAQ,OAAO,MAAM,KAAK;;SAEtB;AAGR,QAAO,KAAK,MAAM,KAAK,QAAQ,GAAG,YAAY;;AAGhD,SAAS,qBAAqB,WAAwB;AACpD,QAAO,UAAU,SAAS,SAAS,IAAI,GACnC,UAAU,SAAS,MAAM,GAAG,GAAG,GAC/B,UAAU;;;;;;;;AAShB,IAAa,yBAAb,MAAiE;CAC/D;CAEA;CACA;CACA;CACA;CACA;CACA;CAGA;CACA;CACA,qBAA8E,EAAE;CAChF;CAEA,YAAY,SAAkD;AAC5D,OAAK,YAAY,QAAQ;AACzB,OAAK,UAAU,QAAQ;AACvB,OAAK,mBAAmB,0BAA0B,QAAQ,iBAAiB;AAC3E,OAAK,YAAY,QAAQ;AACzB,OAAK,YAAY,0BAA0B,QAAQ,UAAU;AAC7D,OAAK,SAAS,IAAI,cAAc;GAC9B,KAAK,QAAQ;GACb,MAAM,QAAQ;GACd,OAAO,KAAK,YAAY,KAAK,KAAK;GAClC,OAAO,EACL,aAAa,GACd;GACD,SAAS;IACP,kBAAkB,QAAQ,gBAAgB;IAC1C,eAAe,QAAQ,gBAAgB;IACxC;GACF,CAAC;AACF,OAAK,oBAAoB,IAAI,cAAc;GACzC,KAAK,QAAQ;GACb,MAAM,QAAQ;GACd,OAAO,KAAK,YAAY,KAAK,KAAK;GAClC,OAAO,EACL,aAAa,GACd;GACD,SAAS;IACP,kBAAkB;IAClB,eAAe,QAAQ,gBAAgB;IACxC;GACF,CAAC;AACF,OAAK,eAAe,QAAQ;AAC5B,OAAK,kBAAkB,QAAQ;;CAGjC,YAAoB,OAAe,MAAuC;AACxE,SAAO,KAAK,UAAU,KAAK,gBAAgB,OAAO,KAAK,EAAE,KAAK;;CAGhE,gBAAwB,OAAe,MAA4B;EACjE,MAAM,YAAY,KAAK;AACvB,MAAI,aAAa,KACf,QAAO;EAET,MAAM,YAAY,KAAK,uBAAuB,OAAO,MAAM,UAAU;EACrE,MAAM,UAAU,KAAK,yBAAyB,WAAW,UAAU;AACnE,MAAI,WAAW,QAAQ,QAAQ,WAAW,EACxC,QAAO;EAET,MAAM,SAAS,IAAI,IAAI,MAAM;EAC7B,MAAM,cAAc,IAAI,IACtB,KAAK,gBAAgB,aAAa,SAAS,QAAQ,CACpD;AACD,SAAO,WAAW,YAAY;AAC9B,SAAO,OAAO,YAAY;AAC1B,SAAO,WAAW;AAClB,SAAO,WAAW;AAClB,SAAO,OAAO,UAAU;;CAG1B,yBACE,WACA,WAC+B;AAC/B,MAAI,aAAa,KACf,QAAO,UAAU;AAQnB,UALE,cAAc,cACV,UAAU,YACV,cAAc,YACZ,UAAU,UACV,UAAU,cACS,UAAU;;CAGvC,gBACE,WACA,SACQ;EACR,MAAM,eACJ,KAAK,mBAAmB,cAAc,uBAAuB,QAAQ,OAAO;AAC9E,OAAK,mBAAmB,cAAc,eAAe,KAAK,QAAQ;AAClE,SAAO,QAAQ,eAAe,QAAQ;;CAGxC,uBACE,OACA,MACA,WAC4B;EAC5B,MAAM,UAAU,MAAM,UAAU,OAAO,aAAa;EACpD,MAAM,aAAa,IAAI,IAAI,MAAM;EACjC,MAAM,YAAY,IAAI,IAAI,KAAK,UAAU;EACzC,MAAM,aAAa,qBAAqB,UAAU;AAElD,MACE,WAAW,SACX,WAAW,aAAa,GAAG,WAAW,YAEtC,QAAO;AAGT,MACE,WAAW,SACX,WAAW,aAAa,UAAU,YAClC,WAAW,aAAa,IAAI,SAAS,IACrC,WAAW,aAAa,IAAI,OAAO,KAAK,MAExC,QAAO;AAGT,MAAI,WAAW,UAAU,WAAW,aAAa,UAAU,SACzD;EAGF,MAAM,aAAa,sBAAsB,MAAM,KAAK;AACpD,MACE,cAAc,QACd,eAAe,KACf,aAAa,UAAU,kBAEvB;AAEF,SAAO;;CAGT,MAAM,sBAAwC;EAC5C,MAAM,iBAAiB,KAAK,QAAQ,yBAChC,MAAM,KAAK,QAAQ,wBAAwB,GAC3C,KAAA;EACJ,MAAM,SAAS,MAAM,KAAK,OAAO,OAAO;GACtC,SAAS;GACT,YAAY,KAAK;GAClB,CAAC;AACF,MAAI,OAAO,GACT,QAAO,OAAO,OAAO;AAEvB,QAAM,KAAK,mBAAmB,OAAO,OAAO;;CAG9C,MAAM,sBAAwC;EAC5C,MAAM,SAAS,MAAM,KAAK,OAAO,QAAQ;AACzC,MAAI,OAAO,GACT,QAAO,OAAO,OAAO;AAEvB,QAAM,KAAK,mBAAmB,OAAO,OAAO;;CAG9C,MAAM,uBACJ,QACA,QACA,WAMA,QACA,QACA,YAC6B;AAC7B,SAAO,KAAK,aACV,QACA,QACA,WACA,QACA,QACA,WACD;;CAGH,MAAM,eACJ,QACA,QACA,MACA,QACA,cAAkC,WAC6B;EAK/D,MAAM,SAAS,OAHb,QAAQ,QAAQ,gBAAgB,YAC5B,KAAK,oBACL,KAAK,QACiB,SAAS;GACnC;GACA;GACA;GACA;GACD,CAAC;AACF,MAAI,CAAC,OAAO,IAAI;AACd,OAAI,OAAO,OAAO,SAAS,OACzB,QAAO,EAAE,MAAM,QAAQ;AAEzB,SAAM,KAAK,mBAAmB,OAAO,OAAO;;EAG9C,MAAM,UAAU,OAAO,OAAO,SAAS,QAAQ,IAAI,YAAY;AAC/D,SAAO;GACL,MAAM;GACN,OAAO;IACL,YAAY,OAAO,OAAO;IAC1B,UAAU,OAAO,OAAO;IACxB,SAAS,QAAQ,eAAe,IAAI,EAAE,GAAG,YAAY,QAAQ;IAC7D,cAAc,OAAO,OAAO;IAC7B;GACF;;CAGH,MAAM,UACJ,QACA,cAAkC,WACR;EAG1B,MAAM,SAAS,OADb,gBAAgB,YAAY,KAAK,oBAAoB,KAAK,QAChC,UAAU,EAAE,QAAQ,CAAC;AACjD,MAAI,CAAC,OAAO,GACV,OAAM,KAAK,mBAAmB,OAAO,OAAO;EAO9C,MAAM,iBAAiB,OAAO,OAAO;EACrC,MAAM,WACJ,mBAAmB,OACf,IAAI,YAAY,GAChB,OAAO,OAAO,UAAU,QAAQ,IAAI,YAAY;AACtD,SAAO;GACL,YAAY,OAAO,OAAO;GAC1B,UAAU,OAAO,OAAO;GACxB;GACA;GACA,SAAS,OAAO,OAAO,QAAQ,SAAS,SACtC,yBAAyB,KAAK,KAAK,CACpC;GACD,cAAc,OAAO,OAAO;GAC7B;;CAGH,MAAM,0BAAkD;EACtD,MAAM,SAAS,MAAM,KAAK,OAAO,MAAM;AACvC,MAAI,CAAC,OAAO,IAAI;AACd,OAAI,OAAO,OAAO,SAAS,YACzB,QAAO;AAET,SAAM,KAAK,mBAAmB,OAAO,OAAO;;EAG9C,MAAM,iBAAiB,OAAO,OAAO;AACrC,SAAO,kBAAkB,QAAQ,eAAe,WAAW,IACvD,OACA;;CAGN,MAAM,0BAA4C;EAChD,MAAM,SAAS,MAAM,KAAK,OAAO,yBAAyB;AAC1D,MAAI,OAAO,GACT,QAAO,OAAO;AAEhB,QAAM,KAAK,mBAAmB,OAAO,OAAO;;CAG9C,MAAM,cACJ,gBACA,UACe;EACf,MAAM,SAAS,MAAM,KAAK,OAAO,YAAY;GAC3C,QAAQ;GACR,MAAM;IACJ,aAAa;IACb,MAAM;IACP;GACF,CAAC;AACF,MAAI,CAAC,OAAO,GACV,OAAM,KAAK,mBAAmB,OAAO,OAAO;;CAIhD,MAAM,cACJ,SACA,iBACuB;EACvB,MAAM,SAAS,MAAM,KAAK,OAAO,OAAO;GACtC,MAAM;IACJ,aAAa;IACb,MAAM,YAAY,QAAQ;IAC3B;GACD,UAAU;IACR,YAAY,gBAAgB;IAC5B,OAAO,gBAAgB;IACvB,KAAK,gBAAgB;IACtB;GACF,CAAC;AACF,MAAI,OAAO,GACT,QAAO;GACL,MAAM;GACN,UAAU,OAAO,OAAO,UAAU,SAAS,gBAAgB;GAC3D,QAAQ,OAAO,OAAO,UAAU,OAAO,gBAAgB;GACvD,YAAY,OAAO,OAAO;GAC3B;EAGH,MAAM,QAAQ,OAAO;AACrB,MACE,MAAM,SAAS,cACf,MAAM,QAAQ,SAAS,mBAEvB,QAAO;GAAE,MAAM;GAAW,aAAa,MAAM,OAAO;GAAa;AAEnE,MACE,MAAM,SAAS,eACf,MAAM,QAAQ,SAAS,uBAEvB,QAAO;GACL,MAAM;GACN,aAAa,MAAM,OAAO;GAC3B;AAEH,QAAM,KAAK,mBAAmB,MAAM;;CAGtC,MAAM,iBACJ,gBACA,SACA,iBACuB;EACvB,MAAM,SAAS,MAAM,KAAK,OAAO,UAAU;GACzC;GACA,MAAM;IACJ,aAAa;IACb,MAAM,YAAY,QAAQ;IAC3B;GACD,UAAU;IACR,YAAY,gBAAgB;IAC5B,OAAO,gBAAgB;IACvB,KAAK,gBAAgB;IACtB;GACF,CAAC;AACF,MAAI,OAAO,IAAI;AACb,OAAI,OAAO,OAAO,SAAS,WACzB,QAAO;IACL,MAAM;IACN,gBAAgB,OAAO,OAAO;IAC9B,eAAe,OAAO,OAAO;IAC9B;AAEH,UAAO;IACL,MAAM;IACN,UACE,OAAO,OAAO,MAAM,UAAU,SAAS,gBAAgB;IACzD,QACE,OAAO,OAAO,MAAM,UAAU,OAAO,gBAAgB;IACvD,YAAY,OAAO,OAAO,MAAM;IACjC;;EAGH,MAAM,QAAQ,OAAO;AACrB,MACE,MAAM,SAAS,cACf,MAAM,QAAQ,SAAS,mBAEvB,QAAO;GAAE,MAAM;GAAW,aAAa,MAAM,OAAO;GAAa;AAEnE,MACE,MAAM,SAAS,eACf,MAAM,QAAQ,SAAS,uBAEvB,QAAO;GACL,MAAM;GACN,aAAa,MAAM,OAAO;GAC3B;AAEH,QAAM,KAAK,mBAAmB,MAAM;;CAGtC,MAAM,YAAY,SAAiD;EACjE,MAAM,SAAS,MAAM,KAAK,OAAO,OAAO,EACtC,MAAM;GACJ,aAAa;GACb,MAAM,YAAY,QAAQ;GAC3B,EACF,CAAC;AACF,MAAI,OAAO,GACT,QAAO,OAAO,OAAO;AAEvB,QAAM,KAAK,mBAAmB,OAAO,OAAO;;CAG9C,MAAM,sBACJ,QACA,QACe;AACf,MAAI,OAAO,eAAe,EACxB;EAEF,MAAM,WAAW,MAAM,KAAK,gBAC1B,KAAK,WACL;GACE,QAAQ;GACR,SAAS,EACP,gBAAgB,qBACjB;GACD,MAAM;GACN;GACD,EACD,KAAK,gBAAgB,kBACrB,UACD;AACD,MAAI,SAAS,WAAW,OAAO,SAAS,WAAW,IACjD;AAEF,QAAM,MAAM,KAAK,2BAA2B,UAAU,mBAAmB;;;;;;;;;CAU3E,MAAM,iBACJ,aACA,QACA,QACA,YAKA;EACA,MAAM,aAAa,KAAK,uBAAuB;EAC/C,MAAM,WAAW,MAAM,KAAK,gBAC1B,YACA;GACE,QAAQ;GACR,SAAS,EACP,QAAQ,qBACT;GACD;GACD,EACD,KAAK,gBAAgB,kBACrB,UACD;AACD,MAAI,SAAS,WAAW,IACtB,QAAO,EAAE,MAAM,aAAa;AAE9B,MAAI,CAAC,SAAS,IAAI;AAChB,OAAI,SAAS,WAAW,KAAK;IAC3B,MAAM,UAAU,MAAM,SAAS,MAAM;AACrC,QAAI,0BAA0B,QAAQ,CACpC,QAAO,EAAE,MAAM,eAAe;AAEhC,UAAM,IAAI,UACR,QAAQ,SAAS,IACb,+BAA+B,YAC/B,8BACJ,SAAS,OACV;;AAEH,SAAM,MAAM,KAAK,2BAA2B,UAAU,sBAAsB;;AAE9E,MAAI,SAAS,QAAQ,KACnB,OAAM,IAAI,cAAc,sCAAsC;AAGhE,MAAI,EADwB,SAAS,QAAQ,IAAI,eAAe,IAAI,IAC3C,aAAa,CAAC,SAAS,oBAAoB,CAClE,OAAM,IAAI,cACR,iEACD;EAGH,MAAM,eACJ,SAAS,QAAQ,IAAI,2BAA2B,IAAI,KAAA;EACtD,MAAM,UAAU,YAAqC;GACnD,IAAI,eAAe;AAInB,cAAW,MAAM,SAAS,cAAc,SAAS,MAAO,EAAE,YAAY,CAAC,EAAE;AACvE,QAAI,CAAC,cAAc;AACjB,SAAI,MAAM,UAAU,YAClB,OAAM,IAAI,cACR,iDAAiD,MAAM,MAAM,GAC9D;AAEH,WAAM,YAAY,iBAAiB,MAAM,MAAM,aAAa,CAAC;AAC7D,oBAAe;AACf;;AAEF,QAAI,MAAM,UAAU,QAAQ;AAC1B,WAAM,OAAO,iBAAiB,MAAM,MAAM,aAAa,CAAC;AACxD;;AAEF,UAAM,IAAI,cACR,mCAAmC,MAAM,MAAM,GAChD;;AAEH,OAAI,CAAC,aACH,OAAM,IAAI,cAAc,uCAAuC;AAEjE,UAAO,EAAE,MAAM,MAAM;;EAGvB,IAAI;EACJ,MAAM,YACJ,UAAU,QAAQ,CAAC,OAAO,UACtB,IAAI,SAAgB,GAAG,WAAW;AAChC,wBAAqB;AACd,aAAS,MAAM,QAAQ,CAAC,YAAY,GAAG;AAC5C,WACE,OAAO,UACL,IAAI,aAAa,8BAA8B,aAAa,CAC/D;;AAEH,UAAO,iBAAiB,SAAS,cAAc,EAAE,MAAM,MAAM,CAAC;IAC9D,GACF;AACN,MAAI;AACF,OAAI,QAAQ,SAAS;AACnB,UAAM,SAAS,KAAK,QAAQ,CAAC,YAAY,GAAG;AAC5C,UACE,OAAO,UACP,IAAI,aAAa,8BAA8B,aAAa;;AAGhE,UAAO,aAAa,OAChB,MAAM,SAAS,GACf,MAAM,QAAQ,KAAK,CAAC,SAAS,EAAE,UAAU,CAAC;YACtC;AACR,OAAI,gBAAgB,QAAQ,UAAU,KACpC,QAAO,oBAAoB,SAAS,aAAa;;;CAKvD,MAAc,gBACZ,OACA,MACA,WACA,OACmB;EACnB,MAAM,aAAa,KAAK,gBAAgB,OAAO,KAAK;EAIpD,MAAM,aACJ,KAAK,gBAAgB,OACjB,KAAA,IACA,MAAM,iBACJ,KAAK,aAAa,EAAE,QAAQ,WAAW,CAAC,EACxC,WACA,MACD;EACP,MAAM,gBAAgB,MAAM,KAAK,iBAC/B,YACA;GACE,GAAG;GACH,SAAS,KAAK,oBAAoB,KAAK,SAAS,WAAW;GAC5D,EACD,WACA,MACD;AACD,MACE,KAAK,gBAAgB,QACpB,cAAc,WAAW,OAAO,cAAc,WAAW,IAE1D,QAAO;AAET,QAAM,cAAc,MAAM,QAAQ,CAAC,YAAY,GAAG;EAClD,MAAM,iBAAiB,MAAM,KAAK,yBAChC,cAAc,QACd,YACA,WACA,MACD;AACD,SAAO,KAAK,iBACV,YACA;GACE,GAAG;GACH,SAAS,KAAK,oBAAoB,KAAK,SAAS,eAAe;GAChE,EACD,WACA,MACD;;CAGH,MAAc,iBACZ,OACA,MACA,WACA,OACmB;EACnB,MAAM,aAAa,IAAI,iBAAiB;EACxC,MAAM,SAAS,kBAAkB,KAAK,QAAQ,WAAW,OAAO;EAChE,MAAM,QAAQ,iBAAiB;AAC7B,cAAW,MAAM,IAAI,aAAa,OAAO,UAAU,CAAC;KACnD,UAAU;AACb,MAAI;AACF,UAAO,MAAM,KAAK,UAAU,OAAO;IACjC,GAAG;IACH;IACD,CAAC;WACK,OAAO;AACd,OACE,WAAW,OAAO,WAClB,WAAW,OAAO,kBAAkB,aAEpC,OAAM,WAAW,OAAO;AAE1B,SAAM;YACE;AACR,gBAAa,MAAM;;;CAIvB,MAAc,aACZ,SAC6B;AAC7B,SAAO,MAAM,KAAK,eAAe,QAAQ;;CAG3C,MAAc,yBACZ,QACA,eACA,WACA,OAC6B;AAC7B,MAAI,KAAK,gBAAgB,KACvB;AAEF,MAAI,KAAK,uBAAuB,MAAM;GACpC,MAAM,WAAwC,KAAK,aAAa;IAC9D,QAAQ;IACR;IACA;IACD,CAAC,CAAC,cAAc;AACf,QAAI,KAAK,wBAAwB,SAC/B,MAAK,sBAAsB,KAAA;KAE7B;AACF,QAAK,sBAAsB;;EAE7B,MAAM,WAAW,KAAK;AACtB,MAAI;AACF,UAAO,MAAM,iBAAiB,UAAU,WAAW,MAAM;WAClD,OAAO;AAEd,OACE,iBAAiB,gBACjB,KAAK,wBAAwB,SAE7B,MAAK,sBAAsB,KAAA;AAE7B,SAAM;;;CAIV,oBACE,MACA,OACS;EACT,MAAM,UAAU,IAAI,QAAQ,KAAK;AACjC,MAAI,SAAS,QAAQ,MAAM,WAAW,GAAG;AACvC,WAAQ,OAAO,gBAAgB;AAC/B,UAAO;;AAET,UAAQ,IAAI,iBAAiB,UAAU,QAAQ;AAC/C,SAAO;;CAGT,MAAc,2BACZ,UACA,SACgB;EAChB,MAAM,WAAW,MAAM,SAAS,MAAM,CAAC,YAAY,GAAG;AACtD,SAAO,IAAI,UACT,SAAS,SAAS,IACd,GAAG,QAAQ,WAAW,aACtB,GAAG,QAAQ,sBAAsB,SAAS,UAC9C,SAAS,QACT;GACE,WAAW,oCAAoC,SAAS,QAAQ;GAChE,cAAc,kBACZ,SAAS,QAAQ,IAAI,cAAc,IAAI,KAAA,EACxC;GACD,MAAM,SAAS,SAAS,IAAI,WAAW,KAAA;GACxC,CACF;;CAGH,wBAAwC;EACtC,MAAM,SAAS,IAAI,IAAI,KAAK,UAAU;AACtC,SAAO,aAAa,IAAI,QAAQ,MAAM;AACtC,SAAO,OAAO,UAAU;;CAG1B,mBAA2B,OAA2B;AACpD,UAAQ,MAAM,MAAd;GACE,KAAK;GACL,KAAK;GACL,KAAK;GACL,KAAK;GACL,KAAK;GACL,KAAK;GACL,KAAK,eACH,QAAO,IAAI,UAAU,MAAM,SAAS,MAAM,QAAQ,EAChD,MAAM,MAAM,UACb,CAAC;GACJ,KAAK,UACH,QAAO,IAAI,aACT,MAAM,OACN,MAAM,UAAU,SACZ,KAAK,gBAAgB,gBACrB,KAAK,gBAAgB,iBAC1B;GACH,KAAK,gBACH,QAAO,MAAM,iBAAiB,YAC1B,MAAM,QACN,IAAI,UAAU,MAAM,QAAQ;GAClC,KAAK,iBACH,QAAO,IAAI,cAAc,MAAM,SAAS,MAAM,OAAO;GACvD,KAAK;AACH,QAAI,OAAO,MAAM,WAAW,SAC1B,QAAO,IAAI,UAAU,MAAM,SAAS,MAAM,QAAQ;KAChD,WAAW,4BAA4B,MAAM,QAAQ;KACrD,cAAc,kBAAkB,MAAM,UAAU,eAAe;KAC/D,MAAM,MAAM;KACb,CAAC;AAEJ,QAAI,MAAM,iBAAiB,UACzB,QAAO,MAAM;AAEf,QAAI,MAAM,iBAAiB,MACzB,QAAO,MAAM;AAEf,WAAO,IAAI,MAAM,MAAM,QAAQ;GACjC,SAAS;IACP,MAAM,aAAoB;AAC1B,WAAO,IAAI,MAAM,OAAO,WAAW,CAAC;;;;CAK1C,MAAc,aACZ,eACA,QACA,WAMA,QACA,QACA,YAC6B;EAC7B,MAAM,SAAS,MAAM,KAAK,oBACxB,eACA,QACA,QACA,QACA,WACD;AACD,MAAI,OAAO,SAAS,KAClB,QAAO;EAMT,IAAI;EACJ,MAAM,YACJ,UAAU,QAAQ,CAAC,OAAO,UACtB,IAAI,SAAgB,GAAG,WAAW;AAChC,wBAAqB;AACd,WAAO,QAAQ,QAAQ;AAC5B,WACE,OAAO,UACL,IAAI,aAAa,8BAA8B,aAAa,CAC/D;;AAEH,UAAO,iBAAiB,SAAS,cAAc,EAAE,MAAM,MAAM,CAAC;IAC9D,GACF;AACN,MAAI;AACF,OAAI,QAAQ,SAAS;AACnB,UAAM,OAAO,QAAQ,QAAQ;AAC7B,UACE,OAAO,UACP,IAAI,aAAa,8BAA8B,aAAa;;GAGhE,MAAM,SACJ,aAAa,OACT,MAAM,QAAQ,KAAK,CACjB,OAAO,QAAQ,SAAS,UAAU,EAClC,UACD,CAAC,GACF,MAAM,OAAO,QAAQ,SAAS,UAAU;AAC9C,OAAI,OAAO,OACT,OAAM,OAAO,QAAQ,QAAQ;AAE/B,UAAO;IACL,MAAM;IACN,cAAc,OAAO;IACrB,cAAc,OAAO;IACtB;YACO;AACR,OAAI,gBAAgB,QAAQ,UAAU,KACpC,QAAO,oBAAoB,SAAS,aAAa;;;CAKvD,MAAc,oBACZ,eACA,QACA,QACA,QACA,YAC6B;EAC7B,MAAM,SAAS,MAAM,KAAK,OAAO,eAAe;GAC9C,QAAQ;GACR;GACA;GACA;GACD,CAAC;AACF,MAAI,CAAC,OAAO,IAAI;AACd,OAAI,OAAO,OAAO,SAAS,OACzB,QAAO,EAAE,MAAM,QAAQ;AAEzB,OAAI,OAAO,OAAO,SAAS,YACzB,QAAO,EAAE,MAAM,aAAa;AAE9B,OACE,OAAO,OAAO,SAAS,iBACvB,0BAA0B,OAAO,OAAO,QAAQ,CAEhD,QAAO,EAAE,MAAM,eAAe;AAEhC,SAAM,KAAK,mBAAmB,OAAO,OAAO;;EAG9C,MAAM,UAAU,OAAO;AACvB,YAAU;EACV,IAAI,oBAA8B,EAAE;EACpC,IAAI,eAAe,QAAQ,cAAc;EACzC,IAAI,eAAe,QAAQ;EAC3B,IAAI,YAAY;AAEhB,SAAO;GACL,MAAM;GACN,SAAS;IACP,UAAU,OAAO,kBAAkB;AACjC,SAAI,UACF,QAAO;MACL,MAAM;MACN;MACA;MACA,QAAQ;MACT;AAEH,gBAAW,MAAM,SAAS,QAAQ,QAAQ;AACxC,UAAI,MAAM,UAAU,QAAQ;AAC1B,yBAAkB,KAAK,MAAM,KAAK;AAClC;;AAEF,UAAI,MAAM,UAAU,UAClB;MAEF,MAAM,UAAU,oBAAoB,MAAM,KAAK;MAC/C,MAAM,UAAwB,EAAE;AAChC,WAAK,MAAM,aAAa,kBACtB,SAAQ,KACN,GAAG,sBAAsB,WAAW,QAAQ,aAAa,CAC1D;AAEH,0BAAoB,EAAE;MACtB,MAAM,gBAAgB,MAAM,cAC1B,SACA,QAAQ,YACR,QAAQ,UACR,QAAQ,aACT;AACD,qBAAe,QAAQ;AACvB,UAAI,QAAQ,gBAAgB,KAC1B,gBAAe,QAAQ;AAEzB,UAAI,kBAAkB,QAAQ;AAC5B,mBAAY;AACZ,aAAM,QAAQ,QAAQ;AACtB,cAAO;QACL,MAAM;QACN;QACA;QACA,QAAQ;QACT;;AAEH,UAAI,kBAAkB,QACpB,QAAO;OACL,MAAM;OACN;OACA;OACA,QAAQ;OACT;;AAGL,iBAAY;AACZ,YAAO;MACL,MAAM;MACN;MACA;MACA,QAAQ;MACT;;IAEH,QAAQ,YAAY;AAClB,SAAI,UACF;AAEF,iBAAY;AACZ,WAAM,QAAQ,QAAQ;;IAEzB;GACF;;;AAIL,SAAS,0BACP,OACoB;AACpB,KAAI,SAAS,KACX;AAEF,KAAI,CAAC,OAAO,cAAc,MAAM,IAAI,QAAQ,EAC1C,OAAM,IAAI,MAAM,uDAAuD;AAEzE,QAAO;;AAGT,MAAM,0BAA0B;CAC9B;CACA;CACA;CACA;CACD;AAED,SAAS,4BACP,SACoB;AACpB,KAAI,WAAW,KACb;CAEF,MAAM,aAAqC,EAAE;AAC7C,MAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,QAAQ,CAChD,YAAW,IAAI,aAAa,IAAI;AAElC,MAAK,MAAM,QAAQ,yBAAyB;EAC1C,MAAM,QAAQ,WAAW;AACzB,MAAI,OAAO,UAAU,YAAY,MAAM,SAAS,EAC9C,QAAO;;;AAMb,SAAS,oCACP,SACoB;AACpB,MAAK,MAAM,QAAQ,yBAAyB;EAC1C,MAAM,QAAQ,QAAQ,IAAI,KAAK;AAC/B,MAAI,OAAO,UAAU,YAAY,MAAM,SAAS,EAC9C,QAAO;;;AASb,SAAS,kBAAkB,OAA+C;AACxE,KAAI,SAAS,KACX;CAEF,MAAM,UAAU,MAAM,MAAM;AAC5B,KAAI,QAAQ,WAAW,EACrB;CAEF,MAAM,UAAU,OAAO,QAAQ;AAC/B,KAAI,OAAO,SAAS,QAAQ,EAAE;AAC5B,MAAI,UAAU,EACZ;AAEF,SAAO,KAAK,MAAM,UAAU,IAAK;;CAEnC,MAAM,SAAS,KAAK,MAAM,QAAQ;AAClC,KAAI,OAAO,MAAM,OAAO,CACtB;CAEF,MAAM,QAAQ,SAAS,KAAK,KAAK;AACjC,QAAO,QAAQ,IAAI,QAAQ,KAAA;;AAG7B,eAAe,iBACb,SACA,WACA,OACY;AACZ,KAAI,EAAE,YAAY,GAChB,QAAO,MAAM;CAEf,IAAI;AACJ,KAAI;AACF,SAAO,MAAM,QAAQ,KAAK,CACxB,SACA,IAAI,SAAgB,GAAG,WAAW;AAChC,WAAQ,iBAAiB;AACvB,WAAO,IAAI,aAAa,OAAO,UAAU,CAAC;MACzC,UAAU;IACb,CACH,CAAC;WACM;AACR,eAAa,MAAM;;;;;;;;;ACnpCvB,MAAM,uCAAuC;AAyG7C,SAAS,oBAA6C;AACpD,QAAO;EACL,qBAAqB,EAAE;EACvB,gBAAgB,SAAS;EACzB,sBAAsB,IAAI,YAAY;EACtC,sBAAsB,EAAE;EACxB,qBAAqB,KAAA;EACrB,2BAA2B,EAAE;EAC7B,mCAAmC,KAAA;EACpC;;AAGH,SAAS,yBAAyB,QAAuC;AACvE,KAAI,UAAU,QAAQ,OAAO,eAAe,EAC1C,QAAO;AAET,QAAO;;AAGT,SAAS,QAAQ,OAAuB;AACtC,QAAO,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,OAAO,MAAM,CAAC;;AAGlE,SAAS,0BAAiC;AACxC,wBAAO,IAAI,MAAM,sBAAsB;;;;;;;;;AAUzC,IAAa,sBAAb,MAAiC;;CAE/B;;CAEA;;CAGA;;CAEA;;CAEA;;CAEA;CAEA,YAAY,SAAqC;AAC/C,OAAK,YAAY,QAAQ;AACzB,OAAK,UAAU,QAAQ;AACvB,OAAK,kBAAkB;GACrB,GAAG;GACH,GAAG,QAAQ;GACZ;AACD,OAAK,QAAQ,QAAQ,SAAS;AAC9B,OAAK,SAAS,IAAI,uBAAuB;GACvC,WAAW,QAAQ;GACnB,SAAS,mBAAmB;GAC5B,cAAc,sBAAsB,QAAQ,KAAK;GACjD,WAAW,QAAQ,SAAS,WAAW,MAAM,KAAK,WAAW;GAC7D,iBAAiB,KAAK;GACtB,WAAW,QAAQ;GACpB,CAAC;;CAGJ,MAAM,KACJ,QAIA;EACA,MAAM,WAAW,KAAK;AACtB,MAAI,YAAY,KACd,KAAI;AACF,SAAM,SAAS;AACf,UAAO;IAAE,IAAI;IAAM,OAAO,KAAK,mBAAmB,UAAU,OAAO;IAAE;WAC9D,OAAO;AACd,UAAO;IAAE,IAAI;IAAO,OAAO,iBAAiB,MAAM;IAAE;;EAIxD,MAAM,QAAQ,KAAK,iBAAiB;AACpC,QAAM,UAAU,KAAK,UAAU,MAAM;AACrC,OAAK,YAAY;AACjB,MAAI;AACF,SAAM,MAAM;AACZ,UAAO;IAAE,IAAI;IAAM,OAAO,KAAK,mBAAmB,OAAO,OAAO;IAAE;WAC3D,OAAO;AACd,SAAM,KAAK,iBAAiB,MAAM;AAClC,UAAO;IAAE,IAAI;IAAO,OAAO,iBAAiB,MAAM;IAAE;;;CAIxD,SAAe;EACb,MAAM,QAAQ,KAAK;AACnB,MAAI,SAAS,QAAQ,MAAM,OACzB;AAEF,QAAM,eAAe;AACrB,QAAM,qBAAqB,OAAO;AAClC,QAAM,uBAAuB,IAAI,iBAAiB;EAElD,MAAM,qBAAqB,MAAM;AACjC,QAAM,oBAAoB,IAAI,iBAAiB;AAC/C,sBAAoB,OAAO;AAE3B,MAAI,MAAM,eAAe,kBAAkB,MAAM,eAAe,SAAS;AACvE,QAAK,cAAc,OAAO,eAAe;AACpC,QAAK,YAAY,MAAM;;AAE9B,MAAI,MAAM,gBAAgB,SAAS;AACjC,QAAK,eAAe,OAAO,eAAe;AACrC,QAAK,kBAAkB,MAAM;;;CAItC,MAAM,QAAuB;EAC3B,MAAM,QAAQ,KAAK;AACnB,MAAI,SAAS,KACX,OAAM,KAAK,iBAAiB,MAAM;;CAItC,kBAA8C;AAC5C,SAAO;GACL,UAAU;GACV,QAAQ;GACR,QAAQ;GACR,YAAY;GACZ,aAAa;GACb,iCAAiB,IAAI,KAAK;GAC1B,cAAc,IAAI,OAAO;GACzB,mCAAmB,IAAI,KAAK;GAC5B,wBAAwB;GACxB,mBAAmB;GACnB,aAAa;GACb,YAAY;GACZ,aAAa,EAAE;GACf,eAAe;GACf,cAAc;GACd,sBAAsB,IAAI,iBAAiB;GAC3C,kBAAkB;GAClB,SAAS,QAAQ,SAAS;GAC3B;;CAGH,MAAc,UAAU,OAA0C;AAChE,QAAM,mBAAmB,KAAK,QAAQ,uBAAuB,WAAW;AACtE,QAAK,aAAa,OAAO,yBAAyB,OAAO,CAAC;IAC1D;AACF,QAAM,oBAAoB,IAAI,iBAAiB;EAC/C,MAAM,YAAY,KAAK,iBAAiB,MAAM;AACzC,OAAK,YAAY,MAAM;AAC5B,QAAM;AAEN,MAAI,MAAM,OACR;AAEF,OAAK,eAAe,OAAO,KAAK;AAChC,QAAM,KAAK,kBAAkB,MAAM;AACnC,MAAI,MAAM,WAAW,WAAW,MAAM,WAAW,eAC/C,OAAM,IAAI,MAAM,oCAAoC,MAAM,SAAS;;CAIvE,iBAAyB,OAA0C;AACjE,SAAO,IAAI,SAAS,SAAS,WAAW;AACtC,SAAM,mBAAmB;AACzB,SAAM,kBAAkB;IACxB;;CAGJ,mBACE,OACA,QAC6B;AAC7B,QAAM,YAAY;EAClB,MAAM,iBAAiB,QAAQ;AAC/B,MAAI,kBAAkB,MAAM;AAC1B,SAAM,gBAAgB,IAAI,eAAe;AACzC,kBAAe,MAAM,OAAO;;EAE9B,IAAI,eAAe;AACnB,SAAO;GACL,mBAAmB;AACjB,QAAI,aACF;AAEF,mBAAe;AACf,QAAI,kBAAkB,KACpB,OAAM,gBAAgB,OAAO,eAAe;AAE9C,UAAM,WAAW,KAAK,IAAI,GAAG,MAAM,WAAW,EAAE;AAChD,QAAI,MAAM,aAAa,KAAK,CAAC,MAAM,OAC5B,MAAK,iBAAiB,MAAM;;GAGrC,IAAI,YAAY;AACd,WAAO,MAAM,WAAW;;GAE1B,IAAI,SAAS;AACX,WAAO,MAAM;;GAEf,IAAI,oBAAoB;AACtB,WAAO,MAAM;;GAEf,iBAAiB,aAAa,KAAK,eAAe,OAAO,SAAS;GAClE,iBAAiB,YAAY;AAC3B,QAAI,MAAM,OACR,OAAM,yBAAyB;IAEjC,MAAM,YAAY,MAAM;IACxB,MAAM,cAAc,MAAM;AAC1B,QAAI,cAAc,EAChB;AAEF,WAAO,IAAI,SAAe,SAAS,WAAW;AAC5C,WAAM,YAAY,KAAK;MAAE;MAAa;MAAW;MAAS;MAAQ,CAAC;MACnE;;GAEL;;CAGH,eACE,OACA,UACY;AACZ,QAAM,gBAAgB,IAAI,SAAS;AACnC,WAAS,MAAM,OAAO;AACtB,eAAa;AACX,SAAM,gBAAgB,OAAO,SAAS;;;CAI1C,MAAc,iBAAiB,OAA0C;AACvE,MAAI,MAAM,OACR;AAEF,QAAM,SAAS;AACf,QAAM,oBAAoB;AAC1B,QAAM,mBAAmB,OAAO;AAChC,QAAM,qBAAqB,OAAO;AAClC,OAAK,gBAAgB,OAAO,yBAAyB,CAAC;AACtD,OAAK,kBAAkB,OAAO,yBAAyB,CAAC;AACxD,MAAI,KAAK,cAAc,MACrB,MAAK,YAAY,KAAA;;CAIrB,iBAAyB,OAAiC;AACxD,MAAI,MAAM,iBACR;AAEF,QAAM,mBAAmB;AACzB,QAAM,oBAAoB;;CAG5B,gBACE,OACA,OACM;AACN,MAAI,MAAM,iBACR;AAEF,QAAM,mBAAmB;AACzB,QAAM,kBAAkB,MAAM;;CAGhC,MAAc,YAAY,OAA0C;AAClE,SAAO,CAAC,MAAM,QAAQ;AACpB,SAAM,sBAAsB,IAAI,iBAAiB;GACjD,MAAM,qBAAqB,MAAM;GAIjC,IAAI,gBAAgB;GAOpB,IAAI,kBAAwD;GAC5D,IAAI,sBAAsB;GAC1B,MAAM,wBAA8B;AAClC,QAAI,mBAAmB,MAAM;AAC3B,kBAAa,gBAAgB;AAC7B,uBAAkB;;;GAGtB,MAAM,wBAA8B;AAClC,qBAAiB;IACjB,MAAM,YAAY,KAAK,gBAAgB;AACvC,QAAI,aAAa,QAAQ,YAAY,KAAK,CAAC,MAAM,OAC/C,mBAAkB,iBAAiB;AACjC,2BAAsB;AACtB,wBAAmB,OAAO;OACzB,UAAU;;AAGjB,OAAI;AACF,qBAAiB;IACjB,MAAM,SAAS,MAAM,KAAK,OAAO,iBAC/B,OAAO,cAAc;AACnB,sBAAiB;AACjB,SAAI,UAAU,aAAa,EACzB,OAAM,KAAK,QAAQ,mBAAmB,CAAC,UAAU,CAAC;AAEpD,qBAAgB,KAAK,KAAK;AAC1B,UAAK,cAAc,OAAO,KAAK;AAC/B,UAAK,iBAAiB,MAAM;OAE9B,OAAO,WAAW;AAChB,sBAAiB;AACjB,SAAI,OAAO,aAAa,EACtB,OAAM,KAAK,QAAQ,mBAAmB,CAAC,OAAO,CAAC;OAGnD,mBAAmB,QACnB,gBACD;AACD,qBAAiB;AACjB,QAAI,MAAM,OACR;AAEF,QAAI,OAAO,SAAS,MAAM;AACxB,WAAM,eAAe;AACrB,UAAK,cAAc,OAAO,eAAe;AACzC,WAAM,MAAM,EAAE;AACd;;AAEF,QAAI,OAAO,SAAS,cAClB,OAAM,IAAI,MAAM,wCAAwC;AAE1D,QAAI,OAAO,SAAS,YAClB,OAAM,IAAI,UAAU,8BAA8B,IAAI;YAEjD,OAAO;AACd,qBAAiB;AACjB,QAAI,MAAM,OACR;AAEF,QAAI,mBAAmB,OAAO,SAAS;AACrC,WAAM,oBAAoB,IAAI,iBAAiB;AAC/C,UAAK,cAAc,OAAO,eAAe;AACzC,SAAI,CAAC,oBAEH;AAOF,SACE,gBAAgB,KAChB,KAAK,KAAK,GAAG,iBAAA,IAEb,OAAM,eAAe;AAEvB,SAAI,MAAM,gBAAgB,KAAK,gBAAgB,aAAa;AAC1D,WAAK,SAAS,mDAAmD,OAAO;OACtE,WAAW,KAAK;OAChB,UAAU,MAAM;OACjB,CAAC;AACF,WAAK,cAAc,OAAO,eAAe;AACzC,WAAK,gBAAgB,OAAO,QAAQ,MAAM,CAAC;AAC3C;;KAEF,MAAM,QAAQ,kBACZ,MAAM,cACN,KAAK,gBACN;AACD,WAAM,gBAAgB;AACtB,WAAM,MAAM,OAAO,MAAM,qBAAqB,OAAO;AACrD;;IAEF,MAAM,aAAa,mBAAmB,MAAM;AAC5C,QAAI,CAAC,WAAW,aAAa,sBAAsB,MAAM,EAAE;AACzD,UAAK,SAAS,8BAA8B,OAAO;MACjD,WAAW,KAAK;MAChB,MAAM,WAAW;MAClB,CAAC;AACF,UAAK,cAAc,OAAO,QAAQ;AAClC,UAAK,gBAAgB,OAAO,QAAQ,MAAM,CAAC;AAC3C;;AAEF,SAAK,cAAc,OAAO,eAAe;AASzC,QACE,gBAAgB,KAChB,KAAK,KAAK,GAAG,iBAAA,IAEb,OAAM,eAAe;AAEvB,QAAI,MAAM,gBAAgB,KAAK,gBAAgB,aAAa;AAC1D,UAAK,SAAS,sCAAsC,OAAO;MACzD,WAAW,KAAK;MAChB,UAAU,MAAM;MACjB,CAAC;AACF,UAAK,cAAc,OAAO,eAAe;AACzC,UAAK,gBAAgB,OAAO,QAAQ,MAAM,CAAC;AAC3C;;IAEF,MAAM,QAAQ,kBACZ,MAAM,cACN,KAAK,gBACN;AACD,UAAM,gBAAgB;AACtB,UAAM,MAAM,OAAO,MAAM,qBAAqB,OAAO;;;;CAK3D,aACE,OACA,QACM;AACN,MAAI,MAAM,UAAU,UAAU,QAAQ,OAAO,eAAe,EAC1D;EAEF,MAAM,MAAM,KAAK,eAAe,OAAO;EAMvC,MAAM,WACJ,OAAO,OAAO,KAAA,IAAY,MAAM,kBAAkB,IAAI,IAAI;EAC5D,IAAI;AACJ,MAAI,YAAY,MAAM;AACpB,SAAM,eAAe;AACrB,SAAM,cAAc;AACpB,aAAU,MAAM;QAEhB,WAAU,SAAS;EAErB,MAAM,QAA4B;GAChC;GACA;GACA,OAAO;GACP;GACA,YAAY;GACZ,UAAU;GACX;AACD,MAAI,YAAY,MAAM;AACpB,YAAS,aAAa;AACtB,YAAS,SAAS;AAClB,SAAM,SAAS,SAAS;AACxB,YAAS,QAAQ;AACjB,SAAM,0BAA0B;;AAElC,MAAI,OAAO,KACT,OAAM,kBAAkB,IAAI,KAAK,MAAM;AAEzC,QAAM,aAAa,SAAS,MAAM;AAClC,QAAM,qBAAqB;AAC3B,OAAK,yBAAyB,MAAM;AACpC,MAAI,MAAM,gBAAgB,aACnB,MAAK,kBAAkB,MAAM;;CAItC,MAAc,kBAAkB,OAA0C;AACxE,MAAI,MAAM,iBAAiB,MAAM,OAC/B;AAEF,QAAM,gBAAgB;AACtB,MAAI;AACF,UAAO,CAAC,MAAM,QAAQ;IACpB,IAAI,UAAU;AACd,WAAO,CAAC,MAAM,QAAQ;KACpB,MAAM,QAAQ,KAAK,iBAAiB,MAAM;AAC1C,SAAI,SAAS,KACX;AAEF,SAAI;AACF,YAAM,KAAK,OAAO,sBAAsB,MAAM,OAAQ;AAGtD,YAAM,aAAa,UAAU;AAC7B,YAAM,qBAAqB,MAAM;AACjC,YAAM,cAAc;AACpB,WAAK,wBAAwB,OAAO,MAAM,QAAQ;AAClD,WAAK,eAAe,OAAO,KAAK;AAChC;cACO,OAAO;MACd,MAAM,aAAa,mBAAmB,MAAM;AAC5C,UAAI,CAAC,WAAW,aAAa,sBAAsB,MAAM,EAAE;AACzD,YAAK,kBACH,uBACA,IAAI,MAAM,4BAA4B,WAAW,OAAO,CACzD;AACD,YAAK,eAAe,OAAO,QAAQ;AACnC;;AAEF,UAAI,WAAW,KAAK,gBAAgB,aAAa;AAC/C,YAAK,kBACH,uBACA,IAAI,MAAM,0CAA0C,CACrD;AACD,YAAK,eAAe,OAAO,QAAQ;AACnC;;AAEF,WAAK,eAAe,OAAO,eAAe;MAC1C,MAAM,QAAQ,kBAAkB,SAAS,KAAK,gBAAgB;AAC9D,iBAAW;AACX,YAAM,MAAM,OAAO,MAAM,qBAAqB,OAAO;;;;YAInD;AACR,SAAM,gBAAgB;;;CAI1B,eAAuB,QAAwC;EAC7D,MAAM,UAAU,KAAK;AACrB,MAAI,QAAQ,kBAAkB,KAC5B;AAEF,MAAI;AACF,UAAO,QAAQ,eAAe,OAAO,IAAI,KAAA;WAClC,OAAO;AACd,QAAK,SAAS,4CAA4C,MAAM;AAChE;;;;;;;CAQJ,iBACE,OACgC;AAChC,WAAS;GACP,MAAM,OAAO,MAAM,aAAa,WAAW;AAC3C,OAAI,QAAQ,KACV;AAEF,OAAI,KAAK,YAAY;AACnB,UAAM,aAAa,UAAU;AAC7B,UAAM,0BAA0B;AAChC;;AAEF,OAAI,CAAC,KAAK,UAAU;AAClB,SAAK,WAAW;AAChB,QACE,KAAK,OAAO,QACZ,MAAM,kBAAkB,IAAI,KAAK,IAAI,KAAK,KAE1C,OAAM,kBAAkB,OAAO,KAAK,IAAI;;AAG5C,UAAO;;;;CAKX,yBAAiC,OAAiC;AAChE,MACE,MAAM,yBAAyB,wCAC/B,MAAM,yBAAyB,KAAK,MAAM,aAAa,OAEvD;EAEF,MAAM,OAAO,MAAM,aAChB,SAAS,CACT,QAAQ,UAAU,CAAC,MAAM,WAAW;AACvC,QAAM,aAAa,OAAO;AAC1B,OAAK,MAAM,SAAS,KAClB,OAAM,aAAa,SAAS,MAAM;AAEpC,QAAM,yBAAyB;;;;;;;;CASjC,wBACE,OACA,SACM;EACN,IAAI,IAAI;AACR,SAAO,IAAI,MAAM,YAAY,QAAQ;GACnC,MAAM,SAAS,MAAM,YAAY;AACjC,OAAI,UAAU,OAAO,aAAa;AAChC,SAAK;AACL;;AAEF,UAAO,aAAa;AACpB,OAAI,OAAO,aAAa,GAAG;AACzB,UAAM,YAAY,OAAO,GAAG,EAAE;AAC9B,WAAO,SAAS;SAEhB,MAAK;;;CAKX,kBAA0B,OAA2B,OAAoB;AACvE,OAAK,MAAM,UAAU,MAAM,YAAY,OAAO,EAAE,CAC9C,QAAO,OAAO,MAAM;;CAIxB,cACE,OACA,YACM;AACN,MAAI,MAAM,eAAe,WACvB;AAEF,QAAM,aAAa;AACnB,OAAK,gBAAgB,MAAM;;CAG7B,eACE,OACA,aACM;AACN,MAAI,MAAM,gBAAgB,YACxB;AAEF,QAAM,cAAc;AACpB,OAAK,gBAAgB,MAAM;;CAG7B,gBAAwB,OAAiC;EACvD,MAAM,SAAS,KAAK,cAAc,MAAM;AACxC,MAAI,MAAM,WAAW,OACnB;AAEF,QAAM,SAAS;AACf,OAAK,MAAM,YAAY,MAAM,gBAC3B,UAAS,OAAO;;CAIpB,cACE,OACoC;AACpC,MAAI,MAAM,eAAe,WAAW,MAAM,gBAAgB,QACxD,QAAO;AAET,MAAI,MAAM,eAAe,eACvB,QAAO;AAET,MACE,MAAM,eAAe,gBACrB,MAAM,gBAAgB,aAEtB,QAAO;AAET,MACE,MAAM,eAAe,kBACrB,MAAM,gBAAgB,eAEtB,QAAO;AAET,SAAO;;CAGT,SACE,SACA,OACA,QACM;AACN,MAAI,CAAC,KAAK,MACR;AAEF,UAAQ,MAAM,kBAAkB,WAAW,OAAO,OAAO;;;;;AClvB7D,SAAgB,mCAGd,GAAG,OAC+C;CAClD,MAAM,SAAS,MAAM,QAClB,SAAuD,QAAQ,KACjE;AACD,KAAI,OAAO,WAAW,EACpB;AAEF,QAAO,OAAO,YAAY;AACxB,OAAK,MAAM,QAAQ,OACjB,OAAM,KAAK,QAAQ;;;AAmBzB,eAAsB,oBAEpB,OAMkC;CAClC,MAAM,OAA+B;EACnC,GAAG,MAAM;EACT,cAAc,MAAM,OAAO,KAAK,MAAM;EACvC;AACD,KAAI,MAAM,0BAA0B,KAClC,OAAM,MAAM,uBAAuB;EACjC,QAAQ;EACR,QAAQ,MAAM;EACf,CAAC;AAEJ,OAAM,MAAM,kBAAkB,KAAK,KAAK;AACxC,QAAO;;AAaT,SAAS,IAAI,WAA2B;AACtC,QAAO;;AAGT,SAAS,aAAa,OAAkD;AACtE,QAAO,OAAO,UAAU,YAAY,SAAS,QAAQ,CAAC,MAAM,QAAQ,MAAM;;AAG5E,SAAS,eAAkB,OAAa;AACtC,QAAO,gBAAgB,MAAM;;AAG/B,SAAS,kBACP,QACwB;AACxB,QAAO;EACL,GAAG;EACH,yBAAyB,eAAe,OAAO,wBAAwB;EACxE;;AAUH,SAAS,uBACP,QACwB;AACxB,KAAI,CAAC,aAAa,OAAO,wBAAwB,CAC/C,OAAM,IAAI,MACR,6EACD;AAEH,QAAO;EACL,WAAW,OAAO;EAClB,YAAY,OAAO;EACnB,yBAAyB,eACvB,OAAO,wBACR;EACD,aAAa,OAAO;EACrB;;AAGH,SAAS,qBACP,QACoB;AACpB,QAAO;EACL,WAAW,OAAO;EAClB,YAAY,OAAO;EACnB,yBAAyB,eAAe,OAAO,wBAAwB;EACvE,aAAa,OAAO;EACrB;;;;;AAMH,IAAa,4BAAb,MAEyC;CACvC,uBAAwB,IAAI,KAAqC;CAEjE,MAAM,KACJ,WACwC;EACxC,MAAM,QAAQ,KAAK,KAAK,IAAI,IAAI,UAAU,CAAC;AAC3C,SAAO,SAAS,OAAO,OAAO,kBAAkB,MAAM;;CAGxD,MAAM,KAAK,QAA+C;AACxD,OAAK,KAAK,IAAI,IAAI,OAAO,UAAU,EAAE,kBAAkB,OAAO,CAAC;;CAGjE,MAAM,OAAO,WAAkC;AAC7C,OAAK,KAAK,OAAO,IAAI,UAAU,CAAC;;;AAIpC,MAAM,kBAAkB;AACxB,MAAM,qBAAqB;AAC3B,MAAM,qBAAqB;;;;;;;;;AAU3B,IAAa,6BAAb,MAEyC;CACvC;CACA;CACA;CACA;CAEA,KAAiC;CACjC,cAA4C;CAE5C,YAAY,SAA6C;AACvD,OAAK,SAAS,SAAS,UAAU;AACjC,OAAK,YAAY,SAAS,aAAa;AACvC,OAAK,YAAY,SAAS,aAAa;AACvC,OAAK,YAAY,SAAS,aAAa,WAAW;;CAGpD,MAAM,KACJ,WACwC;EACxC,MAAM,KAAK,MAAM,KAAK,UAAU;EAChC,MAAM,SAAS,MAAM,IAAI,SAAoC,SAAS,WAAW;GAG/E,MAAM,UAFK,GAAG,YAAY,CAAC,KAAK,UAAU,EAAE,WAAW,CACtC,YAAY,KAAK,UAAU,CACtB,IAAI,IAAI,UAAU,CAAC;AACzC,WAAQ,kBAAkB;AACxB,YAAS,QAAQ,UAA6C,KAAK;;AAErE,WAAQ,gBAAgB;AACtB,WAAO,QAAQ,yBAAS,IAAI,MAAM,wBAAwB,CAAC;;IAE7D;AACF,SAAO,UAAU,OAAO,OAAO,uBAAiC,OAAO;;CAGzE,MAAM,KAAK,QAA+C;EACxD,MAAM,KAAK,MAAM,KAAK,UAAU;EAChC,MAAM,SAAS,qBAAqB,OAAO;AAC3C,QAAM,IAAI,SAAe,SAAS,WAAW;GAC3C,MAAM,KAAK,GAAG,YAAY,CAAC,KAAK,UAAU,EAAE,YAAY;GAExD,MAAM,UADQ,GAAG,YAAY,KAAK,UAAU,CACtB,IAAI,QAAQ,IAAI,OAAO,UAAU,CAAC;AACxD,MAAG,mBAAmB,SAAS;AAC/B,MAAG,gBACD,OAAO,GAAG,SAAS,QAAQ,yBAAS,IAAI,MAAM,wBAAwB,CAAC;AACzE,MAAG,gBACD,OACE,GAAG,SACD,QAAQ,yBACR,IAAI,MAAM,qCAAqC,CAClD;AACH,WAAQ,gBACN,OAAO,QAAQ,yBAAS,IAAI,MAAM,wBAAwB,CAAC;IAC7D;;CAGJ,MAAM,OAAO,WAAkC;EAC7C,MAAM,KAAK,MAAM,KAAK,UAAU;AAChC,QAAM,IAAI,SAAe,SAAS,WAAW;GAC3C,MAAM,KAAK,GAAG,YAAY,CAAC,KAAK,UAAU,EAAE,YAAY;GAExD,MAAM,UADQ,GAAG,YAAY,KAAK,UAAU,CACtB,OAAO,IAAI,UAAU,CAAC;AAC5C,MAAG,mBAAmB,SAAS;AAC/B,MAAG,gBACD,OACE,GAAG,SAAS,QAAQ,yBAAS,IAAI,MAAM,0BAA0B,CAClE;AACH,MAAG,gBACD,OACE,GAAG,SACD,QAAQ,yBACR,IAAI,MAAM,uCAAuC,CACpD;AACH,WAAQ,gBACN,OAAO,QAAQ,yBAAS,IAAI,MAAM,0BAA0B,CAAC;IAC/D;;CAGJ,MAAc,WAAiC;AAC7C,MAAI,KAAK,MAAM,KACb,QAAO,KAAK;AAEd,MAAI,KAAK,eAAe,MAAM;AAC5B,SAAM,KAAK;AACX,OAAI,KAAK,MAAM,KACb,OAAM,IAAI,MAAM,6BAA6B;AAE/C,UAAO,KAAK;;AAEd,MAAI,KAAK,aAAa,KACpB,OAAM,IAAI,MAAM,iDAAiD;AAGnE,OAAK,cAAc,IAAI,SAAe,SAAS,WAAW;GACxD,MAAM,UAAU,KAAK,UAAW,KAAK,KAAK,QAAQ,KAAK,UAAU;AAEjE,WAAQ,wBAAwB;IAC9B,MAAM,KAAK,QAAQ;AACnB,QAAI,CAAC,GAAG,iBAAiB,SAAS,KAAK,UAAU,CAC/C,IAAG,kBAAkB,KAAK,UAAU;;AAGxC,WAAQ,gBACN,OAAO,QAAQ,yBAAS,IAAI,MAAM,wBAAwB,CAAC;AAC7D,WAAQ,kBAAkB;AACxB,SAAK,KAAK,QAAQ;AAClB,SAAK,GAAG,wBAAwB;AAC9B,UAAK,IAAI,OAAO;AAChB,UAAK,KAAK;;AAEZ,aAAS;;IAEX,CAAC,cAAc;AACf,QAAK,cAAc;IACnB;AAEF,QAAM,KAAK;AACX,MAAI,KAAK,MAAM,KACb,OAAM,IAAI,MAAM,qDAAqD;AAEvE,SAAO,KAAK;;;;;;AAOhB,SAAgB,0BAEd,OAIyB;AACzB,QAAO;EACL,WAAW,MAAM;EACjB,YAAY,MAAM;EAClB,yBAAyB,eAAe,MAAM,wBAAwB;EACtE,aAAa,KAAK,KAAK;EACxB;;;;AC3VH,MAAM,uBAAuB;;;;;;AAO7B,SAAS,sBACP,YACQ;AACR,KAAI,WAAW,WAAW,EACxB,QAAO;CAET,MAAM,QAAQ,WACX,MAAM,GAAG,qBAAqB,CAC9B,KAAK,SAAS,GAAG,KAAK,KAAK,IAAI,KAAK,MAAM,IAAI,KAAK,IAAI,GAAG,CAC1D,KAAK,KAAK;CACb,MAAM,SACJ,WAAW,SAAS,uBAChB,MAAM,WAAW,SAAS,qBAAqB,SAC/C;AACN,QAAO,sBAAsB,WAAW,OAAO,YAAY,QAAQ,OAAO;;AAG5E,IAAa,2BAAb,cAA8C,MAAM;CAClD;;;;;;;;CAQA;CAEA,YACE,YACA,gBACA;AAGA,QACE,0DAA0D,sBAAsB,WAAW,GAC5F;AACD,OAAK,aAAa;AAClB,OAAK,iBAAiB;;;AAI1B,SAAS,eACP,OACqC;AACrC,QACE,OAAO,UAAU,YACjB,SAAS,QACT,aAAa,SACb,cAAc,SACd,OAAQ,MAA0C,aAAa;;AAInE,SAAS,qBACP,OAC4B;AAC5B,KAAI,eAAe,MAAM,CACvB,QAAO;AAET,QAAO;EACL,SAAS;EACT,UAAU;EACX;;AAqBH,IAAa,yBAAb,MAAiE;CAC/D;CAIA;CAIA;CAGA;CAIA,oCAAqC,IAAI,SAGtC;CAEH,YAAY,SAAkD;AAC5D,OAAK,UAAU,QAAQ;AACvB,OAAK,iBAAiB,QAAQ;AAC9B,OAAK,oBAAoB,QAAQ;AACjC,OAAK,mBAAmB,QAAQ;;CAGlC,iBAAiB,QAAyC;AACxD,SAAO,CAAC,KAAK,kBAAkB,IAAI,OAAO;;;;;;;;;;;;;;;CAgB5C,qBACE,QACA,YACM;AACN,OAAK,kBAAkB,IAAI,QAAQ,WAAW;;CAGhD,MAAM,sBACJ,QACA,UACA,SACA,YACA,WAAW,OACX,iBAAiB,MACgB;EACjC,IAAI,cAAc,OAAO;EACzB,IAAI,WAAW;EACf,IAAI,aAA4C,EAAE;AAClD,MAAI,SAAS,aAAa,GAAG;AAC3B,OACE,OAAO,mBAAmB,YAC1B,eAAe,WAAW,KAC1B,eAAe,SAAS,QACxB,mBAAmB,SACnB,mBAAmB,KAEnB,OAAM,IAAI,MAAM,oEAAoE;GAEtF,MAAM,UAAU,qBACd,MAAM,KAAK,QAAQ,cACjB,KAAK,kBAAkB,OACnB,WACA,MAAM,KAAK,eAAe,UAAU,eAAe,CACxD,CACF;AACD,iBAAc,QAAQ;AACtB,cAAW,QAAQ;AACnB,gBAAa,QAAQ,cAAc,EAAE;;EAEvC,MAAM,eAAe,MAAM,KAAK,wBAAwB,QAAQ;AAChE,MAAI,aAAa,SAAS,GAAG;GAC3B,MAAM,UAAU,qBACd,MAAM,KAAK,QAAQ,mBACjB,cACA,YACD,CACF;AACD,iBAAc,QAAQ;AACtB,cAAW,QAAQ;AACnB,gBAAa,QAAQ,cAAc,EAAE;;AAEvC,SAAO,MAAM,KAAK,eAChB;GACE,GAAG;GACH;GACA,yBAAyB;GAC1B,EACD,aACA,UACA,YACA,SACD;;CAGH,MAAM,oBACJ,QACA,OACA,YACA,WAAW,OACX,iBAAiB,MACgB;EACjC,MAAM,WAAW,MAAM,MAAM,IAAI,YAAY;AAC7C,SAAO,MAAM,KAAK,sBAChB,QACA,UACA,MAAM,MAAM,EAAE,EACd,YACA,UACA,eACD;;CAGH,MAAM,iBACJ,QACA,OACA,YACiC;AACjC,SAAO,MAAM,KAAK,mBAChB,QACA,CAAC,GAAG,MAAM,EACV,YACA,UACA,KAAA,EACD;;CAGH,MAAM,mBACJ,QACA,QACA,wBAIiC;AACjC,SAAO,MAAM,KAAK,mBAChB,QACA,OAAO,SACP,OAAO,YACP,UACA,wBACA,OAAO,SACR;;CAGH,MAAM,gBACJ,QACA,eACA,QAKA,wBAIiC;EACjC,MAAM,kBAAkB,OAAO;EAC/B,IAAI,cAAc;EAClB,IAAI,WAAW,KAAK,iBAAiB,OAAO;EAC5C,IAAI,aAAa,KAAK,kBAAkB,IAAI,OAAO,IAAI,EAAE;EACzD,MAAM,eAAe,MAAM,KAAK,wBAAwB,OAAO,QAAQ;AACvE,MAAI,aAAa,SAAS,GAAG;GAC3B,MAAM,UAAU,qBACd,MAAM,KAAK,QAAQ,mBACjB,cACA,YACD,CACF;AACD,iBAAc,QAAQ;AACtB,cAAW,QAAQ;AACnB,gBAAa,QAAQ,cAAc,EAAE;AACrC,SAAM,yBAAyB,iBAAiB,YAAY;;AAE9D,SAAO,MAAM,KAAK,eAChB;GACE,GAAG;GACH,YAAY,wCACV,eACA,OAAO,YACP,OAAO,WACR;GACD,yBAAyB;GAC1B,EACD,UACA,UACA,YACA,OAAO,aAAa,KACrB;;CAGH,MAAc,mBACZ,QACA,SACA,YACA,QACA,wBAIA,WAAW,OACsB;EACjC,MAAM,kBAAkB,OAAO;EAC/B,IAAI,cAAc;EAClB,IAAI,WAAW,KAAK,iBAAiB,OAAO;EAC5C,IAAI,aAAa,KAAK,kBAAkB,IAAI,OAAO,IAAI,EAAE;EACzD,MAAM,eAAe,MAAM,KAAK,wBAAwB,QAAQ;AAChE,MAAI,aAAa,SAAS,GAAG;GAC3B,MAAM,UAAU,qBACd,MAAM,KAAK,QAAQ,mBACjB,cACA,YACD,CACF;AACD,iBAAc,QAAQ;AACtB,cAAW,QAAQ;AACnB,gBAAa,QAAQ,cAAc,EAAE;AACrC,SAAM,yBAAyB,iBAAiB,YAAY;;AAE9D,SAAO,MAAM,KAAK,eAChB;GACE,GAAG;GACH;GACA,yBAAyB;GAC1B,EACD,QACA,UACA,YACA,SACD;;CAGH,MAAc,eACZ,QACA,QACA,UACA,YACA,UACiC;AACjC,MAAI,CAAC,UAAU;AACb,OAAI,SACF,OAAM,IAAI,yBACR,YACA,OAAO,wBACR;AAEH,QAAK,kBAAkB,IAAI,QAAQ,WAAW;AAC9C,UAAO;;AAET,SAAO,MAAM,KAAK,iBAAiB,QAAQ,OAAO;;CAGpD,MAAc,wBACZ,OACuB;AACvB,MAAI,MAAM,WAAW,EACnB,QAAO,EAAE;AAEX,SAAO,KAAK,qBAAqB,OAC7B,CAAC,GAAG,MAAM,GACV,MAAM,KAAK,kBAAkB,MAAM;;;;;AC/W3C,SAAgB,+BAAuD;AACrE,QAAO;EACL,uBAAuB;EACvB,eAAe;EACf,gBAAgB;EACjB;;;;;;;;AAmCH,IAAa,2BAAb,MAAmE;CACjE;CACA;CACA;CAIA;CACA;CAIA;CAGA;CAIA;CAKA;CACA;CAIA,YAAY,SAAoD;AAC9D,OAAK,UAAU,QAAQ;AACvB,OAAK,SAAS,QAAQ;AACtB,OAAK,iBAAiB,QAAQ;AAC9B,OAAK,0BAA0B,QAAQ;AACvC,OAAK,gBAAgB,QAAQ;AAC7B,OAAK,mBAAmB,QAAQ;AAChC,OAAK,WAAW,QAAQ;AACxB,OAAK,WAAW,QAAQ;AACxB,OAAK,iBAAiB,QAAQ;AAC9B,OAAK,mBAAmB,QAAQ;;CAGlC,QAAQ,OAAkC;AACxC,OAAK,mBAAmB,MAAM,SAAS;;CAGzC,qBACE,OACA,iBACA,aACM;EACN,MAAM,WAAW,MAAM;AACvB,MAAI,SAAS,iBAAiB,KAC5B;AAEF,MAAI,CAAC,iBAAiB,iBAAiB,YAAY,CACjD,UAAS,wBAAwB;;CAIrC,SAAS,OAAkC;EACzC,MAAM,SAAS,KAAK;AACpB,MAAI,UAAU,QAAQ,MAAM,OAC1B;EAEF,MAAM,WAAW,MAAM;AACvB,OAAK,mBAAmB,SAAS;AACjC,WAAS,wBAAwB;AACjC,WAAS,gBAAgB,iBAAiB;AACnC,QAAK,qBAAqB,MAAM;KACpC,OAAO,WAAW;;CAGvB,mBAA2B,UAAwC;AACjE,MAAI,SAAS,iBAAiB,MAAM;AAClC,gBAAa,SAAS,cAAc;AACpC,YAAS,gBAAgB,KAAA;;;CAI7B,MAAc,qBAAqB,OAA2C;EAC5E,MAAM,WAAW,MAAM;AACvB,WAAS,gBAAgB,KAAA;AACzB,MAAI,KAAK,kBAAkB,MAAM,IAAI,KACnC;AAEF,MAAI,SAAS,eAAe;AAC1B,YAAS,iBAAiB;AAC1B;;AAEF,WAAS,gBAAgB;AACpB,OAAK,iBAAiB,YAAY;AACrC,SAAM,KAAK,YAAY,MAAM;IAC7B,CACC,OAAO,UAAU;AAChB,QAAK,SAAS,+BAA+B,MAAM;IACnD,CACD,cAAc;AACb,YAAS,gBAAgB;AACzB,OAAI,SAAS,kBAAkB,CAAC,MAAM,QAAQ;AAC5C,aAAS,iBAAiB;AACrB,SAAK,qBAAqB,MAAM;;IAEvC;;CAGN,MAAc,YAAY,OAA2C;EACnE,MAAM,SAAS,MAAM,KAAK,oBAAoB,MAAM;AACpD,MAAI,UAAU,KACZ;AAEF,MAAI,CAAC,KAAK,iBAAiB,MAAM,OAAO,EAAE;AACxC,QAAK,SAAS,sDAAsD,EAClE,QAAQ,MAAM,OAAO,YACtB,CAAC;AACF;;EAEF,MAAM,YAAY,wBAAwB,MAAM,OAAO,WAAW;AAClE,MAAI,aAAa,MAAM;AACrB,QAAK,SAAS,kDAAkD,EAC9D,QAAQ,MAAM,OAAO,YACtB,CAAC;AACF;;AAEF,MAAI;GACF,MAAM,uBAAuB,MAAM,KAAK,yBAAyB;GACjE,MAAM,gBAAgB,wBAAwB,qBAAqB;AACnE,OAAI,iBAAiB,MAAM;AACzB,SAAK,SAAS,qDAAqD,EACjE,QAAQ,sBACT,CAAC;AACF;;AAEF,OAAI,aAAa,cACf;AAEF,OAAI,YAAY,gBAAgB,OAAO,4BACrC;GAEF,MAAM,WAAW,KAAK,QAAQ,gBAAgB;AAC9C,SAAM,KAAK,cACT,MAAM,OAAO,YACb,KAAK,kBAAkB,OACnB,WACA,MAAM,KAAK,eAAe,UAAU,MAAM,OAAO,WAAW,CACjE;WACM,OAAO;AACd,QAAK,SAAS,0BAA0B,MAAM;;;CAIlD,kBACE,OACyC;EACzC,MAAM,SAAS,KAAK;AACpB,MAAI,UAAU,QAAQ,MAAM,UAAU,CAAC,KAAK,eAAe,MAAM,CAC/D;AAEF,MACE,MAAM,SAAS,yBACf,MAAM,WAAW,kBACjB,MAAM,WAAW,kBACjB,MAAM,WAAW,QAEjB;AAEF,SAAO;;CAGT,MAAc,oBACZ,OACkD;EAClD,MAAM,SAAS,KAAK,kBAAkB,MAAM;AAC5C,MAAI,UAAU,KACZ;AAEF,MAAI,CAAE,MAAM,KAAK,UAAU,OAAO,CAChC;AAEF,SAAO;;CAGT,MAAc,UACZ,QACkB;AAClB,MAAI;AACF,UAAO,QAAQ,MAAM,OAAO,WAAW,CAAC;WACjC,OAAO;AACd,QAAK,SAAS,2CAA2C,MAAM;AAC/D,UAAO;;;;;;AC5Ob,SAAgB,gBACd,QACA,WAAyB,OACJ;AACrB,QAAO;EACL,UAAU;EACV,QAAQ;EACR,QAAQ;EACR,eAAe;EACf,gBAAgB;EAChB,iCAAiB,IAAI,KAAK;EAC1B,cAAc,IAAI,OAAO;EACzB,eAAe;EACf,cAAc;EACd,2BAA2B;EAC3B,sBAAsB,IAAI,iBAAiB;EAC3C,iBAAiB;EACjB,uBAAuB;EACvB,wBAAwB;EACxB,oBAAoB;EACpB;EACA;EACA,UAAU,8BAA8B;EACxC,SAAS,QAAQ,SAAS;EAC1B,aAAa,EAAE;EACf,mBAAmB;EACpB;;AAQH,eAAsB,iBACpB,OACA,SACe;AACf,OAAM,SAAS;AACf,SAAQ,gBAAgB,QAAQ,MAAM;AACtC,KAAI,MAAM,oBAAoB,MAAM;AAClC,eAAa,MAAM,iBAAiB;AACpC,QAAM,mBAAmB,KAAA;;AAE3B,KAAI,MAAM,mBAAmB,MAAM;AACjC,eAAa,MAAM,gBAAgB;AACnC,QAAM,kBAAkB,KAAA;;CAE1B,MAAM,oBAAoB,MAAM;AAChC,OAAM,oBAAoB,KAAA;CAC1B,MAAM,mBAAmB,MAAM;AAC/B,OAAM,mBAAmB,KAAA;AACzB,oBAAmB,OAAO;AAC1B,OAAM,qBAAqB,OAAO;AAClC,qBAAoB;CACpB,MAAM,gCAAgB,IAAI,MAAM,sBAAsB;AACtD,MAAK,MAAM,UAAU,MAAM,YAAY,OAAO,EAAE,CAC9C,QAAO,OAAO,cAAc;AAE9B,SAAQ,cAAc;;;;ACUxB,MAAM,yBAAyB;AA8B/B,SAAS,wBACP,SACA,QACQ;AACR,QAAO,KAAK,IAAI,kBAAkB,SAAS,OAAO,EAAE,uBAAuB;;AAG7E,SAAS,kBAAkB,aAA6B;CACtD,MAAM,YAAY,KAAK,IAAI,GAAG,KAAK,KAAK,GAAG,YAAY;AACvD,KAAI,aAAA,IACF,QAAO;AAET,QAAO,KAAK,IACV,wBACA,iCAAiC,UAClC;;;;;AAMH,IAAa,cAAb,MAAa,YAE0B;CACrC;CACA;CACA;CAEA;CACA;CACA;CACA;CACA;CAIA;CAIA;CAGA;CAGA;CAGA;CACA;CACA;CACA;CACA,aAA8B,kBAAkB;CAChD,gBAAwB;CACxB,kBAA0B;CAc1B;CACA;CACA;CACA,mBAAoC,IAAI,OAAkC;CAC1E;CACA;CACA;CACA;CACA,UAAiC,QAAQ,SAAS;;CAElD;;;;;CAMA,OAAwB,eAAe,EAAE,MAAM,QAAiB;;;;CAKhE,YAAY,SAAuC;AACjD,OAAK,YAAY,QAAQ;AACzB,OAAK,UAAU,QAAQ;EACvB,MAAM,eAAe,sBAAsB,QAAQ,KAAK;AACxD,OAAK,wBAAwB,QAAQ,yBAAyB;EAC9D,MAAM,YAAY,QAAQ,SAAS,WAAW,MAAM,KAAK,WAAW;AACpE,OAAK,oBACH,QAAQ,qBAAqB,IAAI,2BAAqC;AACxE,OAAK,kBAAkB;GACrB,GAAG;GACH,GAAG,QAAQ;GACZ;AACD,OAAK,QAAQ,QAAQ,SAAS;AAC9B,OAAK,yBAAyB,mCAC5B,QAAQ,wBACR,GAAI,QAAQ,+BAA+B,EAAE,CAC9C;EACD,MAAM,gBAAgB,uBAAuB,QAAQ,cAAc;AACnE,MAAI,QAAQ,8BAA8B,QAAQ,QAAQ,QAAQ,KAChE,OAAM,IAAI,MACR,qEACD;EAEH,MAAM,oBAAoB,2BACxB,QAAQ,MACR,OACD;AACD,MACE,mBAAmB,gBAAgB,eACnC,QAAQ,kBAAkB,QAC1B,QAAQ,eAAe,YAAY,MAEnC,OAAM,IAAI,MACR,qEACD;EAEH,MAAM,uBAAuB,8BAC3B,QAAQ,eACT;AACD,OAAK,iBAAiB,KAAK,uBACzB,mBACA,eAAe,WAChB;AACD,OAAK,iBAAiB,KAAK,uBACzB,mBACA,eAAe,SAChB;AACD,OAAK,oBACH,qBAAqB,OACjB,KAAA,KACC,UAAU,kBAAkB,kBAAkB,MAAM;AAC3D,OAAK,oBACH,qBAAqB,OACjB,KAAA,KACC,YAAY,kBAAkB,kBAAkB,QAAQ;AAC/D,OAAK,+BACH,qBAAqB,OACjB,KAAA,KACC,YAAY,kBAAkB,oCAAoC,QAAQ;AACjF,OAAK,SAAS,IAAI,uBAAuB;GACvC,WAAW,KAAK;GAChB,SAAS,KAAK;GACd,kBAAkB,QAAQ;GAC1B;GACA;GACA,iBAAiB,KAAK;GACtB,WAAW,QAAQ;GACpB,CAAC;AACF,OAAK,gBAAgB,KAAK,oBACxB,KAAK,UACJ,QAAQ,WAAW,KAAK,iBAAiB,QAAQ,OAAO,CAC1D;AACD,OAAK,wBAAwB,KAAK,oBAChC,KAAK,SACL,OAAO,WAAW,OACnB;AACD,OAAK,kBAAkB,IAAI,yBAAyB;GAClD,SAAS,KAAK;GACd,QAAQ;GACR,gBAAgB,KAAK;GACrB,+BAA+B,KAAK,OAAO,yBAAyB;GACpE,gBAAgB,gBAAgB,aAC9B,KAAK,OAAO,cAAc,gBAAgB,SAAS;GACrD,mBAAmB,SAAS,KAAK,iBAAiB,KAAK;GACvD,WAAW,SAAS,WAAW,KAAK,SAAS,SAAS,OAAO;GAC7D,WAAW,SAAS,OAAO,WACzB,KAAK,SAAS,SAAS,OAAO,OAAO;GACvC,iBAAiB,UAAU,KAAK,cAAc;GAC9C,mBAAmB,WAAW,KAAK,cAAc,iBAAiB,OAAO;GAC1E,CAAC;AACF,OAAK,kBAAkB,KAAK,qBAAqB;AACjD,OAAK,4BAA4B,KAAK,QAAQ,uBAAuB,UAAU;AAC7E,OAAI,KAAK,2BAA2B,KAClC;AAEF,QAAK,iBAAiB,SAAS,MAAM;IACrC;;CAGJ,uBACE,mBACA,oBAGY;AACZ,MAAI,qBAAqB,QAAQ,sBAAsB,KACrD;AAEF,SAAO,OAAO,UAAU,uBAAuB;GAC7C,MAAM,gBACJ,qBAAqB,OACjB,WACA,MAAM,kBAAkB,eAAe,UAAU,mBAAmB;AAC1E,UAAO,sBAAsB,OACzB,gBACA,MAAM,mBAAmB,cAAc;;;CAI/C,uBACE,mBACA,kBAGY;AACZ,MAAI,qBAAqB,QAAQ,oBAAoB,KACnD;AAEF,SAAO,OAAO,UAAU,uBAAuB;GAC7C,MAAM,qBACJ,oBAAoB,OAChB,WACA,MAAM,iBAAiB,SAAS;AACtC,UAAO,qBAAqB,OACxB,qBACA,MAAM,kBAAkB,eAAe,oBAAoB,mBAAmB;;;;;;CAOtF,MAAM,QAAuB;AAC3B,OAAK,qBAAqB,mBAAmB;EAC7C,MAAM,QAAQ,KAAK;AACnB,MAAI,SAAS,KACX,OAAM,KAAK,iBAAiB,MAAM;;;;;;;;;;;;CActC,SAAe;EACb,MAAM,QAAQ,KAAK;AACnB,MAAI,SAAS,QAAQ,MAAM,OACzB;AAIF,MAAI,MAAM,mBAAmB;AAC3B,SAAM,oBAAoB;AAC1B,SAAM,YAAY,KAAA;AAClB,SAAM,eAAe;AACrB,SAAM,qBAAqB,OAAO;AAClC,SAAM,uBAAuB,IAAI,iBAAiB;AAClD,SAAM,gBAAgB;AACtB,SAAM,iBAAiB;AACvB,QAAK,oBAAoB,MAAM;AAC/B,SAAM,UAAU,KAAK,UAAU,MAAM;AAGrC,SAAM,QAAQ,OAAO,UAAU;AAC7B,QAAI,MAAM,OACR;IAEF,MAAM,iBAAiB,iBAAiB,MAAM;AAC9C,QAAI,eAAe,WAAW;AAC5B,WAAM,oBAAoB;AAC1B,WAAM,YAAY;UAEb,MAAK,iBAAiB,MAAM;KAEnC;AACF;;AAMF,OACG,MAAM,kBAAkB,QAAQ,MAAM,kBAAkB,iBACzD,MAAM,mBAAmB,KAEzB;AAEF,QAAM,eAAe;AACrB,QAAM,wBAAwB;AAC9B,QAAM,yBAAyB;AAC/B,OAAK,sBAAsB,MAAM;AACjC,OAAK,qBAAqB,MAAM;AAChC,QAAM,qBAAqB,OAAO;AAClC,QAAM,uBAAuB,IAAI,iBAAiB;EAGlD,MAAM,qBAAqB,MAAM;AACjC,QAAM,oBAAoB,IAAI,iBAAiB;AAC/C,sBAAoB,OAAO;AAK3B,MAFE,MAAM,kBAAkB,kBACxB,MAAM,kBAAkB,SACJ;AACpB,QAAK,iBAAiB,OAAO,eAAe;AACvC,QAAK,YAAY,MAAM;;AAK9B,MAAI,MAAM,mBAAmB,SAAS;AACpC,SAAM,iBACJ,MAAM,aAAa,SAAS,IAAI,iBAAiB;AACnD,QAAK,oBAAoB,MAAM;AAC/B,OAAI,MAAM,aAAa,SAAS,EACzB,MAAK,kBAAkB,MAAM;;;;;;;;;;;CAaxC,oBAAoC;EAClC,MAAM,QAAQ,KAAK;AAOnB,SAAO;GACL,mBANA,SAAS,QAAQ,CAAC,MAAM,SACpB,MAAM,aAAa,SACnB,KAAK,2BAA2B,OAC9B,KAAK,iBAAiB,SACtB;GAGN,GAAI,KAAK,wBAAwB,KAAA,KAAa,EAC5C,qBAAqB,KAAK,qBAC3B;GACD,GAAI,OAAO,cAAc,KAAA,KAAa,EAAE,WAAW,MAAM,WAAW;GACpE,GAAI,OAAO,mBAAmB,KAAA,KAAa,EACzC,gBAAgB,MAAM,gBACvB;GACF;;;;;CAMH,MAAM,oBAAoE;AACxE,MAAI;AACF,UAAO;IACL,IAAI;IACJ,OAAO,MAAM,KAAK,OAAO,yBAAyB;IACnD;WACM,OAAO;AACd,UAAO;IAAE,IAAI;IAAO,OAAO,iBAAiB,MAAM;IAAE;;;;;;;;;CAUxD,MAAM,2BAEJ;AACA,MAAI;AACF,SAAM,KAAK,QAAQ,qBAAqB;GACxC,MAAM,SAAS,MAAM,KAAK,SAAS,OAAO;GAC1C,MAAM,cAAc,KAAK,QAAQ,gBAAgB;GACjD,MAAM,WACJ,KAAK,kBAAkB,OACnB,cACA,MAAM,KAAK,eAAe,aAAa,OAAO,WAAW;AAC/D,SAAM,KAAK,OAAO,cAAc,OAAO,YAAY,SAAS;AAC5D,UAAO;IACL,IAAI;IACJ,OAAO;KACL,gBAAgB,OAAO;KACvB,oBAAoB,SAAS;KAC9B;IACF;WACM,OAAO;AACd,UAAO;IAAE,IAAI;IAAO,OAAO,iBAAiB,MAAM;IAAE;;;;;;CAOxD,MAAM,eAEJ;AACA,MAAI;AAEF,UAAO;IAAE,IAAI;IAAM,OAAO,EAAE,SADZ,MAAM,KAAK,OAAO,qBAAqB,EAClB;IAAE;WAChC,OAAO;AACd,UAAO;IAAE,IAAI;IAAO,OAAO,iBAAiB,MAAM;IAAE;;;;;;CAOxD,MAAM,eAEJ;AACA,MAAI;AACF,SAAM,KAAK,OAAO;AAWlB,UAAO;IAAE,IAAI;IAAM,OAAO,EAAE,SAVZ,MAAM,KAAK,iBAAiB,YAAY;KACtD,MAAM,UAAU,MAAM,KAAK,OAAO,qBAAqB;AACvD,WAAM,KAAK,kBAAkB,SAAS,KAAK,UAAU;AACrD,UAAK,gBAAgB;AACrB,UAAK,kBAAkB;AACvB,UAAK,oBAAoB,KAAA;AACzB,UAAK,wBAAwB,KAAA;AAC7B,UAAK,6BAA6B,KAAA;AAClC,YAAO;MACP,EACmC;IAAE;WAChC,OAAO;AACd,UAAO;IAAE,IAAI;IAAO,OAAO,iBAAiB,MAAM;IAAE;;;;;;CAOxD,MAAM,OAEJ;AACA,MAAI;AACF,QAAK,qBACH,oEACD;AAED,UAAO;IAAE,IAAI;IAAM,OAAO,EAAE,QADb,MAAM,KAAK,SAAS,OAAO,EACN;IAAE;WAC/B,OAAO;AACd,UAAO;IAAE,IAAI;IAAO,OAAO,iBAAiB,MAAM;IAAE;;;;;;;;;;;;CAaxD,MAAM,QACJ,SAAiC,EAAE,EACiC;AACpE,MAAI;AACF,QAAK,qBACH,uEACD;GAED,MAAM,QAAQ,KAAK;AACnB,OAAI,SAAS,QAAQ,CAAC,MAAM,OAM1B,QAAO;IAAE,IAAI;IAAM,OAAO,EAAE,QALb,MAAM,KAAK,iBACxB,OACA,QACA,MAAM,mBAAmB,OAC1B,EACmC;IAAE;AAIxC,UAAO;IAAE,IAAI;IAAM,OAAO,EAAE,SADX,MAAM,KAAK,2BAA2B,UAAU,EACpB,QAAQ;IAAE;WAChD,OAAO;AACd,UAAO;IAAE,IAAI;IAAO,OAAO,iBAAiB,MAAM;IAAE;;;;;;;;;;CAWxD,MAAM,kBAEJ;AACA,SAAO,MAAM,KAAK,iBAAiB,YAAY;AAC7C,OAAI;AACF,QAAI,KAAK,2BAA2B,KAClC,OAAM,IAAI,MAAM,KAAK,wBAAwB;AAE/C,UAAM,KAAK,QAAQ,qBAAqB;IACxC,MAAM,UAAU,MAAM,KAAK,4BAA4B;AACvD,QAAI,WAAW,KACb,QAAO;KACL,IAAI;KACJ,OAAO;MACL,UAAU;MACV,cAAc,KAAK,gBAAgB;MACpC;KACF;IAGH,MAAM,WAAW,MAAM,KAAK,uBAC1B,KAAK,iBACL,QAAQ,OACR,QAAQ,OACR,aACD;IACD,MAAM,iBAAiB,SAAS,gBAAgB,QAAQ;AACxD,SAAK,IAAI,IAAI,GAAG,IAAI,gBAAgB,KAAK,EACvC,MAAK,iBAAiB,UAAU;AAElC,SAAK,kBAAkB,SAAS;AAChC,SAAK,kBAAkB,SAAS,YAAY;AAC5C,WAAO;KACL,IAAI;KACJ,OAAO;MACL,UAAU;MACV,YAAY,SAAS,OAAO;MAC5B,cAAc,SAAS,OAAO;MAC/B;KACF;YACM,OAAO;AACd,WAAO;KAAE,IAAI;KAAO,OAAO,iBAAiB,MAAM;KAAE;;IAEtD;;;;;CAMJ,MAAM,KACJ,QACwD;AACxD,OAAK,qBACH,oEACD;EACD,MAAM,WAAW,KAAK;AACtB,MAAI,YAAY,MAAM;AAGpB,OAAI,SAAS,qBAAqB,CAAC,SAAS,OAC1C,QAAO;IAAE,IAAI;IAAM,OAAO,KAAK,mBAAmB,UAAU,OAAO;IAAE;AAEvE,OAAI;AACF,UAAM,SAAS;AACf,WAAO;KAAE,IAAI;KAAM,OAAO,KAAK,mBAAmB,UAAU,OAAO;KAAE;YAC9D,OAAO;AACd,WAAO;KAAE,IAAI;KAAO,OAAO,iBAAiB,MAAM;KAAE;;;EASxD,MAAM,QAAQA,gBALQ,0BAAoC;GACxD,WAAW,KAAK;GAChB,YAAY;GACZ,yBAAyB,KAAK,QAAQ,cAAc;GACrD,CAAC,CACmD;EAGrD,MAAM,iBAAiB,QAAQ;AAC/B,MAAI,kBAAkB,KACpB,OAAM,gBAAgB,IAAI,eAAe;AAE3C,QAAM,UAAU,KAAK,UAAU,MAAM;AACrC,OAAK,YAAY;AACjB,MAAI;AACF,SAAM,MAAM;AACZ,UAAO;IAAE,IAAI;IAAM,OAAO,KAAK,mBAAmB,OAAO,OAAO;IAAE;WAC3D,OAAO;GACd,MAAM,iBAAiB,iBAAiB,MAAM;AAE9C,OAAI,eAAe,aAAa,CAAC,MAAM,QAAQ;AAC7C,UAAM,oBAAoB;AAC1B,UAAM,YAAY;AAIlB,QAAI,kBAAkB,KACpB,OAAM,gBAAgB,OAAO,eAAe;AAE9C,WAAO;KAAE,IAAI;KAAO,OAAO;KAAgB;;AAE7C,SAAM,KAAK,iBAAiB,MAAM;AAClC,UAAO;IAAE,IAAI;IAAO,OAAO;IAAgB;;;CAI/C,mBACE,OACA,QACuB;EAGvB,MAAM,OAAO;AACb,QAAM,YAAY;EAClB,MAAM,iBAAiB,QAAQ;AAC/B,MAAI,kBAAkB;OAKhB,CADsB,MAAM,gBAAgB,IAAI,eAAe,EAC3C;AACtB,UAAM,gBAAgB,IAAI,eAAe;AACzC,mBAAe,MAAM,OAA8B;;;EAGvD,IAAI,eAAe;AACnB,SAAO;GACL,mBAAmB;AACjB,QAAI,aACF;AAEF,mBAAe;AACf,QAAI,kBAAkB,KACpB,OAAM,gBAAgB,OAAO,eAAe;AAE9C,UAAM,WAAW,KAAK,IAAI,GAAG,MAAM,WAAW,EAAE;AAChD,QAAI,MAAM,aAAa,KAAK,MAAM,OAChC;AAEG,SAAK,iBAAiB,MAAM;;GAEnC,IAAI,YAAY;AACd,WAAO,MAAM,WAAW;;GAE1B,IAAI,SAA8B;AAIhC,WAAO,MAAM,WAAW,eACpB,iBACA,MAAM;;GAEZ,IAAI,YAAY;AACd,WAAO,MAAM;;GAEf,IAAI,oBAAoB;AAGtB,WAAO,MAAM,aAAa;;GAE5B,IAAI,sBAAsB;AACxB,WAAO,KAAK;;GAEd,IAAI,iBAAiB;AACnB,WAAO,MAAM;;GAEf,iBAAiB,aAAa;AAC5B,UAAM,gBAAgB,IAAI,SAAS;AACnC,aAAS,MAAM,OAA8B;AAC7C,iBAAa;AACX,WAAM,gBAAgB,OAAO,SAAS;;;GAG1C,iBAAiB,YAAY;AAE3B,QAAI,MAAM,mBAAmB;KAC3B,MAAM,MAAM,MAAM,WAAW,OACzB,wBAAwB,MAAM,UAAU,SACxC;AACJ,WAAM,IAAI,MAAM,IAAI;;AAOtB,QAAI,MAAM,kBAAkB,aAC1B,KAAI;AACF,WAAM,MAAM;aACL,OAAO;KACd,MAAM,KAAK,iBAAiB,MAAM;AAClC,WAAM,IAAI,MACR,GAAG,OACC,wBAAwB,GAAG,SAC3B,sBACL;;AAML,QAAI,MAAM,mBAAmB,SAAS;AACpC,SAAI,MAAM,kBAAkB,KAC1B,OAAM,IAAI,uBAAuB,MAAM,eAAe;KAExD,MAAM,MAAM,MAAM,WAAW,OACzB,wBAAwB,MAAM,UAAU,SACxC;AACJ,WAAM,IAAI,MAAM,IAAI;;AAItB,UAAM,KAAK,QAAQ,qBAAqB;AACxC,QAAI,MAAM,OACR,OAAM,IAAI,MAAM,sBAAsB;IAKxC,MAAM,YAAY,MAAM,aAAa;AACrC,QAAI,cAAc,EAChB;AAEF,WAAO,IAAI,SAAe,SAAS,WAAW;AAC5C,WAAM,YAAY,KAAK;MAAE;MAAW;MAAS;MAAQ,CAAC;MACtD;;GAEL;;CAGH,MAAc,iBAAiB,OAA2C;AACxE,QAAMC,iBAA0B,OAAO;GACrC,iBAAiB,KAAK;GACtB,kBAAkB;AAChB,QAAI,KAAK,cAAc,MACrB,MAAK,YAAY,KAAA;;GAGtB,CAAC;;CAGJ,MAAc,2BACZ,WACA,MACA,cACY;EACZ,MAAM,cAAc,KAAK,KAAK;EAC9B,MAAM,iBAAiB,IAAI,iBAAiB;EAC5C,MAAM,SAAS,kBAAkB,cAAc,eAAe,OAAO;EACrE,MAAM,cAAc,KAAK,gBAAgB;EACzC,MAAM,gBAAgB,KAAK,gBAAgB;EAC3C,IAAI;EACJ,IAAI;EACJ,IAAI;EACJ,IAAI;AAEJ,MACE,OAAO,gBAAgB,YACvB,OAAO,SAAS,YAAY,IAC5B,cAAc,EAEd,aAAY,iBAAiB;AAC3B,QAAK,SAAS,8BAA8B;IAC1C,WAAW,KAAK;IAChB;IACA,WAAW,KAAK,KAAK,GAAG;IACxB;IACD,CAAC;KACD,YAAY;AAGjB,MACE,OAAO,kBAAkB,YACzB,OAAO,SAAS,cAAc,IAC9B,gBAAgB,EAEhB,sBAAqB,IAAI,SAAS,GAAG,WAAW;AAC9C,eAAY,iBAAiB;AAC3B,uBAAmB,IAAI,wBACrB,WACA,eACA,KAAK,KAAK,GAAG,YACd;AACD,mBAAe,MAAM,iBAAiB;AACtC,WAAO,iBAAiB;MACvB,cAAc;IACjB;AAGJ,MAAI;GACF,MAAM,cAAc,KAAK,OAAO;AAChC,UAAO,sBAAsB,OACzB,MAAM,cACN,MAAM,QAAQ,KAAK,CAAC,aAAa,mBAAmB,CAAC;WAClD,OAAO;AACd,OAAI,oBAAoB,KACtB,OAAM;AAER,SAAM;YACE;AACR,OAAI,aAAa,KACf,cAAa,UAAU;AAEzB,OAAI,aAAa,KACf,cAAa,UAAU;;;CAK7B,MAAc,UAAU,OAA2C;AACjE,MAAI;AACF,SAAM,sBAAsB,IAAI,iBAAiB;GACjD,IAAI;AACJ,OAAI;AACF,aAAS,MAAM,KAAK,uBAClB,MAAM,UACN,MAAM,mBAAmB,OAC1B;YACM,OAAO;AACd,QACE,EAAE,iBAAiB,6BACnB,MAAM,UACN,CAAE,MAAM,KAAK,2CACX,OACA,MACD,CAED,OAAM;AAMR,aAAS,MAAM,KAAK,uBAClB,MAAM,UACN,MAAM,mBAAmB,OAC1B;;AAEH,SAAM,SAAS,OAAO;AACtB,SAAM,eAAe,OAAO;AAC5B,SAAM,WAAW,OAAO;GACxB,MAAM,eAAe,KAAK,cACxB,OAAO,sBACR;GACD,MAAM,6BAA6B,aAAa,SAAS;AACzD,OAAI,aAAa,SAAS,KACxB,OAAM,aAAa,SAAS,aAAa,MAAM;AAEjD,OAAI,MAAM,OACR;AAEF,SAAM,mBAAmB,KAAK,QAAQ,uBAAuB,UAAU;AACrE,UAAM,aAAa,SAAS,MAAM;AAC7B,SAAK,kBAAkB,MAAM;KAClC;AACF,OAAI,4BAA4B;AAC9B,UAAM,KAAK,kBAAkB,MAAM;AACnC,QAAI,MAAM,WAAW,WAAW,MAAM,WAAW,eAC/C,OAAM,IAAI,MAAM,0DAA0D;;AAG9E,OAAI,MAAM,OACR;AAEF,SAAM,YAAY,KAAA;AAClB,QAAK,iBAAiB,OAAO,KAAK;AAClC,QAAK,kBAAkB,OAAO,KAAK;AAC9B,QAAK,YAAY,MAAM;WACrB,OAAO;AACd,OAAI,MAAM,OACR;AAEF,QAAK,gBAAgB,QAAQ,MAAM;AACnC,SAAM,YAAY,iBAAiB,MAAM;AACzC,QAAK,SAAS,0BAA0B,OAAO,EAC7C,WAAW,KAAK,WACjB,CAAC;AACF,SAAM,gBAAgB;AACtB,SAAM,iBAAiB;AACvB,QAAK,oBAAoB,MAAM;AAC/B,SAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAgCV,MAAc,2CACZ,OACA,OACkB;EAClB,MAAM,iBAAiB,MAAM;AAC7B,MAAI,kBAAkB,KACpB,QAAO;AAET,MAAI;GAMF,MAAM,WAAW,MAAM,KAAK,iBAAiB,YAAY;IACvD,IAAI,YAAY;IAWhB,MAAM,SAAS,KAAK;AACpB,QAAI,UAAU,MAAM;KAClB,MAAM,MAAM,MAAM,KAAK,6BAA6B,OAAO;AAC3D,UAAK,kBAAkB,IAAI,YAAY;AACvC,iBAAY;;AAEd,UAAM,KAAK,QAAQ,qBAAqB;IACxC,MAAM,aAAa,KAAK,cAAc,eAAe;AACrD,QAAI,WAAW,SAAS,MAAM;KAC5B,MAAM,gBAAgB,MAAM,KAAK,uBAC/B;MAAE,GAAG,MAAM;MAAQ,yBAAyB;MAAgB,EAC5D,WAAW,OACX,KAAA,GACA,SACD;AACD,UAAK,kBAAkB,cAAc,YAAY;AACjD,iBAAY;;AAEd,WAAO;KACP;AACF,OAAI,SACF,MAAK,SAAS,uCAAuC,EACnD,WAAW,KAAK,WACjB,CAAC;AAEJ,UAAO;WACA,cAAc;AAIrB,QAAK,SAAS,8BAA8B,cAAc,EACxD,WAAW,KAAK,WACjB,CAAC;AACF,UAAO;;;CAIX,MAAc,SACZ,WACA,cACiC;EACjC,IAAI,UAAU,MAAM,KAAK,kCACvB,MAAM,KAAK,0BAA0B,WAAW,aAAa,CAC9D,EAAE;EACH,MAAM,aAAa,KAAK,cACtB,OAAO,wBACR;AACD,MAAI,WAAW,SAAS,KACtB,WAAU,MAAM,KAAK,wBACnB,QACA,WAAW,MACZ,EAAE;AAEL,SAAO;;CAGT,MAAc,uBACZ,mBACA,cACmC;EACnC,MAAM,UAAU,MAAM,KAAK,kCACzB,MAAM,KAAK,0BAA0B,QAAQ,aAAa,CAC3D;EACD,IAAI,SAAS,QAAQ;EACrB,IAAI,wBAAwB,OAAO;EACnC,IAAI,eAAe,QAAQ;EAC3B,IAAI,WAAW;EAEf,MAAM,gBAAgB,KAAK,cAAc,sBAAsB;AAC/D,0BAAwB,cAAc;EACtC,MAAM,gBAAgB,cAAc;AACpC,MAAI,iBAAiB,MAAM;GAKzB,MAAM,eAAe;GACrB,MAAM,qBAAqB;GAC3B,MAAM,YAAY,MAAM,KAAK,uBAC3B,KAAK,wBACH,cACA,eACA,KAAA,GACA,UACA,mBACD,CACF;AACD,YAAS,UAAU;AACnB,kBAAe,UAAU;;AAE3B,0BAAwB,OAAO;AAE/B,SAAO;GACL;GACA;GACA;GACA;GACD;;CAGH,sBAAsD;AACpD,SAAO,0BAAoC;GACzC,WAAW,KAAK;GAChB,YAAY;GACZ,yBAAyB,KAAK,QAAQ,cAAc;GACrD,CAAC;;CAGJ,MAAc,0BACZ,WACA,cACqC;EACrC,MAAM,SAAS,MAAM,KAAK,kBAAkB,KAAK,KAAK,UAAU;EAChE,MAAM,SAAS,UAAU,KAAK,qBAAqB;EACnD,MAAM,cAAiB,SACrB,KAAK,2BAA2B,WAAW,MAAM,aAAa;AAEhE,MAAI;AAIF,OAAI,UAAU,KACZ,QAAO,MAAM,YAAY,WACvB,KAAK,eAAe,QAAQ;IAC1B;IACA,aAAa;IACd,CAAC,CACH;GAEH,MAAM,WAAW,MAAM,YAAY,WACjC,KAAK,cAAc,QAAQ;IACzB,cAAc;IACd;IACA,aAAa;IACd,CAAC,CACH;AACD,OAAI,UAAU,SACZ,QAAO,MAAM,KAAK,kCAChB,QACA,WACA,aACD;AAEH,UAAO;WACA,OAAO;AACd,OAAI,CAAC,sBAAsB,MAAM,IAAI,CAAC,KAAK,sBACzC,OAAM;AAER,SAAM,KAAK,oBAAoB;AAC/B,UAAO,MAAM,YAAY,WACvB,KAAK,eAAe,KAAK,qBAAqB,EAAE;IAC9C;IACA,aAAa;IACd,CAAC,CACH;;;CAIL,MAAc,2BACZ,WACA,cACqC;EACrC,MAAM,SAAS,MAAM,KAAK,kBAAkB,KAAK,KAAK,UAAU;EAChE,MAAM,SAAS,UAAU,KAAK,qBAAqB;EACnD,MAAM,cAAiB,SACrB,KAAK,2BAA2B,WAAW,MAAM,aAAa;AAEhE,MAAI;AACF,OAAI,UAAU,KACZ,QAAO,MAAM,YAAY,WACvB,KAAK,eAAe,QAAQ;IAC1B;IACA,aAAa;IACd,CAAC,CACH;AAEH,UAAO,MAAM,YAAY,WACvB,KAAK,kBACH,QACA,KAAA,GACA,KAAA,GACA,KAAA,GACA,QACA,UACD,CACF;WACM,OAAO;AACd,OAAI,CAAC,sBAAsB,MAAM,IAAI,CAAC,KAAK,sBACzC,OAAM;AAER,SAAM,KAAK,oBAAoB;AAC/B,UAAO,MAAM,YAAY,WACvB,KAAK,eAAe,KAAK,qBAAqB,EAAE;IAC9C;IACA,aAAa;IACd,CAAC,CACH;;;CAIL,MAAc,iBACZ,OACA,QACA,cACiC;AACjC,MAAI,MAAM,kBAAkB,cAAc;AACxC,SAAM,MAAM;AACZ,UAAO,MAAM;;AAGf,MAAI,MAAM,kBAAkB,QAAQ,OAAO,UAAU,KACnD,QAAO,MAAM;EAGf,MAAM,SAAS,MAAM,KAAK,iBAAiB,YAAY;AACrD,OAAI,MAAM,OACR,OAAM,IAAI,MAAM,sBAAsB;GAExC,MAAM,WAAW,MAAM,KAAK,2BAC1B,YACC,WACC,KAAK,kBACH,MAAM,QACN,KAAA,GACA,MAAM,eACL,iBAAiB,gBAAgB;AAChC,SAAK,gBAAgB,qBACnB,OACA,iBACA,YACD;MAEH,QACA,UACD,EACH,aACD;AACD,SAAM,SAAS,SAAS;AACxB,SAAM,eAAe,SAAS;AAC9B,UAAO,MAAM;IACb;AAEF,MAAI,MAAM,OACR,QAAO;AAET,QAAM,eAAe;AACrB,QAAM,YAAY,KAAA;AAClB,MAAI,MAAM,kBAAkB,QAAQ,MAAM,mBAAmB,QAC3D,MAAK,QAAQ;WACJ,MAAM,aAAa,SAAS,EAChC,MAAK,kBAAkB,MAAM;AAEpC,SAAO;;CAGT,MAAc,eACZ,QACA,UAII,EAAE,EAC+B;EACrC,MAAM,gBAAgB,QAAQ,iBAAiB,KAAK;EACpD,MAAM,SAAS,MAAM,KAAK,OAAO,UAC/B,QAAQ,QACR,QAAQ,YACT;EACD,IAAI,QAAQ,MAAM,cAAc,sBAC9B,QACA,OAAO,UACP,OAAO,SACP,OAAO,YACP,OAAO,UACP,OAAO,eACR;EACD,IAAI,kBAAkB,OAAO;AAC7B,MAAI,CAAC,OAAO,UAAU;GACpB,MAAM,WAAW,MAAM,KAAK,cAAc,OAAO;IAC/C,cAAc;IACd;IACA,QAAQ,QAAQ;IAChB,aAAa,QAAQ;IACtB,CAAC;AACF,OAAI,UAAU,SACZ,OAAM,IAAI,MAAM,gDAAgD;AAElE,WAAQ,SAAS;AACjB,qBAAkB,SAAS;;AAE7B,SAAO;GAAE,QAAQ;GAAO,cAAc;GAAiB;;CAGzD,MAAc,kBACZ,QACA,MACA,cACA,wBAIA,QACA,aACqC;EACrC,MAAM,SAAS,MAAM,KAAK,cAAc,QAAQ;GAC9C;GACA;GACA;GACA;GACA;GACD,CAAC;AACF,MAAI,UAAU,OACZ,OAAM,IAAI,MAAM,qCAAqC;AAEvD,SAAO;;CAGT,MAAc,cACZ,QACA,UAWI,EAAE,EACiE;EACvE,IAAI,UAAU;EACd,IAAI,sBAAsB,QAAQ;EAClC,MAAM,eAAe,QAAQ,gBAAgB;EAC7C,MAAM,gBAAgB,QAAQ,iBAAiB,KAAK;AACpD,SAAO,MAAM;GACX,MAAM,gBAAgB,QAAQ;GAC9B,MAAM,WAAW,MAAM,KAAK,OAAO,eACjC,eACA,qBACA,QAAQ,MACR,QAAQ,QACR,QAAQ,YACT;AACD,OAAI,SAAS,SAAS,QAAQ;AAC5B,QAAI,iBAAiB,cACnB,QAAO,YAAY;AAErB,WAAO,MAAM,KAAK,eAChB;KACE,GAAG;KACH,YAAY;KACZ,yBAAyB,KAAK,QAAQ,cAAc;KACrD,EACD;KACE;KACA,QAAQ,QAAQ;KAChB,aAAa,QAAQ;KACtB,CACF;;GAEH,MAAM,SAAS,SAAS;AACxB,OAAI,iBAAiB,eAAe,OAAO,CACzC,OAAM,IAAI,MACR,uCAAuC,cAAc,iFACtD;AAEH,OAAI;AACF,cAAU,MAAM,cAAc,mBAC5B,SACA,QACA,QAAQ,uBACT;YACM,OAAO;AACd,QAAI,iBAAiB,4BAA4B,OAAO,SACtD,QAAO,MAAM,KAAK,eAChB;KACE,GAAG;KACH,YAAY;KACZ,yBAAyB,KAAK,QAAQ,cAAc;KACrD,EACD;KACE;KACA,QAAQ,QAAQ;KAChB,aAAa,QAAQ;KACtB,CACF;AAEH,UAAM;;AAER,yBAAsB,OAAO;AAC7B,OAAI,OAAO,SACT,QAAO;IAAE,QAAQ;IAAS,cAAc;IAAqB;;;CAKnE,MAAc,iCACZ,OACA,OAC6C;AAO7C,OAAK,cAAc,qBAAqB,MAAM,QAAQ,MAAM,WAAW;AACvE,MAAI;GACF,MAAM,eAAe,MAAM,KAAK,eAC9B;IACE,GAAG,MAAM;IACT,YAAY;IACZ,yBAAyB,KAAK,QAAQ,cAAc;IACrD,EACD,EACE,QAAQ,MAAM,mBAAmB,QAClC,CACF;AACD,SAAM,SAAS,aAAa;AAC5B,SAAM,eAAe,aAAa;AAClC,SAAM,eAAe;AACrB,SAAM,4BAA4B;AAClC,SAAM,YAAY,KAAA;AAClB,QAAK,iBAAiB,OAAO,KAAK;AAClC,OAAI,MAAM,aAAa,SAAS,EACzB,MAAK,kBAAkB,MAAM;AAEpC,UAAO;WACA,OAAO;GACd,MAAM,aAAa,mBAAmB,MAAM;AAC5C,QAAK,SAAS,yCAAyC,OAAO;IAC5D,WAAW,KAAK;IAChB,MAAM,WAAW;IAClB,CAAC;AACF,SAAM,YAAY,iBAAiB,MAAM;AAMzC,OAAI,MAAM,aAAa,SAAS,EACzB,MAAK,kBAAkB,MAAM;AAEpC,OAAI,WAAW,aAAa,CAAC,sBAAsB,MAAM,EAAE;AAYzD,UAAM,6BAA6B;AACnC,UAAM,eAAe,KAAK,IACxB,MAAM,cACN,MAAM,0BACP;AACD,SAAK,iBAAiB,OAAO,eAAe;AAC5C,WAAO;;AAIT,QAAK,iBAAiB,OAAO,QAAQ;AACrC,UAAO;;;CAIX,MAAc,iBACZ,QACA,OACA,cACA,cACiC;EACjC,MAAM,SACJ,gBAAgB,OAAO,WAAW;EACpC,MAAM,YAAY,MAAM,KAAK,wBAC3B,QACA,OACA,cACA,QACA,aACD;AACD,OAAK,wBAAwB,UAAU;AACvC,OAAK,6BAA6B,UAAU;AAC5C,SAAO,UAAU;;CAGnB,MAAc,wBACZ,QACA,OACA,cACA,SAAkC,gBAAgB,OAAO,WAAW,WACpE,cACuC;EACvC,MAAM,SAAS,MAAM,KAAK,6BACxB,QACA,OACA,cACA,OACD;EACD,MAAM,WAAW,MAAM,KAAK,6BAA6B,OAAO;EAChE,MAAM,WAAW,MAAM,KAAK,mBAAmB,QAAQ,aAAa;AACpE,OAAK,kBAAkB,SAAS,YAAY;AAC5C,SAAO;GACL,QAAQ,SAAS;GACjB,cAAc,OAAO;GACrB,cAAc,SAAS;GACxB;;CAGH,sBACE,QACwB;AACxB,SAAO;GACL,GAAG,OAAO;GACV,yBAAyB,KAAK,QAAQ,cACpC,OAAO,WAAW,yBAClB,OAAO,MAAM,QACd;GACF;;CAGH,MAAc,mBACZ,QACA,cACqC;EACrC,MAAM,WAAW,MAAM,KAAK,cAC1B,KAAK,sBAAsB,OAAO,EAClC;GACE;GACA,eAAe,KAAK;GACrB,CACF;AACD,MAAI,UAAU,SACZ,OAAM,IAAI,MAAM,mDAAmD;AAGrE,SAAO;GACL,QAFY,MAAM,KAAK,iBAAiB,SAAS,QAAQ,QAAQ;GAGjE,cAAc,SAAS;GACxB;;CAGH,MAAc,uBACZ,QACA,OACA,cACA,SAAkC,gBAAgB,OAAO,WAAW,WACU;EAC9E,MAAM,SAAS,MAAM,KAAK,6BACxB,QACA,OACA,cACA,OACD;AAED,SAAO;GACL,GAFe,MAAM,KAAK,6BAA6B,OAAO;GAG9D,cAAc,OAAO;GACtB;;CAGH,MAAc,6BACZ,QACA,OACA,cACA,SAAkC,gBAAgB,OAAO,WAAW,WAC9B;EACtC,MAAM,WAAW,KAAK;AACtB,MAAI,YAAY,MAAM;AACpB,OAAI,SAAS,WAAW,OACtB,OAAM,IAAI,MACR,GAAG,SAAS,OAAO,yCAAyC,OAAO,SACpE;AAEH,UAAO;;EAOT,MAAM,SAAsC;GAC1C;GACA,YAAY;GACZ;GACA;GACA,aARA,KAAK,qBAAqB,OACtB,MAAM,UACN,MAAM,KAAK,kBAAkB,MAAM,QAAQ;GAO/C,iBAAiB,KAAK,8BAA8B;GACrD;AACD,OAAK,oBAAoB;AACzB,SAAO;;CAGT,MAAc,6BACZ,QACyC;AACzC,MAAI,OAAO,gBAAgB,KACzB,QAAO,OAAO;AAGhB,OAAK,IAAI,UAAU,GAAG,UAAU,GAAG,WAAW,GAAG;GAC/C,IAAI;AAgBJ,OAAI;AACF,aAAS,MAAM,KAAK,OAAO,cACzB,OAAO,aACP,OAAO,gBACR;YACM,OAAO;AAKd,QAAI,sBAAsB,MAAM,IAAI,KAAK,uBAAuB;AAC9D,WAAM,KAAK,oBAAoB;AAC/B;;AAEF,UAAM;;AAER,OAAI,OAAO,SAAS,MAAM;IAYxB,MAAM,WAA2C;KAC/C,QATmC;MACnC,GAAG,OAAO;MACV,YAAY,OAAO;MACnB,yBAAyB,KAAK,QAAQ,cACpC,OAAO,WAAW,yBAClB,OAAO,MAAM,QACd;MACF;KAGC,aAAa;MACX,OAAO,OAAO;MACd,KAAK,OAAO;MACb;KACF;AACD,WAAO,eAAe;AACtB,WAAO;;AAET,OAAI,OAAO,SAAS,WAAW;AAC7B,WAAO,kBAAkB;KACvB,GAAG,OAAO;KACV,iBAAiB,OAAO;KACzB;AACD;;AAEF,OAAI,OAAO,SAAS,eAClB,OAAM,IAAI,MAAM,oDAAoD;AAEtE,UAAO,kBAAkB;IACvB,GAAG,OAAO;IACV,eAAe,OAAO,cAAc;IACpC,iBAAiB;IAClB;;AAEH,QAAM,IAAI,MAAM,gCAAgC;;CAGlD,MAAc,kCACZ,OACqC;EACrC,MAAM,SAAS,KAAK;AACpB,MAAI,UAAU,QAAQ,OAAO,gBAAgB,KAC3C,QAAO;EAET,MAAM,WAAW,MAAM,KAAK,6BAA6B,OAAO;EAChE,MAAM,WAAW,MAAM,KAAK,mBAAmB,QAAQ,MAAM,aAAa;AAC1E,OAAK,kBAAkB,SAAS,YAAY;AAC5C,SAAO;;CAGT,+BAAwD;AACtD,SAAO;GACL,YAAY,KAAK;GACjB,eAAe,KAAK;GACpB,iBAAiB,KAAK;GACvB;;CAGH,kBAA0B,KAAwB;AAChD,OAAK,gBAAgB,IAAI;AACzB,OAAK,kBAAkB,IAAI,MAAM;AACjC,OAAK,oBAAoB,KAAA;AAIzB,OAAK,sBAAsB,KAAK,KAAK;;CAGvC,yBACE,QACM;AACN,MAAI,KAAK,sBAAsB,OAC7B;AAEF,OAAK,oBAAoB,KAAA;AACzB,OAAK,gBAAgB,KAAK,IACxB,KAAK,eACL,OAAO,gBAAgB,gBAAgB,EACxC;AACD,OAAK,kBAAkB;;CAGzB,+BAA2D;EACzD,MAAM,QAAQ,KAAK;AACnB,OAAK,wBAAwB,KAAA;AAC7B,SAAO;;CAGT,oCAAgE;EAC9D,MAAM,eAAe,KAAK;AAC1B,OAAK,6BAA6B,KAAA;AAClC,SAAO;;;;;;CAOT,MAAc,qBAAoC;AAChD,MAAI;AACF,SAAM,KAAK,OAAO,qBAAqB;WAChC,OAAO;AACd,OAAI,EAAE,iBAAiB,aAAa,MAAM,WAAW,KACnD,OAAM;;;CAKZ,cAAsB,aAAoD;EACxE,MAAM,WAAW,KAAK,QAAQ,cAAc,YAAY;AACxD,MAAI,YAAY,KACd,QAAO,EAAE,gBAAgB,aAAa;EAExC,MAAM,iBAAiB,KAAK,QAAQ,cAClC,aACA,SAAS,QACV;AACD,MAAI,SAAS,QAAQ,WAAW,EAC9B,QAAO,EAAE,gBAAgB;AAE3B,SAAO;GACL,OAAO;GACP;GACD;;CAGH,MAAc,oBACZ,QACA,OACA,YACiC;AACjC,SAAO,MAAM,KAAK,cAAc,oBAC9B,QACA,OACA,WACD;;CAGH,MAAc,iBACZ,QACA,OACA,YACiC;AACjC,SAAO,MAAM,KAAK,cAAc,iBAAiB,QAAQ,OAAO,WAAW;;CAG7E,oBACE,SACA,kBAIkC;AAClC,SAAO,IAAI,uBAAuB;GAChC;GACA,gBAAgB,KAAK;GACrB,mBAAmB,KAAK;GACxB;GACD,CAAC;;CAGJ,MAAc,8BAEZ;EACA,MAAM,WAAW,MAAM,KAAK,QAAQ,yBAAyB;AAC7D,MAAI,YAAY,KACd,QAAO;AAET,SAAO,KAAK,oBAAoB,UAAU,OAAO,WAAW,OAAO;;;;;;;CAQrE,MAAc,0BACZ,iBACA,eACA,WACA,cACqC;EACrC,MAAM,iBAAiB,MAAM,KAAK,2BAChC,YACC,WACC,KAAK,eAAe,iBAAiB;GACnC;GACA;GACA,aAAa;GACd,CAAC,EACJ,aACD;EACD,IAAI;AACJ,MAAI,KAAK,qBAAqB,MAAM;AAClC,OAAI,KAAK,kBAAkB,gBAAgB,KACzC,OAAM,IAAI,MAAM,0DAA0D;AAE5E,mBAAgB,MAAM,KAAK,6BACzB,KAAK,kBACN;SACI;GACL,MAAM,aAAa,KAAK,cACtB,eAAe,OAAO,wBACvB;AACD,OAAI,WAAW,SAAS,KACtB,iBAAgB,MAAM,KAAK,uBACzB,eAAe,QACf,WAAW,MACZ;;EAOL,MAAM,SAAS,MAAM,KAAK,2BACxB,YACC,WACC,KAAK,eAAe,iBAAiB;GACnC;GACA,aAAa;GACd,CAAC,EACJ,aACD;AACD,MAAI,iBAAiB,KACnB,MAAK,kBAAkB,cAAc,YAAY;AAEnD,SAAO;;CAGT,MAAc,kCACZ,QACA,WACA,cACqC;EACrC,MAAM,wBAAwB,MAAM,KAAK,6BAA6B;EACtE,MAAM,kBAAkB;GACtB,GAAG;GACH,YAAY;GACZ,yBAAyB,KAAK,QAAQ,cAAc;GACrD;AAED,MAAI,yBAAyB,KAC3B,QAAO,MAAM,KAAK,0BAChB,iBACA,uBACA,WACA,aACD;EAGH,MAAM,yBAAyB,KAAK,QAAQ,gBAAgB;EAC5D,IAAI,wBAAwB;AAE5B,MAAI;GAMF,MAAM,iBAAiB,MAAM,KAAK,2BAChC,YACC,WACC,KAAK,eAAe,iBAAiB;IACnC,eAAe,KAAK;IACpB;IACA,aAAa;IACd,CAAC,EACJ,aACD;AACD,SAAM,KAAK,QAAQ,cAAc,uBAAuB;AACxD,2BAAwB;GAExB,IAAI;AACJ,OAAI,KAAK,qBAAqB,MAAM;AAClC,QAAI,KAAK,kBAAkB,gBAAgB,KACzC,OAAM,IAAI,MAAM,0DAA0D;AAE5E,oBAAgB,MAAM,KAAK,6BACzB,KAAK,kBACN;UACI;IACL,MAAM,aAAa,KAAK,cACtB,eAAe,OAAO,wBACvB;AACD,QAAI,WAAW,SAAS,KACtB,iBAAgB,MAAM,KAAK,uBACzB,eAAe,QACf,WAAW,MACZ;;GAML,MAAM,SAAS,MAAM,KAAK,2BACxB,YACC,WACC,KAAK,eAAe,iBAAiB;IACnC;IACA,aAAa;IACd,CAAC,EACJ,aACD;AACD,OAAI,iBAAiB,KACnB,MAAK,kBAAkB,cAAc,YAAY;AAEnD,UAAO;WACA,OAAO;AACd,OAAI,CAAC,sBACH,OAAM,KAAK,QAAQ,cAAc,uBAAuB;AAE1D,SAAM;;;CAIV,MAAc,YAAY,OAA2C;AACnE,MAAI,MAAM,gBACR;AAEF,QAAM,kBAAkB;AACxB,OAAK,sBAAsB,MAAM;AACjC,MAAI;AACF,SAAM,KAAK,sBAAsB,MAAM;YAC/B;AACR,SAAM,kBAAkB;;AAI1B,OAAK,6BAA6B,MAAM;;;;;;;;;;;;;;;;;;;;;CAsB1C,6BAAqC,OAAkC;AACrE,MACE,MAAM,UACN,KAAK,cAAc,SACnB,CAAC,KAAK,qBAAqB,MAAM,IACjC,MAAM,oBAAoB,QAC1B,MAAM,gBAEN;EAEF,MAAM,QAAQ,wBACZ,MAAM,uBACN,KAAK,gBACN;AACD,QAAM,yBAAyB;AAC/B,QAAM,mBAAmB,iBAAiB;AACxC,SAAM,mBAAmB,KAAA;AACzB,OACE,MAAM,UACN,KAAK,cAAc,SACnB,CAAC,KAAK,qBAAqB,MAAM,CAEjC;AAEG,QAAK,YAAY,MAAM;KAC3B,MAAM;;;;;;;;;;;;;;;;CAiBX,qBAA6B,OAAqC;AAChE,SACE,MAAM,kBAAkB,WACxB,MAAM,kBAAkB;;CAI5B,sBAA8B,OAAkC;AAC9D,MAAI,MAAM,oBAAoB,MAAM;AAClC,gBAAa,MAAM,iBAAiB;AACpC,SAAM,mBAAmB,KAAA;;;CAI7B,qBAA6B,OAAkC;AAC7D,MAAI,MAAM,mBAAmB,MAAM;AACjC,gBAAa,MAAM,gBAAgB;AACnC,SAAM,kBAAkB,KAAA;;;;;;;;;;CAW5B,MAAc,sBACZ,OACA,OACkB;EAClB,MAAM,cAAc,KAAK,gBAAgB;AACzC,MAAI,MAAM,gBAAgB,aAAa;AACrC,QAAK,SAAS,iCAAiC,OAAO;IACpD,WAAW,KAAK;IAChB,UAAU,MAAM;IACjB,CAAC;AACF,SAAM,YAAY,iBAAiB,MAAM;AACzC,QAAK,iBAAiB,OAAO,eAAe;AAC5C,UAAO;;EAET,MAAM,QAAQ,kBACZ,MAAM,cACN,KAAK,iBACL,iBAAiB,YAAY,MAAM,eAAe,KAAA,EACnD;AACD,QAAM,gBAAgB;AACtB,QAAM,MAAM,OAAO,MAAM,qBAAqB,OAAO;AACrD,SAAO;;CAGT,MAAc,sBACZ,OACe;AACf,QAAM,sBAAsB,IAAI,iBAAiB;AACjD,SAAO,CAAC,MAAM,QAAQ;GACpB,MAAM,qBAAkD,MAAM;GAC9D,IAAI,kBAAwD;GAC5D,IAAI,sBAAsB;GAK1B,IAAI,gBAAgB;GAEpB,MAAM,wBAA8B;AAClC,QAAI,mBAAmB,MAAM;AAC3B,kBAAa,gBAAgB;AAC7B,uBAAkB;;;GAItB,MAAM,wBAA8B;AAClC,qBAAiB;IACjB,MAAM,YAAY,KAAK,gBAAgB;AACvC,QAAI,aAAa,QAAQ,YAAY,KAAK,CAAC,MAAM,OAC/C,mBAAkB,iBAAiB;AACjC,2BAAsB;AACtB,yBAAoB,OAAO;OAC1B,UAAU;;AAIjB,OAAI;AACF,QAAI,MAAM,aAAa,aAAa;KAClC,MAAM,gBAAgB,MAAM,OAAO;KACnC,MAAM,WAAW,MAAM,KAAK,OAAO,eACjC,eACA,MAAM,cACN,aACA,oBAAoB,OACrB;AACD,SAAI,SAAS,SAAS,QAAQ;MAC5B,MAAM,eAAe,MAAM,KAAK,eAC9B;OACE,GAAG,MAAM;OACT,YAAY;OACZ,yBAAyB,KAAK,QAAQ,cAAc;OACrD,EACD,EACE,QAAQ,oBAAoB,QAC7B,CACF;AACD,YAAM,SAAS,aAAa;AAC5B,YAAM,eAAe,aAAa;AAClC,YAAM,eAAe;AACrB;;KAGF,MAAM,SAAS,SAAS;AACxB,SAAI,iBAAiB,eAAe,OAAO,CACzC,OAAM,IAAI,MACR,uCAAuC,cAAc,iFACtD;AAGH,WAAM,KAAK,iBAAiB,YAAY;AACtC,UAAI,MAAM,OACR;AAEF,YAAM,SAAS,MAAM,KAAK,cAAc,gBACtC,MAAM,QACN,eACA,SACC,iBAAiB,gBAAgB;AAChC,YAAK,gBAAgB,qBACnB,OACA,iBACA,YACD;QAEJ;AACD,UAAI,OAAO,gBAAgB,KACzB,OAAM,eAAe,OAAO;OAE9B;AACF,SAAI,MAAM,OACR;AAEF,WAAM,eAAe;AACrB,UAAK,iBAAiB,OAAO,KAAK;AAClC,SAAI,MAAM,aAAa,SAAS,EACzB,MAAK,kBAAkB,MAAM;AAEpC,WAAM,MAAM,EAAE;AACd;;IAGF,MAAM,mBAAmB,MAAM,OAAO;IACtC,MAAM,gBAKD,EAAE;IACP,IAAI,aAAa,QAAQ,SAAS;AAElC,qBAAiB;IAEjB,MAAM,iBAAiB,KAAK,KAAK;IACjC,MAAM,YAAY,MAAM,KAAK,OAAO,uBAClC,kBACA,MAAM,eACL,SAAS,YAAY,UAAU,iBAAiB;AAC/C,SAAI,oBAAoB,OAAO,QAC7B,QAAO;AAET,sBAAiB;AACjB,mBAAc,KAAK;MAAE;MAAS;MAAY;MAAU;MAAc,CAAC;AACnE,kBAAa,WAAW,KAAK,YAAY;MACvC,MAAM,SAAS,cAAc,OAAO,EAAE;AACtC,UACE,OAAO,WAAW,KAClB,MAAM,UACN,oBAAoB,OAAO,QAE3B;MAEF,MAAM,aAAa,OAAO,SAAS,MAAM,EAAE,QAAQ;MACnD,MAAM,YAAY,OAAO,OAAO,SAAS;AACzC,YAAM,KAAK,iBAAiB,YAAY;AACtC,WAAI,MAAM,OACR;AAEF,aAAM,SAAS,MAAM,KAAK,cAAc,gBACtC,MAAM,QACN,kBACA;QACE,YAAY,UAAU;QACtB,SAAS;QACT,UAAU,UAAU;QACrB,GACA,iBAAiB,gBAAgB;AAChC,aAAK,gBAAgB,qBACnB,OACA,iBACA,YACD;SAEJ;AACD,YAAK,IAAI,IAAI,OAAO,SAAS,GAAG,KAAK,GAAG,IACtC,KAAI,OAAO,GAAI,gBAAgB,MAAM;AACnC,cAAM,eAAe,OAAO,GAAI;AAChC;;QAGJ;AACF,WAAK,iBAAiB,OAAO,KAAK;AAClC,UAAI,MAAM,aAAa,SAAS,EACzB,MAAK,kBAAkB,MAAM;OAEpC,CAAC,OAAO,eAAe;AAWvB,WAAK,SAAS,yBAAyB,YAAY,EACjD,WAAW,KAAK,WACjB,CAAC;AACF,0BAAoB,MAAM,WAAW;OACrC;aAEE;AACJ,sBAAiB;AACjB,qBAAgB,KAAK,KAAK;AAC1B,UAAK,iBAAiB,OAAO,KAAK;OAEpC,oBAAoB,QACpB,gBACD;AACD,qBAAiB;AACjB,UAAM;AACN,QAAI,MAAM,OACR;AAEF,QAAI,UAAU,SAAS,MAAM;AAC3B,WAAM,eAAe;AACrB,WAAM,eAAe,UAAU;AAE/B,WAAM,MADQ,kBAAkB,eAAe,EAC5B,MAAM,qBAAqB,OAAO;AACrD;;AAEF,QAAI,UAAU,SAAS,eAAe;AACpC,WAAM,WAAW;AACjB,WAAM,MAAM,EAAE;AACd;;AAEF,QAAI,UAAU,SAAS,aAAa;KAClC,MAAM,gBAAgB,IAAI,UAAU,oBAAoB,IAAI;AAC5D,UAAK,SACH,oBACA,eACA;MACE,WAAW,KAAK;MAChB,MAAM;MACP,CACF;AACD,WAAM,YAAY,iBAAiB,cAAc;AACjD,UAAK,iBAAiB,OAAO,QAAQ;AAGrC;;IAEF,MAAM,eAAe,MAAM,KAAK,eAC9B;KACE,GAAG,MAAM;KACT,YAAY;KACZ,yBAAyB,KAAK,QAAQ,cAAc;KACrD,EACD,EACE,QAAQ,oBAAoB,QAC7B,CACF;AACD,UAAM,SAAS,aAAa;AAC5B,UAAM,eAAe,aAAa;AAClC,UAAM,eAAe;YACd,OAAO;AACd,qBAAiB;AACjB,QAAI,MAAM,OACR;AAEF,QAAI,iBAAiB,0BAA0B;AAC7C,SACE,oBAAoB,OAAO,WAC3B,MAAM,sBAAsB,mBAE5B,OAAM,oBAAoB,IAAI,iBAAiB;KAEjD,MAAM,WAAW,MAAM,KAAK,iCAC1B,OACA,MACD;AACD,SAAI,aAAa,YACf;AAEF,SAAI,aAAa,WACf;AAIF,SAAI,MAAM,KAAK,sBAAsB,OAAO,MAAM,CAChD;AAEF;;AAKF,QAAI,oBAAoB,OAAO,SAAS;AACtC,SAAI,qBAAqB;AAIvB,UAAI,MAAM,sBAAsB,mBAC9B,OAAM,oBAAoB,IAAI,iBAAiB;AAEjD,WAAK,SAAS,0BAA0B,EACtC,WAAW,KAAK,WACjB,CAAC;AACF,WAAK,iBAAiB,OAAO,eAAe;MAC5C,MAAM,cAAc,KAAK,gBAAgB;AACzC,UAAI,MAAM,gBAAgB,aAAa;AACrC,YAAK,SAAS,8CAA8C,OAAO;QACjE,WAAW,KAAK;QAChB,UAAU,MAAM;QACjB,CAAC;AACF,aAAM,YAAY;QAChB,MAAM;QACN,WAAW;QACX,OAAO;QACP,SAAS;QACV;AACD,YAAK,iBAAiB,OAAO,eAAe;AAC5C;;AAEF,YAAM,gBAAgB;AAKtB,YAAM,MAJQ,wBACZ,MAAM,eAAe,GACrB,KAAK,gBACN,EACkB,MAAM,qBAAqB,OAAO;AACrD;;KASF,MAAM,cAAc,mBAAmB,OAAO;KAC9C,MAAM,aACJ,uBAAuB,QAAQ,cAAc;AAC/C,SAAI,sBAAsB,0BAA0B;AAClD,UAAI,MAAM,sBAAsB,mBAC9B,OAAM,oBAAoB,IAAI,iBAAiB;MAEjD,MAAM,WAAW,MAAM,KAAK,iCAC1B,OACA,WACD;AACD,UAAI,aAAa,YACf;AAEF,UAAI,aAAa,WACf;AAEF,UAAI,MAAM,KAAK,sBAAsB,OAAO,WAAW,CACrD;AAEF;;AAEF,SACE,sBAAsB,SACtB,EAAE,sBAAsB,gBACtB,WAAW,SAAS,eACtB;MACA,MAAM,kBAAkB,mBAAmB,WAAW;AACtD,UACE,CAAC,gBAAgB,aACjB,sBAAsB,WAAW,EACjC;AACA,YAAK,SAAS,kCAAkC,YAAY;QAC1D,WAAW,KAAK;QAChB,MAAM,gBAAgB;QACvB,CAAC;AACF,aAAM,YAAY,iBAAiB,WAAW;AAC9C,YAAK,iBAAiB,OAAO,QAAQ;AAGrC;;;AAMJ,WAAM,eAAe;AACrB,UAAK,iBAAiB,OAAO,eAAe;AAC5C;;IAGF,MAAM,aAAa,mBAAmB,MAAM;AAC5C,QAAI,CAAC,WAAW,aAAa,sBAAsB,MAAM,EAAE;AACzD,UAAK,SAAS,oBAAoB,OAAO;MACvC,WAAW,KAAK;MAChB,MAAM,WAAW;MAClB,CAAC;AACF,WAAM,YAAY,iBAAiB,MAAM;AACzC,UAAK,iBAAiB,OAAO,QAAQ;AAKrC;;AAEF,SAAK,iBAAiB,OAAO,eAAe;AAW5C,QACE,gBAAgB,KAChB,KAAK,KAAK,GAAG,iBAAA,IAEb,OAAM,eAAe;AAEvB,QAAI,CAAE,MAAM,KAAK,sBAAsB,OAAO,MAAM,CAClD;;;;;CAOR,OAAwB,kBAAkB,MAAM;;;;;;;;;;CAWhD,MAAc,iBACZ,cAGA,UAC0E;EAC1E,MAAM,QAAQ,aAAa,WAAW;AACtC,MAAI,SAAS,KACX;EAGF,MAAM,WAAW,YAAY;EAC7B,IAAI,gBAAgB,MAAM;EAC1B,MAAM,aAA2B,MAAM,KAAK,MAAM,QAAQ;EAC1D,IAAI,QAAQ;AAEZ,SACE,QAAQ,aAAa,WACpB,YAAY,QAAQ,QAAQ,WAC7B;GACA,MAAM,OAAO,aAAa,GAAG,MAAM;GACnC,MAAM,mBAAmB,CAAC,GAAG,YAAY,GAAG,KAAK,QAAQ;AAIzD,OAHuB,MAAM,KAAK,4BAChC,iBACD,GACoB,SACnB;AAEF,cAAW,KAAK,GAAG,KAAK,QAAQ;AAChC,mBAAgB,KAAK,QAAQ,cAAc,eAAe,KAAK,QAAQ;AACvE,YAAS;;AAGX,MAAI,UAAU,EACZ,QAAO;GAAE,OAAO;GAAO,OAAO;GAAG;AAEnC,SAAO;GAAE,OAAO;IAAE,SAAS;IAAY,SAAS;IAAe;GAAE;GAAO;;CAG1E,kBACE,OAC0E;AAC1E,SAAO,KAAK,iBACV,MAAM,cAGN,MAAM,qBAAqB,IAAI,KAAA,EAChC;;CAGH,6BAEE;AACA,SAAO,KAAK,iBAAiB,KAAK,iBAAiB;;;;;;;;;;;;;;;;;;;;;;CAuBrD,MAAc,kBAAkB,OAA2C;AACzE,MAAI,MAAM,iBAAiB,MAAM,OAC/B;AAEF,OAAK,qBAAqB,MAAM;AAChC,QAAM,gBAAgB;AACtB,MAAI;AACF,SAAO,QAAO,CAAC,MAAM,QAAQ;IAC3B,MAAM,UAAU,MAAM,KAAK,kBAAkB,MAAM;AACnD,QAAI,WAAW,KACb;IAEF,IAAI,WAAW;AACf,WAAO,CAAC,MAAM,OACZ,KAAI;KACF,IAAI;AACJ,SAAI,KAAK,cAAc,iBAAiB,MAAM,OAAO,EAAE;AACrD,YAAM,SAAS,MAAM,KAAK,iBAAiB,YACzC,KAAK,iBACH,MAAM,QACN,QAAQ,OACR,QAAQ,OACR,MAAM,aACP,CACF;MACD,MAAM,wBACJ,KAAK,mCAAmC;AAC1C,UAAI,yBAAyB,KAC3B,OAAM,eAAe;AAEvB,uBACE,KAAK,8BAA8B,IAAI,QAAQ;AACjD,WAAK,gBAAgB,SAAS,MAAM;YAC/B;MAYL,MAAM,WAAW,MAAM,KAAK,iBAAiB,YAC3C,KAAK,uBACH,MAAM,QACN,QAAQ,OACR,QAAQ,OACR,UACD,CACF;AACD,WAAK,kBAAkB,SAAS,YAAY;AAC5C,uBAAiB,SAAS,gBAAgB,QAAQ;;AAEpD,UAAK,IAAI,IAAI,GAAG,IAAI,gBAAgB,KAAK,EACvC,OAAM,aAAa,UAAU;AAE/B,UAAK,kBAAkB,OAAO,eAAe;AAC7C,WAAM,qBAAqB;AAC3B,WAAM,iBAAiB,KAAA;AACvB,UAAK,kBAAkB,OAAO,KAAK;AACnC;aACO,OAAO;AACd,SACE,uBAAuB,MAAM,IAC7B,QAAQ,QAAQ,KAChB,KAAK,iCAAiC,EACtC;AAOA,YAAM,qBAAqB;AAC3B,eAAS;;KAEX,MAAM,aAAa,mBAAmB,MAAM;KAC5C,MAAM,iBAAiB,iBAAiB,MAAM;AAC9C,SAAI,CAAC,WAAW,aAAa,sBAAsB,MAAM,EAAE;AACzD,UAAI,uBAAuB,MAAM,CAI/B,OAAM,qBAAqB;AAE7B,WAAK,SAAS,uBAAuB,OAAO;OAC1C,WAAW,KAAK;OAChB,MAAM,WAAW;OAClB,CAAC;AACF,YAAM,YAAY;AAClB,YAAM,iBAAiB;AACvB,WAAK,kBACH,OACA,IAAI,uBAAuB,eAAe,CAC3C;AACD,WAAK,kBAAkB,OAAO,QAAQ;AAStC;;KAEF,MAAM,cAAc,KAAK,gBAAgB;AACzC,SAAI,YAAY,aAAa;AAC3B,YAAM,YAAY;AAClB,YAAM,iBAAiB;AACvB,WAAK,kBACH,uBACA,IAAI,MAAM,sCAAsC,CACjD;AACD,WAAK,kBAAkB,OAAO,QAAQ;AACtC;;AAEF,UAAK,kBAAkB,OAAO,eAAe;KAC7C,MAAM,QAAQ,kBACZ,UACA,KAAK,iBACL,iBAAiB,YAAY,MAAM,eAAe,KAAA,EACnD;AACD,iBAAY;AACZ,WAAM,MAAM,OAAO,MAAM,qBAAqB,OAAO;;;YAInD;AACR,SAAM,gBAAgB;AACtB,QAAK,wBAAwB,MAAM;;;;;;;;;CAUvC,kCAAmD;EACjD,MAAM,SAAS,KAAK;AACpB,MAAI,UAAU,QAAQ,OAAO,WAAW,UACtC,QAAO;AAET,OAAK,yBAAyB,OAAO;AACrC,SAAO;;;;;;;;;;;;;;CAeT,wBAAgC,OAAkC;AAChE,MACE,MAAM,UACN,KAAK,cAAc,SACnB,MAAM,mBAAmB,WACzB,MAAM,aAAa,WAAW,KAC9B,MAAM,mBAAmB,QACzB,MAAM,cAEN;EAEF,MAAM,QAAQ,wBACZ,MAAM,wBACN,KAAK,gBACN;AACD,QAAM,0BAA0B;AAChC,QAAM,kBAAkB,iBAAiB;AACvC,SAAM,kBAAkB,KAAA;AACxB,OAAI,MAAM,UAAU,KAAK,cAAc,MACrC;AAEG,QAAK,kBAAkB,MAAM;KACjC,MAAM;;;;;;CAOX,kBACE,OACA,cACM;EACN,IAAI,IAAI;AACR,SAAO,IAAI,MAAM,YAAY,QAAQ;GACnC,MAAM,SAAS,MAAM,YAAY;AACjC,UAAO,aAAa;AACpB,OAAI,OAAO,aAAa,GAAG;AACzB,UAAM,YAAY,OAAO,GAAG,EAAE;AAC9B,WAAO,SAAS;SAEhB,MAAK;;;;CAMX,kBACE,OACA,OACM;AACN,OAAK,MAAM,UAAU,MAAM,YAAY,OAAO,EAAE,CAC9C,QAAO,OAAO,MAAM;;CAIxB,MAAc,iBACZ,QACA,QACiC;EACjC,MAAM,QAAQ,MAAM,oBAAoB;GACtC,mBAAmB,KAAK;GACxB;GACA;GACA,wBAAwB,KAAK;GAC9B,CAAC;EAUF,MAAM,QAAQ,KAAK;AACnB,MAAI,SAAS,QAAQ,CAAC,MAAM,OAC1B,OAAM,4BAA4B;AAEpC,SAAO;;CAGT,iBACE,OACA,KACM;AACN,MAAI,QAAQ,KAEV,OAAM,wBAAwB;AAEhC,MAAI,MAAM,kBAAkB,IAC1B;AAEF,QAAM,gBAAgB;AACtB,OAAK,oBAAoB,MAAM;;CAGjC,kBACE,OACA,KACM;AACN,MAAI,QAAQ,KAEV,OAAM,yBAAyB;AAEjC,MAAI,MAAM,mBAAmB,IAC3B;AAEF,QAAM,iBAAiB;AACvB,OAAK,oBAAoB,MAAM;;CAGjC,oBAA4B,OAAkC;EAC5D,MAAM,SAAS,KAAK,qBAAqB,MAAM;AAC/C,MAAI,MAAM,WAAW,OACnB;AAEF,QAAM,SAAS;AAEf,MAAI,WAAW,SACb,OAAM,YAAY,KAAA;AAEpB,OAAK,MAAM,YAAY,MAAM,gBAC3B,UAAS,OAA8B;;CAI3C,qBACE,OACoC;EACpC,MAAM,EAAE,eAAe,mBAAmB;AAC1C,MAAI,kBAAkB,WAAW,mBAAmB,QAClD,QAAO;AAET,MAAI,kBAAkB,eACpB,QAAO;AAET,MAAI,kBAAkB,aACpB,QAAO;AAET,MACE,kBAAkB,kBAClB,mBAAmB,eAEnB,QAAO;AAET,SAAO;;CAGT,iBAA4B,MAAyC;EACnE,MAAM,MAAM,KAAK,QAAQ,KAAK,MAAM,KAAK;AACzC,OAAK,UAAU,IAAI,WACX,KAAA,SACA,KAAA,EACP;AACD,SAAO;;CAGT,MAAc,4BACZ,SACiB;AACjB,MAAI,KAAK,gCAAgC,KACvC,QAAO,MAAM,KAAK,6BAA6B,QAAQ;AAEzD,SAAO,QAAQ,QAAQ,KAAK,WAAW,MAAM,IAAI,OAAO,YAAY,EAAE;;CAGxE,qBAA6B,QAAsB;EACjD,MAAM,SAAS,KAAK;AACpB,MAAI,QAAQ,WAAW,aACrB,MAAK,yBAAyB,OAAO;AAEvC,MAAI,KAAK,2BAA2B,KAClC;AAEF,OAAK,0BAA0B;AAC/B,OAAK,iBAAiB,OAAO;AAC7B,OAAK,6BAA6B;AAClC,OAAK,4BAA4B,KAAA;;CAGnC,SAAiB,SAAiB,QAAwC;AACxE,MAAI,CAAC,KAAK,MACR;AAEF,MAAI,UAAU,MAAM;AAClB,WAAQ,MAAM,kBAAkB,UAAU;AAC1C;;AAEF,UAAQ,MAAM,kBAAkB,WAAW,OAAO;;CAGpD,SACE,SACA,OACA,QACM;EACN,MAAM,UACJ,SAAS,OACL,SACA;GACE,GAAG;GACH,OAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM;GAC9D;AACP,MAAI,WAAW,MAAM;AACnB,WAAQ,MAAM,kBAAkB,UAAU;AAC1C;;AAEF,UAAQ,MAAM,kBAAkB,WAAW,QAAQ;;;;;ACt6FvD,MAAM,sBAAsB;AAC5B,MAAM,cAAc,IAAI,aAAa;AAErC,SAAS,eAAe,OAAuB;AAC7C,QAAO,YAAY,OAAO,MAAM,CAAC;;;;;AAMnC,SAAgB,cAAc,IAAqB;AACjD,QACE,GAAG,SAAS,KACZ,eAAe,GAAG,IAAI,uBACtB,OAAO,OACP,CAAC,GAAG,SAAS,IAAI,IACjB,CAAC,GAAG,SAAS,KAAK,IAClB,CAAC,GAAG,SAAS,KAAK"}
|