@fitzzero/quickdraw-core 5.1.2 → 5.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (124) hide show
  1. package/README.md +793 -57
  2. package/dist/{admin-DWWrT1h6.d.ts → admin-CS7JB_UH.d.ts} +2 -2
  3. package/dist/{adminSchemas-D4bFKsrb.d.ts → adminSchemas-29cGgrg5.d.ts} +1 -1
  4. package/dist/{auth-DXiSJZaf.d.ts → auth-DtdYjrZ9.d.ts} +11 -7
  5. package/dist/{caller-BT-Mv4bD.d.ts → caller-BTwGl7vm.d.ts} +183 -15
  6. package/dist/chunk-2FDSKLBO.js +82 -0
  7. package/dist/chunk-2FDSKLBO.js.map +1 -0
  8. package/dist/{chunk-GSIPUNJL.js → chunk-64I6MYBC.js} +139 -792
  9. package/dist/chunk-64I6MYBC.js.map +1 -0
  10. package/dist/chunk-7473HK24.js +17 -0
  11. package/dist/chunk-7473HK24.js.map +1 -0
  12. package/dist/chunk-7Y5ROPXL.js +33 -0
  13. package/dist/chunk-7Y5ROPXL.js.map +1 -0
  14. package/dist/chunk-A2RBQD66.js +7 -0
  15. package/dist/chunk-A2RBQD66.js.map +1 -0
  16. package/dist/chunk-ASAMUGHG.js +651 -0
  17. package/dist/chunk-ASAMUGHG.js.map +1 -0
  18. package/dist/{chunk-B2P7AZ25.js → chunk-CYE4UBOB.js} +6 -4
  19. package/dist/chunk-CYE4UBOB.js.map +1 -0
  20. package/dist/chunk-DNP65TD5.js +832 -0
  21. package/dist/chunk-DNP65TD5.js.map +1 -0
  22. package/dist/{chunk-5CGO426C.js → chunk-GYLWEJHW.js} +5 -1
  23. package/dist/chunk-GYLWEJHW.js.map +1 -0
  24. package/dist/{chunk-AONAFOYU.js → chunk-I3JCFS7H.js} +25 -13
  25. package/dist/chunk-I3JCFS7H.js.map +1 -0
  26. package/dist/{chunk-X5CWPX7H.js → chunk-IIQ3WMJK.js} +13 -2
  27. package/dist/chunk-IIQ3WMJK.js.map +1 -0
  28. package/dist/{chunk-6K5BY47X.js → chunk-JVTONQFI.js} +1277 -277
  29. package/dist/chunk-JVTONQFI.js.map +1 -0
  30. package/dist/{chunk-RL4VKLBX.js → chunk-KKQANRLG.js} +2 -2
  31. package/dist/{chunk-RL4VKLBX.js.map → chunk-KKQANRLG.js.map} +1 -1
  32. package/dist/{chunk-GKH3LJ7N.js → chunk-MPQAT55F.js} +38 -205
  33. package/dist/chunk-MPQAT55F.js.map +1 -0
  34. package/dist/chunk-NX2V6SIL.js +202 -0
  35. package/dist/chunk-NX2V6SIL.js.map +1 -0
  36. package/dist/{chunk-7QCUX4TK.js → chunk-NZHA4TZD.js} +72 -175
  37. package/dist/chunk-NZHA4TZD.js.map +1 -0
  38. package/dist/{chunk-MBH6IDSM.js → chunk-OCZJ4EYV.js} +40 -17
  39. package/dist/chunk-OCZJ4EYV.js.map +1 -0
  40. package/dist/{chunk-RJ5I2VO3.js → chunk-PHCURHC7.js} +6 -6
  41. package/dist/chunk-PHCURHC7.js.map +1 -0
  42. package/dist/{chunk-4YO5H5ON.js → chunk-Q4OGQM5I.js} +4 -10
  43. package/dist/chunk-Q4OGQM5I.js.map +1 -0
  44. package/dist/chunk-QSU47XEX.js +13 -0
  45. package/dist/chunk-QSU47XEX.js.map +1 -0
  46. package/dist/{chunk-LCC7HNFP.js → chunk-RZXJTDH5.js} +2 -2
  47. package/dist/{chunk-Z2LZ7IZ7.js → chunk-SN3OZVEQ.js} +5 -31
  48. package/dist/chunk-SN3OZVEQ.js.map +1 -0
  49. package/dist/{chunk-Z5ILULF2.js → chunk-SQ5VUG77.js} +4 -3
  50. package/dist/{chunk-Z5ILULF2.js.map → chunk-SQ5VUG77.js.map} +1 -1
  51. package/dist/chunk-STH5DLQP.js +1192 -0
  52. package/dist/chunk-STH5DLQP.js.map +1 -0
  53. package/dist/{chunk-NMXYENH4.js → chunk-VZFJTKIW.js} +6 -6
  54. package/dist/{chunk-753VJIUP.js → chunk-X4ZU4QH5.js} +43 -9
  55. package/dist/chunk-X4ZU4QH5.js.map +1 -0
  56. package/dist/chunk-XD7SRDUY.js +188 -0
  57. package/dist/chunk-XD7SRDUY.js.map +1 -0
  58. package/dist/chunk-Z7I6SONO.js +825 -0
  59. package/dist/chunk-Z7I6SONO.js.map +1 -0
  60. package/dist/cli/quickdraw-docs.js +131 -113
  61. package/dist/cli/quickdraw-docs.js.map +1 -1
  62. package/dist/client/core.d.ts +387 -0
  63. package/dist/client/core.js +109 -0
  64. package/dist/client/core.js.map +1 -0
  65. package/dist/client/index.d.ts +30 -193
  66. package/dist/client/index.js +56 -217
  67. package/dist/client/index.js.map +1 -1
  68. package/dist/{clientTypes-hWcPUVS9.d.ts → clientTypes-CUUNJwWs.d.ts} +3 -3
  69. package/dist/{connection-CgijdGpT.d.ts → connection-2EeQ_Et0.d.ts} +16 -9
  70. package/dist/{createServer-Cp-Wk500.d.ts → createServer-Bx5HOcYK.d.ts} +37 -9
  71. package/dist/{createTestApp-BB_PkqQ7.d.ts → createTestApp-DAMANT79.d.ts} +36 -9
  72. package/dist/{envelope-DzWyIXjj.d.ts → envelope-BMBaxppV.d.ts} +200 -8
  73. package/dist/{http-DotQ5bj8.d.ts → http-YzLAar3w.d.ts} +15 -50
  74. package/dist/index.d.ts +7 -6
  75. package/dist/index.js +61 -28
  76. package/dist/index.js.map +1 -1
  77. package/dist/{infer-BmoZsL9a.d.ts → infer-Br62cnyW.d.ts} +39 -1
  78. package/dist/{metrics-DW80Exve.d.ts → metrics-CTVpw0gu.d.ts} +2 -2
  79. package/dist/parser.js +2 -1
  80. package/dist/prisma/index.d.ts +13 -6
  81. package/dist/prisma/index.js +155 -9
  82. package/dist/prisma/index.js.map +1 -1
  83. package/dist/remote-N7OUAGOW.js +15 -0
  84. package/dist/remote-N7OUAGOW.js.map +1 -0
  85. package/dist/server/auth/index.d.ts +3 -3
  86. package/dist/server/auth/index.js +5 -3
  87. package/dist/server/auth/index.js.map +1 -1
  88. package/dist/server/index.d.ts +140 -36
  89. package/dist/server/index.js +162 -55
  90. package/dist/server/index.js.map +1 -1
  91. package/dist/server/mcp/index.d.ts +34 -18
  92. package/dist/server/mcp/index.js +29 -12
  93. package/dist/server/mcp/index.js.map +1 -1
  94. package/dist/server/otel.d.ts +3 -3
  95. package/dist/{sharing-UDLn1049.d.ts → sharing-BrsmlDv_.d.ts} +3 -3
  96. package/dist/testing/client.d.ts +13 -13
  97. package/dist/testing/client.js +17 -10
  98. package/dist/testing/client.js.map +1 -1
  99. package/dist/testing/index.d.ts +49 -12
  100. package/dist/testing/index.js +102 -19
  101. package/dist/testing/index.js.map +1 -1
  102. package/dist/testing/mock.d.ts +5 -5
  103. package/dist/testing/mock.js +15 -9
  104. package/dist/tracked-BR5b2O-U.d.ts +43 -0
  105. package/dist/{types-CYliTP65.d.ts → types-CLRCQGnO.d.ts} +4 -3
  106. package/dist/types-tMagHbjy.d.ts +151 -0
  107. package/dist/utils/index.d.ts +16 -4
  108. package/dist/utils/index.js +6 -3
  109. package/package.json +5 -1
  110. package/dist/chunk-4YO5H5ON.js.map +0 -1
  111. package/dist/chunk-5CGO426C.js.map +0 -1
  112. package/dist/chunk-6K5BY47X.js.map +0 -1
  113. package/dist/chunk-753VJIUP.js.map +0 -1
  114. package/dist/chunk-7QCUX4TK.js.map +0 -1
  115. package/dist/chunk-AONAFOYU.js.map +0 -1
  116. package/dist/chunk-B2P7AZ25.js.map +0 -1
  117. package/dist/chunk-GKH3LJ7N.js.map +0 -1
  118. package/dist/chunk-GSIPUNJL.js.map +0 -1
  119. package/dist/chunk-MBH6IDSM.js.map +0 -1
  120. package/dist/chunk-RJ5I2VO3.js.map +0 -1
  121. package/dist/chunk-X5CWPX7H.js.map +0 -1
  122. package/dist/chunk-Z2LZ7IZ7.js.map +0 -1
  123. /package/dist/{chunk-LCC7HNFP.js.map → chunk-RZXJTDH5.js.map} +0 -0
  124. /package/dist/{chunk-NMXYENH4.js.map → chunk-VZFJTKIW.js.map} +0 -0
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/client/auth.ts","../src/client/newId.ts","../src/client/host/local.ts","../src/client/host/serving.ts","../src/client/host/outbox.ts","../src/client/host/runtime.ts","../src/client/host/host.ts"],"sourcesContent":["// The browser side of the auth routes kit (RFC 0003 section 12.6), and\n// token storage. `authProviders`, `signInUrl`, `signOut` and\n// `signOutEverywhere` use the routes `createAuthRoutes` serves (`GET\n// {basePath}/providers`, `GET {basePath}/{provider}/start`, `POST\n// {basePath}/logout`, `POST {basePath}/logout-all`); the last three replace\n// 4.1's `getOAuthUrl`, `logout` and `logoutAllDevices`\n// (4.1 `src/client/utils/auth.ts`), which called routes the kit does not\n// serve and sent only a stored token, so with cookie sessions they signed\n// nobody out (finding F3.3). The token helpers keep a bearer token for\n// clients without cookies; they read `localStorage`, so they stay on\n// `./client`; `parseJWTPayload`, which needs no storage, moved to the\n// isomorphic `./utils` entry (`../utils/jwt.ts`).\n//\n// A POST sends the session cookie (`credentials: \"include\"`: the API is\n// often on another origin, whose CORS must then allow the web app's origin\n// with credentials) and `Content-Type: application/json`, which the routes\n// require (a cross-site form cannot send it), and the stored token as a\n// bearer token when there is one. A refusal or an unreachable server\n// rejects with a `QuickdrawError`: nothing fails silently.\n//\n// 4.1 assumed a browser wherever `window` exists. React Native defines\n// `window` without `localStorage`, and a browser with storage turned off\n// throws when it is used; there the token is kept in memory for the life of\n// the app. Without `window` (a server rendering a page) nothing is kept, so\n// one request's token never reaches another. The API's URL defaults to\n// `NEXT_PUBLIC_API_URL` where `process.env` can be read (Next.js inlines it\n// when it builds a page), and to `http://localhost:4000` elsewhere.\n\nimport { QuickdrawError, isErrorCode } from \"../protocol/errors\";\nimport { isRecord } from \"../protocol/guards\";\n\nconst AUTH_TOKEN_KEY = \"auth_token\";\n\nconst DEFAULT_API_URL = \"http://localhost:4000\";\n\n/** The token, where `localStorage` cannot be used. */\nlet memoryToken: string | null = null;\n\n/** True where a client keeps a token: there is a `window` (a browser, React Native). */\nfunction onClient(): boolean {\n return typeof window !== \"undefined\";\n}\n\n/** The token `localStorage` holds; `undefined` when there is no `localStorage` to use. */\nfunction storedToken(): string | null | undefined {\n try {\n return globalThis.localStorage.getItem(AUTH_TOKEN_KEY);\n } catch {\n return undefined;\n }\n}\n\n/** Tells the page the token changed, where there is a page to tell. */\nfunction announce(): void {\n if (typeof window.dispatchEvent === \"function\" && typeof Event === \"function\") {\n window.dispatchEvent(new Event(\"auth-token-changed\"));\n }\n}\n\n/** The API's URL: `apiUrl`, else `NEXT_PUBLIC_API_URL` where `process.env` can be read, else localhost. */\nfunction apiUrlOf(apiUrl: string | undefined): string {\n if (apiUrl !== undefined) {\n return apiUrl;\n }\n try {\n // Written out whole, so Next.js can inline it when it builds a page.\n return process.env.NEXT_PUBLIC_API_URL ?? DEFAULT_API_URL;\n } catch {\n // No `process` here (React Native, a bundler that provides none).\n return DEFAULT_API_URL;\n }\n}\n\n/**\n * Get the stored auth token: from `localStorage`, or from memory where it\n * cannot be used; `null` without a `window`.\n */\nexport function getAuthToken(): string | null {\n if (!onClient()) return null;\n const stored = storedToken();\n return stored === undefined ? memoryToken : stored;\n}\n\n/**\n * Store the auth token (in `localStorage`, else in memory) and dispatch a\n * change event. Does nothing without a `window`.\n */\nexport function setAuthToken(token: string): void {\n if (!onClient()) return;\n memoryToken = token;\n try {\n globalThis.localStorage.setItem(AUTH_TOKEN_KEY, token);\n } catch {\n // No usable localStorage: the token is kept in memory.\n }\n announce();\n}\n\n/**\n * Remove the auth token and dispatch a change event. Does nothing without a\n * `window`.\n */\nexport function clearAuthToken(): void {\n if (!onClient()) return;\n memoryToken = null;\n try {\n globalThis.localStorage.removeItem(AUTH_TOKEN_KEY);\n } catch {\n // No usable localStorage: forgetting the token in memory is enough.\n }\n announce();\n}\n\n/** Where the auth routes are, for the browser helpers. */\nexport interface AuthRoutesTarget {\n /**\n * The API's URL, where `createAuthRoutes` is mounted:\n * `\"https://api.example.com\"`. Default: `NEXT_PUBLIC_API_URL` where it can\n * be read, else `http://localhost:4000`.\n */\n readonly apiUrl?: string;\n /** The routes' `basePath`, as `createAuthRoutes` was given it. Default `/auth`. */\n readonly basePath?: string;\n}\n\n/** Options of {@link signInUrl}. */\nexport interface SignInUrlOptions extends AuthRoutesTarget {\n /**\n * The page to come back to: an origin or a URL on one, of which the\n * routes keep the origin when `allowedOrigins` lists it, and land on\n * `{origin}{successPath}`. Default: the current page's origin where there\n * is a page; elsewhere the routes take their first allowed origin.\n */\n readonly returnTo?: string;\n}\n\nconst PROVIDER_ID = /^[a-z0-9][a-z0-9-]*$/;\n\n/** The routes' base URL: `{apiUrl}{basePath}`, checked. */\nfunction routesUrl(owner: string, target: AuthRoutesTarget): string {\n const base = apiUrlOf(target.apiUrl).replace(/\\/+$/, \"\");\n const path = target.basePath ?? \"/auth\";\n if (typeof path !== \"string\" || !path.startsWith(\"/\")) {\n throw new TypeError(`${owner}: basePath must start with \"/\", as createAuthRoutes takes it`);\n }\n return `${base}${path.replace(/\\/+$/, \"\")}`;\n}\n\n/** The current page's origin, where there is a page. */\nfunction pageOrigin(): string | undefined {\n try {\n const origin: unknown = (globalThis as { readonly location?: { readonly origin?: unknown } })\n .location?.origin;\n return typeof origin === \"string\" && origin !== \"null\" ? origin : undefined;\n } catch {\n return undefined;\n }\n}\n\n/**\n * The URL that starts a sign-in with `provider`: the auth routes' `GET\n * {basePath}/{provider}/start?returnTo=`, which redirects to the provider\n * and, once the user consents, back to `returnTo`'s origin with the session\n * cookie set (or to the routes' `errorPath` with `?error=`). Navigate to it:\n * a link, or `location.assign`. Throws a `TypeError` for a provider id the\n * routes cannot have (lowercase letters, digits and hyphens).\n *\n * @example\n * <a href={signInUrl(\"google\", { apiUrl: API_URL })}>Sign in with Google</a>\n */\nexport function signInUrl(provider: string, options: SignInUrlOptions = {}): string {\n if (typeof provider !== \"string\" || !PROVIDER_ID.test(provider)) {\n throw new TypeError(\n `signInUrl: a provider id is lowercase letters, digits and hyphens; got ${String(provider)}`,\n );\n }\n const url = new URL(`${routesUrl(\"signInUrl\", options)}/${provider}/start`);\n const returnTo = options.returnTo ?? pageOrigin();\n if (returnTo !== undefined) {\n url.searchParams.set(\"returnTo\", returnTo);\n }\n return url.toString();\n}\n\n/** The error a route's refusal stands for: its `{ error, message }` body, else its HTTP status. */\nasync function refusalOf(response: Response): Promise<QuickdrawError> {\n let body: unknown;\n try {\n body = await response.json();\n } catch {\n body = undefined;\n }\n const code = isRecord(body) && isErrorCode(body.error) ? body.error : undefined;\n const message =\n isRecord(body) && typeof body.message === \"string\"\n ? body.message\n : `The auth route answered ${String(response.status)}`;\n if (code !== undefined) {\n return new QuickdrawError(code, message);\n }\n return new QuickdrawError(response.status === 429 ? \"RATE_LIMITED\" : \"INTERNAL\", message);\n}\n\n/** POSTs to one session route with the cookie, the JSON content type and the stored token. */\nasync function postRoute(owner: string, route: string, target: AuthRoutesTarget): Promise<void> {\n const url = `${routesUrl(owner, target)}${route}`;\n const token = getAuthToken();\n let response: Response;\n try {\n response = await fetch(url, {\n method: \"POST\",\n credentials: \"include\",\n headers: {\n \"Content-Type\": \"application/json\",\n ...(token === null ? {} : { Authorization: `Bearer ${token}` }),\n },\n body: \"{}\",\n });\n } catch (error) {\n throw new QuickdrawError(\"INTERNAL\", `${owner}: ${url} could not be reached`, {\n cause: error instanceof Error ? error.message : String(error),\n });\n }\n if (!response.ok) {\n throw await refusalOf(response);\n }\n}\n\n/**\n * Signs out: `POST {basePath}/logout`, which revokes the request's session\n * and clears its cookie, sent with the session cookie and, when one is\n * stored, the bearer token; the stored token is forgotten either way.\n * Rejects with a `QuickdrawError` when the server cannot be reached or\n * refuses (`RATE_LIMITED`, say), so the app knows the session may still be\n * live. The connection keeps the user it signed in as until it connects\n * again: with cookie sessions, reconnect it (`connection.close()`, then\n * `open()`), or load the next page; a provider whose `auth` was the stored\n * token reconnects when the app clears it.\n *\n * @example\n * await signOut({ apiUrl: API_URL });\n */\nexport async function signOut(options: AuthRoutesTarget = {}): Promise<void> {\n try {\n await postRoute(\"signOut\", \"/logout\", options);\n } finally {\n clearAuthToken();\n }\n}\n\n/** A sign-in the API's auth routes serve, as `GET {basePath}/providers` lists it ({@link authProviders}). */\nexport interface AuthProviderInfo {\n /** Its id: `signInUrl(id)` starts it; a guest's is `POST {basePath}/guest`. */\n readonly id: string;\n /** A name for its button: `\"Google\"`, `\"Discord\"`, `\"Mock\"`, `\"Guest\"`, or an OAuth provider's own. */\n readonly name: string;\n /** `\"oauth\"` (a redirecting provider), `\"mock\"` (the development picker) or `\"guest\"`. */\n readonly kind: \"oauth\" | \"mock\" | \"guest\";\n}\n\nconst PROVIDER_KINDS: ReadonlySet<unknown> = new Set([\"oauth\", \"mock\", \"guest\"]);\n\n/** One entry of the routes' list, its known fields only (a newer server may add others). */\nfunction providerInfoOf(value: unknown): AuthProviderInfo | undefined {\n if (!isRecord(value) || typeof value.id !== \"string\" || !PROVIDER_ID.test(value.id)) {\n return undefined;\n }\n const kind = PROVIDER_KINDS.has(value.kind)\n ? (value.kind as AuthProviderInfo[\"kind\"])\n : undefined;\n return kind === undefined\n ? undefined\n : { id: value.id, name: typeof value.name === \"string\" ? value.name : value.id, kind };\n}\n\n/**\n * The sign-ins the API serves, in the order its routes list them: `GET\n * {basePath}/providers` (finding F9.1 of the owner's QA of the template), so\n * a login page renders a button only for a provider that is there, whatever\n * the web app was built with: a provider whose credentials the server lacks\n * is not listed, nor the mock where it is off. Rejects with a\n * `QuickdrawError` when the server cannot be reached or refuses.\n *\n * @example\n * const { data: providers = [] } = useQuery({\n * queryKey: [\"auth\", \"providers\"],\n * queryFn: () => authProviders({ apiUrl: API_URL }),\n * });\n */\nexport async function authProviders(\n options: AuthRoutesTarget = {},\n): Promise<readonly AuthProviderInfo[]> {\n const url = `${routesUrl(\"authProviders\", options)}/providers`;\n let response: Response;\n try {\n response = await fetch(url, { headers: { Accept: \"application/json\" } });\n } catch (error) {\n throw new QuickdrawError(\"INTERNAL\", `authProviders: ${url} could not be reached`, {\n cause: error instanceof Error ? error.message : String(error),\n });\n }\n if (!response.ok) {\n throw await refusalOf(response);\n }\n let body: unknown;\n try {\n body = await response.json();\n } catch {\n body = undefined;\n }\n if (!isRecord(body) || !Array.isArray(body.providers)) {\n throw new QuickdrawError(\"INTERNAL\", `authProviders: ${url} answered no list of providers`);\n }\n return body.providers.flatMap((entry: unknown) => {\n const info = providerInfoOf(entry);\n return info === undefined ? [] : [info];\n });\n}\n\n/**\n * Signs the user out everywhere: `POST {basePath}/logout-all`, which\n * revokes every session of the user (the app's `onRevoke` can disconnect\n * their sockets) and clears this browser's cookie; the stored token is\n * forgotten either way. Rejects with `UNAUTHENTICATED` when the request\n * carries no live session, and with a `QuickdrawError` when the server\n * cannot be reached or refuses.\n */\nexport async function signOutEverywhere(options: AuthRoutesTarget = {}): Promise<void> {\n try {\n await postRoute(\"signOutEverywhere\", \"/logout-all\", options);\n } finally {\n clearAuthToken();\n }\n}\n","// `newId()` (finding F11.2 of the quickdraw-chat migration): an id for a row\n// the client creates and the server keeps, so that an optimistic item whose\n// call's outcome is unknown can be found by its scope's next load, and its\n// `retry()` cannot write a second row (`additions.ts`). A version 4 UUID,\n// from `crypto.randomUUID()` where the runtime has it, else made from\n// `crypto.getRandomValues()`: browsers give `randomUUID` to secure pages only\n// (https, localhost), and a dev server opened at its LAN address is plain\n// http, while `getRandomValues` is on every page, in Node, and in React\n// Native with a `getRandomValues` polyfill.\n//\n// React-free, and no dependency.\n\n/** The Web Crypto members `newId` uses. */\ninterface IdCrypto {\n readonly randomUUID?: () => string;\n readonly getRandomValues?: (array: Uint8Array) => Uint8Array;\n}\n\n/** Byte `index` of a version 4 UUID made from the random `byte`: the version in byte 6, the RFC 9562 variant in byte 8. */\nfunction uuidByte(byte: number, index: number): number {\n if (index === 6) {\n return 0x40 + (byte % 0x10);\n }\n return index === 8 ? 0x80 + (byte % 0x40) : byte;\n}\n\n/** 16 random bytes as a version 4 UUID. */\nfunction uuidOf(bytes: Uint8Array): string {\n const hex = Array.from(bytes, (byte, index) =>\n uuidByte(byte, index).toString(16).padStart(2, \"0\"),\n ).join(\"\");\n return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;\n}\n\n/**\n * A new id for a row the client creates and the server keeps: a version 4\n * UUID, from `crypto.randomUUID()`, or from `crypto.getRandomValues()` where\n * that is missing (a page over plain http, which browsers do not give\n * `randomUUID`). Give it to an item an optimistic update adds and to the\n * create's input, so the scope's next load can find the item after a lost\n * answer and `retry()` cannot write twice. Throws a `TypeError` in a runtime\n * without Web Crypto.\n *\n * @example\n * create.mutate({ id: newId(), chatId, content });\n */\nexport function newId(): string {\n const { crypto } = globalThis as { readonly crypto?: IdCrypto };\n if (typeof crypto?.randomUUID === \"function\") {\n return crypto.randomUUID();\n }\n if (typeof crypto?.getRandomValues !== \"function\") {\n throw new TypeError(\"newId: this runtime has no crypto.getRandomValues\");\n }\n return uuidOf(crypto.getRandomValues(new Uint8Array(16)));\n}\n","// The link between a room's host and its own client, in process (host\n// mode, RFC 0003 section 12.5): two link cores (`../peer/core.ts`) that hand\n// each other the JSON text a peer's link carries, at once, so the host's\n// own channels and feeds of its room reach it as a peer's do, and the host\n// lists its own client among its peers with a link like theirs: `setLag` on\n// it gives the host's own play the lag its peers have. Both ends open at\n// once, on the `direct` path, each naming this socket and its user; closing\n// one closes the other, with `bye`. Neither has a watchdog: both ends ping\n// on this tab's timers, so a hidden tab's throttled timers would close the\n// link as `lost`, which an in-process link never is.\n//\n// React-free.\n\nimport { createLinkCore, type LinkCore } from \"../peer/core\";\nimport type { PeerLink } from \"../peer/types\";\n\n/**\n * The two ends of the in-process link of `room` on socket `socketId` of\n * `userId`, open: `[near, far]`, this client's end and the host's.\n */\nexport function createLocalLink(\n room: string,\n socketId: string,\n userId: string,\n maxMessageBytes: () => number,\n): readonly [near: PeerLink, far: PeerLink] {\n const ends: LinkCore[] = [];\n const endOf = (other: () => LinkCore | undefined): LinkCore =>\n createLinkCore({\n room,\n peer: socketId,\n maxMessageBytes,\n watchdog: false,\n hooks: {\n send: (label, text) => {\n other()?.receive(text, label);\n return true;\n },\n closed: (_reason, fromHere) => {\n if (fromHere) {\n other()?.end(\"bye\");\n }\n },\n },\n });\n const near = endOf(() => ends[1]);\n const far = endOf(() => ends[0]);\n ends.push(near, far);\n for (const end of ends) {\n end.identify(userId);\n }\n far.carry(\"direct\");\n near.carry(\"direct\");\n return [near.link, far.link];\n}\n","// What one connection serves of one app room as its host (host mode, RFC\n// 0003 section 12.5): the runtime of each contract `serve` was called for,\n// over one set of links to the room's peers. It answers the room's link\n// requests (`onPeerRequest`: peers offer, the host answers) and takes the\n// host's own client over an in-process link (`registerLocalHost`, with\n// `local.ts`'s maker and `controlOf`, which only a host loads). Each peer,\n// either way, is attached to every runtime with its link\n// (`HostPeer.link`), and each of its messages goes to the runtime of the\n// message's service; a subscribe to a service no runtime serves is refused\n// (`NOT_FOUND`) by one that does, within the peer's subscribe bucket there\n// and through its way out, so a peer's feed never waits on it and a peer\n// cannot flood the host with them. A peer is detached when its link\n// closes. The last runtime's removal stops it all: the links close, and no\n// request is answered.\n//\n// React-free.\n\nimport type { HostMessage } from \"../../protocol/envelope\";\nimport type { QuickdrawConnection } from \"../connection\";\nimport { controlOf } from \"../peer/core\";\nimport { onPeerRequest, type PeerRequest } from \"../peer/link\";\nimport type { PeerLink } from \"../peer/types\";\nimport { registerLocalHost } from \"./links\";\nimport { createLocalLink } from \"./local\";\nimport type { AttachedPeer, HostPeer, HostRuntime, HostSend, RuntimePeer } from \"./runtime\";\n\n/** One connection's serving of one room. */\nexport interface RoomServing {\n /** Serves `service` with `runtime`; a `TypeError` when the room serves that service already. */\n add(service: string, runtime: HostRuntime): void;\n /** Stops serving `service` (its runtime closes); the room's last one stops it all. */\n remove(service: string, runtime: HostRuntime): void;\n}\n\n/** A peer of the room, either way, and what each runtime attached of it. */\ninterface Attachment {\n readonly peer: HostPeer;\n readonly send: HostSend;\n readonly close: () => void;\n readonly attached: Map<string, RuntimePeer>;\n}\n\ninterface State {\n readonly room: string;\n readonly runtimes: Map<string, HostRuntime>;\n readonly peers: Set<Attachment>;\n readonly links: Set<PeerLink>;\n}\n\nconst all = new WeakMap<QuickdrawConnection, Map<string, RoomServing>>();\n\nfunction attachAll(state: State, at: Attachment): void {\n for (const [service, runtime] of state.runtimes) {\n if (!at.attached.has(service)) {\n at.attached.set(service, runtime.attach(at.peer, at.send, at.close));\n }\n }\n}\n\n/** A message from a peer: to the runtime of its service, or a subscribe refused. */\nfunction deliver(at: Attachment, message: HostMessage): void {\n const attached = at.attached.get(message[1]);\n if (attached !== undefined) {\n attached.receive(message);\n } else if (message[0] === 4) {\n at.attached.values().next().value?.unserved(message);\n }\n}\n\n/** A peer, either way: attached to every runtime, now and later, until `detach`. */\nfunction join(state: State, peer: HostPeer, send: HostSend, close: () => void): AttachedPeer {\n const at: Attachment = { peer, send, close, attached: new Map() };\n state.peers.add(at);\n attachAll(state, at);\n return {\n receive: (message) => {\n deliver(at, message);\n },\n detach: () => {\n for (const attached of at.attached.values()) {\n attached.detach();\n }\n at.attached.clear();\n state.peers.delete(at);\n },\n };\n}\n\n/** Takes `link` as a peer of the room's, of user `userId`, until it closes; `close` ends it after a flood. */\nfunction adopt(state: State, link: PeerLink, userId: string, close: () => void): void {\n const control = controlOf(link);\n if (control === undefined || link.state === \"closed\") {\n return;\n }\n state.links.add(link);\n const peer = join(state, { socketId: link.peer, userId, link }, control.send, close);\n const off = control.on((message) => {\n peer.receive(message);\n });\n link.on(\"close\", () => {\n off();\n peer.detach();\n state.links.delete(link);\n });\n}\n\nfunction accept(state: State, request: PeerRequest): void {\n let link: PeerLink;\n try {\n link = request.accept();\n } catch {\n // A link the app opened itself to that peer, in this room.\n return;\n }\n adopt(state, link, request.userId, () => {\n link.close();\n });\n}\n\nfunction open(\n connection: QuickdrawConnection,\n room: string,\n rooms: Map<string, RoomServing>,\n): RoomServing {\n const state: State = { room, runtimes: new Map(), peers: new Set(), links: new Set() };\n const stopRequests = onPeerRequest(connection, (request) => {\n if (request.room === room) {\n accept(state, request);\n }\n });\n // The host's own client: a flood of its own is not ended.\n const unregister = registerLocalHost(\n connection,\n room,\n (link) => {\n adopt(state, link, link.userId ?? \"\", () => undefined);\n },\n { createLink: createLocalLink, controlOf },\n );\n return {\n add(service: string, runtime: HostRuntime): void {\n if (state.runtimes.has(service)) {\n throw new TypeError(\n `serve: this connection serves ${service} in room \"${room}\" already; close that Host first`,\n );\n }\n state.runtimes.set(service, runtime);\n for (const at of state.peers) {\n attachAll(state, at);\n }\n },\n remove(service: string, runtime: HostRuntime): void {\n if (state.runtimes.get(service) !== runtime) {\n return;\n }\n state.runtimes.delete(service);\n for (const at of state.peers) {\n at.attached.get(service)?.detach();\n at.attached.delete(service);\n }\n runtime.close();\n if (state.runtimes.size > 0) {\n return;\n }\n rooms.delete(room);\n stopRequests();\n unregister();\n for (const link of [...state.links]) {\n link.close();\n }\n },\n };\n}\n\n/** The serving of `room` on `connection`, opened on first use. */\nexport function serving(connection: QuickdrawConnection, room: string): RoomServing {\n let rooms = all.get(connection);\n if (rooms === undefined) {\n rooms = new Map();\n all.set(connection, rooms);\n }\n let found = rooms.get(room);\n if (found === undefined) {\n found = open(connection, room, rooms);\n rooms.set(room, found);\n }\n return found;\n}\n","// A host's way out to one peer (host mode, RFC 0003 section 12.5). `fast`\n// goes as the link takes it, never resent, as on any link. `rel` keeps its\n// order: a message the link drops (over the relay, once the relay's budget\n// is spent) waits, with the ones after it, and is sent again a little\n// later, so a seed's last chunk never goes missing. A peer whose waiting\n// messages pass `MAX_QUEUED_BYTES` cannot keep up: its link closes, and it\n// links again and subscribes anew.\n//\n// React-free.\n\nimport type { HostMessage, PeerLabel } from \"../../protocol/envelope\";\nimport { encodeMessage, messageBytes } from \"../peer/codec\";\nimport { throwLater } from \"../peer/core\";\n\n/** Sends a host mode message to one peer; false when it was dropped. */\nexport type HostSend = (label: PeerLabel, message: HostMessage) => boolean;\n\n/** One peer's way out. */\nexport interface Outbox {\n send(label: PeerLabel, message: HostMessage): void;\n /** Drops what waits: the peer is gone. */\n clear(): void;\n}\n\n/** The first wait before a dropped `rel` message is sent again; it doubles up to `MAX_RETRY_MS`. */\nconst RETRY_MS = 50;\nconst MAX_RETRY_MS = 1000;\n\n/** The `rel` bytes that may wait for one peer before its link closes. */\nexport const MAX_QUEUED_BYTES = 8 * 1024 * 1024;\n\n/** Creates a peer's way out over `send`; `close` ends the peer's link. */\nexport function createOutbox(send: HostSend, close: () => void): Outbox {\n const queue: { readonly message: HostMessage; readonly bytes: number }[] = [];\n let queued = 0;\n let timer: ReturnType<typeof setTimeout> | undefined;\n let wait = RETRY_MS;\n const clear = (): void => {\n clearTimeout(timer);\n timer = undefined;\n queue.length = 0;\n queued = 0;\n };\n /**\n * Sends one waiting message: false when the link dropped it again. One\n * that throws (the message limit fell since it waited) is dropped and\n * reported, never left to stop the rest.\n */\n const sent = (message: HostMessage): boolean => {\n try {\n return send(\"rel\", message);\n } catch (error) {\n throwLater(error);\n return true;\n }\n };\n const flush = (): void => {\n timer = undefined;\n for (let next = queue[0]; next !== undefined; next = queue[0]) {\n if (!sent(next.message)) {\n timer = setTimeout(flush, wait);\n wait = Math.min(MAX_RETRY_MS, wait * 2);\n return;\n }\n queue.shift();\n queued -= next.bytes;\n }\n wait = RETRY_MS;\n };\n return {\n send(label: PeerLabel, message: HostMessage): void {\n if (label === \"fast\") {\n send(\"fast\", message);\n return;\n }\n if (queue.length === 0 && send(\"rel\", message)) {\n return;\n }\n const bytes = messageBytes(encodeMessage(message));\n queue.push({ message, bytes });\n queued += bytes;\n if (queued > MAX_QUEUED_BYTES) {\n clear();\n close();\n return;\n }\n timer ??= setTimeout(flush, wait);\n },\n clear,\n };\n}\n","// What a room's host does with its peers' messages (host mode, RFC 0003\n// section 12.5), apart from how they reach it: `host.ts` attaches a peer per\n// link (and the host's own client, in process), and this checks each\n// message the way the server checks `qd:ch` and `qd:stream:sub`, and fans\n// pushed items out.\n//\n// - `[3, service, channel, payload]`: a host-served channel of the contract,\n// within the peer's bucket for it (`ratePerSecond`, `burst`), its payload\n// valid for the channel's schema (synchronously: a schema that validates\n// asynchronously drops every message, reported once), for this host's\n// room by the channel's `requires: { room }`. Then the handler runs with\n// `{ peer, room }`. A peer whose drops for one channel within\n// `ABUSE_WINDOW_MS` pass `ABUSE_MULTIPLIER` times its rate loses its link,\n// as a socket is disconnected.\n// - `[4, service, stream, scope]`: a host-served stream whose room for the\n// scope is this host's room; the answer is the seed (`[6]`, in chunks\n// that each fit the message limit): the implementation's `seed(scope)`,\n// or the latest `seed` items pushed. A peer holds at most `MAX_HOST_FEEDS`\n// feeds, and subscribes at most `HOST_SUBSCRIBE_RATE` times a second after\n// a burst of as many (`RATE_LIMITED`; a flood of them, as for a channel,\n// ends its link), its subscribes to a service none of the room's runtimes\n// serves included (`unserved`, which `serving.ts` calls). A seed whose\n// items do not each fit a message alone is refused (`INTERNAL`), never\n// sent in part. Anything else is refused (`[8]`).\n// - `[5, service, stream, scope]` leaves the feed.\n//\n// `push(stream, scope, item)` checks the item against the stream's schema\n// and the message limit (as a seed chunk of its own, so a kept item can\n// always seed), keeps it for the seed, and sends it to the feed's peers\n// (`[7]`) on `fast` for a volatile stream, else `rel`. What goes on `rel`\n// keeps its order and is sent again when the link drops it (`outbox.ts`).\n//\n// React-free.\n\nimport type { AnyContract } from \"../../contract/defineContract\";\nimport { CHANNEL_DEFAULT_RATE, isHostServed, type StreamDef } from \"../../contract/realtime\";\nimport { validateNow } from \"../../contract/validateNow\";\nimport type { HostMessage, HostSubscribe } from \"../../protocol/envelope\";\nimport type { ErrorCode } from \"../../protocol/errors\";\nimport { encodeMessage, messageBytes } from \"../peer/codec\";\nimport { throwLater } from \"../peer/core\";\nimport type { PeerLink } from \"../peer/types\";\nimport { createOutbox, type HostSend, type Outbox } from \"./outbox\";\nimport { channelRoomOf, streamRoomOf } from \"./rooms\";\n\nexport type { HostSend } from \"./outbox\";\n\n/** A peer of a host: its socket and its user, as the server named them, and its link. */\nexport interface HostPeer {\n readonly socketId: string;\n readonly userId: string;\n /**\n * The host's end of the peer's link: its `clock` (the peer's round trip,\n * `rtt`) and `setLag`, which lags the peer's messages both ways. The\n * host's own client has one too, in process.\n */\n readonly link: PeerLink;\n}\n\n/** What a host-served channel's handler receives beside the payload. */\nexport interface HostChannelContext {\n /** Who sent it: the user the server named for the peer's socket, and the peer's link. */\n readonly peer: HostPeer;\n /** The host's room. */\n readonly room: string;\n}\n\n/** The handlers and options of a host, by member, whatever their declared types. */\nexport interface AnyHostImplementation {\n readonly channels?: Readonly<Record<string, (payload: unknown, ctx: HostChannelContext) => void>>;\n readonly streams?: Readonly<\n Record<string, { readonly seed?: (scope: string) => readonly unknown[] } | undefined>\n >;\n}\n\n/** One peer, attached: what it sends, and its end. */\nexport interface AttachedPeer {\n receive(message: HostMessage): void;\n detach(): void;\n}\n\n/** A peer as one runtime holds it: it also refuses for the room's other services. */\nexport interface RuntimePeer extends AttachedPeer {\n /**\n * Refuses a subscribe to a service no runtime of the room serves\n * (`NOT_FOUND`), within the peer's subscribe bucket and through its way out.\n */\n unserved(message: HostSubscribe): void;\n}\n\n/** One room's host, whatever carries its peers' messages. */\nexport interface HostRuntime {\n /** Attaches a peer; `close` ends its link (a flood). */\n attach(peer: HostPeer, send: HostSend, close: () => void): RuntimePeer;\n readonly peers: readonly HostPeer[];\n push(stream: string, scope: string, item: unknown): void;\n close(): void;\n}\n\n/**\n * A host-served channel's drops within this long are counted toward a\n * flood: the server's `CHANNEL_ABUSE_WINDOW_MS`.\n */\nexport const ABUSE_WINDOW_MS = 10_000;\n\n/**\n * A peer whose drops within the window pass this many times the channel's\n * rate loses its link: the server's `CHANNEL_ABUSE_MULTIPLIER`.\n */\nexport const ABUSE_MULTIPLIER = 100;\n\n/** The feeds one peer may hold of a host, and the subscribes it may send it at once. */\nexport const MAX_HOST_FEEDS = 500;\n\n/** The subscribes a second a peer may send a host after a burst of `MAX_HOST_FEEDS`. */\nexport const HOST_SUBSCRIBE_RATE = 50;\n\n/** The scopes whose latest items a stream keeps. */\nconst MAX_KEPT_SCOPES = 10_000;\n\ninterface Bucket {\n tokens: number;\n refilledAt: number;\n windowStart: number;\n dropped: number;\n}\n\ninterface PeerState {\n readonly peer: HostPeer;\n readonly out: Outbox;\n readonly close: () => void;\n readonly buckets: Map<string, Bucket>;\n /** Its subscribes' bucket. */\n readonly subscribes: Bucket;\n readonly feeds: Set<string>;\n}\n\ninterface State {\n readonly contract: AnyContract;\n readonly implementation: AnyHostImplementation;\n readonly room: string;\n readonly maxBytes: () => number;\n readonly peers: Set<PeerState>;\n /** The peers of each feed, by `feedKey`. */\n readonly feeds: Map<string, Set<PeerState>>;\n /** The latest items of each stream's scopes, for a seed. */\n readonly kept: Map<string, Map<string, unknown[]>>;\n readonly warned: Set<string>;\n closed: boolean;\n}\n\nconst feedKey = (stream: string, scope: string): string => `${stream}\\u0000${scope}`;\n\nfunction own<T>(record: Readonly<Record<string, T>>, name: string): T | undefined {\n return Object.hasOwn(record, name) ? record[name] : undefined;\n}\n\n/** Takes a token, refilling first; on a drop, true once the peer floods. */\nfunction overRate(bucket: Bucket, rate: number, burst: number, now: number): boolean | undefined {\n bucket.tokens = Math.min(burst, bucket.tokens + ((now - bucket.refilledAt) / 1000) * rate);\n bucket.refilledAt = now;\n if (bucket.tokens >= 1) {\n bucket.tokens -= 1;\n return undefined;\n }\n if (now - bucket.windowStart > ABUSE_WINDOW_MS) {\n bucket.windowStart = now;\n bucket.dropped = 0;\n }\n bucket.dropped += 1;\n return bucket.dropped > rate * ABUSE_MULTIPLIER;\n}\n\nfunction onChannel(state: State, from: PeerState, name: string, payload: unknown): void {\n const def = own(state.contract.channels, name);\n const handler = own(state.implementation.channels ?? {}, name);\n if (def === undefined || !isHostServed(def) || handler === undefined) {\n return;\n }\n const rate = def.ratePerSecond ?? CHANNEL_DEFAULT_RATE;\n const burst = def.burst ?? Math.max(1, rate * 2);\n const now = Date.now();\n let bucket = from.buckets.get(name);\n if (bucket === undefined) {\n bucket = { tokens: burst, refilledAt: now, windowStart: now, dropped: 0 };\n from.buckets.set(name, bucket);\n }\n const dropped = overRate(bucket, rate, burst, now);\n if (dropped !== undefined) {\n if (dropped) {\n from.close();\n }\n return;\n }\n const parsed = validateNow(def.payload, payload);\n if (parsed === \"async\") {\n if (!state.warned.has(name)) {\n state.warned.add(name);\n throwLater(\n new TypeError(\n `Host of ${state.room}: channel ${state.contract.name}.${name} has a payload schema that validates asynchronously; its messages are dropped, since a host checks them synchronously`,\n ),\n );\n }\n return;\n }\n if (\n parsed.issues !== undefined ||\n channelRoomOf(def, parsed.value, [state.room]) !== state.room\n ) {\n return;\n }\n try {\n handler(parsed.value, { peer: from.peer, room: state.room });\n } catch (error) {\n throwLater(error);\n }\n}\n\n/** The seed of a feed: the implementation's, checked against the item schema, or the latest items pushed. */\nfunction seedOf(state: State, name: string, def: StreamDef, scope: string): readonly unknown[] {\n const seed = own(state.implementation.streams ?? {}, name)?.seed;\n if (seed === undefined) {\n return state.kept.get(name)?.get(scope) ?? [];\n }\n return seed(scope).map((item) => {\n const checked = validateNow(def.item, item);\n if (checked === \"async\" || checked.issues !== undefined) {\n throw new TypeError(\n `Host of ${state.room}: the seed of ${state.contract.name}.${name} holds an item its schema refuses`,\n );\n }\n return checked.value;\n });\n}\n\n/** The bytes `item` takes in a seed chunk of its own: what every item must fit. */\nfunction aloneBytes(state: State, name: string, scope: string, item: unknown): number {\n return messageBytes(encodeMessage([6, state.contract.name, name, scope, [item], false]));\n}\n\n/** A seed's chunks, each within the message limit; `undefined` when an item fits none alone. */\nfunction chunksOf(\n state: State,\n name: string,\n scope: string,\n items: readonly unknown[],\n): unknown[][] | undefined {\n const max = state.maxBytes();\n const overhead = messageBytes(encodeMessage([6, state.contract.name, name, scope, [], false]));\n const chunks: unknown[][] = [[]];\n let size = overhead;\n for (const item of items) {\n // The first item of a chunk takes no comma, as `push` measures it alone.\n const bytes = messageBytes(JSON.stringify(item) ?? \"null\");\n if (overhead + bytes > max) {\n return undefined;\n }\n const chunk = chunks.at(-1) ?? [];\n if (chunk.length === 0) {\n chunk.push(item);\n size += bytes;\n } else if (size + 1 + bytes > max) {\n chunks.push([item]);\n size = overhead + bytes;\n } else {\n chunk.push(item);\n size += 1 + bytes;\n }\n }\n return chunks;\n}\n\ntype Refuse = (code: ErrorCode, message: string) => void;\n\n/** Refuses the peer's subscribe to `service`'s `name` for `scope`, through its way out. */\nfunction refusal(from: PeerState, service: string, name: string, scope: string): Refuse {\n return (code, message) => {\n from.out.send(\"rel\", [8, service, name, scope, { code, message }]);\n };\n}\n\n/**\n * Takes one of the peer's subscribe tokens: true when it had one. Past them\n * the subscribe is refused (`RATE_LIMITED`), and a flood of them ends the\n * peer's link.\n */\nfunction admits(from: PeerState, refuse: Refuse): boolean {\n const flood = overRate(from.subscribes, HOST_SUBSCRIBE_RATE, MAX_HOST_FEEDS, Date.now());\n if (flood === undefined) {\n return true;\n }\n if (flood) {\n from.close();\n } else {\n refuse(\"RATE_LIMITED\", `A peer subscribes at most ${HOST_SUBSCRIBE_RATE} times a second`);\n }\n return false;\n}\n\n/** A subscribe to a service no runtime of the room serves: refused, within the peer's bucket. */\nfunction onUnserved(state: State, from: PeerState, message: HostSubscribe): void {\n const [, service, name, scope] = message;\n const refuse = refusal(from, service, name, scope);\n if (!state.closed && state.peers.has(from) && admits(from, refuse)) {\n refuse(\"NOT_FOUND\", `The host of ${state.room} serves no service \"${service}\"`);\n }\n}\n\nfunction onSubscribe(state: State, from: PeerState, name: string, scope: string): void {\n const service = state.contract.name;\n const refuse = refusal(from, service, name, scope);\n if (!admits(from, refuse)) {\n return;\n }\n const def = own(state.contract.streams, name);\n if (def === undefined || !isHostServed(def)) {\n refuse(\"NOT_FOUND\", `${service} has no host-served stream \"${name}\"`);\n return;\n }\n if (streamRoomOf(def, scope) !== state.room) {\n refuse(\"FORBIDDEN\", `The feed of scope \"${scope}\" is served by the host of another room`);\n return;\n }\n const key = feedKey(name, scope);\n if (!from.feeds.has(key) && from.feeds.size >= MAX_HOST_FEEDS) {\n refuse(\"CONFLICT\", `A peer may hold at most ${MAX_HOST_FEEDS} feeds of a host`);\n return;\n }\n let chunks: unknown[][] | undefined;\n try {\n chunks = chunksOf(state, name, scope, seedOf(state, name, def, scope));\n } catch (error) {\n refuse(\"INTERNAL\", \"The host could not make the feed's seed\");\n throwLater(error);\n return;\n }\n if (chunks === undefined) {\n refuse(\"INTERNAL\", \"The feed's seed holds an item over the message limit\");\n throwLater(\n new RangeError(\n `Host of ${state.room}: the seed of ${service}.${name} holds an item over ${state.maxBytes()} bytes with its message; keep each item smaller`,\n ),\n );\n return;\n }\n from.feeds.add(key);\n const subscribers = state.feeds.get(key) ?? new Set<PeerState>();\n state.feeds.set(key, subscribers);\n subscribers.add(from);\n chunks.forEach((chunk, index) => {\n from.out.send(\"rel\", [6, service, name, scope, chunk, index === chunks.length - 1]);\n });\n}\n\nfunction leave(state: State, from: PeerState, key: string): void {\n from.feeds.delete(key);\n const subscribers = state.feeds.get(key);\n subscribers?.delete(from);\n if (subscribers?.size === 0) {\n state.feeds.delete(key);\n }\n}\n\nfunction receive(state: State, from: PeerState, message: HostMessage): void {\n if (state.closed || !state.peers.has(from) || message[1] !== state.contract.name) {\n return;\n }\n if (message[0] === 3) {\n onChannel(state, from, message[2], message[3]);\n } else if (message[0] === 4) {\n onSubscribe(state, from, message[2], message[3]);\n } else if (message[0] === 5) {\n leave(state, from, feedKey(message[2], message[3]));\n }\n}\n\n/** Keeps the latest items of a scope for a seed: the contract's `seed` of them. */\nfunction keep(state: State, name: string, keepCount: number, scope: string, item: unknown): void {\n const scopes = state.kept.get(name) ?? new Map<string, unknown[]>();\n state.kept.set(name, scopes);\n const items = scopes.get(scope) ?? [];\n scopes.delete(scope);\n scopes.set(scope, [...items, item].slice(-keepCount));\n if (scopes.size > MAX_KEPT_SCOPES) {\n const [oldest] = scopes.keys();\n if (oldest !== undefined) {\n scopes.delete(oldest);\n }\n }\n}\n\nfunction push(state: State, name: string, scope: string, item: unknown): void {\n const service = state.contract.name;\n const def = own(state.contract.streams, name);\n if (def === undefined || !isHostServed(def)) {\n throw new TypeError(`host.stream: ${service} has no host-served stream \"${name}\"`);\n }\n if (typeof scope !== \"string\" || scope === \"\") {\n throw new TypeError(`host.stream(\"${name}\").push: pass (scope, item), the scope a string`);\n }\n const checked = validateNow(def.item, item);\n if (checked === \"async\" || checked.issues !== undefined) {\n throw new TypeError(\n `host.stream(\"${name}\").push: the item does not match the stream's schema${checked === \"async\" ? \" (it validates asynchronously)\" : \"\"}`,\n );\n }\n const message: HostMessage = [7, service, name, scope, checked.value];\n // Within a seed chunk of its own too, so that a kept item always seeds.\n const bytes = aloneBytes(state, name, scope, checked.value);\n if (bytes > state.maxBytes()) {\n throw new RangeError(\n `host.stream(\"${name}\").push: an item holds at most ${state.maxBytes()} bytes as JSON with its message; this one has ${bytes}`,\n );\n }\n const keepCount =\n own(state.implementation.streams ?? {}, name)?.seed === undefined ? (def.seed ?? 0) : 0;\n if (keepCount > 0) {\n keep(state, name, keepCount, scope, checked.value);\n }\n const label = def.volatile === true ? \"fast\" : \"rel\";\n for (const to of state.feeds.get(feedKey(name, scope)) ?? []) {\n to.out.send(label, message);\n }\n}\n\n/** Creates the host of `room` for `contract`'s host-served members; `maxBytes` is the message limit. */\nexport function createHostRuntime(\n contract: AnyContract,\n implementation: AnyHostImplementation,\n room: string,\n maxBytes: () => number,\n): HostRuntime {\n const state: State = {\n contract,\n implementation,\n room,\n maxBytes,\n peers: new Set(),\n feeds: new Map(),\n kept: new Map(),\n warned: new Set(),\n closed: false,\n };\n return Object.freeze({\n attach(peer: HostPeer, send: HostSend, close: () => void): RuntimePeer {\n const now = Date.now();\n const from: PeerState = {\n peer,\n out: createOutbox(send, close),\n close,\n buckets: new Map(),\n subscribes: { tokens: MAX_HOST_FEEDS, refilledAt: now, windowStart: now, dropped: 0 },\n feeds: new Set(),\n };\n state.peers.add(from);\n return {\n receive: (message: HostMessage) => {\n receive(state, from, message);\n },\n unserved: (message: HostSubscribe) => {\n onUnserved(state, from, message);\n },\n detach: () => {\n for (const key of [...from.feeds]) {\n leave(state, from, key);\n }\n from.out.clear();\n state.peers.delete(from);\n },\n };\n },\n get peers(): readonly HostPeer[] {\n return [...state.peers].map(({ peer }) => peer);\n },\n push: (stream: string, scope: string, item: unknown) => {\n push(state, stream, scope, item);\n },\n close(): void {\n state.closed = true;\n for (const from of state.peers) {\n from.out.clear();\n }\n state.peers.clear();\n state.feeds.clear();\n },\n });\n}\n","// `defineHost(contract, { channels, streams })` (host mode, RFC 0003 section\n// 12.5): how a client serves a contract's host-served channels and streams\n// as the host of an app room. `serve(connection, { room })` serves them for\n// one room whose host a method made this connection's socket\n// (`ctx.rooms.setHost(room)`): it answers the link requests of the room's\n// peers (`onPeerRequest`: peers offer, the host answers), runs each\n// channel's handler on messages checked as the server checks `qd:ch`\n// (`runtime.ts`), answers a subscribe with the feed's seed, and sends what\n// `host.stream(name).push(scope, item)` pushes to the feed's subscribers.\n// This client's own channels and feeds of the room reach it over an\n// in-process link (`links.ts`, `local.ts`). Each peer comes with the host's\n// end of its link (`HostPeer.link`), whose `setLag` lags that peer, or the\n// host's own play. One connection may serve several contracts in one room, one\n// `serve` each: they share the room's links, and each message goes to the\n// contract of its service; a subscribe to a service none of them is\n// refused (`NOT_FOUND`).\n//\n// The server never sees what a host and its peers send each other: a host\n// trusts its peers' messages no more than the server would, and a service\n// trusts no result a host reports without checking it.\n//\n// React-free.\n\nimport type { AnyContract } from \"../../contract/defineContract\";\nimport type {\n ChannelPayloadOf,\n HostChannelName,\n HostStreamName,\n StreamItemOf,\n} from \"../../contract/infer\";\nimport { isHostServed } from \"../../contract/realtime\";\nimport { isRoomName } from \"../../protocol/peer\";\nimport { MAX_PEER_MESSAGE_BYTES } from \"../../protocol/version\";\nimport type { QuickdrawConnection } from \"../connection\";\nimport { serving, type RoomServing } from \"./serving\";\nimport {\n createHostRuntime,\n type AnyHostImplementation,\n type HostChannelContext,\n type HostPeer,\n} from \"./runtime\";\n\nexport type { HostChannelContext, HostPeer } from \"./runtime\";\n\n/** A host-served channel's handler: it runs once per message, with the payload its schema parsed. */\nexport type HostChannelHandler<Payload> = (payload: Payload, ctx: HostChannelContext) => void;\n\n/** One host-served stream's options. */\nexport interface HostStreamOptions<Item> {\n /**\n * Computes a subscriber's seed when it subscribes, at once: the current\n * state, where the items that follow are deltas. Each item is checked\n * against the stream's schema. Without it the seed is the latest items\n * pushed to the scope, as many as the contract's `seed` (default none).\n */\n readonly seed?: (scope: string) => readonly Item[];\n}\n\n/**\n * `defineHost`'s second argument: one handler per host-served channel of\n * the contract, and options per host-served stream.\n */\nexport type HostImplementation<C extends AnyContract> = {\n readonly streams?: {\n readonly [K in HostStreamName<C>]?: HostStreamOptions<StreamItemOf<C, K>>;\n };\n} & ([HostChannelName<C>] extends [never]\n ? { readonly channels?: Readonly<Record<string, never>> }\n : {\n readonly channels: {\n readonly [K in HostChannelName<C>]: HostChannelHandler<ChannelPayloadOf<C, K>>;\n };\n });\n\n/** A host-served stream, as its host pushes to it. */\nexport interface HostStream<Item> {\n /**\n * Sends `item` to the subscribers of the feed of `scope`, after checking\n * it against the stream's schema (a `TypeError` when it fails), and keeps\n * it for the seed. A `RangeError` for an item over the message limit\n * (`limits.peers.maxMessageBytes` with its message).\n */\n push(scope: string, item: Item): void;\n}\n\n/** A room served by its host's client. */\nexport interface Host<C extends AnyContract> {\n readonly room: string;\n /**\n * The peers linked to the host now, each with the host's end of its link\n * (this client too, in process, while its own channels or feeds use the\n * room).\n */\n readonly peers: readonly HostPeer[];\n /** A host-served stream of the contract. */\n stream<K extends HostStreamName<C>>(name: K): HostStream<StreamItemOf<C, K>>;\n /** Stops serving the room: closes every peer's link, and answers no request of it. */\n close(): void;\n}\n\n/** What `defineHost` returns: the contract, and how to serve one of its rooms. */\nexport interface HostDefinition<C extends AnyContract> {\n readonly contract: C;\n /**\n * Serves app room `room` on `connection`, whose socket a method made the\n * room's host (`ctx.rooms.setHost`). Call it once per room, and `close()`\n * when the socket is no longer the host (`onHostChange`, a reconnect).\n */\n serve(connection: QuickdrawConnection, options: { readonly room: string }): Host<C>;\n}\n\nfunction checkImplementation(contract: AnyContract, implementation: AnyHostImplementation): void {\n const owner = `defineHost(${contract.name})`;\n const channels = implementation.channels ?? {};\n for (const [name, def] of Object.entries(contract.channels)) {\n const handler = Object.hasOwn(channels, name) ? channels[name] : undefined;\n if (isHostServed(def) && typeof handler !== \"function\") {\n throw new TypeError(\n `${owner}: channels.${name} must be a handler: the room's host serves it`,\n );\n }\n }\n for (const name of Object.keys(channels)) {\n const def = Object.hasOwn(contract.channels, name) ? contract.channels[name] : undefined;\n if (def === undefined || !isHostServed(def)) {\n throw new TypeError(`${owner}: \"${name}\" is not a channel the room's host serves`);\n }\n }\n for (const [name, options] of Object.entries(implementation.streams ?? {})) {\n const def = Object.hasOwn(contract.streams, name) ? contract.streams[name] : undefined;\n if (def === undefined || !isHostServed(def)) {\n throw new TypeError(`${owner}: \"${name}\" is not a stream the room's host serves`);\n }\n if (options?.seed !== undefined && typeof options.seed !== \"function\") {\n throw new TypeError(`${owner}: streams.${name}.seed must be a function of the scope`);\n }\n }\n}\n\nfunction serve<C extends AnyContract>(\n contract: C,\n implementation: AnyHostImplementation,\n connection: QuickdrawConnection,\n room: string,\n): Host<C> {\n if (!isRoomName(room)) {\n throw new TypeError(\"serve: room must name an app room, of 1 to 256 characters\");\n }\n const service = contract.name;\n const runtime = createHostRuntime(\n contract,\n implementation,\n room,\n () => connection.getState().hello?.limits.peers?.maxMessageBytes ?? MAX_PEER_MESSAGE_BYTES,\n );\n const served: RoomServing = serving(connection, room);\n served.add(service, runtime);\n let closed = false;\n return Object.freeze({\n room,\n get peers(): readonly HostPeer[] {\n return runtime.peers;\n },\n stream: (name: string) =>\n Object.freeze({\n push: (scope: string, item: unknown) => {\n runtime.push(name, scope, item);\n },\n }),\n close(): void {\n if (!closed) {\n closed = true;\n served.remove(service, runtime);\n }\n },\n }) as Host<C>;\n}\n\n/**\n * Declares how a client serves a contract's host-served channels and\n * streams (`serve: \"host\"`) as the host of an app room: one handler per\n * channel, and options per stream. Throws a `TypeError` for a handler a\n * host-served channel lacks, or for a member the room's host does not\n * serve.\n *\n * @example\n * const worldHost = defineHost(game, {\n * channels: { move: (move, { peer }) => world.input(peer.userId, move) },\n * streams: { world: { seed: () => [world.snapshot()] } },\n * });\n * const host = worldHost.serve(connection, { room: `world:${worldId}` });\n * host.stream(\"world\").push(worldId, world.snapshot());\n */\nexport function defineHost<const C extends AnyContract>(\n contract: C,\n implementation: HostImplementation<C>,\n): HostDefinition<C> {\n const any = implementation as AnyHostImplementation;\n checkImplementation(contract, any);\n return Object.freeze({\n contract,\n serve: (connection: QuickdrawConnection, options: { readonly room: string }) =>\n serve(contract, any, connection, options.room),\n });\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA+BA,IAAM,iBAAiB;AAEvB,IAAM,kBAAkB;AAGxB,IAAI,cAA6B;AAGjC,SAAS,WAAoB;AAC3B,SAAO,OAAO,WAAW;AAC3B;AAGA,SAAS,cAAyC;AAChD,MAAI;AACF,WAAO,WAAW,aAAa,QAAQ,cAAc;AAAA,EACvD,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAGA,SAAS,WAAiB;AACxB,MAAI,OAAO,OAAO,kBAAkB,cAAc,OAAO,UAAU,YAAY;AAC7E,WAAO,cAAc,IAAI,MAAM,oBAAoB,CAAC;AAAA,EACtD;AACF;AAGA,SAAS,SAAS,QAAoC;AACpD,MAAI,WAAW,QAAW;AACxB,WAAO;AAAA,EACT;AACA,MAAI;AAEF,WAAO,QAAQ,IAAI,uBAAuB;AAAA,EAC5C,QAAQ;AAEN,WAAO;AAAA,EACT;AACF;AAMO,SAAS,eAA8B;AAC5C,MAAI,CAAC,SAAS,EAAG,QAAO;AACxB,QAAM,SAAS,YAAY;AAC3B,SAAO,WAAW,SAAY,cAAc;AAC9C;AAMO,SAAS,aAAa,OAAqB;AAChD,MAAI,CAAC,SAAS,EAAG;AACjB,gBAAc;AACd,MAAI;AACF,eAAW,aAAa,QAAQ,gBAAgB,KAAK;AAAA,EACvD,QAAQ;AAAA,EAER;AACA,WAAS;AACX;AAMO,SAAS,iBAAuB;AACrC,MAAI,CAAC,SAAS,EAAG;AACjB,gBAAc;AACd,MAAI;AACF,eAAW,aAAa,WAAW,cAAc;AAAA,EACnD,QAAQ;AAAA,EAER;AACA,WAAS;AACX;AAyBA,IAAM,cAAc;AAGpB,SAAS,UAAU,OAAe,QAAkC;AAClE,QAAM,OAAO,SAAS,OAAO,MAAM,EAAE,QAAQ,QAAQ,EAAE;AACvD,QAAM,OAAO,OAAO,YAAY;AAChC,MAAI,OAAO,SAAS,YAAY,CAAC,KAAK,WAAW,GAAG,GAAG;AACrD,UAAM,IAAI,UAAU,GAAG,KAAK,8DAA8D;AAAA,EAC5F;AACA,SAAO,GAAG,IAAI,GAAG,KAAK,QAAQ,QAAQ,EAAE,CAAC;AAC3C;AAGA,SAAS,aAAiC;AACxC,MAAI;AACF,UAAM,SAAmB,WACtB,UAAU;AACb,WAAO,OAAO,WAAW,YAAY,WAAW,SAAS,SAAS;AAAA,EACpE,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAaO,SAAS,UAAU,UAAkB,UAA4B,CAAC,GAAW;AAClF,MAAI,OAAO,aAAa,YAAY,CAAC,YAAY,KAAK,QAAQ,GAAG;AAC/D,UAAM,IAAI;AAAA,MACR,0EAA0E,OAAO,QAAQ,CAAC;AAAA,IAC5F;AAAA,EACF;AACA,QAAM,MAAM,IAAI,IAAI,GAAG,UAAU,aAAa,OAAO,CAAC,IAAI,QAAQ,QAAQ;AAC1E,QAAM,WAAW,QAAQ,YAAY,WAAW;AAChD,MAAI,aAAa,QAAW;AAC1B,QAAI,aAAa,IAAI,YAAY,QAAQ;AAAA,EAC3C;AACA,SAAO,IAAI,SAAS;AACtB;AAGA,eAAe,UAAU,UAA6C;AACpE,MAAI;AACJ,MAAI;AACF,WAAO,MAAM,SAAS,KAAK;AAAA,EAC7B,QAAQ;AACN,WAAO;AAAA,EACT;AACA,QAAM,OAAO,SAAS,IAAI,KAAK,YAAY,KAAK,KAAK,IAAI,KAAK,QAAQ;AACtE,QAAM,UACJ,SAAS,IAAI,KAAK,OAAO,KAAK,YAAY,WACtC,KAAK,UACL,2BAA2B,OAAO,SAAS,MAAM,CAAC;AACxD,MAAI,SAAS,QAAW;AACtB,WAAO,IAAI,eAAe,MAAM,OAAO;AAAA,EACzC;AACA,SAAO,IAAI,eAAe,SAAS,WAAW,MAAM,iBAAiB,YAAY,OAAO;AAC1F;AAGA,eAAe,UAAU,OAAe,OAAe,QAAyC;AAC9F,QAAM,MAAM,GAAG,UAAU,OAAO,MAAM,CAAC,GAAG,KAAK;AAC/C,QAAM,QAAQ,aAAa;AAC3B,MAAI;AACJ,MAAI;AACF,eAAW,MAAM,MAAM,KAAK;AAAA,MAC1B,QAAQ;AAAA,MACR,aAAa;AAAA,MACb,SAAS;AAAA,QACP,gBAAgB;AAAA,QAChB,GAAI,UAAU,OAAO,CAAC,IAAI,EAAE,eAAe,UAAU,KAAK,GAAG;AAAA,MAC/D;AAAA,MACA,MAAM;AAAA,IACR,CAAC;AAAA,EACH,SAAS,OAAO;AACd,UAAM,IAAI,eAAe,YAAY,GAAG,KAAK,KAAK,GAAG,yBAAyB;AAAA,MAC5E,OAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AAAA,IAC9D,CAAC;AAAA,EACH;AACA,MAAI,CAAC,SAAS,IAAI;AAChB,UAAM,MAAM,UAAU,QAAQ;AAAA,EAChC;AACF;AAgBA,eAAsB,QAAQ,UAA4B,CAAC,GAAkB;AAC3E,MAAI;AACF,UAAM,UAAU,WAAW,WAAW,OAAO;AAAA,EAC/C,UAAE;AACA,mBAAe;AAAA,EACjB;AACF;AAYA,IAAM,iBAAuC,oBAAI,IAAI,CAAC,SAAS,QAAQ,OAAO,CAAC;AAG/E,SAAS,eAAe,OAA8C;AACpE,MAAI,CAAC,SAAS,KAAK,KAAK,OAAO,MAAM,OAAO,YAAY,CAAC,YAAY,KAAK,MAAM,EAAE,GAAG;AACnF,WAAO;AAAA,EACT;AACA,QAAM,OAAO,eAAe,IAAI,MAAM,IAAI,IACrC,MAAM,OACP;AACJ,SAAO,SAAS,SACZ,SACA,EAAE,IAAI,MAAM,IAAI,MAAM,OAAO,MAAM,SAAS,WAAW,MAAM,OAAO,MAAM,IAAI,KAAK;AACzF;AAgBA,eAAsB,cACpB,UAA4B,CAAC,GACS;AACtC,QAAM,MAAM,GAAG,UAAU,iBAAiB,OAAO,CAAC;AAClD,MAAI;AACJ,MAAI;AACF,eAAW,MAAM,MAAM,KAAK,EAAE,SAAS,EAAE,QAAQ,mBAAmB,EAAE,CAAC;AAAA,EACzE,SAAS,OAAO;AACd,UAAM,IAAI,eAAe,YAAY,kBAAkB,GAAG,yBAAyB;AAAA,MACjF,OAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AAAA,IAC9D,CAAC;AAAA,EACH;AACA,MAAI,CAAC,SAAS,IAAI;AAChB,UAAM,MAAM,UAAU,QAAQ;AAAA,EAChC;AACA,MAAI;AACJ,MAAI;AACF,WAAO,MAAM,SAAS,KAAK;AAAA,EAC7B,QAAQ;AACN,WAAO;AAAA,EACT;AACA,MAAI,CAAC,SAAS,IAAI,KAAK,CAAC,MAAM,QAAQ,KAAK,SAAS,GAAG;AACrD,UAAM,IAAI,eAAe,YAAY,kBAAkB,GAAG,gCAAgC;AAAA,EAC5F;AACA,SAAO,KAAK,UAAU,QAAQ,CAAC,UAAmB;AAChD,UAAM,OAAO,eAAe,KAAK;AACjC,WAAO,SAAS,SAAY,CAAC,IAAI,CAAC,IAAI;AAAA,EACxC,CAAC;AACH;AAUA,eAAsB,kBAAkB,UAA4B,CAAC,GAAkB;AACrF,MAAI;AACF,UAAM,UAAU,qBAAqB,eAAe,OAAO;AAAA,EAC7D,UAAE;AACA,mBAAe;AAAA,EACjB;AACF;;;AC1TA,SAAS,SAAS,MAAc,OAAuB;AACrD,MAAI,UAAU,GAAG;AACf,WAAO,KAAQ,OAAO;AAAA,EACxB;AACA,SAAO,UAAU,IAAI,MAAQ,OAAO,KAAQ;AAC9C;AAGA,SAAS,OAAO,OAA2B;AACzC,QAAM,MAAM,MAAM;AAAA,IAAK;AAAA,IAAO,CAAC,MAAM,UACnC,SAAS,MAAM,KAAK,EAAE,SAAS,EAAE,EAAE,SAAS,GAAG,GAAG;AAAA,EACpD,EAAE,KAAK,EAAE;AACT,SAAO,GAAG,IAAI,MAAM,GAAG,CAAC,CAAC,IAAI,IAAI,MAAM,GAAG,EAAE,CAAC,IAAI,IAAI,MAAM,IAAI,EAAE,CAAC,IAAI,IAAI,MAAM,IAAI,EAAE,CAAC,IAAI,IAAI,MAAM,EAAE,CAAC;AAC1G;AAcO,SAAS,QAAgB;AAC9B,QAAM,EAAE,OAAO,IAAI;AACnB,MAAI,OAAO,QAAQ,eAAe,YAAY;AAC5C,WAAO,OAAO,WAAW;AAAA,EAC3B;AACA,MAAI,OAAO,QAAQ,oBAAoB,YAAY;AACjD,UAAM,IAAI,UAAU,mDAAmD;AAAA,EACzE;AACA,SAAO,OAAO,OAAO,gBAAgB,IAAI,WAAW,EAAE,CAAC,CAAC;AAC1D;;;ACnCO,SAAS,gBACd,MACA,UACA,QACA,iBAC0C;AAC1C,QAAM,OAAmB,CAAC;AAC1B,QAAM,QAAQ,CAAC,UACb,eAAe;AAAA,IACb;AAAA,IACA,MAAM;AAAA,IACN;AAAA,IACA,UAAU;AAAA,IACV,OAAO;AAAA,MACL,MAAM,CAAC,OAAO,SAAS;AACrB,cAAM,GAAG,QAAQ,MAAM,KAAK;AAC5B,eAAO;AAAA,MACT;AAAA,MACA,QAAQ,CAAC,SAAS,aAAa;AAC7B,YAAI,UAAU;AACZ,gBAAM,GAAG,IAAI,KAAK;AAAA,QACpB;AAAA,MACF;AAAA,IACF;AAAA,EACF,CAAC;AACH,QAAM,OAAO,MAAM,MAAM,KAAK,CAAC,CAAC;AAChC,QAAM,MAAM,MAAM,MAAM,KAAK,CAAC,CAAC;AAC/B,OAAK,KAAK,MAAM,GAAG;AACnB,aAAW,OAAO,MAAM;AACtB,QAAI,SAAS,MAAM;AAAA,EACrB;AACA,MAAI,MAAM,QAAQ;AAClB,OAAK,MAAM,QAAQ;AACnB,SAAO,CAAC,KAAK,MAAM,IAAI,IAAI;AAC7B;;;ACLA,IAAM,MAAM,oBAAI,QAAuD;AAEvE,SAAS,UAAU,OAAc,IAAsB;AACrD,aAAW,CAAC,SAAS,OAAO,KAAK,MAAM,UAAU;AAC/C,QAAI,CAAC,GAAG,SAAS,IAAI,OAAO,GAAG;AAC7B,SAAG,SAAS,IAAI,SAAS,QAAQ,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,KAAK,CAAC;AAAA,IACrE;AAAA,EACF;AACF;AAGA,SAAS,QAAQ,IAAgB,SAA4B;AAC3D,QAAM,WAAW,GAAG,SAAS,IAAI,QAAQ,CAAC,CAAC;AAC3C,MAAI,aAAa,QAAW;AAC1B,aAAS,QAAQ,OAAO;AAAA,EAC1B,WAAW,QAAQ,CAAC,MAAM,GAAG;AAC3B,OAAG,SAAS,OAAO,EAAE,KAAK,EAAE,OAAO,SAAS,OAAO;AAAA,EACrD;AACF;AAGA,SAAS,KAAK,OAAc,MAAgB,MAAgB,OAAiC;AAC3F,QAAM,KAAiB,EAAE,MAAM,MAAM,OAAO,UAAU,oBAAI,IAAI,EAAE;AAChE,QAAM,MAAM,IAAI,EAAE;AAClB,YAAU,OAAO,EAAE;AACnB,SAAO;AAAA,IACL,SAAS,CAAC,YAAY;AACpB,cAAQ,IAAI,OAAO;AAAA,IACrB;AAAA,IACA,QAAQ,MAAM;AACZ,iBAAW,YAAY,GAAG,SAAS,OAAO,GAAG;AAC3C,iBAAS,OAAO;AAAA,MAClB;AACA,SAAG,SAAS,MAAM;AAClB,YAAM,MAAM,OAAO,EAAE;AAAA,IACvB;AAAA,EACF;AACF;AAGA,SAAS,MAAM,OAAc,MAAgB,QAAgB,OAAyB;AACpF,QAAM,UAAU,UAAU,IAAI;AAC9B,MAAI,YAAY,UAAa,KAAK,UAAU,UAAU;AACpD;AAAA,EACF;AACA,QAAM,MAAM,IAAI,IAAI;AACpB,QAAM,OAAO,KAAK,OAAO,EAAE,UAAU,KAAK,MAAM,QAAQ,KAAK,GAAG,QAAQ,MAAM,KAAK;AACnF,QAAM,MAAM,QAAQ,GAAG,CAAC,YAAY;AAClC,SAAK,QAAQ,OAAO;AAAA,EACtB,CAAC;AACD,OAAK,GAAG,SAAS,MAAM;AACrB,QAAI;AACJ,SAAK,OAAO;AACZ,UAAM,MAAM,OAAO,IAAI;AAAA,EACzB,CAAC;AACH;AAEA,SAAS,OAAO,OAAc,SAA4B;AACxD,MAAI;AACJ,MAAI;AACF,WAAO,QAAQ,OAAO;AAAA,EACxB,QAAQ;AAEN;AAAA,EACF;AACA,QAAM,OAAO,MAAM,QAAQ,QAAQ,MAAM;AACvC,SAAK,MAAM;AAAA,EACb,CAAC;AACH;AAEA,SAAS,KACP,YACA,MACA,OACa;AACb,QAAM,QAAe,EAAE,MAAM,UAAU,oBAAI,IAAI,GAAG,OAAO,oBAAI,IAAI,GAAG,OAAO,oBAAI,IAAI,EAAE;AACrF,QAAM,eAAe,cAAc,YAAY,CAAC,YAAY;AAC1D,QAAI,QAAQ,SAAS,MAAM;AACzB,aAAO,OAAO,OAAO;AAAA,IACvB;AAAA,EACF,CAAC;AAED,QAAM,aAAa;AAAA,IACjB;AAAA,IACA;AAAA,IACA,CAAC,SAAS;AACR,YAAM,OAAO,MAAM,KAAK,UAAU,IAAI,MAAM,MAAS;AAAA,IACvD;AAAA,IACA,EAAE,YAAY,iBAAiB,UAAU;AAAA,EAC3C;AACA,SAAO;AAAA,IACL,IAAI,SAAiB,SAA4B;AAC/C,UAAI,MAAM,SAAS,IAAI,OAAO,GAAG;AAC/B,cAAM,IAAI;AAAA,UACR,iCAAiC,OAAO,aAAa,IAAI;AAAA,QAC3D;AAAA,MACF;AACA,YAAM,SAAS,IAAI,SAAS,OAAO;AACnC,iBAAW,MAAM,MAAM,OAAO;AAC5B,kBAAU,OAAO,EAAE;AAAA,MACrB;AAAA,IACF;AAAA,IACA,OAAO,SAAiB,SAA4B;AAClD,UAAI,MAAM,SAAS,IAAI,OAAO,MAAM,SAAS;AAC3C;AAAA,MACF;AACA,YAAM,SAAS,OAAO,OAAO;AAC7B,iBAAW,MAAM,MAAM,OAAO;AAC5B,WAAG,SAAS,IAAI,OAAO,GAAG,OAAO;AACjC,WAAG,SAAS,OAAO,OAAO;AAAA,MAC5B;AACA,cAAQ,MAAM;AACd,UAAI,MAAM,SAAS,OAAO,GAAG;AAC3B;AAAA,MACF;AACA,YAAM,OAAO,IAAI;AACjB,mBAAa;AACb,iBAAW;AACX,iBAAW,QAAQ,CAAC,GAAG,MAAM,KAAK,GAAG;AACnC,aAAK,MAAM;AAAA,MACb;AAAA,IACF;AAAA,EACF;AACF;AAGO,SAAS,QAAQ,YAAiC,MAA2B;AAClF,MAAI,QAAQ,IAAI,IAAI,UAAU;AAC9B,MAAI,UAAU,QAAW;AACvB,YAAQ,oBAAI,IAAI;AAChB,QAAI,IAAI,YAAY,KAAK;AAAA,EAC3B;AACA,MAAI,QAAQ,MAAM,IAAI,IAAI;AAC1B,MAAI,UAAU,QAAW;AACvB,YAAQ,KAAK,YAAY,MAAM,KAAK;AACpC,UAAM,IAAI,MAAM,KAAK;AAAA,EACvB;AACA,SAAO;AACT;;;AClKA,IAAM,WAAW;AACjB,IAAM,eAAe;AAGd,IAAM,mBAAmB,IAAI,OAAO;AAGpC,SAAS,aAAa,MAAgB,OAA2B;AACtE,QAAM,QAAqE,CAAC;AAC5E,MAAI,SAAS;AACb,MAAI;AACJ,MAAI,OAAO;AACX,QAAM,QAAQ,MAAY;AACxB,iBAAa,KAAK;AAClB,YAAQ;AACR,UAAM,SAAS;AACf,aAAS;AAAA,EACX;AAMA,QAAM,OAAO,CAAC,YAAkC;AAC9C,QAAI;AACF,aAAO,KAAK,OAAO,OAAO;AAAA,IAC5B,SAAS,OAAO;AACd,iBAAW,KAAK;AAChB,aAAO;AAAA,IACT;AAAA,EACF;AACA,QAAM,QAAQ,MAAY;AACxB,YAAQ;AACR,aAAS,OAAO,MAAM,CAAC,GAAG,SAAS,QAAW,OAAO,MAAM,CAAC,GAAG;AAC7D,UAAI,CAAC,KAAK,KAAK,OAAO,GAAG;AACvB,gBAAQ,WAAW,OAAO,IAAI;AAC9B,eAAO,KAAK,IAAI,cAAc,OAAO,CAAC;AACtC;AAAA,MACF;AACA,YAAM,MAAM;AACZ,gBAAU,KAAK;AAAA,IACjB;AACA,WAAO;AAAA,EACT;AACA,SAAO;AAAA,IACL,KAAK,OAAkB,SAA4B;AACjD,UAAI,UAAU,QAAQ;AACpB,aAAK,QAAQ,OAAO;AACpB;AAAA,MACF;AACA,UAAI,MAAM,WAAW,KAAK,KAAK,OAAO,OAAO,GAAG;AAC9C;AAAA,MACF;AACA,YAAM,QAAQ,aAAa,cAAc,OAAO,CAAC;AACjD,YAAM,KAAK,EAAE,SAAS,MAAM,CAAC;AAC7B,gBAAU;AACV,UAAI,SAAS,kBAAkB;AAC7B,cAAM;AACN,cAAM;AACN;AAAA,MACF;AACA,gBAAU,WAAW,OAAO,IAAI;AAAA,IAClC;AAAA,IACA;AAAA,EACF;AACF;;;ACaO,IAAM,kBAAkB;AAMxB,IAAM,mBAAmB;AAGzB,IAAM,iBAAiB;AAGvB,IAAM,sBAAsB;AAGnC,IAAM,kBAAkB;AAiCxB,IAAM,UAAU,CAAC,QAAgB,UAA0B,GAAG,MAAM,KAAS,KAAK;AAElF,SAAS,IAAO,QAAqC,MAA6B;AAChF,SAAO,OAAO,OAAO,QAAQ,IAAI,IAAI,OAAO,IAAI,IAAI;AACtD;AAGA,SAAS,SAAS,QAAgB,MAAc,OAAe,KAAkC;AAC/F,SAAO,SAAS,KAAK,IAAI,OAAO,OAAO,UAAW,MAAM,OAAO,cAAc,MAAQ,IAAI;AACzF,SAAO,aAAa;AACpB,MAAI,OAAO,UAAU,GAAG;AACtB,WAAO,UAAU;AACjB,WAAO;AAAA,EACT;AACA,MAAI,MAAM,OAAO,cAAc,iBAAiB;AAC9C,WAAO,cAAc;AACrB,WAAO,UAAU;AAAA,EACnB;AACA,SAAO,WAAW;AAClB,SAAO,OAAO,UAAU,OAAO;AACjC;AAEA,SAAS,UAAU,OAAc,MAAiB,MAAc,SAAwB;AACtF,QAAM,MAAM,IAAI,MAAM,SAAS,UAAU,IAAI;AAC7C,QAAM,UAAU,IAAI,MAAM,eAAe,YAAY,CAAC,GAAG,IAAI;AAC7D,MAAI,QAAQ,UAAa,CAAC,aAAa,GAAG,KAAK,YAAY,QAAW;AACpE;AAAA,EACF;AACA,QAAM,OAAO,IAAI,iBAAiB;AAClC,QAAM,QAAQ,IAAI,SAAS,KAAK,IAAI,GAAG,OAAO,CAAC;AAC/C,QAAM,MAAM,KAAK,IAAI;AACrB,MAAI,SAAS,KAAK,QAAQ,IAAI,IAAI;AAClC,MAAI,WAAW,QAAW;AACxB,aAAS,EAAE,QAAQ,OAAO,YAAY,KAAK,aAAa,KAAK,SAAS,EAAE;AACxE,SAAK,QAAQ,IAAI,MAAM,MAAM;AAAA,EAC/B;AACA,QAAM,UAAU,SAAS,QAAQ,MAAM,OAAO,GAAG;AACjD,MAAI,YAAY,QAAW;AACzB,QAAI,SAAS;AACX,WAAK,MAAM;AAAA,IACb;AACA;AAAA,EACF;AACA,QAAM,SAAS,YAAY,IAAI,SAAS,OAAO;AAC/C,MAAI,WAAW,SAAS;AACtB,QAAI,CAAC,MAAM,OAAO,IAAI,IAAI,GAAG;AAC3B,YAAM,OAAO,IAAI,IAAI;AACrB;AAAA,QACE,IAAI;AAAA,UACF,WAAW,MAAM,IAAI,aAAa,MAAM,SAAS,IAAI,IAAI,IAAI;AAAA,QAC/D;AAAA,MACF;AAAA,IACF;AACA;AAAA,EACF;AACA,MACE,OAAO,WAAW,UAClB,cAAc,KAAK,OAAO,OAAO,CAAC,MAAM,IAAI,CAAC,MAAM,MAAM,MACzD;AACA;AAAA,EACF;AACA,MAAI;AACF,YAAQ,OAAO,OAAO,EAAE,MAAM,KAAK,MAAM,MAAM,MAAM,KAAK,CAAC;AAAA,EAC7D,SAAS,OAAO;AACd,eAAW,KAAK;AAAA,EAClB;AACF;AAGA,SAAS,OAAO,OAAc,MAAc,KAAgB,OAAmC;AAC7F,QAAM,OAAO,IAAI,MAAM,eAAe,WAAW,CAAC,GAAG,IAAI,GAAG;AAC5D,MAAI,SAAS,QAAW;AACtB,WAAO,MAAM,KAAK,IAAI,IAAI,GAAG,IAAI,KAAK,KAAK,CAAC;AAAA,EAC9C;AACA,SAAO,KAAK,KAAK,EAAE,IAAI,CAAC,SAAS;AAC/B,UAAM,UAAU,YAAY,IAAI,MAAM,IAAI;AAC1C,QAAI,YAAY,WAAW,QAAQ,WAAW,QAAW;AACvD,YAAM,IAAI;AAAA,QACR,WAAW,MAAM,IAAI,iBAAiB,MAAM,SAAS,IAAI,IAAI,IAAI;AAAA,MACnE;AAAA,IACF;AACA,WAAO,QAAQ;AAAA,EACjB,CAAC;AACH;AAGA,SAAS,WAAW,OAAc,MAAc,OAAe,MAAuB;AACpF,SAAO,aAAa,cAAc,CAAC,GAAG,MAAM,SAAS,MAAM,MAAM,OAAO,CAAC,IAAI,GAAG,KAAK,CAAC,CAAC;AACzF;AAGA,SAAS,SACP,OACA,MACA,OACA,OACyB;AACzB,QAAM,MAAM,MAAM,SAAS;AAC3B,QAAM,WAAW,aAAa,cAAc,CAAC,GAAG,MAAM,SAAS,MAAM,MAAM,OAAO,CAAC,GAAG,KAAK,CAAC,CAAC;AAC7F,QAAM,SAAsB,CAAC,CAAC,CAAC;AAC/B,MAAI,OAAO;AACX,aAAW,QAAQ,OAAO;AAExB,UAAM,QAAQ,aAAa,KAAK,UAAU,IAAI,KAAK,MAAM;AACzD,QAAI,WAAW,QAAQ,KAAK;AAC1B,aAAO;AAAA,IACT;AACA,UAAM,QAAQ,OAAO,GAAG,EAAE,KAAK,CAAC;AAChC,QAAI,MAAM,WAAW,GAAG;AACtB,YAAM,KAAK,IAAI;AACf,cAAQ;AAAA,IACV,WAAW,OAAO,IAAI,QAAQ,KAAK;AACjC,aAAO,KAAK,CAAC,IAAI,CAAC;AAClB,aAAO,WAAW;AAAA,IACpB,OAAO;AACL,YAAM,KAAK,IAAI;AACf,cAAQ,IAAI;AAAA,IACd;AAAA,EACF;AACA,SAAO;AACT;AAKA,SAAS,QAAQ,MAAiB,SAAiB,MAAc,OAAuB;AACtF,SAAO,CAAC,MAAM,YAAY;AACxB,SAAK,IAAI,KAAK,OAAO,CAAC,GAAG,SAAS,MAAM,OAAO,EAAE,MAAM,QAAQ,CAAC,CAAC;AAAA,EACnE;AACF;AAOA,SAAS,OAAO,MAAiB,QAAyB;AACxD,QAAM,QAAQ,SAAS,KAAK,YAAY,qBAAqB,gBAAgB,KAAK,IAAI,CAAC;AACvF,MAAI,UAAU,QAAW;AACvB,WAAO;AAAA,EACT;AACA,MAAI,OAAO;AACT,SAAK,MAAM;AAAA,EACb,OAAO;AACL,WAAO,gBAAgB,6BAA6B,mBAAmB,iBAAiB;AAAA,EAC1F;AACA,SAAO;AACT;AAGA,SAAS,WAAW,OAAc,MAAiB,SAA8B;AAC/E,QAAM,CAAC,EAAE,SAAS,MAAM,KAAK,IAAI;AACjC,QAAM,SAAS,QAAQ,MAAM,SAAS,MAAM,KAAK;AACjD,MAAI,CAAC,MAAM,UAAU,MAAM,MAAM,IAAI,IAAI,KAAK,OAAO,MAAM,MAAM,GAAG;AAClE,WAAO,aAAa,eAAe,MAAM,IAAI,uBAAuB,OAAO,GAAG;AAAA,EAChF;AACF;AAEA,SAAS,YAAY,OAAc,MAAiB,MAAc,OAAqB;AACrF,QAAM,UAAU,MAAM,SAAS;AAC/B,QAAM,SAAS,QAAQ,MAAM,SAAS,MAAM,KAAK;AACjD,MAAI,CAAC,OAAO,MAAM,MAAM,GAAG;AACzB;AAAA,EACF;AACA,QAAM,MAAM,IAAI,MAAM,SAAS,SAAS,IAAI;AAC5C,MAAI,QAAQ,UAAa,CAAC,aAAa,GAAG,GAAG;AAC3C,WAAO,aAAa,GAAG,OAAO,+BAA+B,IAAI,GAAG;AACpE;AAAA,EACF;AACA,MAAI,aAAa,KAAK,KAAK,MAAM,MAAM,MAAM;AAC3C,WAAO,aAAa,sBAAsB,KAAK,yCAAyC;AACxF;AAAA,EACF;AACA,QAAM,MAAM,QAAQ,MAAM,KAAK;AAC/B,MAAI,CAAC,KAAK,MAAM,IAAI,GAAG,KAAK,KAAK,MAAM,QAAQ,gBAAgB;AAC7D,WAAO,YAAY,2BAA2B,cAAc,kBAAkB;AAC9E;AAAA,EACF;AACA,MAAI;AACJ,MAAI;AACF,aAAS,SAAS,OAAO,MAAM,OAAO,OAAO,OAAO,MAAM,KAAK,KAAK,CAAC;AAAA,EACvE,SAAS,OAAO;AACd,WAAO,YAAY,yCAAyC;AAC5D,eAAW,KAAK;AAChB;AAAA,EACF;AACA,MAAI,WAAW,QAAW;AACxB,WAAO,YAAY,sDAAsD;AACzE;AAAA,MACE,IAAI;AAAA,QACF,WAAW,MAAM,IAAI,iBAAiB,OAAO,IAAI,IAAI,uBAAuB,MAAM,SAAS,CAAC;AAAA,MAC9F;AAAA,IACF;AACA;AAAA,EACF;AACA,OAAK,MAAM,IAAI,GAAG;AAClB,QAAM,cAAc,MAAM,MAAM,IAAI,GAAG,KAAK,oBAAI,IAAe;AAC/D,QAAM,MAAM,IAAI,KAAK,WAAW;AAChC,cAAY,IAAI,IAAI;AACpB,SAAO,QAAQ,CAAC,OAAO,UAAU;AAC/B,SAAK,IAAI,KAAK,OAAO,CAAC,GAAG,SAAS,MAAM,OAAO,OAAO,UAAU,OAAO,SAAS,CAAC,CAAC;AAAA,EACpF,CAAC;AACH;AAEA,SAAS,MAAM,OAAc,MAAiB,KAAmB;AAC/D,OAAK,MAAM,OAAO,GAAG;AACrB,QAAM,cAAc,MAAM,MAAM,IAAI,GAAG;AACvC,eAAa,OAAO,IAAI;AACxB,MAAI,aAAa,SAAS,GAAG;AAC3B,UAAM,MAAM,OAAO,GAAG;AAAA,EACxB;AACF;AAEA,SAAS,QAAQ,OAAc,MAAiB,SAA4B;AAC1E,MAAI,MAAM,UAAU,CAAC,MAAM,MAAM,IAAI,IAAI,KAAK,QAAQ,CAAC,MAAM,MAAM,SAAS,MAAM;AAChF;AAAA,EACF;AACA,MAAI,QAAQ,CAAC,MAAM,GAAG;AACpB,cAAU,OAAO,MAAM,QAAQ,CAAC,GAAG,QAAQ,CAAC,CAAC;AAAA,EAC/C,WAAW,QAAQ,CAAC,MAAM,GAAG;AAC3B,gBAAY,OAAO,MAAM,QAAQ,CAAC,GAAG,QAAQ,CAAC,CAAC;AAAA,EACjD,WAAW,QAAQ,CAAC,MAAM,GAAG;AAC3B,UAAM,OAAO,MAAM,QAAQ,QAAQ,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC;AAAA,EACpD;AACF;AAGA,SAAS,KAAK,OAAc,MAAc,WAAmB,OAAe,MAAqB;AAC/F,QAAM,SAAS,MAAM,KAAK,IAAI,IAAI,KAAK,oBAAI,IAAuB;AAClE,QAAM,KAAK,IAAI,MAAM,MAAM;AAC3B,QAAM,QAAQ,OAAO,IAAI,KAAK,KAAK,CAAC;AACpC,SAAO,OAAO,KAAK;AACnB,SAAO,IAAI,OAAO,CAAC,GAAG,OAAO,IAAI,EAAE,MAAM,CAAC,SAAS,CAAC;AACpD,MAAI,OAAO,OAAO,iBAAiB;AACjC,UAAM,CAAC,MAAM,IAAI,OAAO,KAAK;AAC7B,QAAI,WAAW,QAAW;AACxB,aAAO,OAAO,MAAM;AAAA,IACtB;AAAA,EACF;AACF;AAEA,SAAS,KAAK,OAAc,MAAc,OAAe,MAAqB;AAC5E,QAAM,UAAU,MAAM,SAAS;AAC/B,QAAM,MAAM,IAAI,MAAM,SAAS,SAAS,IAAI;AAC5C,MAAI,QAAQ,UAAa,CAAC,aAAa,GAAG,GAAG;AAC3C,UAAM,IAAI,UAAU,gBAAgB,OAAO,+BAA+B,IAAI,GAAG;AAAA,EACnF;AACA,MAAI,OAAO,UAAU,YAAY,UAAU,IAAI;AAC7C,UAAM,IAAI,UAAU,gBAAgB,IAAI,iDAAiD;AAAA,EAC3F;AACA,QAAM,UAAU,YAAY,IAAI,MAAM,IAAI;AAC1C,MAAI,YAAY,WAAW,QAAQ,WAAW,QAAW;AACvD,UAAM,IAAI;AAAA,MACR,gBAAgB,IAAI,uDAAuD,YAAY,UAAU,mCAAmC,EAAE;AAAA,IACxI;AAAA,EACF;AACA,QAAM,UAAuB,CAAC,GAAG,SAAS,MAAM,OAAO,QAAQ,KAAK;AAEpE,QAAM,QAAQ,WAAW,OAAO,MAAM,OAAO,QAAQ,KAAK;AAC1D,MAAI,QAAQ,MAAM,SAAS,GAAG;AAC5B,UAAM,IAAI;AAAA,MACR,gBAAgB,IAAI,kCAAkC,MAAM,SAAS,CAAC,iDAAiD,KAAK;AAAA,IAC9H;AAAA,EACF;AACA,QAAM,YACJ,IAAI,MAAM,eAAe,WAAW,CAAC,GAAG,IAAI,GAAG,SAAS,SAAa,IAAI,QAAQ,IAAK;AACxF,MAAI,YAAY,GAAG;AACjB,SAAK,OAAO,MAAM,WAAW,OAAO,QAAQ,KAAK;AAAA,EACnD;AACA,QAAM,QAAQ,IAAI,aAAa,OAAO,SAAS;AAC/C,aAAW,MAAM,MAAM,MAAM,IAAI,QAAQ,MAAM,KAAK,CAAC,KAAK,CAAC,GAAG;AAC5D,OAAG,IAAI,KAAK,OAAO,OAAO;AAAA,EAC5B;AACF;AAGO,SAAS,kBACd,UACA,gBACA,MACA,UACa;AACb,QAAM,QAAe;AAAA,IACnB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,OAAO,oBAAI,IAAI;AAAA,IACf,OAAO,oBAAI,IAAI;AAAA,IACf,MAAM,oBAAI,IAAI;AAAA,IACd,QAAQ,oBAAI,IAAI;AAAA,IAChB,QAAQ;AAAA,EACV;AACA,SAAO,OAAO,OAAO;AAAA,IACnB,OAAO,MAAgB,MAAgB,OAAgC;AACrE,YAAM,MAAM,KAAK,IAAI;AACrB,YAAM,OAAkB;AAAA,QACtB;AAAA,QACA,KAAK,aAAa,MAAM,KAAK;AAAA,QAC7B;AAAA,QACA,SAAS,oBAAI,IAAI;AAAA,QACjB,YAAY,EAAE,QAAQ,gBAAgB,YAAY,KAAK,aAAa,KAAK,SAAS,EAAE;AAAA,QACpF,OAAO,oBAAI,IAAI;AAAA,MACjB;AACA,YAAM,MAAM,IAAI,IAAI;AACpB,aAAO;AAAA,QACL,SAAS,CAAC,YAAyB;AACjC,kBAAQ,OAAO,MAAM,OAAO;AAAA,QAC9B;AAAA,QACA,UAAU,CAAC,YAA2B;AACpC,qBAAW,OAAO,MAAM,OAAO;AAAA,QACjC;AAAA,QACA,QAAQ,MAAM;AACZ,qBAAW,OAAO,CAAC,GAAG,KAAK,KAAK,GAAG;AACjC,kBAAM,OAAO,MAAM,GAAG;AAAA,UACxB;AACA,eAAK,IAAI,MAAM;AACf,gBAAM,MAAM,OAAO,IAAI;AAAA,QACzB;AAAA,MACF;AAAA,IACF;AAAA,IACA,IAAI,QAA6B;AAC/B,aAAO,CAAC,GAAG,MAAM,KAAK,EAAE,IAAI,CAAC,EAAE,KAAK,MAAM,IAAI;AAAA,IAChD;AAAA,IACA,MAAM,CAAC,QAAgB,OAAe,SAAkB;AACtD,WAAK,OAAO,QAAQ,OAAO,IAAI;AAAA,IACjC;AAAA,IACA,QAAc;AACZ,YAAM,SAAS;AACf,iBAAW,QAAQ,MAAM,OAAO;AAC9B,aAAK,IAAI,MAAM;AAAA,MACjB;AACA,YAAM,MAAM,MAAM;AAClB,YAAM,MAAM,MAAM;AAAA,IACpB;AAAA,EACF,CAAC;AACH;;;ACxXA,SAAS,oBAAoB,UAAuB,gBAA6C;AAC/F,QAAM,QAAQ,cAAc,SAAS,IAAI;AACzC,QAAM,WAAW,eAAe,YAAY,CAAC;AAC7C,aAAW,CAAC,MAAM,GAAG,KAAK,OAAO,QAAQ,SAAS,QAAQ,GAAG;AAC3D,UAAM,UAAU,OAAO,OAAO,UAAU,IAAI,IAAI,SAAS,IAAI,IAAI;AACjE,QAAI,aAAa,GAAG,KAAK,OAAO,YAAY,YAAY;AACtD,YAAM,IAAI;AAAA,QACR,GAAG,KAAK,cAAc,IAAI;AAAA,MAC5B;AAAA,IACF;AAAA,EACF;AACA,aAAW,QAAQ,OAAO,KAAK,QAAQ,GAAG;AACxC,UAAM,MAAM,OAAO,OAAO,SAAS,UAAU,IAAI,IAAI,SAAS,SAAS,IAAI,IAAI;AAC/E,QAAI,QAAQ,UAAa,CAAC,aAAa,GAAG,GAAG;AAC3C,YAAM,IAAI,UAAU,GAAG,KAAK,MAAM,IAAI,2CAA2C;AAAA,IACnF;AAAA,EACF;AACA,aAAW,CAAC,MAAM,OAAO,KAAK,OAAO,QAAQ,eAAe,WAAW,CAAC,CAAC,GAAG;AAC1E,UAAM,MAAM,OAAO,OAAO,SAAS,SAAS,IAAI,IAAI,SAAS,QAAQ,IAAI,IAAI;AAC7E,QAAI,QAAQ,UAAa,CAAC,aAAa,GAAG,GAAG;AAC3C,YAAM,IAAI,UAAU,GAAG,KAAK,MAAM,IAAI,0CAA0C;AAAA,IAClF;AACA,QAAI,SAAS,SAAS,UAAa,OAAO,QAAQ,SAAS,YAAY;AACrE,YAAM,IAAI,UAAU,GAAG,KAAK,aAAa,IAAI,uCAAuC;AAAA,IACtF;AAAA,EACF;AACF;AAEA,SAAS,MACP,UACA,gBACA,YACA,MACS;AACT,MAAI,CAAC,WAAW,IAAI,GAAG;AACrB,UAAM,IAAI,UAAU,2DAA2D;AAAA,EACjF;AACA,QAAM,UAAU,SAAS;AACzB,QAAM,UAAU;AAAA,IACd;AAAA,IACA;AAAA,IACA;AAAA,IACA,MAAM,WAAW,SAAS,EAAE,OAAO,OAAO,OAAO,mBAAmB;AAAA,EACtE;AACA,QAAM,SAAsB,QAAQ,YAAY,IAAI;AACpD,SAAO,IAAI,SAAS,OAAO;AAC3B,MAAI,SAAS;AACb,SAAO,OAAO,OAAO;AAAA,IACnB;AAAA,IACA,IAAI,QAA6B;AAC/B,aAAO,QAAQ;AAAA,IACjB;AAAA,IACA,QAAQ,CAAC,SACP,OAAO,OAAO;AAAA,MACZ,MAAM,CAAC,OAAe,SAAkB;AACtC,gBAAQ,KAAK,MAAM,OAAO,IAAI;AAAA,MAChC;AAAA,IACF,CAAC;AAAA,IACH,QAAc;AACZ,UAAI,CAAC,QAAQ;AACX,iBAAS;AACT,eAAO,OAAO,SAAS,OAAO;AAAA,MAChC;AAAA,IACF;AAAA,EACF,CAAC;AACH;AAiBO,SAAS,WACd,UACA,gBACmB;AACnB,QAAM,MAAM;AACZ,sBAAoB,UAAU,GAAG;AACjC,SAAO,OAAO,OAAO;AAAA,IACnB;AAAA,IACA,OAAO,CAAC,YAAiC,YACvC,MAAM,UAAU,KAAK,YAAY,QAAQ,IAAI;AAAA,EACjD,CAAC;AACH;","names":[]}
@@ -2,14 +2,15 @@
2
2
  import {
3
3
  defaultToolName
4
4
  } from "../chunk-XNLWBZIG.js";
