@kingsleyweb/bun-ui 0.0.0-stage → 3.0.0-rc.2
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/LICENSE +21 -0
- package/README.md +80 -2
- package/app/docs/App.tsx +72 -0
- package/app/docs/api/client.ts +303 -0
- package/app/docs/api/contract.ts +24 -0
- package/app/docs/api/docs.ts +38 -0
- package/app/docs/api/errors.ts +144 -0
- package/app/docs/boot.tsx +58 -0
- package/app/docs/components/Badge.tsx +65 -0
- package/app/docs/components/Button.tsx +37 -0
- package/app/docs/components/Card.tsx +41 -0
- package/app/docs/components/ConfirmDialog.tsx +173 -0
- package/app/docs/components/CopyButton.tsx +68 -0
- package/app/docs/components/Dialog.tsx +230 -0
- package/app/docs/components/EmptyState.tsx +34 -0
- package/app/docs/components/ErrorView.tsx +70 -0
- package/app/docs/components/Field.tsx +93 -0
- package/app/docs/components/JsonEditor.tsx +151 -0
- package/app/docs/components/JsonView.tsx +338 -0
- package/app/docs/components/KeyValue.tsx +63 -0
- package/app/docs/components/ProblemBanner.tsx +116 -0
- package/app/docs/components/ScreenErrorBoundary.tsx +141 -0
- package/app/docs/components/Spinner.tsx +33 -0
- package/app/docs/components/Table.tsx +33 -0
- package/app/docs/components/Tabs.tsx +226 -0
- package/app/docs/components/classNames.ts +4 -0
- package/app/docs/components/components.css +691 -0
- package/app/docs/components/copy.ts +89 -0
- package/app/docs/components/fieldContext.ts +29 -0
- package/app/docs/components/inputValues.ts +142 -0
- package/app/docs/components/inputs.tsx +387 -0
- package/app/docs/components/jsonParse.ts +338 -0
- package/app/docs/components/listKeys.ts +30 -0
- package/app/docs/config.ts +86 -0
- package/app/docs/context.ts +33 -0
- package/app/docs/embed.ts +74 -0
- package/app/docs/env.d.ts +2 -0
- package/app/docs/format.ts +9 -0
- package/app/docs/hooks/useUrlTab.ts +16 -0
- package/app/docs/main.tsx +11 -0
- package/app/docs/queryClient.ts +54 -0
- package/app/docs/router.tsx +194 -0
- package/app/docs/routing.ts +194 -0
- package/app/docs/screens/docs/document.ts +27 -0
- package/app/docs/screens/docs/http/OperationView.tsx +407 -0
- package/app/docs/screens/docs/http/Sidebar.tsx +190 -0
- package/app/docs/screens/docs/http/TryItPanel.tsx +603 -0
- package/app/docs/screens/docs/http/http.css +405 -0
- package/app/docs/screens/docs/http/index.tsx +395 -0
- package/app/docs/screens/docs/http/model.ts +604 -0
- package/app/docs/screens/docs/http/tryIt.ts +455 -0
- package/app/docs/screens/docs/permissions.ts +6 -0
- package/app/docs/screens/docs/schema/Prose.tsx +43 -0
- package/app/docs/screens/docs/schema/SchemaTree.tsx +319 -0
- package/app/docs/screens/docs/schema/index.ts +30 -0
- package/app/docs/screens/docs/schema/resolve.ts +466 -0
- package/app/docs/screens/docs/schema/schema.css +137 -0
- package/app/docs/screens/docs/schema/skeleton.ts +92 -0
- package/app/docs/screens/docs/viewer.tsx +154 -0
- package/app/docs/screens/docs/ws/InlineText.tsx +25 -0
- package/app/docs/screens/docs/ws/PayloadSchema.tsx +39 -0
- package/app/docs/screens/docs/ws/format.ts +82 -0
- package/app/docs/screens/docs/ws/index.tsx +286 -0
- package/app/docs/screens/docs/ws/items.tsx +786 -0
- package/app/docs/screens/docs/ws/model.ts +1178 -0
- package/app/docs/screens/docs/ws/panels.tsx +440 -0
- package/app/docs/screens/docs/ws/ws.css +231 -0
- package/app/docs/shortcuts.ts +94 -0
- package/app/docs/styles/base.css +847 -0
- package/app/docs/styles/tokens.css +143 -0
- package/app/docs/theme.ts +56 -0
- package/app/kit/unauthorized.ts +222 -0
- package/dist/docs/assets/main-0rchxtv5.js +18 -0
- package/dist/docs/assets/main-0rchxtv5.js.map +99 -0
- package/dist/docs/assets/main-cqgyvs86.css +1 -0
- package/dist/docs/manifest.json +26 -0
- package/dist/jobs/assets/AddFlowDialog-h6xkgx9b.js +6 -0
- package/dist/jobs/assets/AddFlowDialog-h6xkgx9b.js.map +11 -0
- package/dist/jobs/assets/AddFlowDialog-n7gtc84v.css +1 -0
- package/dist/jobs/assets/added-cbwsxv3e.js +4 -0
- package/dist/jobs/assets/added-cbwsxv3e.js.map +11 -0
- package/dist/jobs/assets/bodies-yyw5hmrp.js +6 -0
- package/dist/jobs/assets/bodies-yyw5hmrp.js.map +10 -0
- package/dist/jobs/assets/channel-tgkvqm8y.js +4 -0
- package/dist/jobs/assets/channel-tgkvqm8y.js.map +10 -0
- package/dist/jobs/assets/channelChecks-s4yz4yhw.js +4 -0
- package/dist/jobs/assets/channelChecks-s4yz4yhw.js.map +10 -0
- package/dist/jobs/assets/index-02427smp.css +1 -0
- package/dist/jobs/assets/index-09z2cjft.js +4 -0
- package/dist/jobs/assets/index-09z2cjft.js.map +16 -0
- package/dist/jobs/assets/index-18bn0gvy.css +1 -0
- package/dist/jobs/assets/index-47pxmyxn.js +4 -0
- package/dist/jobs/assets/index-47pxmyxn.js.map +10 -0
- package/dist/jobs/assets/index-4e55z986.js +4 -0
- package/dist/jobs/assets/index-4e55z986.js.map +11 -0
- package/dist/jobs/assets/index-5c933424.js +4 -0
- package/dist/jobs/assets/index-5c933424.js.map +18 -0
- package/dist/jobs/assets/index-5kbyqspm.css +1 -0
- package/dist/jobs/assets/index-5ng37hk6.css +1 -0
- package/dist/jobs/assets/index-5w8w7fvc.css +1 -0
- package/dist/jobs/assets/index-71qs5qyx.css +1 -0
- package/dist/jobs/assets/index-btpg4r8b.css +1 -0
- package/dist/jobs/assets/index-d5r5n0mm.js +4 -0
- package/dist/jobs/assets/index-d5r5n0mm.js.map +23 -0
- package/dist/jobs/assets/index-e0yy2skq.css +1 -0
- package/dist/jobs/assets/index-hs60b25v.js +10 -0
- package/dist/jobs/assets/index-hs60b25v.js.map +62 -0
- package/dist/jobs/assets/index-k2smrr70.js +4 -0
- package/dist/jobs/assets/index-k2smrr70.js.map +9 -0
- package/dist/jobs/assets/index-q24jw3w9.js +4 -0
- package/dist/jobs/assets/index-q24jw3w9.js.map +32 -0
- package/dist/jobs/assets/index-qfb80xen.css +1 -0
- package/dist/jobs/assets/index-rtynx6wj.js +4 -0
- package/dist/jobs/assets/index-rtynx6wj.js.map +10 -0
- package/dist/jobs/assets/live-6k7s6358.js +4 -0
- package/dist/jobs/assets/live-6k7s6358.js.map +10 -0
- package/dist/jobs/assets/main-0jmeh6mq.js +6 -0
- package/dist/jobs/assets/main-0jmeh6mq.js.map +12 -0
- package/dist/jobs/assets/main-3yj6bv90.js +5 -0
- package/dist/jobs/assets/main-3yj6bv90.js.map +10 -0
- package/dist/jobs/assets/main-4g69fn3x.js +5 -0
- package/dist/jobs/assets/main-4g69fn3x.js.map +10 -0
- package/dist/jobs/assets/main-4gte3eqq.js +5 -0
- package/dist/jobs/assets/main-4gte3eqq.js.map +10 -0
- package/dist/jobs/assets/main-4j1fcevh.js +5 -0
- package/dist/jobs/assets/main-4j1fcevh.js.map +10 -0
- package/dist/jobs/assets/main-54qwkz28.css +1 -0
- package/dist/jobs/assets/main-5grjvsy7.js +5 -0
- package/dist/jobs/assets/main-5grjvsy7.js.map +11 -0
- package/dist/jobs/assets/main-5zbx60gq.js +5 -0
- package/dist/jobs/assets/main-5zbx60gq.js.map +11 -0
- package/dist/jobs/assets/main-9prgjhg4.js +5 -0
- package/dist/jobs/assets/main-9prgjhg4.js.map +10 -0
- package/dist/jobs/assets/main-a250ffk7.js +17 -0
- package/dist/jobs/assets/main-a250ffk7.js.map +115 -0
- package/dist/jobs/assets/main-d6ge7w08.js +5 -0
- package/dist/jobs/assets/main-d6ge7w08.js.map +10 -0
- package/dist/jobs/assets/main-gngzht43.js +5 -0
- package/dist/jobs/assets/main-gngzht43.js.map +10 -0
- package/dist/jobs/assets/main-hg0hrg8j.js +5 -0
- package/dist/jobs/assets/main-hg0hrg8j.js.map +10 -0
- package/dist/jobs/assets/main-hm1ng3yg.js +5 -0
- package/dist/jobs/assets/main-hm1ng3yg.js.map +12 -0
- package/dist/jobs/assets/main-jkvd92z9.js +5 -0
- package/dist/jobs/assets/main-jkvd92z9.js.map +10 -0
- package/dist/jobs/assets/main-jv5xqd13.js +5 -0
- package/dist/jobs/assets/main-jv5xqd13.js.map +21 -0
- package/dist/jobs/assets/main-jxre7w93.js +5 -0
- package/dist/jobs/assets/main-jxre7w93.js.map +23 -0
- package/dist/jobs/assets/main-kwmqkjfx.js +5 -0
- package/dist/jobs/assets/main-kwmqkjfx.js.map +11 -0
- package/dist/jobs/assets/main-n9cf6bxm.js +5 -0
- package/dist/jobs/assets/main-n9cf6bxm.js.map +11 -0
- package/dist/jobs/assets/main-pegp0ygb.js +5 -0
- package/dist/jobs/assets/main-pegp0ygb.js.map +10 -0
- package/dist/jobs/assets/main-qr573853.js +5 -0
- package/dist/jobs/assets/main-qr573853.js.map +12 -0
- package/dist/jobs/assets/main-rq97rdr4.js +4 -0
- package/dist/jobs/assets/main-rq97rdr4.js.map +22 -0
- package/dist/jobs/assets/main-szjdmydc.js +5 -0
- package/dist/jobs/assets/main-szjdmydc.js.map +13 -0
- package/dist/jobs/assets/main-tnbhrm9n.js +7 -0
- package/dist/jobs/assets/main-tnbhrm9n.js.map +11 -0
- package/dist/jobs/assets/main-ts19j4n0.js +5 -0
- package/dist/jobs/assets/main-ts19j4n0.js.map +25 -0
- package/dist/jobs/assets/main-w93x1dzs.js +5 -0
- package/dist/jobs/assets/main-w93x1dzs.js.map +10 -0
- package/dist/jobs/assets/sections-0ksdxs8m.css +1 -0
- package/dist/jobs/assets/sections-0y7w4fb4.js +4 -0
- package/dist/jobs/assets/sections-0y7w4fb4.js.map +10 -0
- package/dist/jobs/assets/sse-mn0t0fh7.js +5 -0
- package/dist/jobs/assets/sse-mn0t0fh7.js.map +11 -0
- package/dist/jobs/assets/sseChecks-t7tcm7ra.js +4 -0
- package/dist/jobs/assets/sseChecks-t7tcm7ra.js.map +10 -0
- package/dist/jobs/assets/validation-ysjfy072.js +4 -0
- package/dist/jobs/assets/validation-ysjfy072.js.map +9 -0
- package/dist/jobs/manifest.json +511 -0
- package/dts/docs/assets.d.ts +144 -0
- package/dts/docs/assets.d.ts.map +1 -0
- package/dts/docs/browser/config.d.ts +61 -0
- package/dts/docs/browser/config.d.ts.map +1 -0
- package/dts/docs/browser/index.d.ts +12 -0
- package/dts/docs/browser/index.d.ts.map +1 -0
- package/dts/docs/docsUi.d.ts +42 -0
- package/dts/docs/docsUi.d.ts.map +1 -0
- package/dts/docs/hosted.d.ts +22 -0
- package/dts/docs/hosted.d.ts.map +1 -0
- package/dts/docs/index.d.ts +18 -0
- package/dts/docs/index.d.ts.map +1 -0
- package/dts/docs/shell.d.ts +57 -0
- package/dts/docs/shell.d.ts.map +1 -0
- package/dts/docs/types.d.ts +137 -0
- package/dts/docs/types.d.ts.map +1 -0
- package/dts/index.d.ts +21 -0
- package/dts/index.d.ts.map +1 -0
- package/dts/jobs/assets.d.ts +157 -0
- package/dts/jobs/assets.d.ts.map +1 -0
- package/dts/jobs/browser/config.d.ts +67 -0
- package/dts/jobs/browser/config.d.ts.map +1 -0
- package/dts/jobs/browser/index.d.ts +10 -0
- package/dts/jobs/browser/index.d.ts.map +1 -0
- package/dts/jobs/browser/unauthorized.d.ts +9 -0
- package/dts/jobs/browser/unauthorized.d.ts.map +1 -0
- package/dts/jobs/index.d.ts +24 -0
- package/dts/jobs/index.d.ts.map +1 -0
- package/dts/jobs/jobsUi.d.ts +63 -0
- package/dts/jobs/jobsUi.d.ts.map +1 -0
- package/dts/jobs/shell.d.ts +77 -0
- package/dts/jobs/shell.d.ts.map +1 -0
- package/dts/jobs/types.d.ts +146 -0
- package/dts/jobs/types.d.ts.map +1 -0
- package/dts/kit/browser/shared.d.ts +10 -0
- package/dts/kit/browser/shared.d.ts.map +1 -0
- package/dts/kit/browser/unauthorized.d.ts +74 -0
- package/dts/kit/browser/unauthorized.d.ts.map +1 -0
- package/dts/kit/unauthorized.d.ts +47 -0
- package/dts/kit/unauthorized.d.ts.map +1 -0
- package/guides/docs.md +170 -0
- package/guides/jobs.md +1120 -0
- package/lib/docs/assets.ts +427 -0
- package/lib/docs/browser/config.ts +66 -0
- package/lib/docs/browser/index.ts +24 -0
- package/lib/docs/docsUi.ts +623 -0
- package/lib/docs/hosted.ts +31 -0
- package/lib/docs/index.ts +37 -0
- package/lib/docs/shell.ts +156 -0
- package/lib/docs/types.ts +149 -0
- package/lib/index.ts +27 -0
- package/lib/jobs/assets.ts +448 -0
- package/lib/jobs/browser/config.ts +65 -0
- package/lib/jobs/browser/index.ts +9 -0
- package/lib/jobs/browser/unauthorized.ts +11 -0
- package/lib/jobs/index.ts +41 -0
- package/lib/jobs/jobsUi.ts +709 -0
- package/lib/jobs/shell.ts +205 -0
- package/lib/jobs/types.ts +162 -0
- package/lib/kit/browser/shared.ts +11 -0
- package/lib/kit/browser/unauthorized.ts +114 -0
- package/lib/kit/unauthorized.ts +222 -0
- package/package.json +198 -4
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../bun-client/lib/channel.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"/**\n * `@kingsleyweb/bun-client/channel`: the typed WebSocket channel client\n * (typed-client plan, `websocket.md`, PR-W2).\n *\n * ```ts\n * import { channel } from \"@kingsleyweb/bun-client/channel\";\n * import { chat } from \"../shared/chat.contract\"; // channelContract({ … }), browser-safe\n *\n * const room = channel(api, chat, { params: { room: \"lobby\" } });\n * room.send(\"say\", { text: \"hi\" }); // typed by chat.spec.receives.say\n * const said = await room.request(\"say\", { text: \"hi\" }); // typed by sends[replies.say]\n * const off = room.on(\"said\", (m) => console.log(m.by, m.text));\n * for await (const m of room.messages(\"someoneTyping\")) … // ends on close()\n * room.close();\n * ```\n *\n * - **Types** (W-D1, §3): a `channelContract()` result is inferred (the\n * client sends each message's wire input, `WireInput`, and receives it\n * after JSON, `Jsonify`); a generated descriptor (`bun-client generate\n * --asyncapi`, `channels[\"/rooms/:room\"]`) carries the generated\n * `Channels` entry, the same shape.\n * - **One socket per URL** (§4.1): a second `channel()` on the same URL\n * shares the first one's socket and its transport options (reconnection,\n * heartbeat, ping, offline buffer, codec), refcounted; it closes when the\n * last handle does.\n * - **Requests** (§4.2): `request(type, data)` stamps the envelope's id\n * field with a counter per socket and resolves with the reply type's frame\n * carrying the same id; an error frame with that id rejects with\n * {@link ChannelError}. Default timeout: the client's `timeout` (30 s). A\n * drop rejects every waiting request with {@link DisconnectedError},\n * unless the call set `retry: true` (it is sent again after the reconnect).\n * - **Validation** (§5, TC §5.6): a send through the contract's sender guard\n * (before anything is written; a failure throws `ValidationError`), a\n * received frame through its receiver guard (a failure warns and delivers;\n * `\"drop\"` discards it and fires `$invalid`; `\"throw\"` rejects a waiting\n * request), on in development and off in production, BYO per message\n * type. The checks load lazily (`./channelChecks`); a send made before they\n * loaded (or checked by an asynchronous schema) is checked in order once\n * they have, and its failure, which can no longer throw to the caller,\n * reaches `on(\"$error\")`. The error frame is always checked for its fixed\n * shape.\n * - **Reconnection** (§7): 500 ms doubling to 30 s, half-jittered, the count\n * reset once a socket stays open 60 s; never after a close code in\n * `fatalCloseCodes` (1007, 1008, 4400–4499). A declared heartbeat arms a\n * watchdog (2 × its interval + 5 s of silence closes with 4000 and\n * reconnects); a declared ping is sent after `intervalMs` of silence.\n * `resubscribe(room)` runs after every open, before the offline buffer\n * (100 messages by default) is flushed in order.\n * - **Backpressure** (§8): `bufferedAmount`, and `sendWhenReady()`, which\n * waits (an animation frame at a time, 16 ms outside a browser) until the\n * socket's buffer is under `highWaterMark` (1 MiB).\n * - **Codecs** (W-D7, §11): `\"bun-common\"` (the default), `\"bun-nest\"` (a\n * bun-nest gateway: `request()` resolves with the gateway's ack, typed by\n * its `@ValidateMessage` `ack` schema), or a custom `{ encode, decode }`.\n *\n * Browser-safe; it imports nothing of the `.` entry's client (it reads the\n * client's configuration through the client object, as `./sse` does), so an\n * HTTP-only bundle carries none of it.\n */\nimport type {\n ChannelContract,\n ChannelErrorCode,\n ChannelErrorFrame,\n ChannelMessage,\n ChannelSpec,\n StandardSchemaV1,\n WireInput,\n} from \"@kingsleyweb/bun-contract\";\nimport type { ChannelChecks, Verdict } from \"./channelChecks\";\nimport type { Jsonify } from \"./jsonify\";\nimport type { ClientConfig, FailureMode, PartValidation } from \"./types\";\nimport type { ChannelFailureCode } from \"./errors\";\nimport {\n ChannelClosedError,\n ChannelError,\n ClientError,\n DisconnectedError,\n RequestTimeoutError,\n ValidationError,\n} from \"./errors\";\n\nexport {\n ChannelClosedError,\n ChannelError,\n DisconnectedError,\n RequestTimeoutError,\n} from \"./errors\";\n\n/* ------------------------------------------------------------------------ */\n/* Types */\n/* ------------------------------------------------------------------------ */\n\n/**\n * One channel's types, the generated `Channels` map's entry shape (and what\n * a `channelContract()` spec is read as, {@link ChannelEntryOf}). The sides\n * keep the server's names: the client sends what the server `receives` and\n * receives what it `sends` (§3.3).\n */\nexport interface ChannelEntry {\n /** The path params, as the URL takes them. */\n params?: object;\n /** The upgrade's query, when the channel declares one. */\n query?: object;\n /** Client → server: each message type to its data's wire type; `undefined` for a message with no data. */\n receives: object;\n /** Server → client: each message type to its data's wire type (the client sees it after JSON). */\n sends: object;\n /** A received type to the sent type answering it, where one does. */\n replies?: object;\n}\n\n/** A message map's entry as the wire carries it: the schema's `WireInput`, `undefined` for `null`. */\ntype DataOf<D> = D extends StandardSchemaV1 ? WireInput<D> : undefined;\n\n/** A message map's types. */\ntype MessagesOf<M> = { -readonly [T in keyof M]: DataOf<M[T]> };\n\n/** A path param's name, up to the first character a name does not take. */\ntype NameOf<S extends string> =\n S extends `${infer N}${\".\" | \"-\" | \"{\" | \"}\" | \"(\" | \"?\"}${string}`\n ? NameOf<N>\n : S;\n\n/** The params of a path literal (`/rooms/:room` gives `{ room: string }`; `*rest` a `string[]`). */\ntype PathParamsOf<P extends string> =\n P extends `${string}/${infer Segment}/${infer Rest}`\n ? PathParamsOf<`/${Segment}`> & PathParamsOf<`/${Rest}`>\n : P extends `${string}/:${infer Name}`\n ? { [K in NameOf<Name>]: string }\n : P extends `${string}/*${infer Name}`\n ? { [K in NameOf<Name>]: string[] }\n : Record<never, never>;\n\n/** Flattens an intersection. */\ntype Simplify<T> = { [K in keyof T]: T[K] } & {};\n\n/** A declared upgrade part's wire input, else `F`. */\ntype RequestPart<S, P extends string, F> = S extends {\n readonly request: { readonly [K in P]: infer X extends StandardSchemaV1 };\n}\n ? WireInput<X>\n : F;\n\n/**\n * A `channelContract()` spec read as a {@link ChannelEntry}: each message's\n * `WireInput` (`undefined` for a `null` one), the params from the upgrade's\n * `params` schema or else the path literal, the query when one is declared,\n * and the replies.\n */\nexport type ChannelEntryOf<S extends ChannelSpec> = Simplify<\n {\n params: RequestPart<S, \"params\", Simplify<PathParamsOf<S[\"path\"]>>>;\n receives: MessagesOf<S[\"receives\"]>;\n sends: MessagesOf<S[\"sends\"]>;\n replies: S extends { readonly replies: infer R }\n ? { -readonly [K in keyof R]: R[K] }\n : Record<never, never>;\n } & (S extends { readonly request: { readonly query: StandardSchemaV1 } }\n ? { query: RequestPart<S, \"query\", never> }\n : Record<never, never>)\n>;\n\n/**\n * A generated channel's runtime facts (`bun-client generate --asyncapi`\n * writes one per channel, `channels[\"/rooms/:room\"]`), typed by its\n * `Channels` entry: what {@link channel} takes in place of a contract.\n */\nexport interface ChannelDescriptor<E extends ChannelEntry = ChannelEntry> {\n /** The route path the channel is served on. */\n readonly path: string;\n /** The envelope's correlation field. Default `\"id\"`. */\n readonly idField?: string;\n /** A received type to the sent type answering it. Default: none. */\n readonly replies?: Readonly<Record<string, string>>;\n /** The subprotocol the upgrade answers with, offered on connect. Default: none. */\n readonly subprotocol?: string;\n /**\n * The envelope the server speaks, used when `channel()`'s options name\n * none: `\"bun-nest\"` for a document `gatewayAsyncApi()` wrote. Default\n * `\"bun-common\"`.\n */\n readonly codec?: \"bun-common\" | \"bun-nest\";\n /** The entry's type, carried for inference only (never set). */\n readonly \"~entry\"?: E;\n}\n\n/** A generated `channels` object's type: a {@link ChannelDescriptor} per `Channels` entry. */\nexport type ChannelDescriptors<C> = {\n readonly [K in keyof C]: ChannelDescriptor<C[K] & ChannelEntry>;\n};\n\n/** The keys of `T` whose property is required. */\ntype RequiredKeys<T> = {\n [K in keyof T]-?: Record<never, never> extends Pick<T, K> ? never : K;\n}[keyof T];\n\n/** `{ [K]: T }`: required when `T` has a required key, else optional (`Record<string, never>` for none). */\ntype Part<K extends string, T> = [keyof T] extends [never]\n ? { [P in K]?: Record<string, never> }\n : [RequiredKeys<T>] extends [never]\n ? { [P in K]?: T }\n : { [P in K]: T };\n\n/** The URL's typed parts for entry `E`. */\ntype UrlParts<E> = Part<\n \"params\",\n E extends { params: infer P } ? P : Record<never, never>\n> &\n (E extends { query: infer Q } ? Part<\"query\", Q> : { query?: object });\n\n/** `channel()`'s options: the URL's parts and the channel's own. */\ntype ChannelOpenOptions<E extends ChannelEntry> = UrlParts<E> &\n ChannelOptions<E>;\n\n/** `channel()`'s trailing argument: required when a URL part is. */\ntype OpenArgs<E extends ChannelEntry> = [RequiredKeys<UrlParts<E>>] extends [\n never,\n]\n ? [options?: ChannelOpenOptions<E>]\n : [options: ChannelOpenOptions<E>];\n\n/** What a channel's own validation level takes (§5, TC §5.6): per direction, per message type. */\nexport interface ChannelValidationConfig {\n /**\n * What the client sends: `true` runs the built-in schemas, `false` turns\n * every check off (BYO included), or one entry per message type. Default:\n * the client's `validate.request`, else on in development, off in\n * production.\n */\n send?: boolean | { readonly [type: string]: PartValidation };\n /**\n * What the client receives (pushes and replies), as `send` does. Default:\n * the client's `validate.response`, else on in development, off in\n * production.\n */\n receive?: boolean | { readonly [type: string]: PartValidation };\n /**\n * What a failure does: a send `\"throw\"`s (before anything is written),\n * `\"warn\"`s and sends, or calls a hook; a received frame `\"warn\"`s and is\n * delivered, is dropped (`\"drop\"`, firing `$invalid`), `\"throw\"`s (a\n * waiting request rejects; a push is dropped and reaches `$error`), or\n * calls a hook. Default: `{ send: \"throw\", receive: \"warn\" }`, or the\n * client's `onFailure.request` / `.response`.\n */\n onFailure?:\n | FailureMode\n | {\n /** For a message sent. Default: `\"throw\"`. */\n send?: FailureMode;\n /** For a frame received. Default: `\"warn\"`. */\n receive?: FailureMode | \"drop\";\n };\n}\n\n/**\n * A codec (W-D7): how a message is written and how a frame is read.\n * `\"bun-common\"` (the default) is `defineChannel`'s envelope, `{ type,\n * <idField>?, data }` as JSON, its error frame `{ type: \"error\", id?, code,\n * status, issues?, detail? }`. `\"bun-nest\"` is bun-nest's socket.io-style\n * packets (`BunWebSocketAdapter`'s `MessageEventTypes`): a message is an\n * `EVENT` `{ type: 2, namespace: \"/\", id?, data: [type, data] }` (a\n * `BINARY_EVENT`, its data base64, when the data is bytes), a request's\n * reply the `ACK` `{ type: 3, id, data: [ack] }` the gateway answers it\n * with.\n */\nexport interface ChannelCodec {\n /** Writes one message (its `id` set on a request) for `WebSocket.send`. */\n encode: (\n message: ChannelMessage,\n envelope: { readonly idField: string },\n ) => Payload;\n /**\n * Reads one frame: a message, an error frame (`type: \"error\"` with a\n * `code`), or `undefined` to ignore it. A throw is logged and the frame\n * ignored.\n */\n decode: (\n data: string | ArrayBuffer,\n envelope: { readonly idField: string },\n ) => ChannelMessage | CodecErrorFrame | undefined;\n}\n\n/**\n * An error frame a codec returns: the contract's {@link ChannelErrorFrame},\n * its `code` widened by `\"EXCEPTION\"` (what the `\"bun-nest\"` codec makes of a\n * failed handler's `exception` frame).\n */\nexport type CodecErrorFrame = Omit<ChannelErrorFrame, \"code\"> & {\n /** What failed. */\n code: ChannelFailureCode;\n};\n\n/**\n * A generated channel's runtime schemas: the zod module's `channelSchemas`\n * entry for its path (`bun-client generate --asyncapi … --zod`), which the\n * channel reads through the client's `schemas` source. Each message type's\n * data schema; `null` for a message with no data.\n */\nexport interface ChannelSchemas {\n /** Client → server messages. */\n readonly receives?: { readonly [type: string]: StandardSchemaV1 | null };\n /** Server → client messages. */\n readonly sends?: { readonly [type: string]: StandardSchemaV1 | null };\n}\n\n/** What `WebSocket.send` takes: what a codec encodes a message to. */\ntype Payload = Parameters<WebSocket[\"send\"]>[0];\n\n/** A close code, or an inclusive range of them. */\ntype CloseCodes = readonly (number | readonly [from: number, to: number])[];\n\n/** {@link channel}'s own options (§7, §13.3). */\nexport interface ChannelOptions<E extends ChannelEntry = ChannelEntry> {\n /**\n * Reconnection after a drop, exponential and half-jittered; `false` never\n * reconnects (any close then ends the channel). Default: `{ initialMs:\n * 500, maxMs: 30_000, stableAfterMs: 60_000 }`.\n */\n reconnect?:\n | {\n /** The first delay's ceiling, ms. Default `500`. */\n initialMs?: number;\n /** The largest delay, ms. Default `30_000`. */\n maxMs?: number;\n /** How long a socket stays open before the count starts over, ms. Default `60_000`. */\n stableAfterMs?: number;\n }\n | false;\n /**\n * Close codes the client never reconnects after (each a code or an\n * inclusive `[from, to]` range): the channel ends with\n * {@link ChannelClosedError}. Default: `1007` (bun-common's\n * `closeOnInvalid`), `1008` (policy) and `4400`–`4499`.\n */\n fatalCloseCodes?: CloseCodes;\n /**\n * The server's heartbeat: a message it sends every `intervalMs`. Silence\n * of `2 × intervalMs + slackMs` (any frame counts) closes the socket with\n * `4000` and reconnects. Default: none (a bun-common channel sends none).\n */\n heartbeat?: {\n /** The heartbeat's message type. */\n type: keyof E[\"sends\"] & string;\n /** The server's interval, ms. */\n intervalMs: number;\n /** Extra silence allowed, ms. Default `5_000`. */\n slackMs?: number;\n };\n /**\n * A ping the client sends after `intervalMs` without a frame (a message\n * with no data). Default: none.\n */\n ping?: {\n /** The ping's message type. */\n type: keyof E[\"receives\"] & string;\n /** Silence before a ping, ms. */\n intervalMs: number;\n };\n /**\n * Messages sent while disconnected: buffered (at most `buffer`) and\n * flushed in order on open, a send over the limit throwing\n * {@link ChannelClosedError}; `\"fail\"` throws at once. Default:\n * `{ buffer: 100 }`.\n */\n offline?: { buffer: number } | \"fail\";\n /**\n * Runs after every open (the first included), before the offline buffer\n * flushes: where the app re-sends its own `subscribe` messages and resume\n * cursor (§4.3, §7). Messages it sends go out at once. A throw is logged.\n * Default: none.\n */\n resubscribe?: (room: ChannelClient<E>) => void | Promise<void>;\n /**\n * The envelope: `\"bun-common\"` (a `defineChannel` channel), `\"bun-nest\"`\n * (a bun-nest gateway: socket.io-style packets, a request answered by the\n * gateway's ack), or a custom {@link ChannelCodec}. Default: a generated\n * descriptor's `codec`, else `\"bun-common\"`.\n */\n codec?: \"bun-common\" | \"bun-nest\" | ChannelCodec;\n /**\n * This channel's validation, over the client's (`createClient`'s\n * `validate`: `request` for sends, `response` for what arrives); `false`\n * turns it all off. Default: the client's.\n */\n validate?: ChannelValidationConfig | false;\n}\n\n/** A message's arguments after its type: its data (none for a message without), then the call's options. */\ntype DataArgs<D, O> = [D] extends [undefined]\n ? [data?: undefined, options?: O]\n : undefined extends D\n ? [data?: D, options?: O]\n : [data: D, options?: O];\n\n/** The types the client sends. */\ntype SendType<E extends ChannelEntry> = keyof E[\"receives\"] & string;\n\n/** The types the client receives. */\ntype PushType<E extends ChannelEntry> = keyof E[\"sends\"] & string;\n\n/** The type answering `T`, `never` when none does. */\ntype ReplyOf<E extends ChannelEntry, T> = E extends { replies: infer R }\n ? T extends keyof R\n ? R[T] & PushType<E>\n : never\n : never;\n\n/** The types `request()` takes: those `replies` maps. */\ntype RequestType<E extends ChannelEntry> = {\n [T in SendType<E>]: [ReplyOf<E, T>] extends [never] ? never : T;\n}[SendType<E>];\n\n/** A received message's data, after JSON. */\ntype Data<E extends ChannelEntry, T> = Jsonify<\n E[\"sends\"][T & keyof E[\"sends\"]]\n>;\n\n/** A push as `messages()` yields it with no type. */\ntype PushOf<E extends ChannelEntry> = {\n [T in PushType<E>]: { type: T; data: Data<E, T> };\n}[PushType<E>];\n\n/** The channel's state. */\ntype ChannelState = \"connecting\" | \"open\" | \"reconnecting\" | \"closed\";\n\n/** What `on` takes: a received type, or a client event. */\ntype EventName<E extends ChannelEntry> =\n PushType<E> | \"$state\" | \"$error\" | \"$invalid\";\n\n/** The listener for `N`. */\ntype Listener<E extends ChannelEntry, N> = N extends \"$state\"\n ? (state: ChannelState) => void\n : N extends \"$error\"\n ? (error: ChannelError | ValidationError | ChannelClosedError) => void\n : N extends \"$invalid\"\n ? (error: ValidationError) => void\n : (data: Data<E, N>, frame: { type: N; id?: string }) => void;\n\n/** `send`'s options. */\ninterface SendOptions {\n /** This message's validation, over the channel's and the client's; `false` skips it. Default: the channel's. */\n validate?: PartValidation;\n}\n\n/** `request`'s options. */\ninterface RequestOptions extends SendOptions {\n /** Milliseconds before the request rejects with {@link RequestTimeoutError}; `false` for none. Default: the client's `timeout` (30 s). */\n timeout?: number | false;\n /** Send it again after a reconnect instead of rejecting with {@link DisconnectedError} (for an idempotent request). Default `false`. */\n retry?: boolean;\n}\n\n/** `sendWhenReady`'s options. */\ninterface ReadyOptions extends SendOptions {\n /** The socket's `bufferedAmount` to wait under, bytes. Default `1_048_576` (1 MiB). */\n highWaterMark?: number;\n}\n\n/** A typed channel (§4): what {@link channel} returns. */\nexport interface ChannelClient<E extends ChannelEntry = ChannelEntry> {\n /**\n * Sends a message the server receives, checked first (a failure throws\n * `ValidationError` and writes nothing); buffered while disconnected.\n * Never waits. Throws {@link ChannelClosedError} once the channel is\n * closed, with `offline: \"fail\"` while disconnected, or when the offline\n * buffer is full.\n */\n send: <T extends SendType<E>>(\n type: T,\n ...args: DataArgs<E[\"receives\"][T], SendOptions>\n ) => void;\n /** Sends a request and resolves with its reply's data (a type `replies` maps). */\n request: <T extends RequestType<E>>(\n type: T,\n ...args: DataArgs<E[\"receives\"][T], RequestOptions>\n ) => Promise<Data<E, ReplyOf<E, T>>>;\n /** Waits until {@link bufferedAmount} is under `highWaterMark`, then sends; rejects where `send` throws. */\n sendWhenReady: <T extends SendType<E>>(\n type: T,\n ...args: DataArgs<E[\"receives\"][T], ReadyOptions>\n ) => Promise<void>;\n /**\n * Listens for a pushed message type (a frame without an id, or with one no\n * request of this client sent), or a client event: `$state` (every state\n * change), `$error` (an error frame no request waits for, a deferred\n * send's failure, a push refused under `\"throw\"`, the close that ended the\n * channel), `$invalid` (a frame `\"drop\"` discarded). Returns the\n * function that removes it. A listener that throws is logged.\n */\n on: <N extends EventName<E>>(name: N, listener: Listener<E, N>) => () => void;\n /**\n * The pushes, as an async iterator (every type as `{ type, data }`, or one\n * type's data); it ends when the channel closes. It buffers what it has\n * not taken yet.\n */\n messages: {\n (): AsyncIterableIterator<PushOf<E>>;\n <T extends PushType<E>>(type: T): AsyncIterableIterator<Data<E, T>>;\n };\n /** Closes this handle: its requests reject with {@link ChannelClosedError}, its iterators end; the socket closes with the last handle. */\n close: () => void;\n /** `\"connecting\"` until the first open, `\"open\"`, `\"reconnecting\"` after a drop, `\"closed\"`. */\n readonly state: ChannelState;\n /** The socket's `bufferedAmount`: bytes queued, not yet sent (`0` while disconnected). */\n readonly bufferedAmount: number;\n}\n\n/* ------------------------------------------------------------------------ */\n/* Runtime */\n/* ------------------------------------------------------------------------ */\n\n/** The symbol the client keeps its configuration under (`client.ts`). */\nconst CONFIG = Symbol.for(\"@kingsleyweb/bun-client\");\n\n/** The error codes an error frame may carry. */\nconst CODES: readonly ChannelErrorCode[] = [\n \"VALIDATION\",\n \"UNKNOWN_MESSAGE\",\n \"UNSUPPORTED_DATA\",\n];\n\n/** The codes the connection accepts on an error frame: {@link CODES} and `\"EXCEPTION\"`. */\nconst FRAME_CODES: readonly ChannelFailureCode[] = [...CODES, \"EXCEPTION\"];\n\n/** The default fatal close codes (§7). */\nconst FATAL: CloseCodes = [1007, 1008, [4400, 4499]];\n\n/** The `\"bun-common\"` codec: `defineChannel`'s envelope. */\nconst BUN_COMMON: ChannelCodec = {\n encode: ({ type, id, data }, { idField }) =>\n JSON.stringify(\n id === undefined ? { type, data } : { type, [idField]: id, data },\n ),\n decode(raw, { idField }) {\n const frame = JSON.parse(\n typeof raw === \"string\" ? raw : new TextDecoder().decode(raw),\n ) as Record<string, unknown> | null;\n if (typeof frame?.type !== \"string\") return undefined;\n // bun-common's error frame carries the echoed id as `id` whatever the\n // channel's idField (`channelErrorFrame`); a message under the idField.\n const id =\n frame[idField] ?? (frame.type === \"error\" ? frame.id : undefined);\n if (frame.type === \"error\" && \"code\" in frame)\n return {\n ...frame,\n id: typeof id === \"string\" ? id : undefined,\n } as ChannelErrorFrame;\n return typeof id === \"string\"\n ? { type: frame.type, id, data: frame.data }\n : { type: frame.type, data: frame.data };\n },\n};\n\n/** Whether `value` is bytes (bun-nest's adapter sends those base64, in a binary packet). */\nconst isBytes = (value: unknown): value is ArrayBufferView | ArrayBuffer =>\n ArrayBuffer.isView(value) || value instanceof ArrayBuffer;\n\n/** A binary packet's base64 arguments (those `binary` marks, every string without it) as bytes. */\nfunction unbase64(args: unknown[], marks: unknown): unknown[] {\n return args.map((arg, at) =>\n typeof arg === \"string\" && (!Array.isArray(marks) || marks.includes(at))\n ? Uint8Array.from(atob(arg), (char) => char.charCodeAt(0))\n : arg,\n );\n}\n\n/** An event's arguments as one payload, as bun-nest's adapter maps them: the one, an array of several, none. */\nconst payloadOf = (args: unknown[]): unknown =>\n args.length > 1 ? args : args[0];\n\n/**\n * The `\"bun-nest\"` codec (§11): bun-nest's packets, `MessageEventTypes`\n * (`EVENT` 2, `ACK` 3, `ERROR` 4, `BINARY_EVENT` 5, `BINARY_ACK` 6).\n *\n * - A message is an `EVENT` `[type, data]` (`[type]` with no data), a\n * request's carrying the id as a number, as socket.io's client sends it;\n * bytes are a `BINARY_EVENT`, base64.\n * - An `ACK` (or `BINARY_ACK`) is the reply of the request with its id,\n * read as that request's reply type (`replyOf`); one no request waits for\n * is ignored.\n * - An `EVENT` from the server is a push, never a reply (a server event\n * asking for an ack, `emitWithAck`, is not supported: its id is dropped).\n * `[\"exception\", { status, code, issues?, ackId? }]`, what bun-nest's\n * `ValidationExceptionFilter` emits, is an error frame. When the frame\n * carries `ackId`, the id of the event that failed, the request with that\n * id rejects at once with its {@link ChannelError} (and `$error` is not\n * emitted, as for any error frame a request waits for); without one (a\n * server before `ackId`) it reaches `$error` and the request times out.\n * Any other `exception` is a push of that type, except one with an\n * `ackId` a request waits on (a `WsException` or a thrown handler): that\n * request rejects with a `ChannelError` of code `\"EXCEPTION\"`, the\n * frame's `message` as `detail`.\n * - `ERROR` is a push of type `\"error\"`, its data the message; `CONNECT`\n * and `DISCONNECT` are ignored.\n *\n * @param replyOf The reply type of the request waiting with an id.\n */\nfunction bunNestCodec(\n replyOf: (id: string) => string | undefined,\n): ChannelCodec {\n return {\n encode: ({ type, id, data }) => {\n const binary = isBytes(data);\n let arg: unknown = data;\n if (binary) {\n const bytes = ArrayBuffer.isView(data)\n ? new Uint8Array(data.buffer, data.byteOffset, data.byteLength)\n : new Uint8Array(data);\n let text = \"\";\n for (const byte of bytes) text += String.fromCharCode(byte);\n arg = btoa(text);\n }\n return JSON.stringify({\n type: binary ? 5 : 2,\n namespace: \"/\",\n ...(id === undefined ? {} : { id: /^\\d+$/.test(id) ? Number(id) : id }),\n data: data === undefined ? [type] : [type, arg],\n ...(binary ? { binary: [0] } : {}),\n });\n },\n decode(raw) {\n const packet = JSON.parse(\n typeof raw === \"string\" ? raw : new TextDecoder().decode(raw),\n ) as { type?: unknown; id?: unknown; data?: unknown; binary?: unknown };\n const kind = packet?.type;\n if (kind === 4) return { type: \"error\", data: packet.data };\n if (!Array.isArray(packet?.data)) return undefined;\n const args: unknown[] =\n kind === 5 || kind === 6\n ? unbase64(\n kind === 5 ? packet.data.slice(1) : packet.data,\n // An event's marks count from its first argument, after the name.\n packet.binary,\n )\n : kind === 2\n ? packet.data.slice(1)\n : packet.data;\n if (kind === 3 || kind === 6) {\n const id =\n typeof packet.id === \"number\" || typeof packet.id === \"string\"\n ? String(packet.id)\n : undefined;\n const reply = id === undefined ? undefined : replyOf(id);\n return reply === undefined\n ? undefined\n : { type: reply, id, data: payloadOf(args) };\n }\n const type: unknown = packet.data[0];\n if ((kind !== 2 && kind !== 5) || typeof type !== \"string\")\n return undefined;\n const data = payloadOf(args) as Record<string, unknown> | undefined;\n if (type === \"exception\" && data !== undefined) {\n // The id of the event that failed, when it asked for an ack (`ackId`).\n const { ackId, ...frame } = data;\n const id =\n typeof ackId === \"number\" || typeof ackId === \"string\"\n ? String(ackId)\n : undefined;\n if (\n typeof frame.status === \"number\" &&\n CODES.includes(frame.code as ChannelErrorCode)\n )\n // The filter's frame, as an error frame (`Connection` reads `code`),\n // rejecting the request whose id it carries.\n return {\n ...frame,\n type: \"error\",\n ...(id === undefined ? {} : { id }),\n } as CodecErrorFrame;\n // Any other failed handler: when a request waits on that id the ack\n // will never come, so it fails now; otherwise a push, as before.\n if (id !== undefined && replyOf(id) !== undefined)\n return {\n type: \"error\",\n id,\n code: \"EXCEPTION\",\n status: typeof frame.status === \"number\" ? frame.status : 500,\n ...(typeof frame.message === \"string\"\n ? { detail: frame.message }\n : {}),\n };\n }\n return { type, data };\n },\n };\n}\n\n/** The checks module, imported on first use and shared by every channel. */\nlet checksModule: Promise<typeof import(\"./channelChecks\")> | undefined;\n\n/** The sockets, by URL and subprotocol. */\nconst SOCKETS = new Map<string, Connection>();\n\n/**\n * The one `process` read here, `process.env.NODE_ENV`, as `client.ts`\n * reads it (a type, erased; a bundler's define replaces it).\n */\ndeclare const process: {\n /** The environment; only `NODE_ENV` is read. */\n readonly env: Readonly<Record<string, string | undefined>>;\n};\n\n/** Development unless the configuration or `NODE_ENV` says production (TC §5.5). */\nfunction isDevelopment(config: ClientConfig): boolean {\n if (config.mode !== undefined) return config.mode === \"development\";\n try {\n return process.env.NODE_ENV !== \"production\";\n } catch {\n return true;\n }\n}\n\n/** Whether a validation level could turn a check on: an object with a key but `onFailure` that is not `false`. */\nfunction mayCheck(level: object | false | undefined): boolean {\n return (\n typeof level === \"object\" &&\n Object.entries(level).some(\n ([name, value]) =>\n name !== \"onFailure\" && value !== false && value !== undefined,\n )\n );\n}\n\n/** The page's `requestAnimationFrame`, when there is one (`lib/` is checked without the DOM's). */\nconst RAF = (\n globalThis as { requestAnimationFrame?: (callback: () => void) => number }\n).requestAnimationFrame;\n\n/** Waits one animation frame, or 16 ms outside a browser. */\nfunction nextFrame(): Promise<void> {\n return new Promise((resolve) =>\n typeof RAF === \"function\" ? RAF(() => resolve()) : setTimeout(resolve, 16),\n );\n}\n\n/** What a channel is, as a contract or a descriptor gives it. */\ninterface Facts {\n /** The route path. */\n readonly path: string;\n /** The envelope's correlation field. */\n readonly idField: string;\n /** The replies map. */\n readonly replies: Readonly<Record<string, string>>;\n /** The subprotocol to offer. */\n readonly subprotocol: string | undefined;\n /** The shared contract, when one was given. */\n readonly contract: ChannelContract | undefined;\n /** A generated descriptor's codec, when it names one. */\n readonly codec: \"bun-common\" | \"bun-nest\" | undefined;\n}\n\n/** A request waiting for its reply. */\ninterface Pending {\n /** Its correlation id. */\n readonly id: string;\n /** The handle that sent it. */\n readonly handle: Handle;\n /** Its message type. */\n readonly type: string;\n /** The reply's type. */\n readonly reply: string;\n /** The encoded frame, for a resend. */\n readonly payload: Payload;\n /** Send it again after a reconnect. */\n readonly retry: boolean;\n /** Settles it with the reply's data. */\n resolve: (value: unknown) => void;\n /** Settles it with an error. */\n reject: (error: unknown) => void;\n}\n\n/** One socket, shared by every handle on its URL. */\nclass Connection {\n /** The socket, while one is open or opening. */\n ws: WebSocket | undefined;\n /** The socket's state. */\n state: ChannelState = \"connecting\";\n /** The handles on it. */\n readonly handles = new Set<Handle>();\n /** The requests waiting, by id. */\n readonly pending = new Map<string, Pending>();\n /** Requests to send again on the next open (`retry: true`). */\n readonly resend: Pending[] = [];\n /** Messages written while disconnected. */\n readonly buffer: Payload[] = [];\n /** The id counter. */\n #next = 0;\n /** Reconnect attempts since the last stable socket. */\n #attempt = 0;\n /** The reconnect, stable, watchdog and ping timers. */\n readonly #timers: Record<\n \"reconnect\" | \"stable\" | \"watchdog\" | \"ping\",\n ReturnType<typeof setTimeout> | undefined\n > = {\n reconnect: undefined,\n stable: undefined,\n watchdog: undefined,\n ping: undefined,\n };\n /** The codec. */\n readonly codec: ChannelCodec;\n /** The envelope, for the codec. */\n readonly envelope: { readonly idField: string };\n\n /**\n * @param key Its key in {@link SOCKETS}.\n * @param url The socket URL.\n * @param facts The channel's facts.\n * @param options The first handle's options (the transport's).\n * @param logger Where problems are logged.\n */\n constructor(\n readonly key: string,\n readonly url: string,\n readonly facts: Facts,\n readonly options: ChannelOptions,\n readonly logger: Pick<Console, \"warn\" | \"error\">,\n ) {\n const codec = options.codec ?? facts.codec;\n this.codec =\n codec === \"bun-nest\"\n ? bunNestCodec((id) => this.pending.get(id)?.reply)\n : codec === undefined || codec === \"bun-common\"\n ? BUN_COMMON\n : codec;\n this.envelope = { idField: facts.idField };\n this.#open();\n }\n\n /** Opens a socket. */\n #open(): void {\n const ws = new WebSocket(\n this.url,\n this.facts.subprotocol === undefined ? [] : [this.facts.subprotocol],\n );\n ws.binaryType = \"arraybuffer\";\n this.ws = ws;\n ws.onopen = () => void this.#opened(ws);\n ws.onmessage = (event) =>\n this.#frame(ws, event.data as string | ArrayBuffer);\n ws.onclose = (event) => this.#dropped(ws, event.code, event.reason);\n }\n\n /** Moves to `state`, telling every handle. */\n #state(state: ChannelState): void {\n if (this.state === state) return;\n this.state = state;\n for (const handle of this.handles) handle.emit(\"$state\", state);\n }\n\n /** The socket opened: resubscribe, resend, flush. */\n async #opened(ws: WebSocket): Promise<void> {\n if (ws !== this.ws) return;\n this.#state(\"open\");\n this.#arm();\n const stableAfter =\n this.options.reconnect === false\n ? 0\n : (this.options.reconnect?.stableAfterMs ?? 60_000);\n this.#timers.stable = setTimeout(() => {\n this.#attempt = 0;\n }, stableAfter);\n for (const handle of [...this.handles]) {\n try {\n await handle.options.resubscribe?.(handle.client as never);\n } catch (error) {\n handle.logger.error(`${this.facts.path}: resubscribe threw`, error);\n }\n }\n if (ws !== this.ws || ws.readyState !== 1) return;\n for (const pending of this.resend.splice(0)) {\n this.pending.set(pending.id, pending);\n ws.send(pending.payload);\n }\n for (const payload of this.buffer.splice(0)) ws.send(payload);\n }\n\n /** (Re)arms the heartbeat watchdog and the ping timer: any frame counts. */\n #arm(): void {\n const { heartbeat, ping } = this.options;\n clearTimeout(this.#timers.watchdog);\n clearTimeout(this.#timers.ping);\n if (heartbeat !== undefined) {\n this.#timers.watchdog = setTimeout(\n () => this.#lost(),\n heartbeat.intervalMs * 2 + (heartbeat.slackMs ?? 5_000),\n );\n }\n if (ping !== undefined) {\n const send = (): void => {\n if (this.ws?.readyState === 1)\n this.ws.send(this.codec.encode({ type: ping.type }, this.envelope));\n this.#timers.ping = setTimeout(send, ping.intervalMs);\n };\n this.#timers.ping = setTimeout(send, ping.intervalMs);\n }\n }\n\n /** The heartbeat went quiet: close with 4000 and reconnect. */\n #lost(): void {\n const ws = this.ws;\n if (ws === undefined) return;\n ws.onclose = null;\n ws.close(4000, \"heartbeat lost\");\n this.#dropped(ws, 4000, \"heartbeat lost\");\n }\n\n /** Handles one frame. */\n #frame(ws: WebSocket, raw: string | ArrayBuffer): void {\n if (ws !== this.ws) return;\n this.#arm();\n let message: ChannelMessage | CodecErrorFrame | undefined;\n try {\n message = this.codec.decode(raw, this.envelope);\n } catch (error) {\n this.logger.warn(\n `${this.facts.path}: a frame could not be decoded`,\n error,\n );\n return;\n }\n if (message === undefined) return;\n const id = typeof message.id === \"string\" ? message.id : undefined;\n const pending = id === undefined ? undefined : this.pending.get(id);\n if (message.type === \"error\" && \"code\" in message) {\n // The error frame is always checked, with its fixed shape (§5).\n const { code, status, issues, detail } = message;\n if (\n !FRAME_CODES.includes(code) ||\n typeof status !== \"number\" ||\n (issues !== undefined && !Array.isArray(issues)) ||\n (detail !== undefined && typeof detail !== \"string\")\n ) {\n this.logger.warn(\n `${this.facts.path}: an error frame does not have the error frame's shape; ignored`,\n );\n return;\n }\n if (pending !== undefined) {\n this.pending.delete(id!);\n pending.reject(\n new ChannelError(this.facts.path, message, pending.type),\n );\n return;\n }\n const error = new ChannelError(this.facts.path, message);\n // Each handle's `$error`, or its own logger when it has none.\n for (const handle of this.handles)\n if (!handle.emit(\"$error\", error)) handle.logger.warn(error.message);\n return;\n }\n if (pending !== undefined && pending.reply === message.type) {\n this.pending.delete(id!);\n pending.handle.reply(pending, (message as ChannelMessage).data);\n return;\n }\n for (const handle of this.handles) handle.push(message);\n }\n\n /** The socket closed: reject or keep the requests, then reconnect or end. */\n #dropped(ws: WebSocket, code: number, reason: string): void {\n if (ws !== this.ws) return;\n this.ws = undefined;\n for (const timer of Object.values(this.#timers)) clearTimeout(timer);\n for (const [id, pending] of this.pending) {\n this.pending.delete(id);\n if (pending.retry) this.resend.push(pending);\n else pending.reject(new DisconnectedError(this.facts.path, code, reason));\n }\n if (this.state === \"closed\") return;\n const fatal = (this.options.fatalCloseCodes ?? FATAL).some((entry) =>\n typeof entry === \"number\"\n ? entry === code\n : code >= entry[0] && code <= entry[1],\n );\n if (this.options.reconnect === false || fatal) {\n this.end(\n new ChannelClosedError(\n this.facts.path,\n reason || \"the server closed the channel\",\n code,\n ),\n );\n return;\n }\n const { initialMs = 500, maxMs = 30_000 } = this.options.reconnect ?? {};\n const ceiling = Math.min(maxMs, initialMs * 2 ** this.#attempt++);\n this.#state(this.state === \"connecting\" ? \"connecting\" : \"reconnecting\");\n this.#timers.reconnect = setTimeout(\n () => this.#open(),\n ceiling / 2 + (ceiling / 2) * Math.random(),\n );\n }\n\n /** Ends the socket for good: every handle closed with `error` (none for the last `close()`). */\n end(error?: ChannelClosedError): void {\n SOCKETS.delete(this.key);\n for (const timer of Object.values(this.#timers)) clearTimeout(timer);\n const closed =\n error ?? new ChannelClosedError(this.facts.path, \"the channel is closed\");\n for (const pending of [...this.pending.values(), ...this.resend])\n pending.reject(closed);\n this.buffer.length = 0;\n const ws = this.ws;\n this.ws = undefined;\n ws?.close(1000);\n for (const handle of [...this.handles])\n handle.ended(closed, error !== undefined);\n this.state = \"closed\";\n }\n\n /** Writes `payload`, or buffers it while disconnected; throws when it can do neither. */\n write(payload: Payload): void {\n if (this.ws?.readyState === 1) {\n this.ws.send(payload);\n return;\n }\n const offline = this.options.offline ?? { buffer: 100 };\n if (offline === \"fail\" || this.buffer.length >= offline.buffer)\n throw new ChannelClosedError(\n this.facts.path,\n offline === \"fail\"\n ? 'disconnected (offline: \"fail\")'\n : `the offline buffer is full (${offline.buffer} messages)`,\n );\n this.buffer.push(payload);\n }\n\n /** Registers a request and writes it. */\n request(\n handle: Handle,\n type: string,\n data: unknown,\n options: RequestOptions,\n resolve: (value: unknown) => void,\n reject: (error: unknown) => void,\n ): void {\n const reply = this.facts.replies[type];\n if (reply === undefined)\n throw new ClientError(\n `${this.facts.path}: \"${type}\" has no reply declared (replies)`,\n { key: this.facts.path },\n );\n const id = String(++this.#next);\n const timeout = options.timeout ?? handle.timeout;\n let timer: ReturnType<typeof setTimeout> | undefined;\n const pending: Pending = {\n id,\n handle,\n type,\n reply,\n payload: this.codec.encode({ type, id, data }, this.envelope),\n retry: options.retry === true,\n resolve,\n reject,\n };\n // Settling (once) clears the timer and the request's every entry.\n const settle =\n <T>(fn: (value: T) => void) =>\n (value: T): void => {\n clearTimeout(timer);\n this.pending.delete(id);\n const at = this.resend.indexOf(pending);\n if (at !== -1) this.resend.splice(at, 1);\n handle.requests.delete(pending);\n fn(value);\n };\n pending.resolve = settle(resolve);\n pending.reject = settle(reject);\n this.write(pending.payload);\n this.pending.set(id, pending);\n handle.requests.add(pending);\n if (timeout !== false)\n timer = setTimeout(\n () =>\n pending.reject(\n new RequestTimeoutError(this.facts.path, type, id, timeout),\n ),\n timeout,\n );\n }\n}\n\n/** A drop of a frame `\"drop\"` asked for (or a refused push). */\nconst DROPPED = Symbol(\"dropped\");\n\n/** One iterator of `messages()`. */\ninterface Iteration {\n /** The type it yields, or every type. */\n readonly type: string | undefined;\n /** Takes a value. */\n readonly push: (value: unknown) => void;\n /** Ends it. */\n readonly end: () => void;\n}\n\n/** A handle's client as the runtime calls it (untyped; {@link ChannelClient} types it). */\ninterface RuntimeClient {\n /** `ChannelClient.send`. */\n send: (type: string, data?: unknown, options?: SendOptions) => void;\n /** `ChannelClient.request`. */\n request: (\n type: string,\n data?: unknown,\n options?: RequestOptions,\n ) => Promise<unknown>;\n /** `ChannelClient.sendWhenReady`. */\n sendWhenReady: (\n type: string,\n data?: unknown,\n options?: ReadyOptions,\n ) => Promise<void>;\n /** `ChannelClient.on`. */\n on: (name: string, listener: (...args: never[]) => void) => () => void;\n /** `ChannelClient.messages`. */\n messages: (type?: string) => AsyncIterableIterator<unknown>;\n /** `ChannelClient.close`. */\n close: () => void;\n /** `ChannelClient.state`. */\n readonly state: ChannelState;\n /** `ChannelClient.bufferedAmount`. */\n readonly bufferedAmount: number;\n}\n\n/** One `channel()` handle. */\nclass Handle {\n /** The client this handle is. */\n readonly client: RuntimeClient;\n /** The listeners, by name. */\n readonly #listeners = new Map<string, Set<(...args: never[]) => void>>();\n /** The `messages()` iterators. */\n readonly #iterations = new Set<Iteration>();\n /** Its requests waiting. */\n readonly requests = new Set<Pending>();\n /** The checks, once loaded; `undefined` while loading, or when nothing checks. */\n #checks: ChannelChecks | undefined;\n /** The checks loading; `undefined` when nothing checks. */\n readonly #loading: Promise<ChannelChecks | undefined> | undefined;\n /** Sends waiting for a check, in order. */\n #tail: Promise<void> = Promise.resolve();\n /** How many sends wait for a check. */\n #deferred = 0;\n /** Received frames, handled in order. */\n #rx: Promise<void> = Promise.resolve();\n /** Why this handle closed, once it has. */\n #closed: ChannelClosedError | undefined;\n /** The default request timeout. */\n readonly timeout: number | false;\n /**\n * Where this handle's warnings go: its own client's `logger` (a shared\n * socket logs connection problems through the first handle's).\n */\n readonly logger: Pick<Console, \"warn\" | \"error\">;\n\n /**\n * @param conn Its socket.\n * @param facts The channel's facts.\n * @param options Its options.\n * @param config The client's configuration.\n */\n constructor(\n readonly conn: Connection,\n readonly facts: Facts,\n readonly options: ChannelOptions,\n readonly config: ClientConfig,\n ) {\n this.timeout = config.timeout ?? 30_000;\n this.logger = config.logger ?? console;\n const development = isDevelopment(config);\n const hasSource =\n facts.contract !== undefined || config.schemas !== undefined;\n if (\n config.validate !== false &&\n options.validate !== false &&\n ((hasSource && (development || mayCheck(config.validate))) ||\n mayCheck(options.validate))\n ) {\n const source = config.schemas;\n this.#loading = (checksModule ??= import(\"./channelChecks\"))\n .then((module) =>\n module.createChannelChecks({\n key: facts.path,\n development,\n levels: [config.validate, options.validate],\n contract: facts.contract?.contract,\n meta: facts,\n schemas:\n facts.contract !== undefined || source === undefined\n ? undefined\n : async () =>\n (\n (typeof source === \"function\"\n ? await source()\n : source) as { channelSchemas?: object }\n ).channelSchemas?.[facts.path as never],\n ValidationError,\n }),\n )\n .then(\n (checks) => (this.#checks = checks),\n (error: unknown) => {\n this.logger.error(\n `${facts.path}: the channel's checks failed to load; nothing is checked`,\n error,\n );\n return undefined;\n },\n );\n }\n const state = (): ChannelState =>\n this.#closed === undefined ? conn.state : \"closed\";\n this.client = {\n send: (type, data, options) => {\n this.#alive();\n this.#outgoing(type, data, options?.validate, (value) =>\n conn.write(conn.codec.encode({ type, data: value }, conn.envelope)),\n )?.catch((error: unknown) => this.#report(error));\n },\n request: (type, data, options = {}) =>\n new Promise((resolve, reject) => {\n this.#alive();\n this.#outgoing(type, data, options.validate, (value) =>\n conn.request(this, type, value, options, resolve, reject),\n )?.catch(reject);\n }),\n sendWhenReady: async (type, data, options = {}) => {\n while (\n this.#alive() &&\n (conn.ws?.bufferedAmount ?? 0) >= (options.highWaterMark ?? 1 << 20)\n )\n await nextFrame();\n await this.#outgoing(type, data, options.validate, (value) =>\n conn.write(conn.codec.encode({ type, data: value }, conn.envelope)),\n );\n },\n on: (name, listener) => {\n let set = this.#listeners.get(name);\n if (set === undefined) this.#listeners.set(name, (set = new Set()));\n set.add(listener);\n return () => void set.delete(listener);\n },\n messages: (type) => this.#messages(type),\n close: () => this.#close(),\n get state() {\n return state();\n },\n get bufferedAmount() {\n return conn.ws?.bufferedAmount ?? 0;\n },\n };\n }\n\n /** Throws when this handle is closed; `true` otherwise. */\n #alive(): true {\n if (this.#closed !== undefined) throw this.#closed;\n return true;\n }\n\n /**\n * Checks one outgoing message and writes it with `write`: at once when the\n * check is synchronous and nothing waits before it (a failure under\n * `\"throw\"` throws here), else in order once its check settled (a promise\n * that rejects with the failure).\n */\n #outgoing(\n type: string,\n data: unknown,\n validate: PartValidation | undefined,\n write: (value: unknown) => void,\n ): Promise<void> | undefined {\n if (this.#loading === undefined) {\n write(data);\n return undefined;\n }\n const checks = this.#checks;\n if (checks !== undefined && this.#deferred === 0) {\n const verdict = checks.send(type, data, validate);\n if (!(verdict instanceof Promise)) {\n write(this.#sent(checks, verdict));\n return undefined;\n }\n }\n this.#deferred++;\n const run = this.#tail.then(async () => {\n const loaded = await this.#loading;\n this.#alive();\n if (loaded === undefined) return write(data);\n write(this.#sent(loaded, await loaded.send(type, data, validate)));\n });\n this.#tail = run.then(\n () => void this.#deferred--,\n () => void this.#deferred--,\n );\n return run;\n }\n\n /** A send's verdict under the failure mode: the value to write, or a throw. */\n #sent(checks: ChannelChecks, verdict: Verdict): unknown {\n if (verdict.error !== undefined) {\n const mode = checks.onSend;\n if (mode === \"throw\") throw verdict.error;\n if (mode === \"warn\") this.logger.warn(verdict.error.message);\n else mode(verdict.error);\n }\n return verdict.value;\n }\n\n /**\n * A received frame's verdict under the failure mode: the value to deliver,\n * or {@link DROPPED}; `reject` takes a `\"throw\"` (a request's), else it\n * reaches `$error`.\n */\n async #received(\n type: string,\n data: unknown,\n reject?: (error: unknown) => void,\n ): Promise<unknown> {\n const checks = this.#checks ?? (await this.#loading);\n if (checks === undefined) return data;\n const verdict = await checks.receive(type, data);\n const error = verdict.error;\n if (error === undefined) return verdict.value;\n const mode = checks.onReceive;\n if (mode === \"warn\") {\n this.logger.warn(error.message);\n return verdict.value;\n }\n if (mode === \"drop\") {\n this.emit(\"$invalid\", error);\n reject?.(error);\n return DROPPED;\n }\n try {\n if (mode === \"throw\") throw error;\n mode(error);\n return verdict.value;\n } catch (thrown) {\n if (reject === undefined) this.#report(thrown);\n else reject(thrown);\n return DROPPED;\n }\n }\n\n /** Hands a reply to its request, after the receive check (in order with the pushes). */\n reply(pending: Pending, data: unknown): void {\n this.#rx = this.#rx.then(async () => {\n const value = await this.#received(pending.reply, data, pending.reject);\n if (value !== DROPPED) pending.resolve(value);\n });\n }\n\n /** Hands a push to the listeners and iterators, after the receive check, in order. */\n push(message: ChannelMessage): void {\n if (this.#closed !== undefined) return;\n this.#rx = this.#rx.then(async () => {\n const value = await this.#received(message.type, message.data);\n if (value === DROPPED || this.#closed !== undefined) return;\n const frame =\n message.id === undefined\n ? { type: message.type }\n : { type: message.type, id: message.id };\n this.emit(message.type, value, frame);\n for (const iteration of this.#iterations) {\n if (iteration.type === undefined)\n iteration.push({ type: message.type, data: value });\n else if (iteration.type === message.type) iteration.push(value);\n }\n });\n }\n\n /** Calls the listeners of `name`; `true` when there was one. A listener that throws is logged. */\n emit(name: string, ...args: unknown[]): boolean {\n const set = this.#listeners.get(name);\n for (const listener of set ?? []) {\n try {\n (listener as (...values: unknown[]) => void)(...args);\n } catch (error) {\n this.logger.error(\n `${this.facts.path}: a \"${name}\" listener threw`,\n error,\n );\n }\n }\n return set !== undefined && set.size > 0;\n }\n\n /** A failure no caller can see: to `$error`, or the log. */\n #report(error: unknown): void {\n if (!this.emit(\"$error\", error))\n this.logger.warn((error as Error).message ?? error);\n }\n\n /** `messages()`: an iterator over the pushes. */\n #messages(type: string | undefined): AsyncIterableIterator<unknown> {\n const queue: unknown[] = [];\n let done = this.#closed !== undefined;\n let wake: (() => void) | undefined;\n const iteration: Iteration = {\n type,\n push: (value) => {\n queue.push(value);\n wake?.();\n },\n end: () => {\n done = true;\n wake?.();\n },\n };\n if (!done) this.#iterations.add(iteration);\n const iterator: AsyncIterableIterator<unknown> = {\n [Symbol.asyncIterator]: () => iterator,\n next: async () => {\n for (;;) {\n if (queue.length > 0) return { value: queue.shift(), done: false };\n if (done) return { value: undefined, done: true };\n await new Promise<void>((resolve) => (wake = resolve));\n wake = undefined;\n }\n },\n return: () => {\n this.#iterations.delete(iteration);\n iteration.end();\n queue.length = 0;\n return Promise.resolve({ value: undefined, done: true });\n },\n };\n return iterator;\n }\n\n /** This handle's `close()`. */\n #close(): void {\n if (this.#closed !== undefined) return;\n this.ended(\n new ChannelClosedError(this.facts.path, \"the channel is closed\"),\n false,\n );\n if (this.conn.handles.size === 0) this.conn.end();\n }\n\n /** Ends this handle: its requests rejected, its iterators ended, `$state` \"closed\" (and `$error` for a close the client will not retry). */\n ended(error: ChannelClosedError, report: boolean): void {\n if (this.#closed !== undefined) return;\n this.#closed = error;\n this.conn.handles.delete(this);\n for (const pending of [...this.requests]) pending.reject(error);\n for (const iteration of this.#iterations) iteration.end();\n this.#iterations.clear();\n if (report) this.#report(error);\n this.emit(\"$state\", \"closed\");\n }\n}\n\n/**\n * Opens a typed channel (§4.1) from a shared `channelContract()` result:\n * typed by its spec, checked by its own schemas.\n *\n * @param api The client (`createClient`): its `baseUrl` (`http` → `ws`),\n * `mode`, `validate`, `timeout` and `logger` apply.\n * @param contract The channel's contract, from a browser-safe module.\n * @param options The URL's `params` and `query`, and {@link ChannelOptions}.\n * @throws TypeError when a message type starts with `$` (the client's\n * events' prefix).\n */\nexport function channel<const S extends ChannelSpec>(\n api: { readonly url: (...args: never[]) => string },\n contract: ChannelContract<S>,\n ...options: OpenArgs<ChannelEntryOf<S>>\n): ChannelClient<ChannelEntryOf<S>>;\n/**\n * Opens a typed channel (§4.1) from a generated descriptor\n * (`channels[\"/rooms/:room\"]` of `bun-client generate --asyncapi`): typed by\n * its `Channels` entry, checked by the generated zod module's\n * `channelSchemas` when the client's `schemas` source has them.\n *\n * @param api The client (`createClient`).\n * @param descriptor The channel's generated descriptor.\n * @param options The URL's `params` and `query`, and {@link ChannelOptions}.\n */\nexport function channel<E extends ChannelEntry>(\n api: { readonly url: (...args: never[]) => string },\n descriptor: ChannelDescriptor<E>,\n ...options: OpenArgs<E>\n): ChannelClient<E>;\nexport function channel(\n api: { readonly url: (...args: never[]) => string },\n source: ChannelContract | ChannelDescriptor,\n options?: object,\n): ChannelClient {\n const opened = (options ?? {}) as ChannelOpenOptions<ChannelEntry>;\n const config =\n ((api as unknown as Record<symbol, unknown>)[CONFIG] as\n ClientConfig | undefined) ?? {};\n let facts: Facts;\n if (\"spec\" in source) {\n const { meta, spec } = source;\n for (const type of [\n ...Object.keys(spec.receives),\n ...Object.keys(spec.sends),\n ])\n if (type.startsWith(\"$\"))\n throw new TypeError(\n `channel(): the message type \"${type}\" starts with \"$\", which the client's events use`,\n );\n facts = {\n path: meta.path,\n idField: meta.envelope.idField,\n replies: meta.replies,\n subprotocol: meta.subprotocol,\n contract: source,\n codec: undefined,\n };\n } else {\n facts = {\n path: source.path,\n idField: source.idField ?? \"id\",\n replies: source.replies ?? {},\n subprotocol: source.subprotocol,\n contract: undefined,\n codec: source.codec,\n };\n }\n let url = (api.url as (path: string, options: object) => string)(facts.path, {\n params: opened.params,\n query: opened.query,\n });\n if (!/^[a-z][a-z\\d+.-]*:/i.test(url)) {\n const base = (globalThis as { location?: { href?: string } }).location\n ?.href;\n if (!base)\n throw new ClientError(\n `${facts.path}: \"${url}\" is relative, and there is no page to resolve it against; give the client an absolute baseUrl`,\n { key: facts.path },\n );\n url = new URL(url, base).href;\n }\n url = url.replace(/^http(s?):/i, \"ws$1:\");\n const key = `${url} ${facts.subprotocol ?? \"\"}`;\n let conn = SOCKETS.get(key);\n if (conn === undefined) {\n conn = new Connection(key, url, facts, opened, config.logger ?? console);\n SOCKETS.set(key, conn);\n }\n const handle = new Handle(conn, facts, opened, config);\n conn.handles.add(handle);\n // A handle joining an open socket resubscribes at once (the socket's\n // next open runs it with the others).\n if (conn.state === \"open\")\n void (async () => opened.resubscribe?.(handle.client as never))().catch(\n (error: unknown) =>\n handle.logger.error(`${facts.path}: resubscribe threw`, error),\n );\n return handle.client as unknown as ChannelClient;\n}\n"
|
|
6
|
+
],
|
|
7
|
+
"mappings": "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",
|
|
8
|
+
"debugId": "8BB5707F752ACABE64756E2164756E21",
|
|
9
|
+
"names": []
|
|
10
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import{Pe,ut,en,tn,Pn}from"./main-jv5xqd13.js";function p(e,n){if(n===void 0)return;if(typeof n==="boolean"){if(e.enabled=n,!n)e.custom=void 0,e.replace=!1;return}if(n.enabled!==void 0)e.enabled=n.enabled;if(n.custom!==void 0)e.custom=n.custom;if(n.replace!==void 0)e.replace=n.replace}function P(e,n){if(typeof n==="boolean"){p(e.base,n);for(let o of e.types.values())p(o,n)}else if(n!==void 0)for(let[o,u]of Object.entries(n)){let d=e.types.get(o)??{...e.base};p(d,u),e.types.set(o,d)}}function F(e,n){return e.map((o)=>({...o,target:o.target,source:n}))}function E(e,n){return e instanceof Promise?e.then(n):n(e)}async function G(e){let{development:n,key:o,meta:u}=e,d=e.contract;if(d===void 0&&e.schemas!==void 0){let a=await e.schemas();if(a!==void 0)d=Pn({path:u.path,receives:a.receives??{},sends:a.sends??{},replies:u.replies,envelope:{idField:u.idField}}).contract}let M=d===void 0?void 0:tn(d,"inbound"),R=d===void 0?void 0:en(d,"outbound"),C=()=>({base:{enabled:n,custom:void 0,replace:!1},types:new Map}),m=C(),y=C(),h="throw",b="warn",[t,s]=e.levels,g=(a,i,l,r)=>{if(a){for(let c of[m,y])p(c.base,!1),c.types.clear();return}if(P(m,i),P(y,l),typeof r==="string"||typeof r==="function")h=r,b=r;else if(r!==void 0)h=r.send??h,b=r.receive??b};if(t!==void 0){let a=t===!1?void 0:t.onFailure;g(t===!1,t===!1?void 0:t.request,t!==!1&&typeof t.response==="boolean"?t.response:void 0,typeof a==="object"?{send:a.request,receive:a.response}:a)}if(s!==void 0)g(s===!1,s===!1?void 0:s.send,s===!1?void 0:s.receive,s===!1?void 0:s.onFailure);let k=(a,i,l,r,c)=>{let D=i.enabled&&!i.replace&&a!==void 0?a({type:r,data:c}):void 0;return E(D,(v)=>{let V=[],S=c;if(v instanceof ut)V.push(...F(v.issues,"builtin"));else if(v!==void 0&&l==="receive")S=v.data;let w=(f)=>V.length===0?{value:f}:{value:c,error:new e.ValidationError(o,l,V)};if(i.custom===void 0)return w(c);return E(Pe(i.custom,S,"data",r,void 0,l==="receive"?{stripProtoKeys:"nest"}:void 0),(f)=>{if(!f.ok)V.push(...F(f.issues,"custom"));return w(f.ok?f.value:c)})})};return{send:(a,i,l)=>{let r=m.types.get(a)??m.base;if(l!==void 0)r={...r},p(r,l);return k(M,r,"send",a,i)},receive:(a,i)=>k(R,y.types.get(a)??y.base,"receive",a,i),onSend:h,onReceive:b}}export{G as createChannelChecks};
|
|
2
|
+
|
|
3
|
+
//# debugId=A6A575F535E55BFA64756E2164756E21
|
|
4
|
+
//# sourceMappingURL=channelChecks-s4yz4yhw.js.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../bun-client/lib/channelChecks.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"/**\n * A channel's checks (typed-client plan, `websocket.md` §5, PR-W2): what the\n * client sends through the contract's **sender** over its `inbound` side\n * (the server's receives), what arrives through its **receiver** over\n * `outbound` (the server's sends), the very guards `defineChannel` runs\n * (D20); then a BYO schema per message type, on the same policy as HTTP\n * (TC §5.6): built-in first, BYO after on the built-in's output (or the raw\n * data when it failed), `replace` dropping the built-in, a passing BYO\n * schema's output being what is sent or delivered.\n *\n * **Loaded lazily**, by `./channel` alone; it takes bun-contract's guards\n * and `channelContract` through `./validation`, their one import site, so a\n * bundler keeps only what this module uses of it.\n */\nimport type {\n AnyContract,\n StandardOutcome,\n StandardSchemaV1,\n ValidationIssue,\n} from \"@kingsleyweb/bun-contract\";\nimport type { ChannelSchemas, ChannelValidationConfig } from \"./channel\";\nimport type { ValidationError, ValidationErrorIssue } from \"./errors\";\nimport type { FailureMode, PartValidation, ValidationConfig } from \"./types\";\nimport {\n channelContract,\n ContractError,\n createReceiver,\n createSender,\n validateStandard,\n} from \"./validation\";\n\n/** A check's outcome: what to send or deliver, and the failure, when there was one. */\nexport interface Verdict {\n /** The data as given, or a passing BYO schema's output. */\n readonly value: unknown;\n /** Every issue found, the built-in ones first; `undefined` when it passed. */\n readonly error?: ValidationError;\n}\n\n/** One channel's checks. */\nexport interface ChannelChecks {\n /** Checks a message about to be sent; synchronous when every schema it runs is. */\n readonly send: (\n type: string,\n data: unknown,\n call?: PartValidation,\n ) => Verdict | Promise<Verdict>;\n /** Checks a frame that arrived (a push or a reply); synchronous when every schema it runs is. */\n readonly receive: (type: string, data: unknown) => Verdict | Promise<Verdict>;\n /** What a send's failure does. */\n readonly onSend: FailureMode;\n /** What a received frame's failure does. */\n readonly onReceive: FailureMode | \"drop\";\n}\n\n/** What `./channel` hands the checks. */\nexport interface ChannelChecksContext {\n /** The channel's path: the errors' key. */\n readonly key: string;\n /** The client's mode is development (its defaults turn checks on). */\n readonly development: boolean;\n /** The client's validation, then the channel's. */\n readonly levels: readonly [\n ValidationConfig | false | undefined,\n ChannelValidationConfig | false | undefined,\n ];\n /** The shared contract's contract, when the channel was opened with one. */\n readonly contract: AnyContract | undefined;\n /** The channel's envelope and replies, to build a contract from generated schemas. */\n readonly meta: {\n readonly path: string;\n readonly idField: string;\n readonly replies: Readonly<Record<string, string>>;\n };\n /** The generated schemas (the client's `schemas` source's `channelSchemas` entry), when there is no contract. */\n readonly schemas: (() => Promise<ChannelSchemas | undefined>) | undefined;\n /** The error class, handed over so `./errors` stays in `./channel`'s chunk. */\n readonly ValidationError: typeof ValidationError;\n}\n\n/** One message type's validation, as the levels resolved it. */\ninterface State {\n /** Run the built-in schema. */\n enabled: boolean;\n /** The BYO schema, run after it. */\n custom: StandardSchemaV1 | undefined;\n /** Run only the BYO schema. */\n replace: boolean;\n}\n\n/** One direction's validation: every type's default, and the types with their own. */\ninterface Direction {\n /** Every type not in {@link types}. */\n base: State;\n /** Per type. */\n types: Map<string, State>;\n}\n\n/** Applies one level's entry (`true` on, `false` off, an object per key), as HTTP's parts do. */\nfunction apply(state: State, entry: PartValidation | undefined): void {\n if (entry === undefined) return;\n if (typeof entry === \"boolean\") {\n state.enabled = entry;\n if (!entry) {\n state.custom = undefined;\n state.replace = false;\n }\n return;\n }\n if (entry.enabled !== undefined) state.enabled = entry.enabled;\n if (entry.custom !== undefined) state.custom = entry.custom;\n if (entry.replace !== undefined) state.replace = entry.replace;\n}\n\n/** Applies a direction's entry at one level: a switch for every type, or per type. */\nfunction applyDirection(\n direction: Direction,\n entry: boolean | { readonly [type: string]: PartValidation } | undefined,\n): void {\n if (typeof entry === \"boolean\") {\n apply(direction.base, entry);\n for (const state of direction.types.values()) apply(state, entry);\n } else if (entry !== undefined) {\n for (const [type, part] of Object.entries(entry)) {\n const state = direction.types.get(type) ?? { ...direction.base };\n apply(state, part);\n direction.types.set(type, state);\n }\n }\n}\n\n/** `issues`, marked as `source`'s. */\nfunction mark(\n issues: readonly ValidationIssue[],\n source: \"builtin\" | \"custom\",\n): ValidationErrorIssue[] {\n return issues.map((issue) => ({\n ...issue,\n target: issue.target as ValidationErrorIssue[\"target\"],\n source,\n }));\n}\n\n/** `f(value)`, after `value` settles when it is a promise. */\nfunction then<T, R>(\n value: T | Promise<T>,\n f: (settled: T) => R | Promise<R>,\n): R | Promise<R> {\n return value instanceof Promise ? value.then(f) : f(value);\n}\n\n/**\n * Builds a channel's checks: its guards (from the shared contract, or a\n * contract built from the generated schemas, loaded now), and the levels\n * resolved over the mode's defaults (the client's `validate.request` for\n * sends and `validate.response` for what arrives, then the channel's).\n */\nexport async function createChannelChecks(\n context: ChannelChecksContext,\n): Promise<ChannelChecks> {\n const { development, key, meta } = context;\n let contract = context.contract;\n if (contract === undefined && context.schemas !== undefined) {\n const schemas = await context.schemas();\n if (schemas !== undefined)\n contract = channelContract({\n path: meta.path,\n receives: schemas.receives ?? {},\n sends: schemas.sends ?? {},\n replies: meta.replies,\n envelope: { idField: meta.idField },\n }).contract;\n }\n type Guard = (message: object) => unknown;\n const sender =\n contract === undefined\n ? undefined\n : (createSender(contract, \"inbound\") as unknown as Guard);\n const receiver =\n contract === undefined\n ? undefined\n : (createReceiver(contract, \"outbound\") as unknown as Guard);\n\n const fresh = (): Direction => ({\n base: { enabled: development, custom: undefined, replace: false },\n types: new Map(),\n });\n const send = fresh();\n const receive = fresh();\n let onSend: FailureMode = \"throw\";\n let onReceive: FailureMode | \"drop\" = \"warn\";\n const [client, channel] = context.levels;\n /** Applies one level: `false` turns everything off; else its two directions' entries and its failure modes. */\n const level = (\n off: boolean,\n sends: Parameters<typeof applyDirection>[1],\n receives: Parameters<typeof applyDirection>[1],\n failure:\n | FailureMode\n | {\n readonly send?: FailureMode;\n readonly receive?: FailureMode | \"drop\";\n }\n | undefined,\n ): void => {\n if (off) {\n for (const direction of [send, receive]) {\n apply(direction.base, false);\n direction.types.clear();\n }\n return;\n }\n applyDirection(send, sends);\n applyDirection(receive, receives);\n if (typeof failure === \"string\" || typeof failure === \"function\") {\n onSend = failure;\n onReceive = failure;\n } else if (failure !== undefined) {\n onSend = failure.send ?? onSend;\n onReceive = failure.receive ?? onReceive;\n }\n };\n // The client's level: `request` for sends, a boolean `response` for what\n // arrives (a per-status map is HTTP's alone), `onFailure` likewise.\n if (client !== undefined) {\n const failure = client === false ? undefined : client.onFailure;\n level(\n client === false,\n client === false ? undefined : client.request,\n client !== false && typeof client.response === \"boolean\"\n ? client.response\n : undefined,\n typeof failure === \"object\"\n ? { send: failure.request, receive: failure.response }\n : failure,\n );\n }\n if (channel !== undefined)\n level(\n channel === false,\n channel === false ? undefined : channel.send,\n channel === false ? undefined : channel.receive,\n channel === false ? undefined : channel.onFailure,\n );\n\n /** Runs one direction's checks on one message. */\n const run = (\n guard: Guard | undefined,\n state: State,\n direction: \"send\" | \"receive\",\n type: string,\n data: unknown,\n ): Verdict | Promise<Verdict> => {\n const builtin =\n state.enabled && !state.replace && guard !== undefined\n ? guard({ type, data })\n : undefined;\n return then(builtin, (outcome) => {\n const issues: ValidationErrorIssue[] = [];\n // A receiver's output (the built-in schema's) is what a BYO schema\n // reads; the data delivered stays as it arrived (§5.4).\n let checked = data;\n if (outcome instanceof ContractError)\n issues.push(...mark(outcome.issues, \"builtin\"));\n else if (outcome !== undefined && direction === \"receive\")\n checked = (outcome as { data?: unknown }).data;\n const verdict = (value: unknown): Verdict =>\n issues.length === 0\n ? { value }\n : {\n value: data,\n error: new context.ValidationError(key, direction, issues),\n };\n if (state.custom === undefined) return verdict(data);\n return then(\n validateStandard(\n state.custom,\n checked,\n \"data\",\n type,\n undefined,\n direction === \"receive\" ? { stripProtoKeys: \"nest\" } : undefined,\n ) as StandardOutcome<unknown> | Promise<StandardOutcome<unknown>>,\n (result) => {\n if (!result.ok) issues.push(...mark(result.issues, \"custom\"));\n return verdict(result.ok ? result.value : data);\n },\n );\n });\n };\n\n return {\n send: (type, data, call) => {\n let state = send.types.get(type) ?? send.base;\n if (call !== undefined) {\n state = { ...state };\n apply(state, call);\n }\n return run(sender, state, \"send\", type, data);\n },\n receive: (type, data) =>\n run(\n receiver,\n receive.types.get(type) ?? receive.base,\n \"receive\",\n type,\n data,\n ),\n onSend,\n onReceive,\n };\n}\n"
|
|
6
|
+
],
|
|
7
|
+
"mappings": "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",
|
|
8
|
+
"debugId": "A6A575F535E55BFA64756E2164756E21",
|
|
9
|
+
"names": []
|
|
10
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
.schema-tree{font-size:var(--text-sm);overflow-x:auto}.schema-list{list-style:none;margin:0;padding:0}.schema-list .schema-list{margin-left:var(--space-3);padding-left:var(--space-3);border-left:1px solid var(--border)}.schema-node{padding:var(--space-1)0}.schema-row{display:flex;align-items:baseline;gap:var(--space-1)var(--space-2);flex-wrap:wrap}.schema-toggle,.schema-toggle-spacer{text-align:center;flex:none;width:1.25rem}.schema-toggle{color:var(--text-muted);cursor:pointer;font:inherit;background:0 0;border:0;padding:0}.schema-toggle:focus-visible{outline:2px solid var(--accent);outline-offset:1px;border-radius:var(--radius-sm)}.schema-ref-link:focus-visible{outline:2px solid var(--accent);outline-offset:1px;border-radius:var(--radius-sm)}.schema-name{font-family:var(--font-mono);overflow-wrap:anywhere;font-weight:600}.schema-required{color:var(--danger-text);font-size:var(--text-xs);text-transform:uppercase;letter-spacing:.03em}.schema-type{font-family:var(--font-mono);color:var(--accent);overflow-wrap:anywhere}.schema-ref{font-family:var(--font-mono);font-size:var(--text-sm)}.schema-ref-link{color:var(--accent);text-decoration:underline;cursor:pointer;background:0 0;border:0;padding:0}.schema-note-inline{font-size:var(--text-xs);color:var(--text-muted);font-style:italic}.schema-unresolved{color:var(--danger-text)}.schema-fact{padding:0 var(--space-1);border-radius:var(--radius-sm);background:var(--surface-2);font-size:var(--text-xs);overflow-wrap:anywhere}.schema-fact-label{color:var(--text-muted)}.schema-description,.schema-closed,.schema-open{margin:0 0 0 calc(1.25rem + var(--space-2));color:var(--text-muted)}.schema-description p{margin:var(--space-1)0 0;overflow-wrap:anywhere}.schema-closed,.schema-open{font-size:var(--text-xs)}.schema-closed{color:var(--warning-text)}.schema-composition{padding-top:var(--space-1);color:var(--text-muted);font-size:var(--text-xs);text-transform:uppercase;letter-spacing:.03em;font-weight:600}.http-docs-title{display:flex;justify-content:space-between;align-items:baseline;gap:var(--space-2)var(--space-4);flex-wrap:wrap}.http-docs-layout{display:grid;grid-template-columns:minmax(14rem,20rem)minmax(0,1fr);gap:var(--space-4);align-items:start}@media (max-width:800px){.http-docs-layout{grid-template-columns:minmax(0,1fr)}}.http-muted{color:var(--text-muted)}.http-sidebar{position:sticky;top:var(--space-3);display:flex;gap:var(--space-2);overflow-y:auto;flex-direction:column;max-height:calc(100vh - 6rem)}@media (max-width:800px){.http-sidebar{position:static;max-height:22rem}}.http-sidebar-count{font-size:var(--text-xs);margin:0}.http-tag{margin-bottom:var(--space-3)}.http-section{margin-bottom:var(--space-4)}.http-section-name{margin:0 0 var(--space-2);font-size:var(--text-md);color:var(--text)}.http-section .http-tag{padding-left:var(--space-2);border-left:1px solid var(--border)}.http-overview-tag+.http-overview-tag{margin-top:var(--space-3)}.http-overview-tag-name,.http-tag-name{margin:0 0 var(--space-1);font-size:var(--text-sm);text-transform:uppercase;letter-spacing:.04em;color:var(--text-muted)}.http-tag-ops,.http-overview-ops,.http-params,.http-responses,.http-components{list-style:none;margin:0;padding:0}.http-op-link{display:flex;gap:var(--space-2);padding:var(--space-1)var(--space-2);border-radius:var(--radius-sm);color:var(--text);text-decoration:none;align-items:flex-start}.http-op-link:hover{background:var(--surface-2)}.http-op-link:focus-visible{background:var(--surface-2)}.http-op-link:focus-visible{outline:2px solid var(--accent);outline-offset:-2px}.http-op-link[aria-current=page]{background:var(--info-bg);color:var(--info-text)}.http-method{font-family:var(--font-mono);flex:none;justify-content:center;min-width:3.5rem}.http-op-text{display:flex;flex-direction:column;min-width:0}.http-op-path{font-family:var(--font-mono);font-size:var(--text-sm);overflow-wrap:anywhere}.http-op-summary{font-size:var(--text-xs);color:var(--text-muted)}.http-docs-main{display:flex;gap:var(--space-4);flex-direction:column;min-width:0}.http-info-line,.http-operation-line,.http-markers,.http-param-head,.http-response-head,.http-tryit-status,.http-tryit-actions{display:flex;align-items:center;gap:var(--space-2);flex-wrap:wrap}.http-servers{display:grid;gap:var(--space-2);margin:var(--space-3)0}.http-servers dt{font-size:var(--text-sm);color:var(--text-muted)}.http-servers dd{display:flex;gap:var(--space-2);overflow-wrap:anywhere;align-items:center;margin:0}.http-overview{display:flex;gap:var(--space-3);flex-direction:column}.http-overview-ops li{padding:var(--space-1)0;overflow-wrap:anywhere}.http-components{display:flex;gap:var(--space-2)var(--space-3);flex-wrap:wrap}.http-operation{display:flex;gap:var(--space-3);flex-direction:column}.http-operation-path{font-size:var(--text-md);overflow-wrap:anywhere}.http-operation-title{margin:var(--space-2)0 0;font-size:var(--text-lg)}.http-operation-id{display:flex;align-items:center;gap:var(--space-2);margin:var(--space-1)0 0;font-size:var(--text-sm);flex-wrap:wrap}.http-requires code{margin-right:var(--space-1)}.http-csrf,.http-tryit-disabled{padding:var(--space-2)var(--space-3);border-radius:var(--radius-sm);background:var(--info-bg);color:var(--info-text)}.http-csrf p{margin:0}.http-csrf p+p{margin-top:var(--space-1)}.http-tryit-disabled{margin:0 0 var(--space-3);background:var(--warning-bg);color:var(--warning-text)}.http-description p{margin:0 0 var(--space-2);overflow-wrap:anywhere}.http-section h3,.http-tryit h3{margin:0 0 var(--space-2);font-size:var(--text-md)}.http-param,.http-response{padding:var(--space-2)0;border-top:1px solid var(--border)}.http-param-name{font-weight:600}.http-param-description p,.http-param-style{margin:var(--space-1)0;font-size:var(--text-sm)}.http-media{margin-top:var(--space-2)}.http-media-type{display:inline-block;margin-bottom:var(--space-1);font-size:var(--text-xs);color:var(--text-muted)}.http-codes{display:flex;gap:var(--space-1);list-style:none;margin:var(--space-1)0 0;flex-wrap:wrap;padding:0}.http-codes code{padding:0 var(--space-1);border-radius:var(--radius-sm);background:var(--surface-2);font-size:var(--text-xs)}.http-tryit{padding:var(--space-3);border:1px solid var(--border);border-radius:var(--radius);background:var(--surface)}.http-tryit-form{display:flex;gap:var(--space-2);flex-direction:column}.http-tryit-checks{display:flex;gap:var(--space-1)var(--space-3);flex-wrap:wrap}.http-snippets{margin-top:var(--space-3)}.http-snippet{position:relative}.http-snippet .copy-button,.http-snippet>button{position:absolute;top:var(--space-2);right:var(--space-2)}.http-snippet pre{padding:var(--space-3);border-radius:var(--radius-sm);background:var(--surface-2);font-family:var(--font-mono);font-size:var(--text-xs);overflow-x:auto;white-space:pre;margin:0}.http-tryit-result{display:flex;gap:var(--space-2);margin-top:var(--space-3);padding-top:var(--space-3);border-top:1px solid var(--border);flex-direction:column}.http-tryit-result p{font-size:var(--text-sm);margin:0}.http-tryit-timing{font-size:var(--text-sm);color:var(--text-muted)}.http-tryit-headers summary{cursor:pointer;font-size:var(--text-sm)}.http-tryit-headers dl{display:grid;grid-template-columns:max-content minmax(0,1fr);gap:var(--space-1)var(--space-3);margin:var(--space-2)0 0;font-size:var(--text-xs)}.http-tryit-headers dl>div{display:contents}.http-tryit-headers dd{overflow-wrap:anywhere;margin:0}.http-tryit-text{padding:var(--space-2);border-radius:var(--radius-sm);background:var(--surface-2);font-family:var(--font-mono);font-size:var(--text-xs);overflow-x:auto;white-space:pre-wrap;margin:0}.ws-docs{display:flex;gap:var(--space-4);flex-direction:column}.ws-header .screen-title{display:flex;align-items:center;gap:var(--space-2);overflow-wrap:anywhere;flex-wrap:wrap}.ws-kicker{margin:0}.ws-description summary{cursor:pointer}.ws-description p{overflow-wrap:anywhere;max-width:72ch}.ws-overview{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,22rem),1fr));gap:var(--space-4)}.ws-layout{display:grid;grid-template-columns:minmax(14rem,18rem)minmax(0,1fr);gap:var(--space-4);align-items:start}@media (max-width:800px){.ws-layout{grid-template-columns:minmax(0,1fr)}}.ws-sidebar{position:sticky;top:var(--space-4);max-height:calc(100vh - 2*var(--space-4));overflow-y:auto;padding:var(--space-3);border:1px solid var(--border);border-radius:var(--radius);background:var(--surface)}@media (max-width:800px){.ws-sidebar{position:static;max-height:22rem}}.ws-group+.ws-group{margin-top:var(--space-3)}.ws-group-title{margin:0 0 var(--space-1);font-size:var(--text-xs);letter-spacing:.04em;text-transform:uppercase;color:var(--text-muted);font-weight:600}.ws-group ul{list-style:none;margin:0;padding:0}.ws-nav-link{display:flex;padding:var(--space-1)var(--space-2);border-radius:var(--radius-sm);color:var(--text);text-decoration:none;flex-direction:column}.ws-nav-link:hover{background:var(--surface-2)}.ws-nav-link[data-selected=true]{background:var(--info-bg);color:var(--info-text)}.ws-nav-label{font-family:var(--font-mono);font-size:var(--text-sm);overflow-wrap:anywhere}.ws-nav-hint{font-size:var(--text-xs);color:var(--text-muted);overflow-wrap:anywhere}.ws-main,.ws-pane{display:flex;gap:var(--space-4);flex-direction:column;min-width:0}.ws-links{display:flex;gap:var(--space-1)var(--space-3);list-style:none;flex-wrap:wrap;margin:0;padding:0}.ws-permissions{display:flex;gap:var(--space-2);flex-wrap:wrap}.ws-permission{display:inline-flex;align-items:center;gap:var(--space-1);flex-wrap:wrap}.ws-note{display:block;padding:var(--space-2)var(--space-3);border-radius:var(--radius-sm);background:var(--warning-bg);color:var(--warning-text);overflow-wrap:anywhere}.ws-requirements{margin:0 0 var(--space-3);padding-left:var(--space-5)}.ws-table td{vertical-align:top;overflow-wrap:anywhere}.ws-value{white-space:nowrap;font-variant-numeric:tabular-nums}.ws-highlight{border-color:var(--accent);box-shadow:inset 3px 0 0 var(--accent)}.ws-schema{overflow-x:auto}.ws-example+.ws-example{margin-top:var(--space-3)}.ws-try{display:flex;align-items:flex-start;gap:var(--space-2);flex-direction:column}.ws-try p{margin:0}.ws-try-fields{display:flex;gap:var(--space-2)var(--space-3);flex-wrap:wrap}.ws-try-fields .field{min-width:12rem;margin:0}.ws-try-channel{overflow-wrap:anywhere}.ws-disabled{opacity:.55;cursor:not-allowed}.ws-example-head{display:flex;justify-content:space-between;align-items:center;gap:var(--space-2)}.ws-example-event{margin-block:var(--space-2);padding:var(--space-2)var(--space-3);border:1px solid var(--accent);border-radius:var(--radius,6px)}.ws-try-reason{padding-left:var(--space-4);margin:0}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import{v,c,T,r,_t,Dn,Re,It,b,P,n,e,t,f,i,m,J,a,_,s,oe,p,pe,Ke,ce,o,z,h,l,V,S,w,I,le}from"./main-a250ffk7.js";import{U}from"./main-hg0hrg8j.js";import{O}from"./main-4j1fcevh.js";import{A}from"./main-0jmeh6mq.js";import{D}from"./main-hm1ng3yg.js";import{F}from"./main-pegp0ygb.js";var He=1000;function Te(d){return Math.max(1,Math.min(He,d))}function fe(d){return[..._.queue(d),"job-defaults"]}function ve(d,u=""){return`/queues/${P(d)}/job-defaults${u}`}function Ve(d){return Ke(d,"queue")&&typeof d.seq==="number"&&Array.isArray(d.overridden)&&typeof d.effective==="object"&&d.effective!==null&&typeof d.code==="object"&&d.code!==null&&typeof d.pending==="object"&&d.pending!==null}async function me(d,u,y){let x=ve(u),j=await d.request("GET",x,{signal:y});return pe(j,Ve,"job defaults",x)}function Be(d,u,y){return d.request("PUT",ve(u),{body:y})}function Oe(d,u,y){return d.request("DELETE",ve(u),{query:{expectedSeq:y}})}function Ce(d,u,y){return d.request("POST",ve(u,"/apply"),{body:y})}function Ae(){return{examined:0,rewritten:0,unchanged:0,skippedExplicit:0,skippedUnmarked:0,moved:0,exhausted:0,batches:0}}function We(d,u){return{examined:d.examined+u.examined,rewritten:d.rewritten+u.rewritten,unchanged:d.unchanged+u.unchanged,skippedExplicit:d.skippedExplicit+u.skippedExplicit,skippedUnmarked:d.skippedUnmarked+u.skippedUnmarked,moved:d.moved+u.moved,exhausted:d.exhausted+u.exhausted,batches:d.batches+1}}async function Ne(d){let u=d.cursor,y=d.totals??Ae(),x=!0;for(;;){if(!x&&u!==void 0&&d.shouldStop())return{status:"cancelled",totals:y,cursor:u};x=!1;let j;try{j=await d.request(u)}catch(R){return{status:"failed",totals:y,cursor:u,error:R}}if(y=We(y,j),d.onBatch?.(y,j),j.done||j.next===null)return{status:"done",totals:y};if(j.next===u)return{status:"failed",totals:y,cursor:u,error:Error("The API answered the same cursor it was sent, so the walk would never end.")};u=j.next}}var be={attempts:"number",backoff:"backoff",timeout:"number",priority:"number",removeOnComplete:"retention",removeOnFail:"retention",keepLogs:"number",keepStacktraces:"number"},W={attempts:{label:"Attempts",hint:"How many times a job runs before it is dead, the first run included."},backoff:{label:"Backoff",hint:"How long a failed job waits before its next attempt."},timeout:{label:"Timeout (ms)",hint:"How long one attempt may run before it fails; 0 means no timeout."},priority:{label:"Priority",hint:"Lower runs first."},removeOnComplete:{label:"Completed jobs",hint:"How long a completed job is kept."},removeOnFail:{label:"Dead jobs",hint:"How long a job that ran out of attempts is kept."},keepLogs:{label:"Log lines kept",hint:"The newest log lines each job keeps."},keepStacktraces:{label:"Stack traces kept",hint:"The failure stack traces each job keeps."}};function je(d){return Re[d]}var De="code";function Qe(d){return Dn.includes(d)}function Xe(d){if(typeof d==="number")return{type:"fixed",delay:d,max:void 0,jitter:void 0};return{type:Qe(d.type)?d.type:"fixed",delay:d.delay,max:d.max,jitter:typeof d.jitter==="number"?d.jitter:void 0}}function ze(d){if(d===!0)return{mode:"remove",count:void 0,ttl:void 0};if(d===!1)return{mode:"keep",count:void 0,ttl:void 0};if(typeof d==="number")return{mode:"limit",count:d,ttl:void 0};return{mode:"limit",count:d.count,ttl:d.ttl}}function Pe(d,u){let y=be[d];if(y==="backoff")return Xe(u);if(y==="retention")return ze(u);return u}function ge(d,u,y){if(d===void 0)return`Enter ${y}.`;if(!Number.isInteger(d)||d<u.min||d>u.max)return`${Ge(y)} must be a whole number from ${o(u.min)} to ${o(u.max)}.`;return}function Ge(d){return d.charAt(0).toUpperCase()+d.slice(1)}function Ze(d){let u=ge(d.delay,Re.backoffDelay,"a delay");if(u)return{error:u};let y=d.delay;if(d.max!==void 0){let j=ge(d.max,Re.backoffMax,"the longest delay");if(j)return{error:j};if(d.max<y)return{error:"The longest delay must be at least the delay."}}let x=Re.backoffJitter;if(d.jitter!==void 0&&!(d.jitter>=x.min&&d.jitter<=x.max))return{error:`Jitter must be from ${x.min} to ${x.max}.`};return{value:{type:d.type,delay:y,...d.max===void 0?{}:{max:d.max},...d.jitter===void 0?{}:{jitter:d.jitter}}}}function et(d){if(d.mode==="keep")return{value:!1};if(d.mode==="remove")return{value:!0};if(d.count===void 0&&d.ttl===void 0)return{error:"Enter a count, an age, or both."};if(d.count!==void 0){let u=ge(d.count,Re.retentionCount,"the count");if(u)return{error:u}}if(d.ttl!==void 0){let u=ge(d.ttl,Re.retentionTtl,"the age");if(u)return{error:u}}return{value:{...d.count===void 0?{}:{count:d.count},...d.ttl===void 0?{}:{ttl:d.ttl}}}}function tt(d,u){let y=be[d];if(y==="backoff")return Ze(u);if(y==="retention")return et(u);let x=ge(u,je(d),"a value");return x?{error:x}:{value:u}}function Se(d,u){let y=u;if(be[d]==="backoff"&&typeof u==="number")y={type:"fixed",delay:u};else if(be[d]==="retention"&&typeof u==="number")y={count:u};if(typeof y==="object"&&y!==null){let x=y;return JSON.stringify(Object.fromEntries(Object.keys(x).filter((j)=>x[j]!==void 0).sort().map((j)=>[j,x[j]])))}return JSON.stringify(y)}function _e(d,u){let y={},x=y,j=[],R={};for(let k of _t){if(!(k in u))continue;let B=u[k];if(B===De){if(d.overridden.includes(k))x[k]=null,j.push(k);continue}let L=tt(k,B);if("error"in L){R[k]=L.error;continue}if(Se(k,L.value)!==Se(k,d.effective[k]))x[k]=L.value,j.push(k)}return y.expectedSeq=d.seq,{body:y,changed:j,errors:R}}function ot(d){if(d===!0)return"removed at once";if(d===!1)return"kept forever";if(typeof d==="number")return`the newest ${o(d)} kept`;let u=[d.count!==void 0&&`the newest ${o(d.count)}`,d.ttl!==void 0&&`for ${U(d.ttl)}`].filter(Boolean);return u.length>0?`kept: ${u.join(", ")}`:"kept forever"}function nt(d){if(typeof d==="number")return d===0?"none":`fixed, ${U(d)}`;return[`${d.type??"fixed"}, ${U(d.delay??0)}`,d.max!==void 0&&`at most ${U(d.max)}`,d.jitter===!0&&"jitter",typeof d.jitter==="number"&&d.jitter>0&&`${Math.round(d.jitter*100)}% jitter`].filter(Boolean).join(", ")}function re(d,u){if(u===void 0||u===null)return"—";switch(d){case"backoff":return nt(u);case"removeOnComplete":case"removeOnFail":return ot(u);case"timeout":return u===0?"none":U(u);case"keepLogs":return u===0?"every line":o(u);default:return o(u)}}var ke="“Code” is what this API's own service is configured with. A producer in another service may be configured differently; a stored value replaces them all alike.",$e="Resetting changes jobs added from now on. It cannot restore jobs already rewritten by Apply to pending jobs: they keep the values written to them.",Le="This rewrites the jobs in place and cannot be undone: their previous values are not kept, so a later reset does not restore them.",Fe="Jobs added before this version of bun-jobs did not record which options their add() passed explicitly. Left unticked, they are skipped and counted. Ticked, every option of theirs is treated as a default, so a value their add() did pass may be replaced.",Ue="Attempts is being lowered. A job that has already made at least the new number of attempts is not dropped: it gets ONE final attempt, and dies if that fails.";function rt(d){return d.overridden.includes("attempts")&&d.effective.attempts<d.code.attempts}function Ie({queue:d,defaults:u,onClose:y}){let x=i(),j=m(),R=J(),[k,B]=r(u),[L,C]=r(It),[N,Q]=r(!1),[g,H]=r({kind:"confirm"}),[te,xe]=r(!1),[G,ae]=r(null),M=T(!1);v(()=>()=>{M.current=!0},[]);let he=g.kind==="running",ie=It.filter((K)=>L.includes(K)),ne=ie.reduce((K,q)=>K+k.pending[q],0),X=g.kind==="confirm"?g.preview:void 0,Ee=rt(k)||X!==void 0&&X.outcome.totals.exhausted>0,E=async(K,q)=>{M.current=!1;let ye=q?.totals??Ae();H({kind:"running",dryRun:K,totals:ye,stopping:!1});let ee=await Ne({cursor:q?.cursor,totals:ye,request:(de)=>{let ue={seq:k.seq,states:[...ie],limit:Te(j.limits.maxApplyDefaults),dryRun:K,includeUnmarked:N};if(de!==void 0)ue.cursor=de;return Ce(x,d,ue)},onBatch:(de)=>H((ue)=>ue.kind==="running"?{...ue,totals:de}:ue),shouldStop:()=>M.current});if(!K&&ee.totals.batches>0)for(let de of ce(d))R.invalidateQueries({queryKey:de});if(ee.status==="failed"&&b(ee.error)&&ee.error.code==="DEFAULTS_CHANGED"){H({kind:"conflict",dryRun:K,totals:ee.totals});return}let Je={kind:"finished",dryRun:K,outcome:ee};H(K&&ee.status==="done"?{kind:"confirm",preview:Je}:Je)},Y=async()=>{xe(!0),ae(null);try{let K=await me(x,d);R.setQueryData(fe(d),K),B(K),H({kind:"confirm"})}catch(K){ae(K)}finally{xe(!1)}},Z=(K,q)=>C((ye)=>q?[...ye,K]:ye.filter((ee)=>ee!==K)),se=k.overridden.length===0,we=`Apply to ${o(ne)} pending ${ne===1?"job":"jobs"}`;return t(oe,{open:!0,onClose:y,closeOnEscape:!he,closeOnBackdrop:!he,showCloseButton:!he,size:"lg",role:"alertdialog",title:`Apply the job defaults of ${d} to pending jobs?`,description:Le,footer:g.kind==="confirm"?t(n,{children:[e(s,{onClick:y,children:"Cancel"}),e(s,{onClick:()=>void E(!0),disabled:ie.length===0||se,children:X?"Preview again":"Preview (dry run)"}),e(s,{variant:"danger",onClick:()=>void E(!1),disabled:ie.length===0||se,children:we})]}):g.kind==="running"?e(s,{onClick:()=>{M.current=!0,H({...g,stopping:!0})},disabled:g.stopping,children:g.stopping?"Stopping after this batch…":"Cancel"}):t(n,{children:[g.kind==="finished"&&g.outcome.status!=="done"&&e(s,{onClick:()=>{let{outcome:K}=g;if(K.status!=="done")E(g.dryRun,{cursor:K.cursor,totals:K.totals})},children:"Continue from where it stopped"}),e(s,{variant:"primary",onClick:y,children:"Close"})]}),children:[g.kind==="confirm"&&t(n,{children:[t("p",{children:["Writes, into each pending job whose own ",e("code",{children:"add()"})," did not pass it:"]}),e("ul",{className:"job-defaults-apply-keys","aria-label":"Values written",children:k.overridden.map((K)=>t("li",{children:[W[K].label,":"," ",re(K,k.effective[K])]},K))}),se&&e("p",{role:"alert",children:"Nothing is overridden, so there is nothing to apply."}),t("fieldset",{className:"job-defaults-apply-states",children:[e("legend",{children:"States"}),It.map((K)=>e(w,{label:`${z[K]} (${o(k.pending[K])})`,checked:L.includes(K),onChange:(q)=>Z(K,q)},K)),ie.length===0&&e("p",{className:"field-error",children:"Tick at least one state."})]}),e(w,{label:"Include jobs added before this version",hint:Fe,checked:N,onChange:Q}),t("p",{className:"muted",children:[h(ne,"pending job")," is an upper bound: jobs that passed these options explicitly, or were added before this version, are counted in it and left alone. Run a preview for the exact figure. Active, completed and dead jobs are never touched."]}),k.overridden.includes("priority")&&e("p",{className:"muted",children:"Changing priority re-sorts the backlog: until the walk ends, some jobs are re-sorted and others not yet."}),Ee&&t("p",{className:"job-defaults-warning","data-testid":"exhausted-warning",children:[Ue,X!==void 0&&X.outcome.totals.exhausted>0&&t(n,{children:[" ","The preview found"," ",h(X.outcome.totals.exhausted,"such job"),"."]})]}),X&&t("section",{"aria-label":"Preview",className:"job-defaults-preview",children:[e("h3",{children:"Preview: nothing was written"}),e(Me,{totals:X.outcome.totals,dryRun:!0})]})]}),g.kind==="running"&&t("div",{role:"status","aria-live":"polite","data-testid":"apply-progress",children:[t("p",{children:[g.dryRun?"Previewing":"Applying","… examined"," ",o(g.totals.examined)," of about"," ",o(ne),";"," ",g.dryRun?"would rewrite":"rewritten"," ",o(g.totals.rewritten)," so far (",h(g.totals.batches,"batch","batches"),")."]}),e("progress",{max:Math.max(ne,g.totals.examined,1),value:g.totals.examined,"aria-label":"Jobs examined"})]}),g.kind==="finished"&&e(at,{dryRun:g.dryRun,outcome:g.outcome}),g.kind==="conflict"&&t("div",{role:"alert",children:[t("p",{children:["The job defaults changed since you confirmed them — someone saved or reset them. Nothing more was"," ",g.dryRun?"previewed":"applied",".",!g.dryRun&&g.totals.rewritten>0&&` ${h(g.totals.rewritten,"job")} had already been rewritten with the values you confirmed.`," ","Re-read them, check them, and apply again."]}),G!==null&&e(D,{error:G}),e(s,{onClick:()=>void Y(),disabled:te,"aria-busy":te||void 0,children:te?"Re-reading…":"Re-read the defaults"})]})]})}function at({dryRun:d,outcome:u}){let y=d?"Previewed":"Applied";return t("div",{"data-testid":"apply-result",children:[u.status==="done"&&t("p",{role:"status",children:[y," to every pending job in the chosen states."]}),u.status==="cancelled"&&t("p",{role:"status",children:["Stopped after ",h(u.totals.batches,"batch","batches"),". The jobs not reached yet keep their values; apply again to continue."]}),u.status==="failed"&&e(D,{error:u.error,title:`Stopped: a batch failed after ${h(u.totals.batches,"batch","batches")}`}),e(Me,{totals:u.totals,dryRun:d})]})}function Me({totals:d,dryRun:u}){return t(n,{children:[e(F,{className:"job-defaults-result",items:[{label:"Examined",value:o(d.examined)},{label:u?"Would be rewritten":"Rewritten",value:o(d.rewritten)},{label:"Unchanged",value:o(d.unchanged)},{label:"Skipped as explicit",value:o(d.skippedExplicit),hint:"their own add() passed every value that would change"},{label:"Skipped, added before this version",value:o(d.skippedUnmarked)},{label:"Moved",value:o(d.moved),hint:"left the chosen states before their write; at least this many"},{label:"Exhausted",value:o(d.exhausted),hint:"part of rewritten: already at the new attempts, so one final attempt each"}]}),d.exhausted>0&&t("p",{className:"job-defaults-warning",children:[h(d.exhausted,"job")," ",u?"would get":"got"," one final attempt: each has already made at least the new number of attempts, and dies if that attempt fails."]})]})}var it=new Set(["CONTROL_CONTENDED","DEFAULTS_CHANGED"]);function Ye(d){return`Producers pick this up within about ${U(d.propagationMs)}.`}function qe({queue:d,defaults:u,onClose:y}){let x=i(),j=J(),[R,k]=r(u),[B,L]=r({}),[C,N]=r(null),[Q,g]=r(!1),H=c(),te=_e(R,B),xe=Object.keys(te.errors).length>0,G=O({mutationFn:()=>Be(x,d,te.body),invalidate:ce(d),successMessage:(E)=>`Saved the job defaults of ${d}. ${Ye(E)}`,toastErrors:!1,onSuccess:y,onError:N}),ae=O({mutationFn:()=>Oe(x,d,R.seq),invalidate:ce(d),successMessage:(E)=>`Reset the job defaults of ${d} to the code values. ${Ye(E)}`,toastErrors:!1,onSuccess:y,onError:N}),M=G.isPending||ae.isPending||Q,he=b(C)&&it.has(C.code),ie=async()=>{g(!0);try{let E=await me(x,d);j.setQueryData(fe(d),E),k(E),L({}),N(null)}catch(E){N(E)}finally{g(!1)}},ne=(E,Y)=>L((Z)=>({...Z,[E]:Y})),X=(E)=>L((Y)=>{let Z={...Y};return delete Z[E],Z}),Ee=(E)=>{let Y=B[E];return Y!==void 0&&Y!==De?Y:Pe(E,R.effective[E])};return t(oe,{open:!0,onClose:y,closeOnEscape:!M,closeOnBackdrop:!M,size:"lg",title:`Job defaults of ${d}`,description:t(n,{children:["What a job added to ",e("code",{children:d})," gets for an option its"," ",e("code",{children:"add()"})," does not pass. A stored value here beats the code’s defaults, ",e("code",{children:"define()"})," defaults included; only an option passed explicitly on ",e("code",{children:"add()"})," wins. Saving changes jobs added from now on; jobs already waiting keep their values unless you apply the defaults to them."]}),footer:t(n,{children:[e(s,{onClick:y,disabled:M,children:"Cancel"}),e(s,{variant:"primary",onClick:()=>{N(null),G.mutate()},disabled:M||xe||te.changed.length===0,"aria-busy":G.isPending||void 0,children:G.isPending?"Saving…":"Save defaults"})]}),children:[C!==null&&(he?t("div",{className:"job-defaults-conflict",role:"alert",children:[e("p",{children:"The job defaults changed since you opened them — someone else saved or reset them. Nothing was saved. Re-read them, then make your change again."}),e(s,{size:"sm",onClick:()=>void ie(),disabled:Q,"aria-busy":Q||void 0,children:Q?"Re-reading…":"Re-read the defaults"})]}):e(D,{error:C,title:"Could not save the job defaults"})),e("p",{className:"job-defaults-note muted",children:ke}),e("div",{className:"job-defaults-form",children:_t.map((E)=>{let Y=R.overridden.includes(E),Z=B[E]===De,se=re(E,R.code[E]),we=G.fieldErrors[E]??Object.entries(G.fieldErrors).find(([q])=>q.startsWith(`${E}.`))?.[1],K=te.errors[E]??we;return t("fieldset",{className:"job-defaults-row","data-testid":`job-default-${E}`,children:[t("legend",{children:[W[E].label," ",Y&&e(f,{tone:"accent",children:"Overridden"})]}),t("p",{className:"job-defaults-values muted",children:["Now: ",re(E,R.effective[E])," · Code:"," ",se]}),Z?t("p",{className:"job-defaults-uses-code",children:["Will use the code value (",se,") once saved."," ",e(s,{size:"sm",variant:"ghost",onClick:()=>X(E),disabled:M,children:"Keep the stored value"})]}):e(st,{keyName:E,value:Ee(E),onChange:(q)=>ne(E,q),error:K}),Y&&!Z&&e(s,{size:"sm",onClick:()=>ne(E,De),disabled:M,children:"Use code value"})]},E)})}),R.overridden.length>0&&t("div",{className:"job-defaults-reset",children:[e(s,{variant:"danger",onClick:()=>{N(null),ae.mutate()},disabled:M,"aria-busy":ae.isPending||void 0,"aria-describedby":H,children:ae.isPending?"Resetting…":"Reset to code values"}),e("p",{id:H,className:"job-defaults-reset-note",children:$e})]})]})}function st({keyName:d,value:u,onChange:y,error:x}){let j=be[d];if(j==="backoff")return e(lt,{value:u,onChange:y,error:x});if(j==="retention")return e(ut,{label:W[d].label,hint:W[d].hint,value:u,onChange:y,error:x});let R=je(d);return e(l,{label:W[d].label,hint:`${W[d].hint} ${R.min}–${R.max}.`,error:x,children:e(V,{value:u,min:R.min,max:R.max,onChange:y})})}function lt({value:d,onChange:u,error:y}){let x=Re.backoffDelay,j=Re.backoffMax,R=Re.backoffJitter;return t("div",{className:"field-row job-defaults-backoff",children:[e(l,{label:"Backoff strategy",hint:"Fixed waits the delay every time; exponential doubles it after each attempt. Other strategies can be set only in code.",children:e(S,{options:Dn.map((k)=>({value:k})),value:d.type,onChange:(k)=>u({...d,type:k})})}),e(l,{label:"Backoff delay (ms)",hint:`${x.min}–${x.max}.`,error:y,children:e(V,{value:d.delay,min:x.min,max:x.max,onChange:(k)=>u({...d,delay:k})})}),e(l,{label:"Backoff longest delay (ms)",hint:`Optional, at least the delay; empty means uncapped. Up to ${j.max}.`,children:e(V,{value:d.max,min:j.min,max:j.max,onChange:(k)=>u({...d,max:k})})}),e(l,{label:"Backoff jitter",hint:`Optional fraction of each delay randomised, ${R.min}–${R.max}.`,children:e(V,{value:d.jitter,min:R.min,max:R.max,step:"any",onChange:(k)=>u({...d,jitter:k})})})]})}var dt=[{value:"keep",label:"Keep forever"},{value:"limit",label:"Keep the newest, or for a while"},{value:"remove",label:"Remove at once"}];function ut({label:d,hint:u,value:y,onChange:x,error:j}){let R=Re.retentionCount,k=Re.retentionTtl;return t("div",{className:"field-row job-defaults-retention",children:[e(l,{label:d,hint:y.mode==="remove"?`${u} Removing at once discards every result (or failure) as the job settles.`:u,error:y.mode==="limit"?void 0:j,children:e(S,{options:dt,value:y.mode,onChange:(B)=>x({...y,mode:B})})}),y.mode==="limit"&&t(n,{children:[e(l,{label:`${d}: most kept`,hint:`Optional, ${R.min}–${R.max}.`,error:j,children:e(V,{value:y.count,min:R.min,max:R.max,onChange:(B)=>x({...y,count:B})})}),e(l,{label:`${d}: kept for (ms)`,hint:`Optional, up to ${k.max} (a year).`,children:e(V,{value:y.ttl,min:k.min,max:k.max,onChange:(B)=>x({...y,ttl:B})})})]})]})}function ft({queue:d,canEdit:u,canApply:y}){let x=i(),j=le("counts"),[R,k]=r(null),B=a({queryKey:fe(d),queryFn:({signal:g})=>me(x,d,g),refetchInterval:R===null?j:!1}),L=()=>k(null);if(B.data===void 0)return B.isError?e(D,{error:B.error,title:"Could not load the job defaults",onRetry:()=>void B.refetch()}):e(p,{label:"Loading the job defaults",showLabel:!0});let C=B.data,{total:N}=C.pending,Q=C.overridden.length===0;return t("div",{className:"job-defaults","data-testid":"job-defaults-panel",children:[t("p",{children:["What a job added to this queue gets for an option its ",e("code",{children:"add()"})," ","does not pass. A stored override beats the code’s defaults and"," ",e("code",{children:"define()"})," defaults; only an option passed explicitly on"," ",e("code",{children:"add()"})," wins. A change reaches producers within about"," ",U(C.propagationMs),"."]}),t(I,{label:"Job defaults",children:[e("thead",{children:t("tr",{children:[e("th",{scope:"col",children:"Option"}),e("th",{scope:"col",children:"A job gets"}),e("th",{scope:"col",children:"Code"}),e("th",{scope:"col",children:"Override"})]})}),e("tbody",{children:_t.map((g)=>{let H=C.overridden.includes(g);return t("tr",{"data-testid":`job-default-row-${g}`,children:[e("th",{scope:"row",title:W[g].hint,children:W[g].label}),e("td",{children:re(g,C.effective[g])}),e("td",{children:re(g,C.code[g])}),e("td",{children:H?e(f,{tone:"accent",children:"Overridden"}):e("span",{className:"muted",children:"code value"})})]},g)})})]}),t("p",{className:"muted job-defaults-note",children:[ke,C.updatedAt!==void 0&&t(n,{children:[" ","Last changed ",e(A,{value:C.updatedAt}),"."]})]}),e("h3",{className:"job-defaults-heading",children:"Pending jobs"}),e(F,{className:"job-defaults-pending",items:[...It.map((g)=>({key:g,label:z[g],value:o(C.pending[g])})),{key:"total",label:"Total",value:o(N)}]}),e("p",{className:"muted",children:"Pending jobs keep the values they were added with: saving changes only jobs added afterwards. Applying the defaults to them is a separate action."}),t("div",{className:"form-actions",children:[u&&e(s,{onClick:()=>k("settings"),children:"Settings…"}),y&&!Q&&N>0&&t(s,{variant:"danger",onClick:()=>k("apply"),children:["Apply to ",o(N)," pending"," ",N===1?"job":"jobs","…"]})]}),y&&(Q||N===0)&&e("p",{className:"muted","data-testid":"apply-unavailable",children:Q?"Nothing is overridden, so there is nothing to apply to pending jobs.":"No jobs are pending in the states the defaults can be applied to."}),R==="settings"&&e(qe,{queue:d,defaults:C,onClose:L}),R==="apply"&&e(Ie,{queue:d,defaults:C,onClose:L})]})}export{ft as JobDefaultsPanel};
|
|
2
|
+
|
|
3
|
+
//# debugId=BA9076DA5653324964756E2164756E21
|
|
4
|
+
//# sourceMappingURL=index-09z2cjft.js.map
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["app/jobs/api/jobDefaults.ts", "app/jobs/screens/queues/panels/jobDefaults/applyLoop.ts", "app/jobs/screens/queues/panels/jobDefaults/draft.ts", "app/jobs/screens/queues/panels/jobDefaults/text.ts", "app/jobs/screens/queues/panels/jobDefaults/ApplyDefaultsDialog.tsx", "app/jobs/screens/queues/panels/jobDefaults/JobDefaultsDialog.tsx", "app/jobs/screens/queues/panels/jobDefaults/JobDefaultsPanel.tsx"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"import type { ApiClient } from \"./client\";\nimport type { ShapeFields } from \"./shape\";\nimport type {\n ApplyJobDefaultsBody,\n ApplyJobDefaultsResultDto,\n JobDefaultsBody,\n JobDefaultsDto,\n} from \"./types\";\nimport { segment } from \"./client\";\nimport { queryKeys } from \"./queryKeys\";\nimport { assertShape, hasStrings } from \"./shape\";\n\n/**\n * A queue's job defaults: `GET`/`PUT`/`DELETE /queues/:queue/job-defaults`\n * and `POST /queues/:queue/job-defaults/apply`.\n *\n * The key sits under `queryKeys.queue(q)`, so every queue mutation's\n * invalidation (`mutationInvalidations`) re-reads the defaults too, and the\n * pending counts they carry.\n */\n\n/**\n * Most jobs one apply call examines: the API's own default `limit`.\n *\n * The API's own ceiling is `meta.limits.maxApplyDefaults` (default 1000, a\n * host may raise it to 10 000), so a batch is the smaller of the two — see\n * {@link applyBatchLimit}. Over the ceiling the API answers 400 VALIDATION.\n */\nexport const APPLY_BATCH_LIMIT = 1_000;\n\n/** One apply batch's size: {@link APPLY_BATCH_LIMIT}, capped by the API's `maxApplyDefaults`. */\nexport function applyBatchLimit(maxApplyDefaults: number): number {\n return Math.max(1, Math.min(APPLY_BATCH_LIMIT, maxApplyDefaults));\n}\n\n/** `GET /queues/:queue/job-defaults`'s query key. */\nexport function jobDefaultsKey(queue: string) {\n return [...queryKeys.queue(queue), \"job-defaults\"] as const;\n}\n\n/** The API path of a queue's job defaults, plus an optional suffix. */\nfunction path(queue: string, suffix = \"\"): string {\n return `/queues/${segment(queue)}/job-defaults${suffix}`;\n}\n\n/** Whether a body is shaped like a {@link JobDefaultsDto}. */\nfunction isJobDefaults(fields: ShapeFields): boolean {\n return (\n hasStrings(fields, \"queue\") &&\n typeof fields.seq === \"number\" &&\n Array.isArray(fields.overridden) &&\n typeof fields.effective === \"object\" &&\n fields.effective !== null &&\n typeof fields.code === \"object\" &&\n fields.code !== null &&\n typeof fields.pending === \"object\" &&\n fields.pending !== null\n );\n}\n\n/**\n * `GET /queues/:queue/job-defaults`. A body that is not job defaults rejects\n * with an `UNEXPECTED_RESPONSE` `ApiError`, so the panel shows an error rather\n * than a form with nothing in it.\n */\nexport async function getJobDefaults(\n api: ApiClient,\n queue: string,\n signal?: AbortSignal,\n): Promise<JobDefaultsDto> {\n const url = path(queue);\n const body = await api.request<unknown>(\"GET\", url, { signal });\n return assertShape<JobDefaultsDto>(body, isJobDefaults, \"job defaults\", url);\n}\n\n/** `PUT /queues/:queue/job-defaults`: a merge patch (`null` clears one key). Resolves the defaults after the write. */\nexport function setJobDefaults(\n api: ApiClient,\n queue: string,\n body: JobDefaultsBody,\n) {\n return api.request<JobDefaultsDto>(\"PUT\", path(queue), { body });\n}\n\n/**\n * `DELETE /queues/:queue/job-defaults?expectedSeq=`: every key back to the\n * code's value. `expectedSeq` is the `seq` read, so a reset racing someone\n * else's save answers 409 `CONTROL_CONTENDED` instead of silently erasing it.\n * Resolves the defaults after the write.\n */\nexport function resetJobDefaults(\n api: ApiClient,\n queue: string,\n expectedSeq: number,\n) {\n return api.request<JobDefaultsDto>(\"DELETE\", path(queue), {\n query: { expectedSeq },\n });\n}\n\n/** `POST /queues/:queue/job-defaults/apply`: one batch of the rewrite. */\nexport function applyJobDefaults(\n api: ApiClient,\n queue: string,\n body: ApplyJobDefaultsBody,\n) {\n return api.request<ApplyJobDefaultsResultDto>(\"POST\", path(queue, \"/apply\"), {\n body,\n });\n}\n",
|
|
6
|
+
"import type { ApplyJobDefaultsResultDto } from \"../../../../api/types\";\n\n/**\n * The client side of `POST /queues/:queue/job-defaults/apply`: one call\n * examines at most `limit` jobs and answers a cursor (`next`), so rewriting a\n * backlog is a loop of calls until `done`. It stops early when asked to\n * (Cancel, checked between batches: a batch already sent completes on the\n * server either way), and on the first failure, keeping what was done and the\n * cursor to resume from.\n */\n\n/** What a walk did so far, summed over its batches. */\nexport interface ApplyTotals {\n /** Jobs looked at. */\n examined: number;\n /** Jobs at least one of whose options changed (or would, in a dry run). */\n rewritten: number;\n /** Jobs that already had every value. */\n unchanged: number;\n /** Jobs left alone because every changing key was passed explicitly on their `add()`. */\n skippedExplicit: number;\n /** Jobs added before bun-jobs recorded which options were explicit, left alone. */\n skippedUnmarked: number;\n /** Jobs that left the walked states before their write (a lower bound). */\n moved: number;\n /** Rewritten jobs whose attempts made already reach the new `attempts`: each gets one final attempt. Part of `rewritten`. */\n exhausted: number;\n /** Calls answered. */\n batches: number;\n}\n\n/** Totals before the first batch. */\nexport function emptyTotals(): ApplyTotals {\n return {\n examined: 0,\n rewritten: 0,\n unchanged: 0,\n skippedExplicit: 0,\n skippedUnmarked: 0,\n moved: 0,\n exhausted: 0,\n batches: 0,\n };\n}\n\n/** Totals with one more batch's answer added. */\nexport function addBatch(\n totals: ApplyTotals,\n result: ApplyJobDefaultsResultDto,\n): ApplyTotals {\n return {\n examined: totals.examined + result.examined,\n rewritten: totals.rewritten + result.rewritten,\n unchanged: totals.unchanged + result.unchanged,\n skippedExplicit: totals.skippedExplicit + result.skippedExplicit,\n skippedUnmarked: totals.skippedUnmarked + result.skippedUnmarked,\n moved: totals.moved + result.moved,\n exhausted: totals.exhausted + result.exhausted,\n batches: totals.batches + 1,\n };\n}\n\n/** How a walk ended. */\nexport type ApplyOutcome =\n | {\n /** Every batch answered; the walk is complete. */\n status: \"done\";\n /** What it did. */\n totals: ApplyTotals;\n }\n | {\n /** Stopped between batches because the user asked. */\n status: \"cancelled\";\n /** What it did before stopping. */\n totals: ApplyTotals;\n /** Where a later walk would continue. */\n cursor: string;\n }\n | {\n /** A call failed. */\n status: \"failed\";\n /** What it did before the failure. */\n totals: ApplyTotals;\n /** The cursor the failed call was sent with (`undefined` for the first), to resume from. */\n cursor: string | undefined;\n /** The failure, usually an `ApiError`. */\n error: unknown;\n };\n\n/** Options of {@link runApplyLoop}. */\nexport interface RunApplyLoopOptions {\n /** Sends one call, continuing from `cursor` (`undefined` for the first). */\n request: (cursor: string | undefined) => Promise<ApplyJobDefaultsResultDto>;\n /** The cursor to start from, to resume a stopped walk. Defaults to the start. */\n cursor?: string;\n /** Totals to add to, when resuming. Defaults to {@link emptyTotals}. */\n totals?: ApplyTotals;\n /** Called after each batch with the running totals. */\n onBatch?: (totals: ApplyTotals, result: ApplyJobDefaultsResultDto) => void;\n /** Asked before each call after the first: `true` stops the walk there. */\n shouldStop: () => boolean;\n}\n\n/**\n * Calls `request` repeatedly, `cursor` → `next`, until an answer is `done`,\n * `shouldStop()` says so between batches, or a call fails. Never throws: a\n * failure is an outcome carrying the totals so far. An answer that is not\n * done but repeats the cursor it was sent (the walk would never end) is a\n * failure too.\n */\nexport async function runApplyLoop(\n options: RunApplyLoopOptions,\n): Promise<ApplyOutcome> {\n let cursor = options.cursor;\n let totals = options.totals ?? emptyTotals();\n let first = true;\n for (;;) {\n if (!first && cursor !== undefined && options.shouldStop()) {\n return { status: \"cancelled\", totals, cursor };\n }\n first = false;\n let result: ApplyJobDefaultsResultDto;\n try {\n result = await options.request(cursor);\n } catch (error) {\n return { status: \"failed\", totals, cursor, error };\n }\n totals = addBatch(totals, result);\n options.onBatch?.(totals, result);\n if (result.done || result.next === null) {\n return { status: \"done\", totals };\n }\n if (result.next === cursor) {\n return {\n status: \"failed\",\n totals,\n cursor,\n error: new Error(\n \"The API answered the same cursor it was sent, so the walk would never end.\",\n ),\n };\n }\n cursor = result.next;\n }\n}\n",
|
|
7
|
+
"import type {\n JobDefaultBackoffType,\n JobDefaultKey,\n} from \"../../../../api/contract\";\nimport type {\n JobDefaultBackoff,\n JobDefaultsBody,\n JobDefaultsDto,\n JobDefaultsValues,\n RetentionDto,\n} from \"../../../../api/types\";\nimport {\n JOB_DEFAULT_BACKOFF_TYPES,\n JOB_DEFAULT_KEYS,\n JOB_DEFAULTS_BOUNDS,\n} from \"../../../../api/contract\";\nimport { formatNumber } from \"../../../../format\";\nimport { formatMs } from \"../../duration\";\n\n/**\n * The job-defaults form's model: what each contract key edits as, the draft\n * the inputs hold, and the merge patch a save sends. Every list and bound\n * comes from the contract (`JOB_DEFAULT_KEYS`, `JOB_DEFAULT_BACKOFF_TYPES`,\n * `JOB_DEFAULTS_BOUNDS`); {@link KEY_KIND} is typed against `JobDefaultKey`,\n * so a key the contract adds fails to compile here until it has an editor.\n */\n\n/** How one key is edited: a whole number, a backoff, or a retention. */\nexport type KeyKind = \"number\" | \"backoff\" | \"retention\";\n\n/** The editor of each key the contract lets a queue store. */\nexport const KEY_KIND = {\n attempts: \"number\",\n backoff: \"backoff\",\n timeout: \"number\",\n priority: \"number\",\n removeOnComplete: \"retention\",\n removeOnFail: \"retention\",\n keepLogs: \"number\",\n keepStacktraces: \"number\",\n} as const satisfies Readonly<Record<JobDefaultKey, KeyKind>>;\n\n/** The keys edited as one whole number; each has its own `JOB_DEFAULTS_BOUNDS` entry. */\nexport type NumberKey = {\n [K in JobDefaultKey]: (typeof KEY_KIND)[K] extends \"number\" ? K : never;\n}[JobDefaultKey];\n\n/** The keys edited as a retention. */\nexport type RetentionKey = {\n [K in JobDefaultKey]: (typeof KEY_KIND)[K] extends \"retention\" ? K : never;\n}[JobDefaultKey];\n\n/** Each key's label and one-line explanation, under its input and in the summary. */\nexport const KEY_TEXT: Readonly<\n Record<JobDefaultKey, { label: string; hint: string }>\n> = {\n attempts: {\n label: \"Attempts\",\n hint: \"How many times a job runs before it is dead, the first run included.\",\n },\n backoff: {\n label: \"Backoff\",\n hint: \"How long a failed job waits before its next attempt.\",\n },\n timeout: {\n label: \"Timeout (ms)\",\n hint: \"How long one attempt may run before it fails; 0 means no timeout.\",\n },\n priority: {\n label: \"Priority\",\n hint: \"Lower runs first.\",\n },\n removeOnComplete: {\n label: \"Completed jobs\",\n hint: \"How long a completed job is kept.\",\n },\n removeOnFail: {\n label: \"Dead jobs\",\n hint: \"How long a job that ran out of attempts is kept.\",\n },\n keepLogs: {\n label: \"Log lines kept\",\n hint: \"The newest log lines each job keeps.\",\n },\n keepStacktraces: {\n label: \"Stack traces kept\",\n hint: \"The failure stack traces each job keeps.\",\n },\n};\n\n/** The inclusive bounds of a number key, from the contract. */\nexport function numberBounds(key: NumberKey): { min: number; max: number } {\n return JOB_DEFAULTS_BOUNDS[key];\n}\n\n/** A backoff as the form holds it. */\nexport interface BackoffDraft {\n /** The strategy: one of `JOB_DEFAULT_BACKOFF_TYPES`. */\n type: JobDefaultBackoffType;\n /** Base delay, ms; `undefined` while the box is empty. */\n delay: number | undefined;\n /** Longest delay, ms; `undefined` for uncapped. */\n max: number | undefined;\n /** Fraction of each delay randomised, 0 … 1; `undefined` for none. */\n jitter: number | undefined;\n}\n\n/** What a retention does: keep forever (`false`), remove at once (`true`), or keep some. */\nexport type RetentionMode = \"keep\" | \"remove\" | \"limit\";\n\n/** A retention as the form holds it. */\nexport interface RetentionDraft {\n /** Keep forever, remove at once, or keep up to a count and/or an age. */\n mode: RetentionMode;\n /** With `limit`: most jobs kept; `undefined` for no count. */\n count: number | undefined;\n /** With `limit`: oldest job kept, ms; `undefined` for no age. */\n ttl: number | undefined;\n}\n\n/** The form's value of one key. */\nexport type DraftOf<K extends JobDefaultKey> = K extends JobDefaultKey\n ? (typeof KEY_KIND)[K] extends \"number\"\n ? number | undefined\n : (typeof KEY_KIND)[K] extends \"backoff\"\n ? BackoffDraft\n : RetentionDraft\n : never;\n\n/** A key marked \"use the code value\": the save sends `null` for it. */\nexport const USE_CODE = \"code\" as const;\n\n/**\n * What has been edited: only keys the user touched are present, each with its\n * value or {@link USE_CODE}. An untouched key is left out of the patch.\n */\nexport type Draft = {\n [K in JobDefaultKey]?: DraftOf<K> | typeof USE_CODE;\n};\n\n/** Whether a backoff strategy may be stored remotely. */\nfunction isStorableType(type: unknown): type is JobDefaultBackoffType {\n return (JOB_DEFAULT_BACKOFF_TYPES as readonly unknown[]).includes(type);\n}\n\n/** The form's starting value of a backoff: a bare number is a fixed delay; a strategy that cannot be stored starts as `fixed`. */\nexport function backoffDraft(\n value: JobDefaultsValues[\"backoff\"],\n): BackoffDraft {\n if (typeof value === \"number\") {\n return { type: \"fixed\", delay: value, max: undefined, jitter: undefined };\n }\n return {\n type: isStorableType(value.type) ? value.type : \"fixed\",\n delay: value.delay,\n max: value.max,\n // A code value may say `jitter: true`, which a stored default cannot.\n jitter: typeof value.jitter === \"number\" ? value.jitter : undefined,\n };\n}\n\n/** The form's starting value of a retention. */\nexport function retentionDraft(value: RetentionDto): RetentionDraft {\n if (value === true) {\n return { mode: \"remove\", count: undefined, ttl: undefined };\n }\n if (value === false) {\n return { mode: \"keep\", count: undefined, ttl: undefined };\n }\n if (typeof value === \"number\") {\n return { mode: \"limit\", count: value, ttl: undefined };\n }\n return { mode: \"limit\", count: value.count, ttl: value.ttl };\n}\n\n/** The form's value of `key`, starting from a value the API sent. */\nexport function draftOf<K extends JobDefaultKey>(\n key: K,\n value: JobDefaultsValues[K],\n): DraftOf<K> {\n const kind: KeyKind = KEY_KIND[key];\n if (kind === \"backoff\") {\n return backoffDraft(value as JobDefaultsValues[\"backoff\"]) as DraftOf<K>;\n }\n if (kind === \"retention\") {\n return retentionDraft(value as RetentionDto) as DraftOf<K>;\n }\n return value as DraftOf<K>;\n}\n\n/** Why a whole number is refused, or `undefined` when it is fine. */\nfunction wholeProblem(\n value: number | undefined,\n bounds: { min: number; max: number },\n what: string,\n): string | undefined {\n if (value === undefined) {\n return `Enter ${what}.`;\n }\n if (!Number.isInteger(value) || value < bounds.min || value > bounds.max) {\n return `${capitalise(what)} must be a whole number from ${formatNumber(bounds.min)} to ${formatNumber(bounds.max)}.`;\n }\n return undefined;\n}\n\n/** `\"a delay\"` → `\"A delay\"`. */\nfunction capitalise(text: string): string {\n return text.charAt(0).toUpperCase() + text.slice(1);\n}\n\n/** A key's value to store, or why the form cannot send it. */\nexport type Parsed<T> = { value: T } | { error: string };\n\n/** The backoff to store, from the form. */\nexport function parseBackoff(draft: BackoffDraft): Parsed<JobDefaultBackoff> {\n const delayProblem = wholeProblem(\n draft.delay,\n JOB_DEFAULTS_BOUNDS.backoffDelay,\n \"a delay\",\n );\n if (delayProblem) {\n return { error: delayProblem };\n }\n const delay = draft.delay!;\n if (draft.max !== undefined) {\n const maxProblem = wholeProblem(\n draft.max,\n JOB_DEFAULTS_BOUNDS.backoffMax,\n \"the longest delay\",\n );\n if (maxProblem) {\n return { error: maxProblem };\n }\n if (draft.max < delay) {\n return { error: \"The longest delay must be at least the delay.\" };\n }\n }\n const jitterBounds = JOB_DEFAULTS_BOUNDS.backoffJitter;\n if (\n draft.jitter !== undefined &&\n !(draft.jitter >= jitterBounds.min && draft.jitter <= jitterBounds.max)\n ) {\n return {\n error: `Jitter must be from ${jitterBounds.min} to ${jitterBounds.max}.`,\n };\n }\n return {\n value: {\n type: draft.type,\n delay,\n ...(draft.max === undefined ? {} : { max: draft.max }),\n ...(draft.jitter === undefined ? {} : { jitter: draft.jitter }),\n },\n };\n}\n\n/** The retention to store, from the form. */\nexport function parseRetention(draft: RetentionDraft): Parsed<RetentionDto> {\n if (draft.mode === \"keep\") {\n return { value: false };\n }\n if (draft.mode === \"remove\") {\n return { value: true };\n }\n if (draft.count === undefined && draft.ttl === undefined) {\n return { error: \"Enter a count, an age, or both.\" };\n }\n if (draft.count !== undefined) {\n const problem = wholeProblem(\n draft.count,\n JOB_DEFAULTS_BOUNDS.retentionCount,\n \"the count\",\n );\n if (problem) {\n return { error: problem };\n }\n }\n if (draft.ttl !== undefined) {\n const problem = wholeProblem(\n draft.ttl,\n JOB_DEFAULTS_BOUNDS.retentionTtl,\n \"the age\",\n );\n if (problem) {\n return { error: problem };\n }\n }\n return {\n value: {\n ...(draft.count === undefined ? {} : { count: draft.count }),\n ...(draft.ttl === undefined ? {} : { ttl: draft.ttl }),\n },\n };\n}\n\n/** The value to store for one key, from its form value. */\nexport function parseKey(\n key: JobDefaultKey,\n draft: DraftOf<JobDefaultKey>,\n): Parsed<unknown> {\n const kind: KeyKind = KEY_KIND[key];\n if (kind === \"backoff\") {\n return parseBackoff(draft as BackoffDraft);\n }\n if (kind === \"retention\") {\n return parseRetention(draft as RetentionDraft);\n }\n const problem = wholeProblem(\n draft as number | undefined,\n numberBounds(key as NumberKey),\n \"a value\",\n );\n return problem ? { error: problem } : { value: draft };\n}\n\n/**\n * A value in one canonical form, so two spellings of one setting compare\n * equal: a bare-number backoff is a fixed delay, a bare-number retention a\n * count, and object keys are sorted with absent fields dropped.\n */\nexport function canonical(key: JobDefaultKey, value: unknown): string {\n let normal = value;\n if (KEY_KIND[key] === \"backoff\" && typeof value === \"number\") {\n normal = { type: \"fixed\", delay: value };\n } else if (KEY_KIND[key] === \"retention\" && typeof value === \"number\") {\n normal = { count: value };\n }\n if (typeof normal === \"object\" && normal !== null) {\n const record = normal as Record<string, unknown>;\n return JSON.stringify(\n Object.fromEntries(\n Object.keys(record)\n .filter((name) => record[name] !== undefined)\n .sort()\n .map((name) => [name, record[name]]),\n ),\n );\n }\n return JSON.stringify(normal);\n}\n\n/** What a save would send, and what stops it. */\nexport interface PatchPlan {\n /** The merge patch: only changed keys, `null` for \"use the code value\", and `expectedSeq`. */\n body: JobDefaultsBody;\n /** The keys the patch changes, in `JOB_DEFAULT_KEYS` order. */\n changed: JobDefaultKey[];\n /** Why a key cannot be sent, by key; a save is refused while any is present. */\n errors: Partial<Record<JobDefaultKey, string>>;\n}\n\n/**\n * The merge patch for a draft, against the defaults it was edited from. A key\n * is sent only when its value differs from what a job gets now (`effective`);\n * \"use the code value\" sends `null`, and only for a key that is overridden\n * (otherwise it already is the code's). `expectedSeq` is the `seq` read, so a\n * save racing another answers 409 instead of overwriting it.\n */\nexport function planPatch(defaults: JobDefaultsDto, draft: Draft): PatchPlan {\n const body: JobDefaultsBody = {};\n const fields = body as Record<string, unknown>;\n const changed: JobDefaultKey[] = [];\n const errors: Partial<Record<JobDefaultKey, string>> = {};\n for (const key of JOB_DEFAULT_KEYS) {\n if (!(key in draft)) {\n continue;\n }\n const edited = draft[key];\n if (edited === USE_CODE) {\n if (defaults.overridden.includes(key)) {\n fields[key] = null;\n changed.push(key);\n }\n continue;\n }\n const parsed = parseKey(key, edited);\n if (\"error\" in parsed) {\n errors[key] = parsed.error;\n continue;\n }\n if (\n canonical(key, parsed.value) !== canonical(key, defaults.effective[key])\n ) {\n fields[key] = parsed.value;\n changed.push(key);\n }\n }\n body.expectedSeq = defaults.seq;\n return { body, changed, errors };\n}\n\n/** A retention in words. */\nfunction describeRetention(value: RetentionDto): string {\n if (value === true) {\n return \"removed at once\";\n }\n if (value === false) {\n return \"kept forever\";\n }\n if (typeof value === \"number\") {\n return `the newest ${formatNumber(value)} kept`;\n }\n const parts = [\n value.count !== undefined && `the newest ${formatNumber(value.count)}`,\n value.ttl !== undefined && `for ${formatMs(value.ttl)}`,\n ].filter(Boolean);\n return parts.length > 0 ? `kept: ${parts.join(\", \")}` : \"kept forever\";\n}\n\n/** A backoff in words. */\nfunction describeBackoff(value: JobDefaultsValues[\"backoff\"]): string {\n if (typeof value === \"number\") {\n return value === 0 ? \"none\" : `fixed, ${formatMs(value)}`;\n }\n const parts = [\n `${value.type ?? \"fixed\"}, ${formatMs(value.delay ?? 0)}`,\n value.max !== undefined && `at most ${formatMs(value.max)}`,\n value.jitter === true && \"jitter\",\n typeof value.jitter === \"number\" &&\n value.jitter > 0 &&\n `${Math.round(value.jitter * 100)}% jitter`,\n ].filter(Boolean);\n return parts.join(\", \");\n}\n\n/** One key's value in words, for the summary and the dialogs. */\nexport function describeValue(key: JobDefaultKey, value: unknown): string {\n if (value === undefined || value === null) {\n return \"—\";\n }\n switch (key) {\n case \"backoff\":\n return describeBackoff(value);\n case \"removeOnComplete\":\n case \"removeOnFail\":\n return describeRetention(value);\n case \"timeout\":\n return value === 0 ? \"none\" : formatMs(value as number);\n case \"keepLogs\":\n return value === 0 ? \"every line\" : formatNumber(value as number);\n default:\n return formatNumber(value as number);\n }\n}\n",
|
|
8
|
+
"/**\n * Sentences the job-defaults panel and its dialogs share, and the tests\n * assert: said once, so the panel, the editor and the apply dialog cannot\n * disagree.\n */\n\n/** What the \"code\" values are (`JobDefaultsDto.codeSource` `\"api\"`). */\nexport const CODE_SOURCE_HINT =\n \"“Code” is what this API's own service is configured with. A producer in another service may be configured differently; a stored value replaces them all alike.\";\n\n/** Beside Reset: what a reset cannot undo. */\nexport const RESET_CANNOT_RESTORE =\n \"Resetting changes jobs added from now on. It cannot restore jobs already rewritten by Apply to pending jobs: they keep the values written to them.\";\n\n/** Whether the apply action is reversible. */\nexport const APPLY_IRREVERSIBLE =\n \"This rewrites the jobs in place and cannot be undone: their previous values are not kept, so a later reset does not restore them.\";\n\n/** What `includeUnmarked` means, under its checkbox. */\nexport const INCLUDE_UNMARKED_HINT =\n \"Jobs added before this version of bun-jobs did not record which options their add() passed explicitly. Left unticked, they are skipped and counted. Ticked, every option of theirs is treated as a default, so a value their add() did pass may be replaced.\";\n\n/** What lowering `attempts` does to a job that has used them up. */\nexport const EXHAUSTED_WARNING =\n \"Attempts is being lowered. A job that has already made at least the new number of attempts is not dropped: it gets ONE final attempt, and dies if that fails.\";\n",
|
|
9
|
+
"import type { JobDefaultsApplyState } from \"../../../../api/contract\";\nimport type {\n ApplyJobDefaultsBody,\n JobDefaultsDto,\n} from \"../../../../api/types\";\nimport type { ApplyOutcome, ApplyTotals } from \"./applyLoop\";\nimport { useQueryClient } from \"@tanstack/react-query\";\nimport { useEffect, useRef, useState } from \"react\";\nimport { JOB_DEFAULTS_APPLY_STATES } from \"../../../../api/contract\";\nimport { isApiError } from \"../../../../api/errors\";\nimport {\n applyBatchLimit,\n applyJobDefaults,\n getJobDefaults,\n jobDefaultsKey,\n} from \"../../../../api/jobDefaults\";\nimport { mutationInvalidations } from \"../../../../api/queues\";\nimport { Button } from \"../../../../components/Button\";\nimport { Dialog } from \"../../../../components/Dialog\";\nimport { Checkbox } from \"../../../../components/inputs\";\nimport { KeyValue } from \"../../../../components/KeyValue\";\nimport { ProblemBanner } from \"../../../../components/ProblemBanner\";\nimport { useApiClient } from \"../../../../context\";\nimport { formatNumber, plural, STATE_LABELS } from \"../../../../format\";\nimport { useMeta } from \"../../../../meta/hooks\";\nimport { emptyTotals, runApplyLoop } from \"./applyLoop\";\nimport { describeValue, KEY_TEXT } from \"./draft\";\nimport {\n APPLY_IRREVERSIBLE,\n EXHAUSTED_WARNING,\n INCLUDE_UNMARKED_HINT,\n} from \"./text\";\n\n/** Props of {@link ApplyDefaultsDialog}. */\nexport interface ApplyDefaultsDialogProps {\n /** The queue. */\n queue: string;\n /** The defaults as read when the dialog opened: their `seq` is what every call applies, and their `pending` counts what the states offer. */\n defaults: JobDefaultsDto;\n /** Asks to close. */\n onClose: () => void;\n}\n\n/** Where the dialog is: choosing, walking, or showing how a walk ended. */\ntype Phase =\n | {\n /** Choosing states and options. */\n kind: \"confirm\";\n /** The last dry run's outcome, when one ran, to show beside the choices. */\n preview?: Finished;\n }\n | {\n /** Calls in flight, batch after batch. */\n kind: \"running\";\n /** Whether this walk writes nothing. */\n dryRun: boolean;\n /** Totals so far. */\n totals: ApplyTotals;\n /** Cancel was pressed: the walk stops after the batch in flight. */\n stopping: boolean;\n }\n | Finished\n | {\n /** The stored defaults moved on (409 `DEFAULTS_CHANGED`): nothing more was applied. */\n kind: \"conflict\";\n /** Whether the walk was a dry run. */\n dryRun: boolean;\n /** What had been done before the refusal. */\n totals: ApplyTotals;\n };\n\n/** A walk that ended: done, cancelled, or failed. */\ninterface Finished {\n /** A walk that ended. */\n kind: \"finished\";\n /** Whether it wrote nothing. */\n dryRun: boolean;\n /** How it ended. */\n outcome: ApplyOutcome;\n}\n\n/** Whether any walked job will have `attempts` lowered: the stored value is below the code's. */\nfunction lowersAttempts(defaults: JobDefaultsDto): boolean {\n return (\n defaults.overridden.includes(\"attempts\") &&\n defaults.effective.attempts < defaults.code.attempts\n );\n}\n\n/**\n * Apply a queue's stored job defaults to the jobs already pending: an\n * explicit, separate action, never a side effect of saving.\n *\n * The user picks the states (`JOB_DEFAULTS_APPLY_STATES`, all ticked) and\n * whether jobs added before the explicit record are included (unticked), may\n * run a dry run first to see what WOULD change, and confirms. The walk then\n * calls the API batch by batch (`cursor` → `next`) until `done`, pinned to the\n * `seq` read when the dialog opened, showing progress, with Cancel between\n * batches. A 409 `DEFAULTS_CHANGED` stops it and asks to re-read.\n */\nexport function ApplyDefaultsDialog({\n queue,\n defaults: initial,\n onClose,\n}: ApplyDefaultsDialogProps) {\n const api = useApiClient();\n const meta = useMeta();\n const queryClient = useQueryClient();\n const [defaults, setDefaults] = useState(initial);\n const [states, setStates] = useState<readonly JobDefaultsApplyState[]>(\n JOB_DEFAULTS_APPLY_STATES,\n );\n const [includeUnmarked, setIncludeUnmarked] = useState(false);\n const [phase, setPhase] = useState<Phase>({ kind: \"confirm\" });\n const [rereading, setRereading] = useState(false);\n const [rereadError, setRereadError] = useState<unknown>(null);\n const stopRef = useRef(false);\n // A dialog unmounted mid-walk (the screen navigated away) stops the walk\n // after the batch in flight, rather than rewriting on unseen.\n useEffect(\n () => () => {\n stopRef.current = true;\n },\n [],\n );\n\n const running = phase.kind === \"running\";\n const selected = JOB_DEFAULTS_APPLY_STATES.filter((state) =>\n states.includes(state),\n );\n const upperBound = selected.reduce(\n (sum, state) => sum + defaults.pending[state],\n 0,\n );\n const preview = phase.kind === \"confirm\" ? phase.preview : undefined;\n const warnExhausted =\n lowersAttempts(defaults) ||\n (preview !== undefined && preview.outcome.totals.exhausted > 0);\n\n /**\n * Runs one walk (a dry run or the real thing), updating the phase as batches\n * answer; `resume` continues a stopped or failed walk from its cursor.\n */\n const walk = async (\n dryRun: boolean,\n resume?: { cursor: string | undefined; totals: ApplyTotals },\n ) => {\n stopRef.current = false;\n const start = resume?.totals ?? emptyTotals();\n setPhase({ kind: \"running\", dryRun, totals: start, stopping: false });\n const outcome = await runApplyLoop({\n cursor: resume?.cursor,\n totals: start,\n request: (cursor) => {\n const body: ApplyJobDefaultsBody = {\n seq: defaults.seq,\n states: [...selected],\n limit: applyBatchLimit(meta.limits.maxApplyDefaults),\n dryRun,\n includeUnmarked,\n };\n if (cursor !== undefined) {\n body.cursor = cursor;\n }\n return applyJobDefaults(api, queue, body);\n },\n onBatch: (totals) =>\n setPhase((current) =>\n current.kind === \"running\" ? { ...current, totals } : current,\n ),\n shouldStop: () => stopRef.current,\n });\n if (!dryRun && outcome.totals.batches > 0) {\n // The jobs changed: re-read the counts, the table and the defaults.\n for (const queryKey of mutationInvalidations(queue)) {\n void queryClient.invalidateQueries({ queryKey });\n }\n }\n if (\n outcome.status === \"failed\" &&\n isApiError(outcome.error) &&\n outcome.error.code === \"DEFAULTS_CHANGED\"\n ) {\n setPhase({ kind: \"conflict\", dryRun, totals: outcome.totals });\n return;\n }\n const finished: Finished = { kind: \"finished\", dryRun, outcome };\n setPhase(\n dryRun && outcome.status === \"done\"\n ? { kind: \"confirm\", preview: finished }\n : finished,\n );\n };\n\n /** Reads the defaults again and goes back to the choices. */\n const reread = async () => {\n setRereading(true);\n setRereadError(null);\n try {\n const fresh = await getJobDefaults(api, queue);\n queryClient.setQueryData(jobDefaultsKey(queue), fresh);\n setDefaults(fresh);\n setPhase({ kind: \"confirm\" });\n } catch (caught) {\n setRereadError(caught);\n } finally {\n setRereading(false);\n }\n };\n\n const toggle = (state: JobDefaultsApplyState, on: boolean) =>\n setStates((current) =>\n on ? [...current, state] : current.filter((item) => item !== state),\n );\n\n const nothingOverridden = defaults.overridden.length === 0;\n const applyLabel = `Apply to ${formatNumber(upperBound)} pending ${upperBound === 1 ? \"job\" : \"jobs\"}`;\n\n return (\n <Dialog\n open\n onClose={onClose}\n closeOnEscape={!running}\n closeOnBackdrop={!running}\n showCloseButton={!running}\n size=\"lg\"\n role=\"alertdialog\"\n title={`Apply the job defaults of ${queue} to pending jobs?`}\n description={APPLY_IRREVERSIBLE}\n footer={\n phase.kind === \"confirm\" ? (\n <>\n <Button onClick={onClose}>Cancel</Button>\n <Button\n onClick={() => void walk(true)}\n disabled={selected.length === 0 || nothingOverridden}\n >\n {preview ? \"Preview again\" : \"Preview (dry run)\"}\n </Button>\n <Button\n variant=\"danger\"\n onClick={() => void walk(false)}\n disabled={selected.length === 0 || nothingOverridden}\n >\n {applyLabel}\n </Button>\n </>\n ) : phase.kind === \"running\" ? (\n <Button\n onClick={() => {\n stopRef.current = true;\n setPhase({ ...phase, stopping: true });\n }}\n disabled={phase.stopping}\n >\n {phase.stopping ? \"Stopping after this batch…\" : \"Cancel\"}\n </Button>\n ) : (\n <>\n {phase.kind === \"finished\" && phase.outcome.status !== \"done\" && (\n <Button\n onClick={() => {\n const { outcome } = phase;\n if (outcome.status !== \"done\") {\n void walk(phase.dryRun, {\n cursor: outcome.cursor,\n totals: outcome.totals,\n });\n }\n }}\n >\n Continue from where it stopped\n </Button>\n )}\n <Button\n variant=\"primary\"\n onClick={onClose}\n >\n Close\n </Button>\n </>\n )\n }\n >\n {phase.kind === \"confirm\" && (\n <>\n <p>\n Writes, into each pending job whose own <code>add()</code> did not\n pass it:\n </p>\n <ul\n className=\"job-defaults-apply-keys\"\n aria-label=\"Values written\"\n >\n {defaults.overridden.map((key) => (\n <li key={key}>\n {KEY_TEXT[key].label}:{\" \"}\n {describeValue(key, defaults.effective[key])}\n </li>\n ))}\n </ul>\n {nothingOverridden && (\n <p role=\"alert\">\n Nothing is overridden, so there is nothing to apply.\n </p>\n )}\n <fieldset className=\"job-defaults-apply-states\">\n <legend>States</legend>\n {JOB_DEFAULTS_APPLY_STATES.map((state) => (\n <Checkbox\n key={state}\n label={`${STATE_LABELS[state]} (${formatNumber(defaults.pending[state])})`}\n checked={states.includes(state)}\n onChange={(on) => toggle(state, on)}\n />\n ))}\n {selected.length === 0 && (\n <p className=\"field-error\">Tick at least one state.</p>\n )}\n </fieldset>\n <Checkbox\n label=\"Include jobs added before this version\"\n hint={INCLUDE_UNMARKED_HINT}\n checked={includeUnmarked}\n onChange={setIncludeUnmarked}\n />\n <p className=\"muted\">\n {plural(upperBound, \"pending job\")} is an upper bound: jobs that\n passed these options explicitly, or were added before this version,\n are counted in it and left alone. Run a preview for the exact\n figure. Active, completed and dead jobs are never touched.\n </p>\n {defaults.overridden.includes(\"priority\") && (\n <p className=\"muted\">\n Changing priority re-sorts the backlog: until the walk ends, some\n jobs are re-sorted and others not yet.\n </p>\n )}\n {warnExhausted && (\n <p\n className=\"job-defaults-warning\"\n data-testid=\"exhausted-warning\"\n >\n {EXHAUSTED_WARNING}\n {preview !== undefined &&\n preview.outcome.totals.exhausted > 0 && (\n <>\n {\" \"}\n The preview found{\" \"}\n {plural(preview.outcome.totals.exhausted, \"such job\")}.\n </>\n )}\n </p>\n )}\n {preview && (\n <section\n aria-label=\"Preview\"\n className=\"job-defaults-preview\"\n >\n <h3>Preview: nothing was written</h3>\n <ApplyResult\n totals={preview.outcome.totals}\n dryRun\n />\n </section>\n )}\n </>\n )}\n {phase.kind === \"running\" && (\n <div\n role=\"status\"\n aria-live=\"polite\"\n data-testid=\"apply-progress\"\n >\n <p>\n {phase.dryRun ? \"Previewing\" : \"Applying\"}… examined{\" \"}\n {formatNumber(phase.totals.examined)} of about{\" \"}\n {formatNumber(upperBound)};{\" \"}\n {phase.dryRun ? \"would rewrite\" : \"rewritten\"}{\" \"}\n {formatNumber(phase.totals.rewritten)} so far (\n {plural(phase.totals.batches, \"batch\", \"batches\")}).\n </p>\n <progress\n max={Math.max(upperBound, phase.totals.examined, 1)}\n value={phase.totals.examined}\n aria-label=\"Jobs examined\"\n />\n </div>\n )}\n {phase.kind === \"finished\" && (\n <FinishedView\n dryRun={phase.dryRun}\n outcome={phase.outcome}\n />\n )}\n {phase.kind === \"conflict\" && (\n <div role=\"alert\">\n <p>\n The job defaults changed since you confirmed them — someone saved or\n reset them. Nothing more was{\" \"}\n {phase.dryRun ? \"previewed\" : \"applied\"}.\n {!phase.dryRun &&\n phase.totals.rewritten > 0 &&\n ` ${plural(phase.totals.rewritten, \"job\")} had already been rewritten with the values you confirmed.`}{\" \"}\n Re-read them, check them, and apply again.\n </p>\n {rereadError !== null && <ProblemBanner error={rereadError} />}\n <Button\n onClick={() => void reread()}\n disabled={rereading}\n aria-busy={rereading || undefined}\n >\n {rereading ? \"Re-reading…\" : \"Re-read the defaults\"}\n </Button>\n </div>\n )}\n </Dialog>\n );\n}\n\n/** How a walk ended, and what it did. */\nfunction FinishedView({\n dryRun,\n outcome,\n}: {\n /** Whether it wrote nothing. */\n dryRun: boolean;\n /** How it ended. */\n outcome: ApplyOutcome;\n}) {\n const verb = dryRun ? \"Previewed\" : \"Applied\";\n return (\n <div data-testid=\"apply-result\">\n {outcome.status === \"done\" && (\n <p role=\"status\">{verb} to every pending job in the chosen states.</p>\n )}\n {outcome.status === \"cancelled\" && (\n <p role=\"status\">\n Stopped after {plural(outcome.totals.batches, \"batch\", \"batches\")}.\n The jobs not reached yet keep their values; apply again to continue.\n </p>\n )}\n {outcome.status === \"failed\" && (\n <ProblemBanner\n error={outcome.error}\n title={`Stopped: a batch failed after ${plural(outcome.totals.batches, \"batch\", \"batches\")}`}\n />\n )}\n <ApplyResult\n totals={outcome.totals}\n dryRun={dryRun}\n />\n </div>\n );\n}\n\n/** The counts of a walk: the five that add up to examined, and exhausted within rewritten. */\nfunction ApplyResult({\n totals,\n dryRun,\n}: {\n /** What the walk did. */\n totals: ApplyTotals;\n /** Whether it wrote nothing (\"would be rewritten\"). */\n dryRun: boolean;\n}) {\n return (\n <>\n <KeyValue\n className=\"job-defaults-result\"\n items={[\n { label: \"Examined\", value: formatNumber(totals.examined) },\n {\n label: dryRun ? \"Would be rewritten\" : \"Rewritten\",\n value: formatNumber(totals.rewritten),\n },\n { label: \"Unchanged\", value: formatNumber(totals.unchanged) },\n {\n label: \"Skipped as explicit\",\n value: formatNumber(totals.skippedExplicit),\n hint: \"their own add() passed every value that would change\",\n },\n {\n label: \"Skipped, added before this version\",\n value: formatNumber(totals.skippedUnmarked),\n },\n {\n label: \"Moved\",\n value: formatNumber(totals.moved),\n hint: \"left the chosen states before their write; at least this many\",\n },\n {\n label: \"Exhausted\",\n value: formatNumber(totals.exhausted),\n hint: \"part of rewritten: already at the new attempts, so one final attempt each\",\n },\n ]}\n />\n {totals.exhausted > 0 && (\n <p className=\"job-defaults-warning\">\n {plural(totals.exhausted, \"job\")} {dryRun ? \"would get\" : \"got\"} one\n final attempt: each has already made at least the new number of\n attempts, and dies if that attempt fails.\n </p>\n )}\n </>\n );\n}\n",
|
|
10
|
+
"import type { JobDefaultKey } from \"../../../../api/contract\";\nimport type { JobDefaultsDto } from \"../../../../api/types\";\nimport type {\n BackoffDraft,\n Draft,\n DraftOf,\n NumberKey,\n RetentionDraft,\n RetentionMode,\n} from \"./draft\";\nimport { useQueryClient } from \"@tanstack/react-query\";\nimport { useId, useState } from \"react\";\nimport {\n JOB_DEFAULT_BACKOFF_TYPES,\n JOB_DEFAULT_KEYS,\n JOB_DEFAULTS_BOUNDS,\n} from \"../../../../api/contract\";\nimport { isApiError } from \"../../../../api/errors\";\nimport {\n getJobDefaults,\n jobDefaultsKey,\n resetJobDefaults,\n setJobDefaults,\n} from \"../../../../api/jobDefaults\";\nimport { mutationInvalidations } from \"../../../../api/queues\";\nimport { Badge } from \"../../../../components/Badge\";\nimport { Button } from \"../../../../components/Button\";\nimport { Dialog } from \"../../../../components/Dialog\";\nimport { Field } from \"../../../../components/Field\";\nimport { NumberInput, Select } from \"../../../../components/inputs\";\nimport { ProblemBanner } from \"../../../../components/ProblemBanner\";\nimport { useApiClient } from \"../../../../context\";\nimport { useApiMutation } from \"../../../../hooks/useApiMutation\";\nimport { formatMs } from \"../../duration\";\nimport {\n describeValue,\n draftOf,\n KEY_KIND,\n KEY_TEXT,\n numberBounds,\n planPatch,\n USE_CODE,\n} from \"./draft\";\nimport { CODE_SOURCE_HINT, RESET_CANNOT_RESTORE } from \"./text\";\n\n/** Props of {@link JobDefaultsDialog}. */\nexport interface JobDefaultsDialogProps {\n /** The queue. */\n queue: string;\n /** The defaults as read when the dialog opened: the form starts from them, and a save sends their `seq` as `expectedSeq`. */\n defaults: JobDefaultsDto;\n /** Asks to close. */\n onClose: () => void;\n}\n\n/** The codes a write answers when the stored defaults moved on since they were read. */\nconst CONFLICT_CODES: ReadonlySet<string> = new Set([\n \"CONTROL_CONTENDED\",\n \"DEFAULTS_CHANGED\",\n]);\n\n/** What saving or resetting says: the change reaches producers within `propagationMs`. */\nfunction propagationNote(result: JobDefaultsDto): string {\n return `Producers pick this up within about ${formatMs(result.propagationMs)}.`;\n}\n\n/**\n * Edit a queue's job defaults: every key of `JOB_DEFAULT_KEYS`, bounded by\n * `JOB_DEFAULTS_BOUNDS`, each showing what a job gets now, what the code\n * asks for, and whether the stored override replaces it.\n *\n * A save sends a merge patch — only the changed keys, `null` for one set back\n * to the code's value — with `expectedSeq`, so it cannot silently overwrite a\n * change someone else made meanwhile: that answers 409, and the dialog asks\n * to re-read. Saving never touches jobs already pending; that is the separate\n * Apply to pending jobs… action.\n */\nexport function JobDefaultsDialog({\n queue,\n defaults: initial,\n onClose,\n}: JobDefaultsDialogProps) {\n const api = useApiClient();\n const queryClient = useQueryClient();\n const [defaults, setDefaults] = useState(initial);\n const [draft, setDraft] = useState<Draft>({});\n const [error, setError] = useState<unknown>(null);\n const [rereading, setRereading] = useState(false);\n const resetNoteId = useId();\n\n const plan = planPatch(defaults, draft);\n const invalid = Object.keys(plan.errors).length > 0;\n\n const save = useApiMutation({\n mutationFn: () => setJobDefaults(api, queue, plan.body),\n invalidate: mutationInvalidations(queue),\n successMessage: (result) =>\n `Saved the job defaults of ${queue}. ${propagationNote(result)}`,\n toastErrors: false,\n onSuccess: onClose,\n onError: setError,\n });\n const reset = useApiMutation({\n mutationFn: () => resetJobDefaults(api, queue, defaults.seq),\n invalidate: mutationInvalidations(queue),\n successMessage: (result) =>\n `Reset the job defaults of ${queue} to the code values. ${propagationNote(result)}`,\n toastErrors: false,\n onSuccess: onClose,\n onError: setError,\n });\n const pending = save.isPending || reset.isPending || rereading;\n const conflict = isApiError(error) && CONFLICT_CODES.has(error.code);\n\n /** Reads the defaults again and starts the form over from them. */\n const reread = async () => {\n setRereading(true);\n try {\n const fresh = await getJobDefaults(api, queue);\n queryClient.setQueryData(jobDefaultsKey(queue), fresh);\n setDefaults(fresh);\n setDraft({});\n setError(null);\n } catch (caught) {\n setError(caught);\n } finally {\n setRereading(false);\n }\n };\n\n /** Replaces one key's draft. */\n const edit = <K extends JobDefaultKey>(\n key: K,\n value: DraftOf<K> | typeof USE_CODE,\n ) => setDraft((current) => ({ ...current, [key]: value }));\n\n /** Forgets one key's edit, so it is left as it is. */\n const undo = (key: JobDefaultKey) =>\n setDraft((current) => {\n const next = { ...current };\n delete next[key];\n return next;\n });\n\n /** The form value shown for a key: its edit, else what a job gets now. */\n const shown = <K extends JobDefaultKey>(key: K): DraftOf<K> => {\n const edited = draft[key];\n return edited !== undefined && edited !== USE_CODE\n ? (edited as DraftOf<K>)\n : draftOf(key, defaults.effective[key]);\n };\n\n return (\n <Dialog\n open\n onClose={onClose}\n closeOnEscape={!pending}\n closeOnBackdrop={!pending}\n size=\"lg\"\n title={`Job defaults of ${queue}`}\n description={\n <>\n What a job added to <code>{queue}</code> gets for an option its{\" \"}\n <code>add()</code> does not pass. A stored value here beats the\n code’s defaults, <code>define()</code> defaults included; only\n an option passed explicitly on <code>add()</code> wins. Saving changes\n jobs added from now on; jobs already waiting keep their values unless\n you apply the defaults to them.\n </>\n }\n footer={\n <>\n <Button\n onClick={onClose}\n disabled={pending}\n >\n Cancel\n </Button>\n <Button\n variant=\"primary\"\n onClick={() => {\n setError(null);\n save.mutate();\n }}\n disabled={pending || invalid || plan.changed.length === 0}\n aria-busy={save.isPending || undefined}\n >\n {save.isPending ? \"Saving…\" : \"Save defaults\"}\n </Button>\n </>\n }\n >\n {error !== null &&\n (conflict ? (\n <div\n className=\"job-defaults-conflict\"\n role=\"alert\"\n >\n <p>\n The job defaults changed since you opened them — someone else\n saved or reset them. Nothing was saved. Re-read them, then make\n your change again.\n </p>\n <Button\n size=\"sm\"\n onClick={() => void reread()}\n disabled={rereading}\n aria-busy={rereading || undefined}\n >\n {rereading ? \"Re-reading…\" : \"Re-read the defaults\"}\n </Button>\n </div>\n ) : (\n <ProblemBanner\n error={error}\n title=\"Could not save the job defaults\"\n />\n ))}\n <p className=\"job-defaults-note muted\">{CODE_SOURCE_HINT}</p>\n <div className=\"job-defaults-form\">\n {JOB_DEFAULT_KEYS.map((key) => {\n const overridden = defaults.overridden.includes(key);\n const usesCode = draft[key] === USE_CODE;\n const code = describeValue(key, defaults.code[key]);\n const serverError =\n save.fieldErrors[key] ??\n Object.entries(save.fieldErrors).find(([path]) =>\n path.startsWith(`${key}.`),\n )?.[1];\n const error = plan.errors[key] ?? serverError;\n return (\n <fieldset\n key={key}\n className=\"job-defaults-row\"\n data-testid={`job-default-${key}`}\n >\n <legend>\n {KEY_TEXT[key].label}{\" \"}\n {overridden && <Badge tone=\"accent\">Overridden</Badge>}\n </legend>\n <p className=\"job-defaults-values muted\">\n Now: {describeValue(key, defaults.effective[key])} · Code:{\" \"}\n {code}\n </p>\n {usesCode ? (\n <p className=\"job-defaults-uses-code\">\n Will use the code value ({code}) once saved.{\" \"}\n <Button\n size=\"sm\"\n variant=\"ghost\"\n onClick={() => undo(key)}\n disabled={pending}\n >\n Keep the stored value\n </Button>\n </p>\n ) : (\n <KeyEditor\n keyName={key}\n value={shown(key)}\n onChange={(value) => edit(key, value)}\n error={error}\n />\n )}\n {overridden && !usesCode && (\n <Button\n size=\"sm\"\n onClick={() => edit(key, USE_CODE)}\n disabled={pending}\n >\n Use code value\n </Button>\n )}\n </fieldset>\n );\n })}\n </div>\n {defaults.overridden.length > 0 && (\n <div className=\"job-defaults-reset\">\n <Button\n variant=\"danger\"\n onClick={() => {\n setError(null);\n reset.mutate();\n }}\n disabled={pending}\n aria-busy={reset.isPending || undefined}\n aria-describedby={resetNoteId}\n >\n {reset.isPending ? \"Resetting…\" : \"Reset to code values\"}\n </Button>\n <p\n id={resetNoteId}\n className=\"job-defaults-reset-note\"\n >\n {RESET_CANNOT_RESTORE}\n </p>\n </div>\n )}\n </Dialog>\n );\n}\n\n/** Props of {@link KeyEditor}. */\ninterface KeyEditorProps {\n /** The key edited. */\n keyName: JobDefaultKey;\n /** Its form value. */\n value: DraftOf<JobDefaultKey>;\n /** Called with the new form value. */\n onChange: (value: DraftOf<JobDefaultKey>) => void;\n /** Why the value cannot be saved, if it cannot. */\n error: string | undefined;\n}\n\n/** The inputs of one key, by its kind. */\nfunction KeyEditor({ keyName, value, onChange, error }: KeyEditorProps) {\n const kind = KEY_KIND[keyName];\n if (kind === \"backoff\") {\n return (\n <BackoffEditor\n value={value as BackoffDraft}\n onChange={onChange}\n error={error}\n />\n );\n }\n if (kind === \"retention\") {\n return (\n <RetentionEditor\n label={KEY_TEXT[keyName].label}\n hint={KEY_TEXT[keyName].hint}\n value={value as RetentionDraft}\n onChange={onChange}\n error={error}\n />\n );\n }\n const bounds = numberBounds(keyName as NumberKey);\n return (\n <Field\n label={KEY_TEXT[keyName].label}\n hint={`${KEY_TEXT[keyName].hint} ${bounds.min}–${bounds.max}.`}\n error={error}\n >\n <NumberInput\n value={value as number | undefined}\n min={bounds.min}\n max={bounds.max}\n onChange={onChange}\n />\n </Field>\n );\n}\n\n/** Props of {@link BackoffEditor}. */\ninterface BackoffEditorProps {\n /** The backoff as the form holds it. */\n value: BackoffDraft;\n /** Called with the new backoff. */\n onChange: (value: BackoffDraft) => void;\n /** Why it cannot be saved, if it cannot. */\n error: string | undefined;\n}\n\n/** Strategy, delay, longest delay and jitter. */\nfunction BackoffEditor({ value, onChange, error }: BackoffEditorProps) {\n const delay = JOB_DEFAULTS_BOUNDS.backoffDelay;\n const max = JOB_DEFAULTS_BOUNDS.backoffMax;\n const jitter = JOB_DEFAULTS_BOUNDS.backoffJitter;\n return (\n <div className=\"field-row job-defaults-backoff\">\n <Field\n label=\"Backoff strategy\"\n hint=\"Fixed waits the delay every time; exponential doubles it after each attempt. Other strategies can be set only in code.\"\n >\n <Select\n options={JOB_DEFAULT_BACKOFF_TYPES.map((type) => ({ value: type }))}\n value={value.type}\n onChange={(type) => onChange({ ...value, type })}\n />\n </Field>\n <Field\n label=\"Backoff delay (ms)\"\n hint={`${delay.min}–${delay.max}.`}\n error={error}\n >\n <NumberInput\n value={value.delay}\n min={delay.min}\n max={delay.max}\n onChange={(next) => onChange({ ...value, delay: next })}\n />\n </Field>\n <Field\n label=\"Backoff longest delay (ms)\"\n hint={`Optional, at least the delay; empty means uncapped. Up to ${max.max}.`}\n >\n <NumberInput\n value={value.max}\n min={max.min}\n max={max.max}\n onChange={(next) => onChange({ ...value, max: next })}\n />\n </Field>\n <Field\n label=\"Backoff jitter\"\n hint={`Optional fraction of each delay randomised, ${jitter.min}–${jitter.max}.`}\n >\n <NumberInput\n value={value.jitter}\n min={jitter.min}\n max={jitter.max}\n step=\"any\"\n onChange={(next) => onChange({ ...value, jitter: next })}\n />\n </Field>\n </div>\n );\n}\n\n/** The retention modes, in the select's order. */\nconst RETENTION_OPTIONS: readonly { value: RetentionMode; label: string }[] = [\n { value: \"keep\", label: \"Keep forever\" },\n { value: \"limit\", label: \"Keep the newest, or for a while\" },\n { value: \"remove\", label: \"Remove at once\" },\n];\n\n/** Props of {@link RetentionEditor}. */\ninterface RetentionEditorProps {\n /** The key's label, e.g. \"Completed jobs\". */\n label: string;\n /** What it is. */\n hint: string;\n /** The retention as the form holds it. */\n value: RetentionDraft;\n /** Called with the new retention. */\n onChange: (value: RetentionDraft) => void;\n /** Why it cannot be saved, if it cannot. */\n error: string | undefined;\n}\n\n/** Keep forever, remove at once, or keep a count and/or an age. */\nfunction RetentionEditor({\n label,\n hint,\n value,\n onChange,\n error,\n}: RetentionEditorProps) {\n const count = JOB_DEFAULTS_BOUNDS.retentionCount;\n const ttl = JOB_DEFAULTS_BOUNDS.retentionTtl;\n return (\n <div className=\"field-row job-defaults-retention\">\n <Field\n label={label}\n hint={\n value.mode === \"remove\"\n ? `${hint} Removing at once discards every result (or failure) as the job settles.`\n : hint\n }\n error={value.mode === \"limit\" ? undefined : error}\n >\n <Select\n options={RETENTION_OPTIONS}\n value={value.mode}\n onChange={(mode) => onChange({ ...value, mode })}\n />\n </Field>\n {value.mode === \"limit\" && (\n <>\n <Field\n label={`${label}: most kept`}\n hint={`Optional, ${count.min}–${count.max}.`}\n error={error}\n >\n <NumberInput\n value={value.count}\n min={count.min}\n max={count.max}\n onChange={(next) => onChange({ ...value, count: next })}\n />\n </Field>\n <Field\n label={`${label}: kept for (ms)`}\n hint={`Optional, up to ${ttl.max} (a year).`}\n >\n <NumberInput\n value={value.ttl}\n min={ttl.min}\n max={ttl.max}\n onChange={(next) => onChange({ ...value, ttl: next })}\n />\n </Field>\n </>\n )}\n </div>\n );\n}\n",
|
|
11
|
+
"import { useQuery } from \"@tanstack/react-query\";\nimport { useState } from \"react\";\nimport {\n JOB_DEFAULT_KEYS,\n JOB_DEFAULTS_APPLY_STATES,\n} from \"../../../../api/contract\";\nimport { getJobDefaults, jobDefaultsKey } from \"../../../../api/jobDefaults\";\nimport { Badge } from \"../../../../components/Badge\";\nimport { Button } from \"../../../../components/Button\";\nimport { KeyValue } from \"../../../../components/KeyValue\";\nimport { ProblemBanner } from \"../../../../components/ProblemBanner\";\nimport { RelativeTime } from \"../../../../components/RelativeTime\";\nimport { Spinner } from \"../../../../components/Spinner\";\nimport { Table } from \"../../../../components/Table\";\nimport { useApiClient } from \"../../../../context\";\nimport { formatNumber, STATE_LABELS } from \"../../../../format\";\nimport { formatMs } from \"../../duration\";\nimport { useRefreshInterval } from \"../../live\";\nimport { ApplyDefaultsDialog } from \"./ApplyDefaultsDialog\";\nimport { describeValue, KEY_TEXT } from \"./draft\";\nimport { JobDefaultsDialog } from \"./JobDefaultsDialog\";\nimport { CODE_SOURCE_HINT } from \"./text\";\nimport \"./jobDefaults.css\";\n\n/** Props of {@link JobDefaultsPanel}. */\nexport interface JobDefaultsPanelProps {\n /** The queue. */\n queue: string;\n /** Whether Settings… is offered: mutation `queues.defaults`. */\n canEdit: boolean;\n /** Whether Apply to pending jobs… is offered: mutation `queues.applyDefaults`. Independent of `canEdit`. */\n canApply: boolean;\n}\n\n/** Which dialog is open. */\ntype OpenDialog = \"settings\" | \"apply\" | null;\n\n/**\n * A queue's job defaults: a read-only table of every option — what a job\n * added now gets, what the code asks for, and the stored override — with the\n * jobs pending in the states the apply action rewrites, and, when permitted,\n * Settings… (edit) and Apply to N pending jobs… (a separate action).\n */\nexport function JobDefaultsPanel({\n queue,\n canEdit,\n canApply,\n}: JobDefaultsPanelProps) {\n const api = useApiClient();\n const interval = useRefreshInterval(\"counts\");\n const [open, setOpen] = useState<OpenDialog>(null);\n const read = useQuery({\n queryKey: jobDefaultsKey(queue),\n queryFn: ({ signal }) => getJobDefaults(api, queue, signal),\n // The pending counts move with the queue; the defaults themselves change\n // only by a save, after which the key is invalidated.\n refetchInterval: open === null ? interval : false,\n });\n // The dialog returns focus to the button that opened it.\n const close = () => setOpen(null);\n\n if (read.data === undefined) {\n return read.isError ? (\n <ProblemBanner\n error={read.error}\n title=\"Could not load the job defaults\"\n onRetry={() => void read.refetch()}\n />\n ) : (\n <Spinner\n label=\"Loading the job defaults\"\n showLabel\n />\n );\n }\n const defaults = read.data;\n const { total } = defaults.pending;\n const nothingOverridden = defaults.overridden.length === 0;\n\n return (\n <div\n className=\"job-defaults\"\n data-testid=\"job-defaults-panel\"\n >\n <p>\n What a job added to this queue gets for an option its <code>add()</code>{\" \"}\n does not pass. A stored override beats the code’s defaults and{\" \"}\n <code>define()</code> defaults; only an option passed explicitly on{\" \"}\n <code>add()</code> wins. A change reaches producers within about{\" \"}\n {formatMs(defaults.propagationMs)}.\n </p>\n <Table label=\"Job defaults\">\n <thead>\n <tr>\n <th scope=\"col\">Option</th>\n <th scope=\"col\">A job gets</th>\n <th scope=\"col\">Code</th>\n <th scope=\"col\">Override</th>\n </tr>\n </thead>\n <tbody>\n {JOB_DEFAULT_KEYS.map((key) => {\n const overridden = defaults.overridden.includes(key);\n return (\n <tr\n key={key}\n data-testid={`job-default-row-${key}`}\n >\n <th\n scope=\"row\"\n title={KEY_TEXT[key].hint}\n >\n {KEY_TEXT[key].label}\n </th>\n <td>{describeValue(key, defaults.effective[key])}</td>\n <td>{describeValue(key, defaults.code[key])}</td>\n <td>\n {overridden ? (\n <Badge tone=\"accent\">Overridden</Badge>\n ) : (\n <span className=\"muted\">code value</span>\n )}\n </td>\n </tr>\n );\n })}\n </tbody>\n </Table>\n <p className=\"muted job-defaults-note\">\n {CODE_SOURCE_HINT}\n {defaults.updatedAt !== undefined && (\n <>\n {\" \"}\n Last changed <RelativeTime value={defaults.updatedAt} />.\n </>\n )}\n </p>\n <h3 className=\"job-defaults-heading\">Pending jobs</h3>\n <KeyValue\n className=\"job-defaults-pending\"\n items={[\n ...JOB_DEFAULTS_APPLY_STATES.map((state) => ({\n key: state,\n label: STATE_LABELS[state],\n value: formatNumber(defaults.pending[state]),\n })),\n { key: \"total\", label: \"Total\", value: formatNumber(total) },\n ]}\n />\n <p className=\"muted\">\n Pending jobs keep the values they were added with: saving changes only\n jobs added afterwards. Applying the defaults to them is a separate\n action.\n </p>\n <div className=\"form-actions\">\n {canEdit && (\n <Button onClick={() => setOpen(\"settings\")}>Settings…</Button>\n )}\n {canApply && !nothingOverridden && total > 0 && (\n <Button\n variant=\"danger\"\n onClick={() => setOpen(\"apply\")}\n >\n Apply to {formatNumber(total)} pending{\" \"}\n {total === 1 ? \"job\" : \"jobs\"}…\n </Button>\n )}\n </div>\n {canApply && (nothingOverridden || total === 0) && (\n <p\n className=\"muted\"\n data-testid=\"apply-unavailable\"\n >\n {nothingOverridden\n ? \"Nothing is overridden, so there is nothing to apply to pending jobs.\"\n : \"No jobs are pending in the states the defaults can be applied to.\"}\n </p>\n )}\n {open === \"settings\" && (\n <JobDefaultsDialog\n queue={queue}\n defaults={defaults}\n onClose={close}\n />\n )}\n {open === \"apply\" && (\n <ApplyDefaultsDialog\n queue={queue}\n defaults={defaults}\n onClose={close}\n />\n )}\n </div>\n );\n}\n"
|
|
12
|
+
],
|
|
13
|
+
"mappings": "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",
|
|
14
|
+
"debugId": "BA9076DA5653324964756E2164756E21",
|
|
15
|
+
"names": []
|
|
16
|
+
}
|