5
- import {
6
- CHANNEL_DEFAULT_RATE,
7
- isScopedStream
8
- } from "../chunk-5CGO426C.js";
9
5
  import {
10
6
  DEFAULT_COLLECTION_LIMIT,
11
7
  DEFAULT_COLLECTION_MAX_LIMIT
12
8
  } from "../chunk-4D2DOGAW.js";
9
+ import {
10
+ CHANNEL_DEFAULT_RATE,
11
+ isHostServed,
12
+ isScopedStream
13
+ } from "../chunk-GYLWEJHW.js";
13
14
  import {
14
15
  hasJsonSchema,
15
16
  isStandardSchema
@@ -65,6 +66,15 @@ function section(heading, blocks) {
65
66
  }
66
67
  return lines;
67
68
  }
69
+ function described(text) {
70
+ return text === void 0 ? [] : [paragraph(text)];
71
+ }
72
+ function optionTable(rows) {
73
+ return table(
74
+ ["Option", "Value"],
75
+ rows.filter((row) => row[1] !== void 0)
76
+ );
77
+ }
68
78
 
69
79
  // src/cli/access.ts
70
80
  function isRecord(value) {
@@ -120,7 +130,8 @@ function serviceDocOf(value) {
120
130
  ),
121
131
  collections: mapEach(collections, (collection) => ({
122
132
  level: collection.access,
123
- anchor: collection.anchor
133
+ anchor: collection.anchor,
134
+ requires: collection.requires
124
135
  })),
125
136
  channels: mapEach(channels, (channel) => channel.access),
126
137
  streams: mapEach(streams, (stream) => ({
@@ -232,10 +243,10 @@ function watchAccessText(watch) {
232
243
  return watch === void 0 ? `closed: the service declares no ${code("watchAccess")}` : accessFormText(watch);
233
244
  }
234
245
  function collectionAccessText(collection) {
235
- if (collection.anchor === void 0) {
236
- return `the subscriber's own user id (${code('scopeAccess: "self"')})`;
237
- }
238
- return `${String(collection.level)} or more on the ${code(serviceName(collection.anchor))} row the scope names`;
246
+ const scope = collection.anchor === void 0 ? `the subscriber's own user id (${code('scopeAccess: "self"')})` : `${String(collection.level)} or more on the ${code(serviceName(collection.anchor))} row the scope names`;
247
+ const { requires } = collection;
248
+ const level = isRecord(requires) && typeof requires.service === "string" ? requires.service : void 0;
249
+ return level === void 0 ? scope : `${scope}, and a service-wide grant of ${level} or more (${code(`requires: { service: "${level}" }`)})`;
239
250
  }
240
251
 
241
252
  // src/cli/dependencies.ts
@@ -628,6 +639,115 @@ function schemaFields(schema) {
628
639
  notes: isRecord3(value) ? schemaNotes(value) : ""
629
640
  }));
630
641
  }
642
+ function typeOf(schema, mode) {
643
+ const json = jsonSchemaOf(schema, mode);
644
+ return json === void 0 ? NO_JSON_SCHEMA : schemaText(json);
645
+ }
646
+
647
+ // src/cli/realtimeText.ts
648
+ function seedText(seed, computed, known) {
649
+ if (computed) {
650
+ return "computed by the service when a socket subscribes";
651
+ }
652
+ if (seed === void 0) {
653
+ return known ? "none (default)" : "none in the contract; the service may compute one";
654
+ }
655
+ if (seed === 0) {
656
+ return "none";
657
+ }
658
+ return seed === 1 ? "the latest item" : `the latest ${seed} items`;
659
+ }
660
+ function servedByText(def) {
661
+ return isHostServed(def) ? `the room's host (${code('serve: "host"')}), over peer links: the server never handles it` : void 0;
662
+ }
663
+ function hostSeedText(seed) {
664
+ if (seed === void 0 || seed === 0) {
665
+ return "none kept by the host; the host may compute one";
666
+ }
667
+ return `the latest ${seed === 1 ? "item" : `${seed} items`} the host kept, or the host's own`;
668
+ }
669
+ function requiresText(requires) {
670
+ if (requires === void 0) {
671
+ return void 0;
672
+ }
673
+ const selector = (value) => typeof value === "string" ? code(value) : "a function of the payload";
674
+ if (requires.room !== void 0) {
675
+ return typeof requires.room === "string" ? `the sending socket in the app room ${code(requires.room)}` : "the sending socket in the app room a function of the payload names";
676
+ }
677
+ return requires.collection === void 0 ? `a subscription to the row ${selector(requires.entity)} names` : `a subscription to the ${code(requires.collection)} scope ${selector(requires.scope)} names`;
678
+ }
679
+ function accessText(access) {
680
+ if (access === void 0) {
681
+ return "none: closed to every client";
682
+ }
683
+ if (typeof access === "string") {
684
+ return code(JSON.stringify(access));
685
+ }
686
+ if (access.room !== void 0) {
687
+ const { room } = access;
688
+ if (typeof room === "string") {
689
+ return code(`{ room: "${room}" }`);
690
+ }
691
+ return typeof room === "function" ? code("{ room: (scope) => ... }") : code(`{ room: { prefix: "${room.prefix}" } }`);
692
+ }
693
+ if (access.scope !== void 0) {
694
+ return code(`{ scope: "${access.scope}", of: ${access.of.name} }`);
695
+ }
696
+ const parts = [
697
+ access.service === void 0 ? void 0 : `service: "${access.service}"`,
698
+ access.entry === void 0 ? void 0 : `entry: "${access.entry}"`
699
+ ].filter((part) => part !== void 0);
700
+ return code(`{ ${parts.join(", ")} }`);
701
+ }
702
+ function streamSection(name, stream, service) {
703
+ const scoped = isScopedStream(stream);
704
+ const served = service?.streams.get(name);
705
+ return section(`### ${code(name)}`, [
706
+ ...described(stream.describe),
707
+ optionTable([
708
+ ["Served by", servedByText(stream)],
709
+ ["Item", code(typeOf(stream.item, "output"))],
710
+ ["Scope", scoped ? `one feed per ${code(stream.scope ?? "")}` : "one global feed"],
711
+ [
712
+ "Seed",
713
+ isHostServed(stream) ? hostSeedText(stream.seed) : seedText(stream.seed, served?.computedSeed === true, served !== void 0)
714
+ ],
715
+ ["Volatile", stream.volatile === true ? "yes" : "no"],
716
+ ["Access", accessText(stream.access)],
717
+ [
718
+ "Checked",
719
+ served === void 0 ? void 0 : served.validate === "development" ? `in development only (${code('validate: "development"')})` : "always"
720
+ ]
721
+ ])
722
+ ]);
723
+ }
724
+ function channelSection(name, channel, service) {
725
+ const rate = channel.ratePerSecond ?? CHANNEL_DEFAULT_RATE;
726
+ const served = service?.channels.has(name) === true;
727
+ return section(`### ${code(name)}`, [
728
+ ...described(channel.describe),
729
+ optionTable([
730
+ ["Served by", servedByText(channel)],
731
+ ["Payload", code(typeOf(channel.payload, "input"))],
732
+ [
733
+ "Rate",
734
+ `${rate} a second per socket${channel.ratePerSecond === void 0 ? " (default)" : ""}`
735
+ ],
736
+ [
737
+ "Burst",
738
+ channel.burst === void 0 ? `${Math.max(1, 2 * rate)} (default)` : String(channel.burst)
739
+ ],
740
+ ["Requires", requiresText(channel.requires)],
741
+ ["Access", served ? accessFormText(service?.channels.get(name)) : void 0]
742
+ ])
743
+ ]);
744
+ }
745
+ function eventSection(name, event) {
746
+ return section(`### ${code(name)}`, [
747
+ ...described(event.describe),
748
+ optionTable([["Payload", code(typeOf(event.payload, "output"))]])
749
+ ]);
750
+ }
631
751
 
632
752
  // src/cli/render.ts
633
753
  var GENERATED_MARKER = "<!-- Generated by quickdraw-docs from the app's contracts. Do not edit: change the contract and run quickdraw-docs again. -->";
@@ -639,10 +759,6 @@ function acceptsUndefined(schema) {
639
759
  return false;
640
760
  }
641
761
  }
642
- function typeOf(schema, mode) {
643
- const json = jsonSchemaOf(schema, mode);
644
- return json === void 0 ? NO_JSON_SCHEMA : schemaText(json);
645
- }
646
762
  function fieldTable(fields, levels = {}) {
647
763
  const hasLevels = fields.some((field) => levels[field.name] !== void 0);
648
764
  const hasNotes = fields.some((field) => field.notes !== "");
@@ -716,9 +832,6 @@ function kindLine(method) {
716
832
  }
717
833
  return `A query that watches the ${code(method.watch.collection)} collection: a cached result is fetched again when the scope its input names changes.`;
718
834
  }
719
- function described(text) {
720
- return text === void 0 ? [] : [paragraph(text)];
721
- }
722
835
  function callableBy(name, access) {
723
836
  const method = access?.service?.methods.get(name);
724
837
  if (method?.kinds === void 0) {
@@ -778,12 +891,6 @@ function whereText(where) {
778
891
  }
779
892
  return Object.entries(where).map(([column, value]) => code(`${column} = ${JSON.stringify(value)}`)).join(", ");
780
893
  }
781
- function optionTable(rows) {
782
- return table(
783
- ["Option", "Value"],
784
- rows.filter((row) => row[1] !== void 0)
785
- );
786
- }
787
894
  function collectionSection(name, collection, access) {
788
895
  const views = collection.views === void 0 ? [] : Object.keys(collection.views);
789
896
  const limit = collection.limit ?? DEFAULT_COLLECTION_LIMIT;
@@ -822,95 +929,6 @@ function collectionsSection(contract, access) {
822
929
  ...entries.flatMap(([name, collection]) => collectionSection(name, collection, access))
823
930
  ];
824
931
  }
825
- function accessText(access) {
826
- if (access === void 0) {
827
- return "none: closed to every client";
828
- }
829
- if (typeof access === "string") {
830
- return code(JSON.stringify(access));
831
- }
832
- if (access.room !== void 0) {
833
- const { room } = access;
834
- if (typeof room === "string") {
835
- return code(`{ room: "${room}" }`);
836
- }
837
- return typeof room === "function" ? code("{ room: (scope) => ... }") : code(`{ room: { prefix: "${room.prefix}" } }`);
838
- }
839
- if (access.scope !== void 0) {
840
- return code(`{ scope: "${access.scope}", of: ${access.of.name} }`);
841
- }
842
- const parts = [
843
- access.service === void 0 ? void 0 : `service: "${access.service}"`,
844
- access.entry === void 0 ? void 0 : `entry: "${access.entry}"`
845
- ].filter((part) => part !== void 0);
846
- return code(`{ ${parts.join(", ")} }`);
847
- }
848
- function seedText(seed, computed, known) {
849
- if (computed) {
850
- return "computed by the service when a socket subscribes";
851
- }
852
- if (seed === void 0) {
853
- return known ? "none (default)" : "none in the contract; the service may compute one";
854
- }
855
- if (seed === 0) {
856
- return "none";
857
- }
858
- return seed === 1 ? "the latest item" : `the latest ${seed} items`;
859
- }
860
- function streamSection(name, stream, access) {
861
- const scoped = isScopedStream(stream);
862
- const served = access?.service?.streams.get(name);
863
- return section(`### ${code(name)}`, [
864
- ...described(stream.describe),
865
- optionTable([
866
- ["Item", code(typeOf(stream.item, "output"))],
867
- ["Scope", scoped ? `one feed per ${code(stream.scope ?? "")}` : "one global feed"],
868
- ["Seed", seedText(stream.seed, served?.computedSeed === true, served !== void 0)],
869
- ["Volatile", stream.volatile === true ? "yes" : "no"],
870
- ["Access", accessText(stream.access)],
871
- [
872
- "Checked",
873
- served === void 0 ? void 0 : served.validate === "development" ? `in development only (${code('validate: "development"')})` : "always"
874
- ]
875
- ])
876
- ]);
877
- }
878
- function requiresText(requires) {
879
- if (requires === void 0) {
880
- return void 0;
881
- }
882
- const selector = (value) => typeof value === "string" ? code(value) : "a function of the payload";
883
- if (requires.room !== void 0) {
884
- return typeof requires.room === "string" ? `the sending socket in the app room ${code(requires.room)}` : "the sending socket in the app room a function of the payload names";
885
- }
886
- return requires.collection === void 0 ? `a subscription to the row ${selector(requires.entity)} names` : `a subscription to the ${code(requires.collection)} scope ${selector(requires.scope)} names`;
887
- }
888
- function channelSection(name, channel, access) {
889
- const rate = channel.ratePerSecond ?? CHANNEL_DEFAULT_RATE;
890
- const served = access?.service?.channels.has(name) === true;
891
- return section(`### ${code(name)}`, [
892
- ...described(channel.describe),
893
- optionTable([
894
- ["Payload", code(typeOf(channel.payload, "input"))],
895
- [
896
- "Rate",
897
- `${rate} a second per socket${channel.ratePerSecond === void 0 ? " (default)" : ""}`
898
- ],
899
- [
900
- "Burst",
901
- channel.burst === void 0 ? `${Math.max(1, 2 * rate)} (default)` : String(channel.burst)
902
- ],
903
- ["Requires", requiresText(channel.requires)],
904
- ["Access", served ? accessFormText(access?.service?.channels.get(name)) : void 0]
905
- ])
906
- ]);
907
- }
908
- function eventSection(name, event) {
909
- return section(`### ${code(name)}`, [
910
- ...described(event.describe),
911
- optionTable([["Payload", code(typeOf(event.payload, "output"))]])
912
- ]);
913
- }
914
932
  function membersSection(heading, intro, members, render) {
915
933
  const entries = Object.entries(members);
916
934
  return entries.length === 0 ? [] : [heading, "", intro, "", ...entries.flatMap(([name, def]) => render(name, def))];
@@ -963,13 +981,13 @@ function renderService(contract, access) {
963
981
  "## Streams",
964
982
  "Feeds of items the server appends; a subscriber starts from the stream's seed.",
965
983
  contract.streams,
966
- (name, stream) => streamSection(name, stream, access)
984
+ (name, stream) => streamSection(name, stream, access?.service)
967
985
  ),
968
986
  ...membersSection(
969
987
  "## Channels",
970
988
  "Fire-and-forget messages from clients, dropped over the rate.",
971
989
  contract.channels,
972
- (name, channel) => channelSection(name, channel, access)
990
+ (name, channel) => channelSection(name, channel, access?.service)
973
991
  ),
974
992
  ...membersSection(
975
993
  "## Events",