@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 @@
|
|
|
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}:root{color-scheme:light;--font-sans:system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;--font-mono:ui-monospace,SFMono-Regular,Menlo,Consolas,"Liberation Mono",monospace;--space-1:4px;--space-2:8px;--space-3:12px;--space-4:16px;--space-5:24px;--space-6:32px;--radius-sm:4px;--radius:8px;--radius-pill:999px;--text-xs:.75rem;--text-sm:.8125rem;--text-md:.9375rem;--text-lg:1.125rem;--text-xl:1.5rem;--bg:#f6f7f9;--surface:#fff;--surface-2:#f0f2f5;--border:#d9dde3;--border-strong:#838c99;--text:#1b1f24;--text-muted:#5b6573;--accent:#2957c7;--accent-contrast:#fff;--focus:#2957c7;--danger:#b42318;--danger-contrast:#fff;--warning-bg:#fff4d6;--warning-text:#7a4b00;--info-bg:#e3ecff;--info-text:#1f3f8f;--success-bg:#ddf5e6;--success-text:#13603a;--danger-bg:#fde5e3;--danger-text:#92211a;--neutral-bg:#eceff3;--neutral-text:#3b4450;--state-waiting:#2f6fdb;--state-delayed:#7a4fcf;--state-active:#0f8a8a;--state-completed:#238a4a;--state-failed:#d4541a;--state-dead:#b42318;--state-waiting-children:#7a6a1f;--shadow:0 1px 2px #1018280f,0 1px 3px #10182814;--header-height:56px;--nav-width:200px}@media (prefers-color-scheme:dark){:root:not([data-theme=light]){color-scheme:dark;--bg:#0f1216;--surface:#171b21;--surface-2:#1e232b;--border:#2d343e;--border-strong:#65717f;--text:#e6e9ee;--text-muted:#9aa4b2;--accent:#7ea2ff;--accent-contrast:#0b1020;--focus:#7ea2ff;--danger:#ff7b70;--danger-contrast:#1a0806;--warning-bg:#3a2c0a;--warning-text:#ffd27a;--info-bg:#182646;--info-text:#b7caff;--success-bg:#11301f;--success-text:#8fe0ad;--danger-bg:#3d1512;--danger-text:#ffb4ad;--neutral-bg:#242a33;--neutral-text:#c9d0da;--state-waiting:#6f9ef0;--state-delayed:#b08cf0;--state-active:#3cc4c4;--state-completed:#52c47f;--state-failed:#f08a52;--state-dead:#ff7b70;--state-waiting-children:#cdb64a;--shadow:0 1px 2px #0006}}:root[data-theme=dark]{color-scheme:dark;--bg:#0f1216;--surface:#171b21;--surface-2:#1e232b;--border:#2d343e;--border-strong:#65717f;--text:#e6e9ee;--text-muted:#9aa4b2;--accent:#7ea2ff;--accent-contrast:#0b1020;--focus:#7ea2ff;--danger:#ff7b70;--danger-contrast:#1a0806;--warning-bg:#3a2c0a;--warning-text:#ffd27a;--info-bg:#182646;--info-text:#b7caff;--success-bg:#11301f;--success-text:#8fe0ad;--danger-bg:#3d1512;--danger-text:#ffb4ad;--neutral-bg:#242a33;--neutral-text:#c9d0da;--state-waiting:#6f9ef0;--state-delayed:#b08cf0;--state-active:#3cc4c4;--state-completed:#52c47f;--state-failed:#f08a52;--state-dead:#ff7b70;--state-waiting-children:#cdb64a;--shadow:0 1px 2px #0006}*,*:before,*:after{box-sizing:border-box}html,body{margin:0;padding:0}body{background:var(--bg);color:var(--text);font-family:var(--font-sans);font-size:var(--text-md);-webkit-font-smoothing:antialiased;line-height:1.45}a{color:var(--accent)}code{font-family:var(--font-mono);font-size:.92em}:focus-visible{outline:2px solid var(--focus);outline-offset:2px}.visually-hidden{overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute!important}.muted{color:var(--text-muted)}.skip-link{position:absolute;left:var(--space-2);padding:var(--space-2)var(--space-3);background:var(--surface);border:1px solid var(--border);border-radius:var(--radius-sm);z-index:10;top:-40px}.skip-link:focus{top:var(--space-2)}.app{display:flex;flex-direction:column;min-height:100vh}.app-header{position:sticky;z-index:5;display:flex;align-items:center;gap:var(--space-2)var(--space-4);min-height:var(--header-height);padding:var(--space-2)var(--space-4);background:var(--surface);border-bottom:1px solid var(--border);flex-wrap:wrap;top:0}.app-brand{font-weight:650;font-size:var(--text-lg);color:var(--text);text-decoration:none}.app-chips{display:flex;gap:var(--space-2);flex-wrap:wrap;min-width:0}.app-header-end{display:flex;align-items:center;gap:var(--space-2);min-width:0;margin-left:auto}.app-body{display:flex;flex:1;min-height:0}.app-nav{flex:0 0 var(--nav-width);padding:var(--space-4)var(--space-2);border-right:1px solid var(--border);background:var(--surface)}.app-nav ul{list-style:none;display:flex;gap:var(--space-1);flex-direction:column;margin:0;padding:0}.app-nav-link{display:block;padding:var(--space-2)var(--space-3);border-radius:var(--radius-sm);color:var(--text);text-decoration:none;white-space:nowrap}.app-nav .app-nav-sub{margin-top:var(--space-1);padding-left:var(--space-3)}.app-nav-sub .app-nav-link{font-size:var(--text-sm)}.app-nav-link:hover{background:var(--surface-2)}.app-nav-link[aria-current=page]{background:var(--info-bg);color:var(--info-text);font-weight:600}.app-main{padding:var(--space-5);flex:1;min-width:0}.app-main:focus{outline:none}.screen{display:flex;gap:var(--space-4);flex-direction:column;max-width:1400px}.screen-title{font-size:var(--text-xl);margin:0}.bootstrap-screen{display:flex;justify-content:center;align-items:center;gap:var(--space-4);padding:var(--space-4);flex-direction:column;min-height:100vh}.bootstrap-title{font-size:var(--text-xl);margin:0}.btn{display:inline-flex;align-items:center;gap:var(--space-2);padding:var(--space-2)var(--space-3);border:1px solid var(--border);border-radius:var(--radius-sm);background:var(--surface);color:var(--text);font:inherit;font-size:var(--text-sm);cursor:pointer}.btn:hover{background:var(--surface-2)}.btn:disabled{opacity:.55;cursor:not-allowed}.btn-primary{background:var(--accent);border-color:var(--accent);color:var(--accent-contrast)}.btn-primary:hover{background:var(--accent);filter:brightness(1.08)}.btn-danger{background:var(--danger);border-color:var(--danger);color:var(--danger-contrast)}.btn-danger:hover{background:var(--danger);filter:brightness(1.08)}.btn-ghost{background:0 0;border-color:#0000}.btn-sm{padding:var(--space-1)var(--space-2);font-size:var(--text-xs)}.badge{display:inline-flex;align-items:center;gap:var(--space-1);padding:2px var(--space-2);border-radius:var(--radius-pill);font-size:var(--text-xs);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:100%;font-weight:550}.badge-neutral{background:var(--neutral-bg);color:var(--neutral-text)}.badge-info{background:var(--info-bg);color:var(--info-text)}.badge-success{background:var(--success-bg);color:var(--success-text)}.badge-warning{background:var(--warning-bg);color:var(--warning-text)}.badge-danger{background:var(--danger-bg);color:var(--danger-text)}.badge-accent{background:var(--accent);color:var(--accent-contrast)}.chip{display:inline-flex;align-items:baseline;gap:var(--space-1);padding:2px var(--space-2);border:1px solid var(--border);border-radius:var(--radius-pill);font-size:var(--text-xs);max-width:100%}.chip-label{color:var(--text-muted)}.chip-value{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-weight:600}.live-dot{background:currentColor;border-radius:50%;width:6px;height:6px}.card{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);box-shadow:var(--shadow);min-width:0}.card-header{display:flex;justify-content:space-between;align-items:center;gap:var(--space-3);padding:var(--space-3)var(--space-4);border-bottom:1px solid var(--border);flex-wrap:wrap}.card-title{font-size:var(--text-lg);margin:0}.card-body{padding:var(--space-4)}.input{padding:var(--space-2)var(--space-3);border:1px solid var(--border-strong);border-radius:var(--radius-sm);background:var(--surface);color:var(--text);font:inherit;font-size:var(--text-sm);width:100%;min-width:0}.search{flex:0 260px;min-width:0}.screen>.search,.queues-search{flex:none;width:min(100%,260px)}.table-wrap{overflow:auto;border:1px solid var(--border);border-radius:var(--radius-sm);max-height:70vh}.table{border-collapse:separate;border-spacing:0;font-size:var(--text-sm);width:100%}.table th,.table td{padding:var(--space-2)var(--space-3);border-bottom:1px solid var(--border);text-align:left;vertical-align:middle;white-space:nowrap}.table thead th{position:sticky;z-index:1;background:var(--surface-2);font-weight:600;top:0}.table tbody tr:hover{background:var(--surface-2)}.table .num{text-align:right;font-variant-numeric:tabular-nums}.table .zero{color:var(--text-muted)}.queue-name{font-weight:500}.queue-paused{margin-left:var(--space-2)}.empty-state{padding:var(--space-5)var(--space-4);text-align:center;color:var(--text-muted)}.empty-state-title{margin:0 0 var(--space-1);font-size:inherit;color:var(--text);font-weight:600}.empty-state-description{margin:0}.empty-state-action{margin-top:var(--space-3)}.error-view{display:flex;align-items:flex-start;gap:var(--space-2);padding:var(--space-3)var(--space-4);border:1px solid var(--danger);border-radius:var(--radius-sm);background:var(--danger-bg);color:var(--danger-text);flex-direction:column;max-width:640px}.error-view p{margin:0}.error-view-title{font-size:inherit;margin:0;font-weight:650}.error-view-issues{padding-left:var(--space-4);margin:0}.notice{margin:var(--space-3)0 0;padding:var(--space-2)var(--space-3);border-radius:var(--radius-sm);background:var(--warning-bg);color:var(--warning-text);font-size:var(--text-sm)}.spinner-wrap{display:inline-flex;align-items:center;gap:var(--space-2);color:var(--text-muted)}.spinner{border:2px solid var(--border);border-top-color:var(--accent);animation:.8s linear infinite spin;border-radius:50%;width:16px;height:16px}@keyframes spin{to{transform:rotate(360deg)}}@media (prefers-reduced-motion:reduce){*,*:before,*:after{animation-duration:.01ms!important;animation-iteration-count:1!important;scroll-behavior:auto!important;transition-duration:.01ms!important;transition-delay:0s!important}.spinner{animation-duration:2.4s!important;animation-iteration-count:infinite!important}}.state-badge{display:inline-flex;white-space:nowrap;align-items:center;gap:6px}.state-dot{background:var(--state-color,var(--text-muted));border-radius:50%;flex:none;width:8px;height:8px}.state-waiting{--state-color:var(--state-waiting)}.state-delayed{--state-color:var(--state-delayed)}.state-active{--state-color:var(--state-active)}.state-completed{--state-color:var(--state-completed)}.state-failed{--state-color:var(--state-failed)}.state-dead{--state-color:var(--state-dead)}.state-waiting-children{--state-color:var(--state-waiting-children)}.overview-summary{display:flex;gap:var(--space-3);flex-direction:column}.stat-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(140px,1fr));gap:var(--space-3);margin:0}.stat{padding:var(--space-3);border:1px solid var(--border);border-radius:var(--radius-sm);background:var(--surface);min-width:0}.stat-state{border-top:3px solid var(--state-color)}.stat dd{margin:0}.stat-label{font-size:var(--text-sm);color:var(--text-muted)}.stat-value{font-size:var(--text-xl);font-variant-numeric:tabular-nums;overflow-wrap:anywhere;font-weight:650}.stat-hint{font-size:var(--text-xs);color:var(--text-muted)}.range-band{margin-top:var(--space-3);padding:var(--space-3);border:1px solid var(--border);border-radius:var(--radius-sm);background:var(--surface)}.range-band-head{display:flex;align-items:baseline;gap:var(--space-2);margin:0 0 var(--space-2);font-size:var(--text-sm);flex-wrap:wrap}.range-band-title{font-weight:600}.range-band-groups{display:flex;gap:var(--space-3)var(--space-5);flex-wrap:wrap}.range-group{flex:0 auto;min-width:0}.range-group-wide{flex:32rem}.range-group-title{margin:0 0 var(--space-1);font-size:var(--text-xs);color:var(--text-muted);font-weight:600}.range-cells{display:flex;gap:var(--space-1)var(--space-4);flex-wrap:wrap;margin:0}.range-cell dt{font-size:var(--text-xs);color:var(--text-muted);white-space:nowrap}.range-cell dd{font-variant-numeric:tabular-nums;margin:0;font-weight:600}.range-cell-total dt{color:var(--text);font-weight:600}.range-note{margin:var(--space-1)0 0;font-size:var(--text-xs);color:var(--text-muted)}.sparkline-cell{display:inline-flex;align-items:center;min-width:120px;min-height:28px}.sparkline{display:block;overflow:visible}.sparkline polyline{fill:none;stroke-width:1.5;stroke-linejoin:round;stroke-linecap:round}.sparkline-primary{stroke:var(--state-completed)}.sparkline-secondary{stroke:var(--state-failed);opacity:.8}.sparkline-baseline{stroke:var(--border);stroke-width:1}.sparkline-placeholder{display:block;background:var(--border);width:120px;height:2px}@media (max-width:720px){.app-body{flex-direction:column}.app-nav{padding:var(--space-2);border-right:0;border-bottom:1px solid var(--border);overflow-x:auto;flex:none}.app-nav ul{flex-direction:row}.app-nav li{display:flex;gap:var(--space-1)}.app-nav .app-nav-sub{margin-top:0;padding-left:0}.app-main{padding:var(--space-4)}.app-header{position:static;padding:var(--space-2)var(--space-4)}.app-header-end{justify-content:space-between;width:100%;margin-left:0}.live-status{overflow:hidden;min-width:0}.search{flex-basis:100%}.stat-grid{grid-template-columns:repeat(2,minmax(0,1fr))}}.overview-header{display:flex;justify-content:space-between;align-items:flex-start;gap:var(--space-3);margin-bottom:var(--space-4);flex-wrap:wrap}.overview-header .screen-title{margin:0}.overview-range{display:flex;align-items:flex-end;gap:var(--space-2);flex-direction:column;margin-left:auto}@media (max-width:720px){.overview-range{align-items:stretch;width:100%;margin-left:0}}.queues-search{margin-bottom:var(--space-3)}.docs-viewer{padding:var(--space-5)}:root{--json-key:var(--text);--json-string:var(--success-text);--json-number:var(--info-text);--json-boolean:var(--state-delayed);--json-null:var(--text-muted);--overlay:#10182873}@media (prefers-color-scheme:dark){:root:not([data-theme=light]){--overlay:#0009}}:root[data-theme=dark]{--overlay:#0009}.dialog{border:1px solid var(--border);border-radius:var(--radius);background:var(--surface);color:var(--text);box-shadow:var(--shadow);width:min(560px,calc(100vw - 2*var(--space-4)));max-height:calc(100vh - 2*var(--space-5));padding:0}.dialog-sm{width:min(400px,calc(100vw - 2*var(--space-4)))}.dialog-lg{width:min(800px,calc(100vw - 2*var(--space-4)))}.dialog::backdrop{background:var(--overlay)}.dialog:focus{outline:none}.dialog-panel{display:flex;gap:var(--space-3);padding:var(--space-4);max-height:inherit;flex-direction:column}.dialog-header{display:flex;justify-content:space-between;align-items:flex-start;gap:var(--space-3)}.dialog-title{font-size:var(--text-lg);margin:0}.dialog-close{margin:calc(-1*var(--space-1))calc(-1*var(--space-2))0 0;font-size:var(--text-lg);line-height:1}.dialog-description{color:var(--text-muted);font-size:var(--text-sm);margin:0}.dialog-body{overflow:auto;min-height:0}.dialog-footer{display:flex;justify-content:flex-end;gap:var(--space-2);flex-wrap:wrap}.confirm-dialog-body{display:flex;gap:var(--space-3);flex-direction:column}.problem-banner{display:flex;align-items:flex-start;gap:var(--space-3);padding:var(--space-2)var(--space-3);border:1px solid var(--danger);border-radius:var(--radius-sm);background:var(--danger-bg);color:var(--danger-text);font-size:var(--text-sm);border-left-width:3px;min-width:0}.problem-banner p{margin:0}.problem-banner-main{display:flex;gap:var(--space-1);overflow-wrap:anywhere;flex-direction:column;flex:1;min-width:0}.problem-banner-title{display:flex;align-items:baseline;gap:var(--space-2);flex-wrap:wrap}.problem-banner-code{font-size:var(--text-xs);opacity:.85}.problem-banner-context,.problem-banner-issues{list-style:none;display:flex;gap:var(--space-1)var(--space-3);font-size:var(--text-xs);flex-wrap:wrap;margin:0;padding:0}.problem-banner-issues{flex-direction:column}.problem-banner-key{opacity:.85}.problem-banner-actions{display:flex;gap:var(--space-1);flex:none}.toast-viewport{position:fixed;right:var(--space-4);bottom:var(--space-4);z-index:20;display:flex;gap:var(--space-2);width:min(380px,calc(100vw - 2*var(--space-4)));pointer-events:none;flex-direction:column}.toast-list{list-style:none;display:flex;gap:var(--space-2);flex-direction:column;margin:0;padding:0}.toast{display:flex;align-items:flex-start;gap:var(--space-2);padding:var(--space-3);border:1px solid var(--border);border-left:4px solid var(--toast-color,var(--border));border-radius:var(--radius-sm);background:var(--surface);color:var(--text);box-shadow:var(--shadow);font-size:var(--text-sm);pointer-events:auto}.toast-success{--toast-color:var(--state-completed)}.toast-error{--toast-color:var(--danger)}.toast-info{--toast-color:var(--accent)}.toast-text{overflow-wrap:anywhere;flex:1;min-width:0}.toast-text p{margin:0}.toast-message{font-weight:600}.toast-description{color:var(--text-muted)}.toast-dismiss{font-size:var(--text-md);line-height:1}.tabs{display:flex;gap:var(--space-3);flex-direction:column;min-width:0}.tabs-list{display:flex;gap:var(--space-1);border-bottom:1px solid var(--border);overflow-x:auto}.tab{display:inline-flex;align-items:center;gap:var(--space-2);padding:var(--space-2)var(--space-3);color:var(--text-muted);font:inherit;font-size:var(--text-sm);white-space:nowrap;cursor:pointer;background:0 0;border:0;border-bottom:2px solid #0000;margin-bottom:-1px}.tab:hover{color:var(--text)}.tab[aria-selected=true]{border-bottom-color:var(--accent);color:var(--text);font-weight:600}.tab:disabled{opacity:.5;cursor:not-allowed}.tab-count{padding:0 var(--space-2);border-radius:var(--radius-pill);background:var(--neutral-bg);color:var(--neutral-text);font-size:var(--text-xs);font-variant-numeric:tabular-nums;font-weight:550}.tab[aria-selected=true] .tab-count{background:var(--info-bg);color:var(--info-text)}.tabs-panel:focus-visible{outline-offset:4px}.pager{display:flex;justify-content:flex-end;align-items:center;gap:var(--space-2)var(--space-4);font-size:var(--text-sm);flex-wrap:wrap}.pager-range{color:var(--text-muted);font-variant-numeric:tabular-nums;margin-right:auto}.pager-size{display:inline-flex;align-items:center;gap:var(--space-2);color:var(--text-muted)}.pager-select{padding:var(--space-1)var(--space-2);width:auto}.pager-page{display:inline-flex;align-items:center;gap:var(--space-2);color:var(--text-muted)}.pager-page-input{padding:var(--space-1)var(--space-2);font-variant-numeric:tabular-nums;width:9ch}.pager-page-count{font-variant-numeric:tabular-nums}.pager-buttons{display:inline-flex;gap:var(--space-2)}.field{display:flex;gap:var(--space-1);flex-direction:column;min-width:0}.field-label{font-size:var(--text-sm);font-weight:600}.field-required{color:var(--danger)}.field-hint,.field-error{font-size:var(--text-xs);margin:0}.field-hint{color:var(--text-muted)}.field-error{color:var(--danger)}.input[aria-invalid=true]{border-color:var(--danger)}.input:disabled{opacity:.6;cursor:not-allowed}.input-group{display:flex;align-items:center;gap:var(--space-2);min-width:0}.input-suffix{color:var(--text-muted);font-size:var(--text-xs);flex:none}.checkbox{display:grid;grid-template-columns:auto 1fr;align-items:center;column-gap:var(--space-2);font-size:var(--text-sm)}.checkbox input{accent-color:var(--accent);margin:0}.checkbox-hint{grid-column:2}.copy-button[data-status=copied]{color:var(--success-text)}.copy-button[data-status=failed]{color:var(--danger)}.json-view{position:relative;padding:var(--space-2)var(--space-3);border:1px solid var(--border);border-radius:var(--radius-sm);background:var(--surface-2);font-family:var(--font-mono);font-size:var(--text-sm);overflow:auto;max-height:60vh}.json-view-toolbar{position:sticky;float:right;font-family:var(--font-sans);top:0}.json-tree,.json-children{list-style:none;margin:0;padding:0}.json-children{padding-left:var(--space-4);border-left:1px dotted var(--border);margin-left:5px}.json-row{white-space:pre-wrap;overflow-wrap:anywhere}.json-toggle,.json-more{color:inherit;font:inherit;cursor:pointer;text-align:left;background:0 0;border:0;padding:0}.json-more{margin-left:var(--space-2);color:var(--accent);font-family:var(--font-sans);font-size:var(--text-xs);text-decoration:underline}.json-caret{display:inline-block;margin-right:var(--space-1);border-top:4px solid #0000;border-bottom:4px solid #0000;border-left:6px solid var(--text-muted);width:0;height:0;transition:transform .1s}.json-toggle[aria-expanded=true] .json-caret{transform:rotate(90deg)}.json-key{color:var(--json-key);font-weight:550}.json-string{color:var(--json-string)}.json-number{color:var(--json-number)}.json-boolean{color:var(--json-boolean)}.json-null,.json-undefined,.json-other,.json-circular{color:var(--json-null);font-style:italic}.json-punct{color:var(--text-muted)}.json-summary{margin:0 var(--space-1);color:var(--text-muted);font-family:var(--font-sans);font-size:var(--text-xs)}.json-editor{display:flex;gap:var(--space-1);flex-direction:column;min-width:0}.json-editor-text{font-family:var(--font-mono);font-size:var(--text-sm);resize:vertical;tab-size:2;line-height:1.5}.json-editor-bar{display:flex;align-items:center;gap:var(--space-1)var(--space-3);font-size:var(--text-xs);flex-wrap:wrap}.json-editor-bar p{flex:1;min-width:0;margin:0}.json-editor-error{color:var(--danger)}.json-editor-size{display:inline-flex;align-items:center;gap:var(--space-2);color:var(--text-muted);font-variant-numeric:tabular-nums}.json-editor-size.is-over{color:var(--danger);font-weight:600}.json-editor-meter{width:80px;height:8px}.json-editor-actions{display:inline-flex;gap:var(--space-1)}.kv{display:grid;gap:var(--space-2)var(--space-4);font-size:var(--text-sm);margin:0}.kv-grid{grid-template-columns:max-content minmax(0,1fr)}.kv-row{display:contents}.kv-stacked{grid-template-columns:repeat(auto-fill,minmax(160px,1fr))}.kv-stacked .kv-row{display:flex;flex-direction:column;gap:2px;min-width:0}.kv-label{color:var(--text-muted)}.kv-value{overflow-wrap:anywhere;min-width:0;margin:0}.kv-hint{display:block;color:var(--text-muted);font-size:var(--text-xs)}.relative-time{white-space:nowrap}@media (prefers-reduced-motion:reduce){.json-caret{transition:none}}@media (max-width:720px){.kv-grid{grid-template-columns:minmax(0,1fr);gap:var(--space-1)}.kv-grid .kv-value{margin-bottom:var(--space-2)}.pager-range{flex-basis:100%}}.shortcut-list{display:grid;gap:var(--space-2);font-size:var(--text-sm);margin:0}.shortcut-row{display:grid;grid-template-columns:minmax(7rem,max-content)minmax(0,1fr);gap:var(--space-3);align-items:baseline}.shortcut-row dd{margin:0}kbd{display:inline-block;padding:0 var(--space-1);border:1px solid var(--border-strong);border-radius:var(--radius-sm);background:var(--surface-2);font-family:var(--font-mono);font-size:var(--text-xs);text-align:center;min-width:1.6em}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 1,
|
|
3
|
+
"entry": {
|
|
4
|
+
"js": "main-0rchxtv5.js",
|
|
5
|
+
"css": [
|
|
6
|
+
"main-cqgyvs86.css"
|
|
7
|
+
]
|
|
8
|
+
},
|
|
9
|
+
"files": {
|
|
10
|
+
"main-0rchxtv5.js": {
|
|
11
|
+
"type": "text/javascript; charset=utf-8",
|
|
12
|
+
"size": 347709,
|
|
13
|
+
"integrity": "sha384-ciO+g1eTXqjBaXy6WpJBtZie+6qCiJ78BLFHdGT/lNy4WwxY6C7RRbR93ijAgDfb"
|
|
14
|
+
},
|
|
15
|
+
"main-cqgyvs86.css": {
|
|
16
|
+
"type": "text/css; charset=utf-8",
|
|
17
|
+
"size": 32967,
|
|
18
|
+
"integrity": "sha384-NHk5MkoK+kYjEVNmWAzeMnVDV53CXHU8AOd4cx6EicbB+GX56KwOuujnVYfOh9eW"
|
|
19
|
+
},
|
|
20
|
+
"main-0rchxtv5.js.map": {
|
|
21
|
+
"type": "application/json; charset=utf-8",
|
|
22
|
+
"size": 1600353,
|
|
23
|
+
"integrity": "sha384-fcy2uw8HyXP2DP/QUg0y1AwslVct2hK7sxtXCXtb+vnbiLTCSoBlbUGhfLRFiYzV"
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import{v,c,g,T,r,Ct,ht,b,n,e,t,i,yt,m,y,a,_,s,E,oe,d,Ce,l,M,S,w}from"./main-a250ffk7.js";import{rn}from"./main-5zbx60gq.js";import{We,uo,lo,on,br,Ft,sn,Ot,Nr,an,un,ln}from"./main-szjdmydc.js";import"./main-tnbhrm9n.js";import{O}from"./main-4j1fcevh.js";import{D}from"./main-hm1ng3yg.js";var ye=0;function Le(){return ye+=1,`flow-node-${ye}`}function ue(o){return{key:Le(),form:o,queue:"",ignoreFailure:!1,children:[]}}function xe(o,f){return o===""?f:`${o}.${f}`}function U(o,f){let p=[],u=(h,F,N,A,P)=>{p.push({node:h,path:F,queue:N,level:A,parent:P}),h.children.forEach((k,j)=>u(k,xe(F,`children.${j}`),k.queue===""?N:k.queue,A+1,h.key))};return u(o,"",f,1,null),p}function Ee(o){return o.children.reduce((f,p)=>f+Ee(p),1)}function ve(o,f){return[...new Set(U(o,f).map((p)=>p.queue))]}function ce(o,f){if(o.key===f)return o;for(let p of o.children){let u=ce(p,f);if(u)return u}return}function fe(o,f,p){if(o.key===f)return p(o);let u=!1,h=o.children.map((F)=>{let N=fe(F,f,p);return u||=N!==F,N});return u?{...o,children:h}:o}function ke(o,f,p){return fe(o,f,(u)=>({...u,...p}))}function Pe(o,f,p){return fe(o,f,(u)=>({...u,children:[...u.children,p]}))}function me(o,f){if(!o.children.some((p)=>p.key===f)){let p=!1,u=o.children.map((h)=>{let F=me(h,f);return p||=F!==h,F});return p?{...o,children:u}:o}return{...o,children:o.children.filter((p)=>p.key!==f)}}function pe(o,f,p){if(Ee(o)>=p.maxNodes)return`The flow holds ${p.maxNodes} jobs, the most the API accepts.`;let u=U(o,"").find((h)=>h.node.key===f);if(u&&u.level>=p.maxDepth)return`This job is at level ${u.level}; a flow nests at most ${p.maxDepth} levels.`;return null}function Fe(o){if(o==="")return;if(o.length>ht)return`At most ${ht} characters.`;return new RegExp(Ct).test(o)?void 0:"Letters, digits, “_”, “.” and “-” only, and not “.” or “..”."}function J(o,f,p,u){let h=o[f]??={};h[p]??=u}function we(o,f,p){let u={},h=U(o,f),F=new Map,N=!1,A=!1;h.forEach((P,k)=>{let{node:j,queue:I,level:H,parent:z}=P;for(let[B,G]of Object.entries(Ft(j.form)))J(u,j.key,B,G);if(z!==null){let B=Fe(j.queue);if(B)J(u,j.key,"queue",B)}else if(j.ignoreFailure)J(u,j.key,"opts.ignoreFailure","The top job has no parent to carry on without it.");if(!N&&k>=p.maxNodes)N=!0,J(u,j.key,"node",`A flow may hold at most ${p.maxNodes} jobs.`);if(!A&&H>p.maxDepth)A=!0,J(u,j.key,"node",`A flow may nest at most ${p.maxDepth} levels.`);if(j.form.jobId!==""&&br(j.form.jobId)===void 0){let B=`${I}:${j.form.jobId}`;F.set(B,[...F.get(B)??[],P])}});for(let[P,k]of F)if(k.length>1)for(let{node:j}of k)J(u,j.key,"opts.jobId",`Another job of this flow has the same queue and id (${P}).`);return u}function qe(o){return Object.values(o).some((f)=>Object.keys(f).length>0)}function De(o,f){let p=(u,h)=>{let F=sn(u.form),N=h!==null,A={...F.opts,...N&&u.ignoreFailure?{ignoreFailure:!0}:{}},P=!N?f:u.queue===""?h:u.queue;return{name:F.name,data:F.data,...Object.keys(A).length>0?{opts:A}:{},...N&&P!==h?{queue:P}:{},...u.children.length>0?{children:u.children.map((k)=>p(k,P))}:{}}};return p(o,null)}var Je=new Set(["name","data","queue","opts.jobId","opts.priority","opts.delay","opts.runAt","opts.attempts","opts.backoff","opts.timeout","opts.ignoreFailure"]);function Ne(o,f){let p=new Map(o.map((k)=>[k.path,k.node.key])),u=f===""?[]:f.split("."),h="",F=0;while(u[F]==="children"&&/^\d+$/.test(u[F+1]??""))h=xe(h,`children.${u[F+1]}`),F+=2;let N=p.get(h);if(N===void 0)return;let A=u.slice(F).join("."),P=A===""?"node":Je.has(A)?A:A.startsWith("data.")?"data":"node";return{key:N,field:P}}function Me(o,f){let p=o.context.queue;return typeof p==="string"&&p!==""?p:f}function _e(o,f){if(o.code==="QUEUE_NOT_FOUND")return`The API does not manage a queue named “${f}”.`;return o.status===401?`Sign in to add jobs to “${f}”.`:`You may not add jobs to “${f}”.`}function je(o,f,p){let u={},h=U(f,p),F=!1;if(o.code==="VALIDATION"&&o.issues.length>0){for(let N of o.issues){let A=N.target==="body"?Ne(h,N.path):void 0;if(A)J(u,A.key,A.field,N.message);else F=!0}return{errors:u,unplaced:F}}if(o.code==="NAME_NOT_ADDABLE"){let N=o.context.path,A=typeof N==="string"?Ne(h,N):void 0;if(!A)return{errors:u,unplaced:!0};return J(u,A.key,A.field,Ot(o,"add")??o.detail??o.title),{errors:u,unplaced:!1}}if(o.code==="FORBIDDEN"||o.code==="UNAUTHORIZED"||o.code==="QUEUE_NOT_FOUND"){let N=Me(o,p),A=_e(o,N),P=h.filter((k)=>k.queue===N);for(let{node:k}of P)J(u,k.key,"queue",A);return{errors:u,unplaced:P.length===0}}return{errors:u,unplaced:!0}}function Ae(o,f,p){let u=o.find((F)=>F.node.key===f);if(u===void 0)return;let{node:h}=u;switch(p){case"node":return u.path;case"queue":return u.queue;case"opts.ignoreFailure":return h.ignoreFailure;case"name":return h.form.name;case"data":return h.form.data.text;case"opts.jobId":return h.form.jobId;case"opts.priority":return h.form.priority;case"opts.delay":return`${h.form.timing}:${h.form.delay}`;case"opts.runAt":return`${h.form.timing}:${h.form.runAt}`;case"opts.attempts":return h.form.attempts;case"opts.backoff":return h.form.backoff;case"opts.timeout":return h.form.timeout}}function Re(o,f,p,u,h){return Ae(U(o,p),u,h)===Ae(U(f,p),u,h)}var he="\x00other",Ue=250,He=["opts.jobId","opts.priority","opts.delay","opts.runAt","opts.attempts","opts.backoff","opts.timeout"];function Ke(o){return o.length===0?"Top job":`Child ${o.map((f)=>f+1).join(".")}`}function Eo(o){return o.open?e(ze,{...o}):null}function ze({queue:o,onClose:f}){let p=i(),u=m(),h=y("queues.list"),F=c(),N=u.limits.maxJobDataBytes,A=u.addableNames,P=A!==null&&A.length===0,k={maxNodes:u.limits.maxFlowNodes,maxDepth:u.limits.maxFlowDepth},j=()=>on({addableNames:A,maxBytes:N}),[I,H]=r(()=>ue(j())),[z,B]=r(!1),[G,Q]=r(null),V=Ce(),C=O({mutationFn:(x)=>uo(p,o,De(x,o)),invalidate:(x,R)=>lo(ve(R,o)),toastErrors:!1}),K=U(I,o),W=K.length,X=[...new Set(K.map((x)=>x.queue))].filter((x)=>x!==o&&Fe(x)===void 0).sort().join(`
|
|
2
|
+
`),[Y,Z]=r("");v(()=>{let x=setTimeout(Z,Ue,X);return()=>clearTimeout(x)},[X]);let ee=Y===""?[]:Y.split(`
|
|
3
|
+
`),te=rn({queries:ee.map((x)=>({queryKey:_.permissions({queue:x}),queryFn:({signal:R})=>p.getPermissions({queue:x},R),retry:!1}))}),q=new Set(ee.filter((x,R)=>{let L=te[R]?.data;return L!==void 0&&!yt(L,"jobs.add")})),re=a({queryKey:_.queues(""),queryFn:({signal:x})=>p.listQueues("",x),enabled:h}),$e=h&&!re.isError?re.data?.items.map((x)=>x.name)??[]:null,Qe=z?we(I,o,k):{},se=b(C.error)?C.error:null,ne=C.variables,le=g(()=>se&&ne?je(se,ne,o):null,[se,ne,o]),ge=C.error&&(le===null||le.unplaced)?Nr(C.error,"add"):null,Te=new Set(K.map((x)=>x.node.key)),be=Object.keys(V.problems).some((x)=>Te.has(x)),Se={topQueue:o,addableNames:A,maxBytes:N,listedQueues:$e,refusedQueues:q,errorOf:(x,R)=>(R==="opts.runAt"?V.problems[x]:void 0)??Qe[x]?.[R]??(ne&&Re(I,ne,o,x,R)?le?.errors[x]?.[R]:void 0),blockedReason:(x)=>pe(I,x,k),patch:(x,R)=>H((L)=>ke(L,x,R)),addChild:(x)=>{if(pe(I,x,k)!==null)return;let R=ue(j());H((L)=>Pe(L,x,R)),Q({key:R.key,target:"first"})},remove:(x,R)=>{let L=ce(I,x);if(L)for(let ae of U(L,""))V.report(ae.node.key,void 0);H((ae)=>me(ae,x)),Q({key:R,target:"add"})},reportRunAt:V.report,focus:G,focused:()=>Q(null)},Be=(x)=>{if(x.preventDefault(),B(!0),P||be||qe(we(I,o,k)))return;C.mutate(I)},ie=()=>{if(!C.isPending)f()},de=C.isSuccess?C.data:void 0;return e(oe,{open:!0,onClose:ie,title:t(n,{children:["Add a flow to ",e("code",{children:o})]}),description:"A flow is a job and the jobs it waits on: they are added first, and it runs once they have all completed.",size:"lg",closeOnEscape:!C.isPending,closeOnBackdrop:!C.isPending,footer:de?e(s,{variant:"primary",onClick:ie,children:"Done"}):t(n,{children:[e(s,{onClick:ie,disabled:C.isPending,children:"Cancel"}),e(s,{variant:"primary",type:"submit",form:F,disabled:C.isPending||P||be,"aria-busy":C.isPending||void 0,children:C.isPending?"Adding…":"Add flow"})]}),children:de?e(Ye,{queue:o,result:de}):P?e(d,{title:"No flow can be added here",description:"This API accepts no job names: adding jobs is switched off (the jobs.add action is not enabled, or the API is read-only), or no name is allowed. Ask whoever runs it to list the names in addableNames."}):t("form",{id:F,className:"job-form flow-form",onSubmit:Be,noValidate:!0,children:[t("p",{className:"flow-count","data-testid":"flow-count","aria-live":"polite",children:[W," of ",k.maxNodes," jobs",W>=k.maxNodes?": the most one flow may hold.":`, nested at most ${k.maxDepth} levels.`]}),e("ol",{className:"flow-tree","aria-label":"Flow",children:e(Ie,{node:I,indices:[],level:1,parent:null,parentQueue:null,editor:Se})}),ge!==null&&e(D,{error:ge})]})})}function Ie({node:o,indices:f,level:p,parent:u,parentQueue:h,editor:F}){let{key:N}=o,A=Ke(f),P=u===null,k=P?F.topQueue:o.queue===""?h:o.queue,j=T(null),I=T(null),H=c(),z=c(),[B,G]=r(!1),Q=(q)=>F.errorOf(N,q),V=He.some((q)=>Q(q)),C=B||V,K=F.blockedReason(N),W=F.refusedQueues.has(k),X=Q("node")??(P?Q("opts.ignoreFailure"):void 0),Y=Q("queue")??(W?`You may not add jobs to “${k}”: the API will refuse this flow.`:void 0),{focus:Z,focused:ee}=F;v(()=>{if(Z?.key!==N)return;(Z.target==="add"&&I.current&&!I.current.disabled?I.current:j.current?.querySelector("input, select, textarea"))?.focus(),ee()},[Z,N,ee]);let te=(q)=>F.patch(N,{form:{...o.form,...q}});return t("li",{className:"flow-item",children:[t("fieldset",{ref:j,className:"flow-node","data-refused":W||void 0,"data-testid":"flow-node",children:[e("legend",{children:A}),X&&e("p",{className:"field-error job-form-error",role:"alert",children:X}),e(an,{value:o.form.name,onChange:(q)=>te({name:q}),error:Q("name"),addableNames:F.addableNames}),e(un,{value:o.form.data.text,onChange:(q)=>te({data:q}),error:Q("data"),maxBytes:F.maxBytes,rows:4}),P?t("div",{className:"flow-node-queue",children:[t("p",{className:"field-label",children:["Queue: ",e("code",{children:k})]}),Y&&e("p",{className:"field-error",children:Y})]}):e(Ve,{value:o.queue,onChange:(q)=>F.patch(N,{queue:q}),parentQueue:h,listed:F.listedQueues,error:Y}),!P&&t("div",{children:[e(w,{checked:o.ignoreFailure,onChange:(q)=>F.patch(N,{ignoreFailure:q}),label:"Parent carries on if this fails",hint:"Its failure is recorded for the parent to read, instead of failing it."}),Q("opts.ignoreFailure")&&e("p",{className:"field-error",children:Q("opts.ignoreFailure")})]}),t("div",{className:"flow-node-actions",children:[t(s,{size:"sm",variant:"ghost","aria-expanded":C,"aria-controls":C?z:void 0,onClick:()=>G(!C),children:["Options",t("span",{className:"visually-hidden",children:[" of ",A]})]}),t(s,{ref:I,size:"sm",onClick:()=>F.addChild(N),disabled:K!==null,"aria-describedby":K!==null?H:void 0,children:["Add child",t("span",{className:"visually-hidden",children:[" to ",A]})]}),u!==null&&t(s,{size:"sm",variant:"danger",onClick:()=>F.remove(N,u),children:["Remove",t("span",{className:"visually-hidden",children:[" ",A]})]}),K!==null&&e("p",{id:H,className:"field-hint flow-node-blocked",children:K})]}),C&&e("div",{id:z,children:e(ln,{form:o.form,patch:te,fieldError:(q)=>Q(q),onRunAtProblem:(q)=>F.reportRunAt(N,q)})})]}),o.children.length>0&&e("ol",{className:"flow-tree flow-children","aria-label":`Jobs ${A} waits on`,children:o.children.map((q,re)=>e(Ie,{node:q,indices:[...f,re],level:p+1,parent:N,parentQueue:k,editor:F},q.key))})]})}function Ve({value:o,onChange:f,parentQueue:p,listed:u,error:h}){let[F,N]=r(()=>o!==""&&u!==null&&!u.includes(o));if(u===null)return e(l,{label:"Queue",hint:`Leave empty for its parent's, ${p}.`,error:h,children:e(M,{value:o,onChange:f,autoComplete:"off",spellCheck:!1})});let A=F||o!==""&&!u.includes(o);return t(n,{children:[e(l,{label:"Queue",error:A?void 0:h,children:e(S,{options:[{value:"",label:`Its parent's (${p})`},...u.map((P)=>({value:P})),{value:he,label:"Another queue…"}],value:A?he:o,onChange:(P)=>{if(P===he)N(!0),f("");else N(!1),f(P)}})}),A&&e(l,{label:"Another queue",hint:"A queue need not exist yet. Leave empty for its parent's.",error:h,children:e(M,{value:o,onChange:f,autoComplete:"off",spellCheck:!1})})]})}function Oe(o){return[o,...o.children.flatMap(Oe)]}function Ye({queue:o,result:f}){let p=T(null);v(()=>p.current?.focus(),[]);let u=f.job,h=Oe(f).filter((F)=>F.added);return e("div",{ref:p,className:"flow-result",tabIndex:-1,role:"status","data-testid":"flow-result",children:f.added?t(n,{children:[t("p",{children:["Added ",h.length," ",h.length===1?"job":"jobs",". The top job,"," ",t(E,{to:We(u.queue||o,u.id),children:[u.name," (",u.id,")"]}),", runs once the jobs it waits on have completed."]}),e("ul",{className:"flow-result-list",children:h.map(({job:F})=>t("li",{children:[e(E,{to:We(F.queue,F.id),children:F.name})," ",t("span",{className:"muted",children:["in ",e("code",{children:F.queue}),", id ",e("code",{children:F.id})]})]},`${F.queue}:${F.id}`))})]}):t("p",{children:["The top job already existed, so nothing was added:"," ",t(E,{to:We(u.queue||o,u.id),children:["view ",u.name," (",u.id,")"]}),"."]})})}export{Eo as AddFlowDialog};
|
|
4
|
+
|
|
5
|
+
//# debugId=CD129209ADBCED6164756E2164756E21
|
|
6
|
+
//# sourceMappingURL=AddFlowDialog-h6xkgx9b.js.map
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["app/jobs/screens/job/addFlowForm.ts", "app/jobs/screens/job/AddFlowDialog.tsx"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"import type { ApiError } from \"../../api/errors\";\nimport type { AddFlowBody, AddFlowJobOptions } from \"../../api/types\";\nimport type { AddJobField, AddJobForm } from \"./addJobForm\";\nimport { MAX_NAME_LENGTH, NAME_PARAM_PATTERN } from \"../../api/contract\";\nimport { addBody, checkJobId, validateAddForm } from \"./addJobForm\";\nimport { explainJobError } from \"./jobErrors\";\n\n/**\n * The add-flow form: a tree of {@link AddJobForm}s, its client-side checks\n * (the API's, run before sending), its request body, and the placing of an\n * API problem on the node and field it is about. No React here.\n *\n * A node's **path** is where it sits in the body the API is sent: `\"\"` for\n * the top job, `children.1.children.0` below it, which is how the API names\n * nodes in its issue paths and `context.path`. Errors are keyed by node\n * **key** instead, so they stay with their node while the tree is edited.\n */\n\n/** One job of the flow, with the jobs it waits on. */\nexport interface FlowNode {\n /** A stable React key, unique within the flow; errors are keyed by it too. */\n key: string;\n /** The job's own fields, as the add-job dialog has them. */\n form: AddJobForm;\n /** The queue it goes in; `\"\"` for its parent's. Always `\"\"` on the top job, which goes in the screen's queue. */\n queue: string;\n /** Its parent carries on if it fails (`opts.ignoreFailure`). Children only: `true` on the top job is refused. */\n ignoreFailure: boolean;\n /** The jobs it waits on, in body order. */\n children: FlowNode[];\n}\n\n/** The API's bounds on one flow (`MetaDto.limits`). */\nexport interface FlowLimits {\n /** `limits.maxFlowNodes`: most jobs in one flow, the top job included. */\n maxNodes: number;\n /** `limits.maxFlowDepth`: most levels, the top job being level 1. */\n maxDepth: number;\n}\n\n/** A field of a flow node that can carry an error. */\nexport type FlowField =\n | AddJobField\n /** Its queue: a malformed name, or the API refusing or not knowing it. */\n | \"queue\"\n /** `ignoreFailure` where it is not allowed (the top job). */\n | \"opts.ignoreFailure\"\n /** The node itself: past a bound, or an issue at a field the form does not show. */\n | \"node\";\n\n/** Problems of one node, per field. */\nexport type FlowNodeErrors = Partial<Record<FlowField, string>>;\n\n/** Problems of a flow, per node key; a node without any is absent. */\nexport type FlowErrors = Record<string, FlowNodeErrors>;\n\n/** One node of a flow as the body places it. */\nexport interface PlacedFlowNode {\n /** The node. */\n node: FlowNode;\n /** Its path in the body (see the module comment). */\n path: string;\n /** The queue it goes in: its own, else its parent's (the top job's is the screen's). */\n queue: string;\n /** Its level, the top job being 1. */\n level: number;\n /** Its parent's key; `null` for the top job. */\n parent: string | null;\n}\n\n/** The counter behind {@link nextFlowKey}. */\nlet keyCounter = 0;\n\n/** A fresh node key, never handed out before in this page's life. */\nexport function nextFlowKey(): string {\n keyCounter += 1;\n return `flow-node-${keyCounter}`;\n}\n\n/** A node with `form` and no children, going in its parent's queue. */\nexport function flowNode(form: AddJobForm): FlowNode {\n return {\n key: nextFlowKey(),\n form,\n queue: \"\",\n ignoreFailure: false,\n children: [],\n };\n}\n\n/** `path` joined to a segment below it, without a leading dot at the top. */\nfunction pathAt(path: string, segment: string): string {\n return path === \"\" ? segment : `${path}.${segment}`;\n}\n\n/** Every node in body order (each before its children), placed. */\nexport function placeFlow(root: FlowNode, topQueue: string): PlacedFlowNode[] {\n const placed: PlacedFlowNode[] = [];\n const visit = (\n node: FlowNode,\n path: string,\n queue: string,\n level: number,\n parent: string | null,\n ) => {\n placed.push({ node, path, queue, level, parent });\n node.children.forEach((child, index) =>\n visit(\n child,\n pathAt(path, `children.${index}`),\n child.queue === \"\" ? queue : child.queue,\n level + 1,\n node.key,\n ),\n );\n };\n visit(root, \"\", topQueue, 1, null);\n return placed;\n}\n\n/** How many jobs the flow holds, the top job included. */\nexport function countFlowNodes(root: FlowNode): number {\n return root.children.reduce((sum, child) => sum + countFlowNodes(child), 1);\n}\n\n/** How many levels the flow nests, the top job being 1. */\nexport function flowDepth(root: FlowNode): number {\n return 1 + Math.max(0, ...root.children.map(flowDepth));\n}\n\n/** The distinct queues the flow writes to, the screen's first. */\nexport function flowQueues(root: FlowNode, topQueue: string): string[] {\n return [...new Set(placeFlow(root, topQueue).map((entry) => entry.queue))];\n}\n\n/** The node `key`, or `undefined`. */\nexport function findFlowNode(\n root: FlowNode,\n key: string,\n): FlowNode | undefined {\n if (root.key === key) {\n return root;\n }\n for (const child of root.children) {\n const found = findFlowNode(child, key);\n if (found) {\n return found;\n }\n }\n return undefined;\n}\n\n/** `root` with node `key` replaced by what `change` makes of it (the same object when `key` is not there). */\nfunction mapNode(\n root: FlowNode,\n key: string,\n change: (node: FlowNode) => FlowNode,\n): FlowNode {\n if (root.key === key) {\n return change(root);\n }\n let changed = false;\n const children = root.children.map((child) => {\n const next = mapNode(child, key, change);\n changed ||= next !== child;\n return next;\n });\n return changed ? { ...root, children } : root;\n}\n\n/** What {@link updateFlowNode} may change: everything but the key and the children. */\nexport type FlowNodePatch = Partial<Omit<FlowNode, \"key\" | \"children\">>;\n\n/** `root` with `patch` applied to node `key`. */\nexport function updateFlowNode(\n root: FlowNode,\n key: string,\n patch: FlowNodePatch,\n): FlowNode {\n return mapNode(root, key, (node) => ({ ...node, ...patch }));\n}\n\n/** `root` with `child` appended to node `parentKey`'s children. Limits are the caller's to check ({@link addChildBlocked}). */\nexport function addFlowChild(\n root: FlowNode,\n parentKey: string,\n child: FlowNode,\n): FlowNode {\n return mapNode(root, parentKey, (node) => ({\n ...node,\n children: [...node.children, child],\n }));\n}\n\n/** `root` without node `key` and everything below it. The top job cannot be removed: `root` comes back as it was. */\nexport function removeFlowNode(root: FlowNode, key: string): FlowNode {\n if (!root.children.some((child) => child.key === key)) {\n let changed = false;\n const children = root.children.map((child) => {\n const next = removeFlowNode(child, key);\n changed ||= next !== child;\n return next;\n });\n return changed ? { ...root, children } : root;\n }\n return {\n ...root,\n children: root.children.filter((child) => child.key !== key),\n };\n}\n\n/** `root` with node `key` moved `by` places among its siblings, clamped to the ends. */\nexport function moveFlowNode(\n root: FlowNode,\n key: string,\n by: number,\n): FlowNode {\n const index = root.children.findIndex((child) => child.key === key);\n if (index === -1) {\n let changed = false;\n const children = root.children.map((child) => {\n const next = moveFlowNode(child, key, by);\n changed ||= next !== child;\n return next;\n });\n return changed ? { ...root, children } : root;\n }\n const target = Math.min(root.children.length - 1, Math.max(0, index + by));\n if (target === index) {\n return root;\n }\n const children = [...root.children];\n const [moved] = children.splice(index, 1);\n children.splice(target, 0, moved);\n return { ...root, children };\n}\n\n/**\n * Why node `key` cannot take another child, or `null` when it can: the flow\n * already holds `maxNodes` jobs, or the node sits at `maxDepth` (a child\n * would be one level too deep).\n */\nexport function addChildBlocked(\n root: FlowNode,\n key: string,\n limits: FlowLimits,\n): string | null {\n if (countFlowNodes(root) >= limits.maxNodes) {\n return `The flow holds ${limits.maxNodes} jobs, the most the API accepts.`;\n }\n const entry = placeFlow(root, \"\").find((placed) => placed.node.key === key);\n if (entry && entry.level >= limits.maxDepth) {\n return `This job is at level ${entry.level}; a flow nests at most ${limits.maxDepth} levels.`;\n }\n return null;\n}\n\n/** Why a child's own queue would be refused, or `undefined` (`\"\"` takes its parent's). */\nexport function checkFlowQueue(queue: string): string | undefined {\n if (queue === \"\") {\n return undefined;\n }\n if (queue.length > MAX_NAME_LENGTH) {\n return `At most ${MAX_NAME_LENGTH} characters.`;\n }\n return new RegExp(NAME_PARAM_PATTERN).test(queue)\n ? undefined\n : \"Letters, digits, “_”, “.” and “-” only, and not “.” or “..”.\";\n}\n\n/** Records `message` on `key`'s `field` unless something got there first. */\nfunction put(\n errors: FlowErrors,\n key: string,\n field: FlowField,\n message: string,\n): void {\n const node = (errors[key] ??= {});\n node[field] ??= message;\n}\n\n/**\n * Checks the flow the way the API would: each job's own fields (as the\n * add-job dialog checks them), a child's queue name, the bounds (at the\n * first job past each, as the API reports it), `ignoreFailure` on the top\n * job, and a `queue:jobId` given twice (on every job sharing it, the queue\n * being each job's effective one). `{}` when it may be sent.\n */\nexport function validateFlow(\n root: FlowNode,\n topQueue: string,\n limits: FlowLimits,\n): FlowErrors {\n const errors: FlowErrors = {};\n const placed = placeFlow(root, topQueue);\n const byId = new Map<string, PlacedFlowNode[]>();\n let tooMany = false;\n let tooDeep = false;\n placed.forEach((entry, index) => {\n const { node, queue, level, parent } = entry;\n for (const [field, message] of Object.entries(validateAddForm(node.form))) {\n put(errors, node.key, field as AddJobField, message);\n }\n if (parent !== null) {\n const queueProblem = checkFlowQueue(node.queue);\n if (queueProblem) {\n put(errors, node.key, \"queue\", queueProblem);\n }\n } else if (node.ignoreFailure) {\n put(\n errors,\n node.key,\n \"opts.ignoreFailure\",\n \"The top job has no parent to carry on without it.\",\n );\n }\n if (!tooMany && index >= limits.maxNodes) {\n tooMany = true;\n put(\n errors,\n node.key,\n \"node\",\n `A flow may hold at most ${limits.maxNodes} jobs.`,\n );\n }\n if (!tooDeep && level > limits.maxDepth) {\n tooDeep = true;\n put(\n errors,\n node.key,\n \"node\",\n `A flow may nest at most ${limits.maxDepth} levels.`,\n );\n }\n if (node.form.jobId !== \"\" && checkJobId(node.form.jobId) === undefined) {\n const id = `${queue}:${node.form.jobId}`;\n byId.set(id, [...(byId.get(id) ?? []), entry]);\n }\n });\n for (const [id, entries] of byId) {\n if (entries.length > 1) {\n for (const { node } of entries) {\n put(\n errors,\n node.key,\n \"opts.jobId\",\n `Another job of this flow has the same queue and id (${id}).`,\n );\n }\n }\n }\n return errors;\n}\n\n/** Whether {@link validateFlow} found anything. */\nexport function hasFlowErrors(errors: FlowErrors): boolean {\n return Object.values(errors).some((node) => Object.keys(node).length > 0);\n}\n\n/**\n * The request body for a valid flow: each job as the add-job dialog sends\n * it (empty options left out), `ignoreFailure` only where set on a child,\n * `queue` only on a child going somewhere other than its parent's queue\n * (naming the parent's own is left out too), and `children` only where\n * there are some. The top job never names its queue: the path does.\n */\nexport function flowBody(root: FlowNode, topQueue: string): AddFlowBody {\n const build = (node: FlowNode, parentQueue: string | null): AddFlowBody => {\n const job = addBody(node.form);\n const isChild = parentQueue !== null;\n const opts: AddFlowJobOptions = {\n ...job.opts,\n ...(isChild && node.ignoreFailure ? { ignoreFailure: true } : {}),\n };\n const queue = !isChild\n ? topQueue\n : node.queue === \"\"\n ? parentQueue\n : node.queue;\n return {\n name: job.name,\n data: job.data,\n ...(Object.keys(opts).length > 0 ? { opts } : {}),\n ...(isChild && queue !== parentQueue ? { queue } : {}),\n ...(node.children.length > 0\n ? { children: node.children.map((child) => build(child, queue)) }\n : {}),\n };\n };\n return build(root, null);\n}\n\n/** What {@link flowProblemErrors} made of a problem. */\nexport interface PlacedFlowProblem {\n /** The messages it placed, per node and field. */\n errors: FlowErrors;\n /** Whether some of it could not be placed on a node, so the dialog shows it in a banner as well. */\n unplaced: boolean;\n}\n\n/** The fields an issue path's remainder can name directly. */\nconst PLACEABLE_FIELDS: ReadonlySet<string> = new Set<FlowField>([\n \"name\",\n \"data\",\n \"queue\",\n \"opts.jobId\",\n \"opts.priority\",\n \"opts.delay\",\n \"opts.runAt\",\n \"opts.attempts\",\n \"opts.backoff\",\n \"opts.timeout\",\n \"opts.ignoreFailure\",\n]);\n\n/**\n * The node a body path points into, and the field below it: `children.1.name`\n * is the second child's `name`; `children.99` is that node itself\n * (`\"node\"`). A path into `data` is the data. A field the form does not show\n * is the node's. `undefined` when the path leaves the tree.\n */\nexport function resolveFlowPath(\n placed: readonly PlacedFlowNode[],\n path: string,\n): { key: string; field: FlowField } | undefined {\n const byPath = new Map(placed.map((entry) => [entry.path, entry.node.key]));\n const segments = path === \"\" ? [] : path.split(\".\");\n let nodePath = \"\";\n let index = 0;\n while (\n segments[index] === \"children\" &&\n /^\\d+$/.test(segments[index + 1] ?? \"\")\n ) {\n nodePath = pathAt(nodePath, `children.${segments[index + 1]}`);\n index += 2;\n }\n const key = byPath.get(nodePath);\n if (key === undefined) {\n return undefined;\n }\n const rest = segments.slice(index).join(\".\");\n const field: FlowField =\n rest === \"\"\n ? \"node\"\n : PLACEABLE_FIELDS.has(rest)\n ? (rest as FlowField)\n : rest.startsWith(\"data.\")\n ? \"data\"\n : \"node\";\n return { key, field };\n}\n\n/** The queue a queue-level refusal is about: `context.queue`, else the path's own (which the API leaves out). */\nfunction refusedQueue(error: ApiError, topQueue: string): string {\n const queue = error.context.queue;\n return typeof queue === \"string\" && queue !== \"\" ? queue : topQueue;\n}\n\n/** What to say on a node whose queue the API refused. */\nfunction queueRefusal(error: ApiError, queue: string): string {\n if (error.code === \"QUEUE_NOT_FOUND\") {\n return `The API does not manage a queue named “${queue}”.`;\n }\n return error.status === 401\n ? `Sign in to add jobs to “${queue}”.`\n : `You may not add jobs to “${queue}”.`;\n}\n\n/**\n * Places an API problem on the nodes of the flow it was sent as (`root`),\n * by key, so the messages survive later edits:\n *\n * - `VALIDATION`: each body issue at its path (the first per field wins);\n * - `NAME_NOT_ADDABLE`: on the name at `context.path`;\n * - a refused queue (`FORBIDDEN`/`UNAUTHORIZED`, `QUEUE_NOT_FOUND`): on the\n * queue of every job going in `context.queue`, or in the path's queue when\n * the context names none (a refusal of the path's own queue does not);\n * - anything else, or a part that matches no node: `unplaced`.\n */\nexport function flowProblemErrors(\n error: ApiError,\n root: FlowNode,\n topQueue: string,\n): PlacedFlowProblem {\n const errors: FlowErrors = {};\n const placed = placeFlow(root, topQueue);\n let unplaced = false;\n if (error.code === \"VALIDATION\" && error.issues.length > 0) {\n for (const issue of error.issues) {\n const at =\n issue.target === \"body\"\n ? resolveFlowPath(placed, issue.path)\n : undefined;\n if (at) {\n put(errors, at.key, at.field, issue.message);\n } else {\n unplaced = true;\n }\n }\n return { errors, unplaced };\n }\n if (error.code === \"NAME_NOT_ADDABLE\") {\n const path = error.context.path;\n const at =\n typeof path === \"string\" ? resolveFlowPath(placed, path) : undefined;\n if (!at) {\n return { errors, unplaced: true };\n }\n put(\n errors,\n at.key,\n at.field,\n explainJobError(error, \"add\") ?? error.detail ?? error.title,\n );\n return { errors, unplaced: false };\n }\n if (\n error.code === \"FORBIDDEN\" ||\n error.code === \"UNAUTHORIZED\" ||\n error.code === \"QUEUE_NOT_FOUND\"\n ) {\n const queue = refusedQueue(error, topQueue);\n const message = queueRefusal(error, queue);\n const nodes = placed.filter((entry) => entry.queue === queue);\n for (const { node } of nodes) {\n put(errors, node.key, \"queue\", message);\n }\n return { errors, unplaced: nodes.length === 0 };\n }\n return { errors, unplaced: true };\n}\n\n/**\n * What node `key`'s `field` holds in a placed flow, in a form two placings\n * can be compared by: the job's effective queue for `queue` (so a child\n * taking its parent's queue changes when the parent's does), its path for\n * `node`, the timing with the value for `opts.delay` and `opts.runAt`.\n * `undefined` when the node is not in the flow.\n */\nfunction fieldValue(\n placed: readonly PlacedFlowNode[],\n key: string,\n field: FlowField,\n): unknown {\n const entry = placed.find((candidate) => candidate.node.key === key);\n if (entry === undefined) {\n return undefined;\n }\n const { node } = entry;\n switch (field) {\n case \"node\":\n return entry.path;\n case \"queue\":\n return entry.queue;\n case \"opts.ignoreFailure\":\n return node.ignoreFailure;\n case \"name\":\n return node.form.name;\n case \"data\":\n return node.form.data.text;\n case \"opts.jobId\":\n return node.form.jobId;\n case \"opts.priority\":\n return node.form.priority;\n case \"opts.delay\":\n return `${node.form.timing}:${node.form.delay}`;\n case \"opts.runAt\":\n return `${node.form.timing}:${node.form.runAt}`;\n case \"opts.attempts\":\n return node.form.attempts;\n case \"opts.backoff\":\n return node.form.backoff;\n case \"opts.timeout\":\n return node.form.timeout;\n }\n}\n\n/**\n * Whether node `key`'s `field` still holds what the flow `sent` had there, so\n * an API error about it still applies. Once the user edits the field (or, for\n * `queue`, anything that changes where the job goes), the error is stale and\n * the dialog stops showing it; the next send is the API's next word.\n */\nexport function fieldUnchangedSince(\n current: FlowNode,\n sent: FlowNode,\n topQueue: string,\n key: string,\n field: FlowField,\n): boolean {\n return (\n fieldValue(placeFlow(current, topQueue), key, field) ===\n fieldValue(placeFlow(sent, topQueue), key, field)\n );\n}\n",
|
|
6
|
+
"import type { FormEvent } from \"react\";\nimport type { AddFlowResultDto } from \"../../api/types\";\nimport type {\n FlowField,\n FlowLimits,\n FlowNode,\n FlowNodePatch,\n} from \"./addFlowForm\";\nimport type { AddJobField, AddJobForm } from \"./addJobForm\";\nimport { useQueries, useQuery } from \"@tanstack/react-query\";\nimport { useEffect, useId, useMemo, useRef, useState } from \"react\";\nimport { isApiError } from \"../../api/errors\";\nimport { addFlow, flowInvalidation, jobScreenPath } from \"../../api/jobs\";\nimport { queryKeys } from \"../../api/queryKeys\";\nimport { Button } from \"../../components/Button\";\nimport { Dialog } from \"../../components/Dialog\";\nimport { EmptyState } from \"../../components/EmptyState\";\nimport { Field } from \"../../components/Field\";\nimport { Checkbox, Select, TextInput } from \"../../components/inputs\";\nimport { ProblemBanner } from \"../../components/ProblemBanner\";\nimport { useApiClient } from \"../../context\";\nimport { useApiMutation } from \"../../hooks/useApiMutation\";\nimport { useFieldProblems } from \"../../hooks/useFieldProblems\";\nimport { canPerform, useCan, useMeta } from \"../../meta/hooks\";\nimport { Link } from \"../../router\";\nimport {\n addChildBlocked,\n addFlowChild,\n checkFlowQueue,\n fieldUnchangedSince,\n findFlowNode,\n flowBody,\n flowNode,\n flowProblemErrors,\n flowQueues,\n hasFlowErrors,\n placeFlow,\n removeFlowNode,\n updateFlowNode,\n validateFlow,\n} from \"./addFlowForm\";\nimport { emptyAddForm } from \"./addJobForm\";\nimport { friendlyJobError } from \"./jobErrors\";\nimport { JobDataField, JobNameField, JobOptionsFields } from \"./JobFormFields\";\nimport \"./job.css\";\n\n/** Props of {@link AddFlowDialog}: the same contract as `AddJobDialogProps`. */\nexport interface AddFlowDialogProps {\n /** The queue the top job is added to (the screen's). */\n queue: string;\n /** Whether the dialog is open. */\n open: boolean;\n /** Called to close it: Cancel, Escape, or Done once the result is shown. */\n onClose: () => void;\n}\n\n/** A select value no queue can have (queue names are `[\\w.-]+`): \"type another queue\". */\nconst OTHER_QUEUE = \"\\u0000other\";\n\n/** How long the queues typed into the flow settle before their permissions are asked, ms. */\nconst PERMISSION_CHECK_DELAY_MS = 250;\n\n/** The option fields, which open the node's options when one has an error. */\nconst OPTION_FIELDS: readonly AddJobField[] = [\n \"opts.jobId\",\n \"opts.priority\",\n \"opts.delay\",\n \"opts.runAt\",\n \"opts.attempts\",\n \"opts.backoff\",\n \"opts.timeout\",\n];\n\n/** Where focus goes after the tree changes: a node's first field, or its Add child button. */\ninterface FocusRequest {\n /** The node. */\n key: string;\n /** `\"first\"` for its first field (a new node), `\"add\"` for its Add child button (after a child went). */\n target: \"first\" | \"add\";\n}\n\n/** What every node editor shares: the tree's operations, its errors and what it may offer. */\ninterface FlowEditor {\n /** The screen's queue, which the top job goes in. */\n topQueue: string;\n /** `MetaDto.addableNames`: `null` for any name. */\n addableNames: string[] | null;\n /** `limits.maxJobDataBytes`. */\n maxBytes: number;\n /** The queues the caller may list, for a child's queue select; `null` when it may not list them. */\n listedQueues: string[] | null;\n /** Queues whose own permissions refuse `jobs.add` (the pre-check; the API decides). */\n refusedQueues: ReadonlySet<string>;\n /** The error on a node's field, if any. */\n errorOf: (key: string, field: FlowField) => string | undefined;\n /** Why a node cannot take another child, or `null`. */\n blockedReason: (key: string) => string | null;\n /** Changes a node's own fields. */\n patch: (key: string, patch: FlowNodePatch) => void;\n /** Appends an empty child to a node. */\n addChild: (key: string) => void;\n /** Removes a node and everything below it. */\n remove: (key: string, parent: string) => void;\n /** Records or clears a node's run-time input problem. */\n reportRunAt: (key: string, problem: string | undefined) => void;\n /** A pending focus move, or `null`. */\n focus: FocusRequest | null;\n /** Called once a node has taken the focus it was asked to. */\n focused: () => void;\n}\n\n/** A node's label: \"Top job\", or \"Child 1.2\" for the second child of the first child. */\nfunction nodeLabel(indices: readonly number[]): string {\n return indices.length === 0\n ? \"Top job\"\n : `Child ${indices.map((index) => index + 1).join(\".\")}`;\n}\n\n/**\n * Adds a flow (`POST /queues/:queue/flows`): a top job in the screen's queue\n * and the jobs it waits on, each in its parent's queue or one of its own.\n * Loaded on demand: the queue screen renders `AddFlowDialog` from `../lazy`.\n */\nexport function AddFlowDialog(props: AddFlowDialogProps) {\n return props.open ? <OpenAddFlowDialog {...props} /> : null;\n}\n\n/** The mounted dialog; its state lives exactly as long as one opening. */\nfunction OpenAddFlowDialog({ queue, onClose }: AddFlowDialogProps) {\n const api = useApiClient();\n const meta = useMeta();\n const canListQueues = useCan(\"queues.list\");\n const formId = useId();\n const maxBytes = meta.limits.maxJobDataBytes;\n const addableNames = meta.addableNames;\n const nothingAddable = addableNames !== null && addableNames.length === 0;\n const limits: FlowLimits = {\n maxNodes: meta.limits.maxFlowNodes,\n maxDepth: meta.limits.maxFlowDepth,\n };\n const newForm = (): AddJobForm => emptyAddForm({ addableNames, maxBytes });\n const [root, setRoot] = useState<FlowNode>(() => flowNode(newForm()));\n const [submitted, setSubmitted] = useState(false);\n const [focus, setFocus] = useState<FocusRequest | null>(null);\n const times = useFieldProblems<string>();\n\n const add = useApiMutation({\n mutationFn: (sent: FlowNode) => addFlow(api, queue, flowBody(sent, queue)),\n invalidate: (_result: AddFlowResultDto, sent: FlowNode) =>\n flowInvalidation(flowQueues(sent, queue)),\n toastErrors: false,\n });\n\n const placed = placeFlow(root, queue);\n const count = placed.length;\n\n // The pre-check: each other queue the flow writes to, asked about once\n // its name has settled, through the same targeted map a queue screen uses.\n const otherQueues = [...new Set(placed.map((entry) => entry.queue))]\n .filter((name) => name !== queue && checkFlowQueue(name) === undefined)\n .sort()\n .join(\"\\n\");\n const [checkedQueues, setCheckedQueues] = useState(\"\");\n useEffect(() => {\n const timer = setTimeout(\n setCheckedQueues,\n PERMISSION_CHECK_DELAY_MS,\n otherQueues,\n );\n return () => clearTimeout(timer);\n }, [otherQueues]);\n const toCheck = checkedQueues === \"\" ? [] : checkedQueues.split(\"\\n\");\n const permissions = useQueries({\n queries: toCheck.map((name) => ({\n queryKey: queryKeys.permissions({ queue: name }),\n queryFn: ({ signal }: { signal: AbortSignal }) =>\n api.getPermissions({ queue: name }, signal),\n retry: false,\n })),\n });\n const refusedQueues = new Set(\n toCheck.filter((_name, index) => {\n const answer = permissions[index]?.data;\n return answer !== undefined && !canPerform(answer, \"jobs.add\");\n }),\n );\n\n const listing = useQuery({\n queryKey: queryKeys.queues(\"\"),\n queryFn: ({ signal }) => api.listQueues(\"\", signal),\n enabled: canListQueues,\n });\n const listedQueues =\n canListQueues && !listing.isError\n ? (listing.data?.items.map((item) => item.name) ?? [])\n : null;\n\n const clientErrors = submitted ? validateFlow(root, queue, limits) : {};\n const apiError = isApiError(add.error) ? add.error : null;\n const sent = add.variables;\n const server = useMemo(\n () => (apiError && sent ? flowProblemErrors(apiError, sent, queue) : null),\n [apiError, sent, queue],\n );\n const bannerError =\n add.error && (server === null || server.unplaced)\n ? friendlyJobError(add.error, \"add\")\n : null;\n const liveKeys = new Set(placed.map((entry) => entry.node.key));\n const timesBlocked = Object.keys(times.problems).some((key) =>\n liveKeys.has(key),\n );\n\n const editor: FlowEditor = {\n topQueue: queue,\n addableNames,\n maxBytes,\n listedQueues,\n refusedQueues,\n errorOf: (key, field) =>\n (field === \"opts.runAt\" ? times.problems[key] : undefined) ??\n clientErrors[key]?.[field] ??\n // An API error stays only while the field holds what was sent.\n (sent && fieldUnchangedSince(root, sent, queue, key, field)\n ? server?.errors[key]?.[field]\n : undefined),\n blockedReason: (key) => addChildBlocked(root, key, limits),\n patch: (key, patch) =>\n setRoot((current) => updateFlowNode(current, key, patch)),\n addChild: (key) => {\n if (addChildBlocked(root, key, limits) !== null) {\n return;\n }\n const child = flowNode(newForm());\n setRoot((current) => addFlowChild(current, key, child));\n setFocus({ key: child.key, target: \"first\" });\n },\n remove: (key, parent) => {\n const gone = findFlowNode(root, key);\n if (gone) {\n for (const entry of placeFlow(gone, \"\")) {\n times.report(entry.node.key, undefined);\n }\n }\n setRoot((current) => removeFlowNode(current, key));\n setFocus({ key: parent, target: \"add\" });\n },\n reportRunAt: times.report,\n focus,\n focused: () => setFocus(null),\n };\n\n const onSubmit = (event: FormEvent<HTMLFormElement>) => {\n event.preventDefault();\n setSubmitted(true);\n if (\n nothingAddable ||\n timesBlocked ||\n hasFlowErrors(validateFlow(root, queue, limits))\n ) {\n return;\n }\n add.mutate(root);\n };\n\n const close = () => {\n if (!add.isPending) {\n onClose();\n }\n };\n\n const result = add.isSuccess ? add.data : undefined;\n\n return (\n <Dialog\n open\n onClose={close}\n title={\n <>\n Add a flow to <code>{queue}</code>\n </>\n }\n description=\"A flow is a job and the jobs it waits on: they are added first, and it runs once they have all completed.\"\n size=\"lg\"\n closeOnEscape={!add.isPending}\n closeOnBackdrop={!add.isPending}\n footer={\n result ? (\n <Button\n variant=\"primary\"\n onClick={close}\n >\n Done\n </Button>\n ) : (\n <>\n <Button\n onClick={close}\n disabled={add.isPending}\n >\n Cancel\n </Button>\n <Button\n variant=\"primary\"\n type=\"submit\"\n form={formId}\n disabled={add.isPending || nothingAddable || timesBlocked}\n aria-busy={add.isPending || undefined}\n >\n {add.isPending ? \"Adding…\" : \"Add flow\"}\n </Button>\n </>\n )\n }\n >\n {result ? (\n <FlowResult\n queue={queue}\n result={result}\n />\n ) : nothingAddable ? (\n <EmptyState\n title=\"No flow can be added here\"\n description=\"This API accepts no job names: adding jobs is switched off (the jobs.add action is not enabled, or the API is read-only), or no name is allowed. Ask whoever runs it to list the names in addableNames.\"\n />\n ) : (\n <form\n id={formId}\n className=\"job-form flow-form\"\n onSubmit={onSubmit}\n noValidate\n >\n <p\n className=\"flow-count\"\n data-testid=\"flow-count\"\n aria-live=\"polite\"\n >\n {count} of {limits.maxNodes} jobs\n {count >= limits.maxNodes\n ? \": the most one flow may hold.\"\n : `, nested at most ${limits.maxDepth} levels.`}\n </p>\n <ol\n className=\"flow-tree\"\n aria-label=\"Flow\"\n >\n <FlowNodeEditor\n node={root}\n indices={[]}\n level={1}\n parent={null}\n parentQueue={null}\n editor={editor}\n />\n </ol>\n {bannerError !== null && <ProblemBanner error={bannerError} />}\n </form>\n )}\n </Dialog>\n );\n}\n\n/** Props of {@link FlowNodeEditor}. */\ninterface FlowNodeEditorProps {\n /** The node. */\n node: FlowNode;\n /** Its position under the top job, `[]` for the top job itself (see {@link nodeLabel}). */\n indices: readonly number[];\n /** Its level, the top job being 1. */\n level: number;\n /** Its parent's key; `null` for the top job. */\n parent: string | null;\n /** The queue its parent goes in; `null` for the top job. */\n parentQueue: string | null;\n /** The shared operations and state. */\n editor: FlowEditor;\n}\n\n/** One job of the flow: its fields and actions, then the jobs it waits on. */\nfunction FlowNodeEditor({\n node,\n indices,\n level,\n parent,\n parentQueue,\n editor,\n}: FlowNodeEditorProps) {\n const { key } = node;\n const label = nodeLabel(indices);\n const isTop = parent === null;\n const queue = isTop\n ? editor.topQueue\n : node.queue === \"\"\n ? parentQueue!\n : node.queue;\n const fieldsetRef = useRef<HTMLFieldSetElement>(null);\n const addRef = useRef<HTMLButtonElement>(null);\n const reasonId = useId();\n const optionsId = useId();\n const [showOptions, setShowOptions] = useState(false);\n const errorOf = (field: FlowField) => editor.errorOf(key, field);\n const optionError = OPTION_FIELDS.some((field) => errorOf(field));\n const optionsShown = showOptions || optionError;\n const blocked = editor.blockedReason(key);\n const refused = editor.refusedQueues.has(queue);\n const nodeError =\n errorOf(\"node\") ?? (isTop ? errorOf(\"opts.ignoreFailure\") : undefined);\n const queueError =\n errorOf(\"queue\") ??\n (refused\n ? `You may not add jobs to “${queue}”: the API will refuse this flow.`\n : undefined);\n\n const { focus, focused } = editor;\n useEffect(() => {\n if (focus?.key !== key) {\n return;\n }\n const target =\n focus.target === \"add\" && addRef.current && !addRef.current.disabled\n ? addRef.current\n : fieldsetRef.current?.querySelector<HTMLElement>(\n \"input, select, textarea\",\n );\n target?.focus();\n focused();\n }, [focus, key, focused]);\n\n const patchForm = (next: Partial<AddJobForm>) =>\n editor.patch(key, { form: { ...node.form, ...next } });\n\n return (\n <li className=\"flow-item\">\n <fieldset\n ref={fieldsetRef}\n className=\"flow-node\"\n data-refused={refused || undefined}\n data-testid=\"flow-node\"\n >\n <legend>{label}</legend>\n {nodeError && (\n <p\n className=\"field-error job-form-error\"\n role=\"alert\"\n >\n {nodeError}\n </p>\n )}\n <JobNameField\n value={node.form.name}\n onChange={(name) => patchForm({ name })}\n error={errorOf(\"name\")}\n addableNames={editor.addableNames}\n />\n <JobDataField\n value={node.form.data.text}\n onChange={(data) => patchForm({ data })}\n error={errorOf(\"data\")}\n maxBytes={editor.maxBytes}\n rows={4}\n />\n {isTop ? (\n <div className=\"flow-node-queue\">\n <p className=\"field-label\">\n Queue: <code>{queue}</code>\n </p>\n {queueError && <p className=\"field-error\">{queueError}</p>}\n </div>\n ) : (\n <FlowQueueField\n value={node.queue}\n onChange={(next) => editor.patch(key, { queue: next })}\n parentQueue={parentQueue!}\n listed={editor.listedQueues}\n error={queueError}\n />\n )}\n {!isTop && (\n <div>\n <Checkbox\n checked={node.ignoreFailure}\n onChange={(ignoreFailure) => editor.patch(key, { ignoreFailure })}\n label=\"Parent carries on if this fails\"\n hint=\"Its failure is recorded for the parent to read, instead of failing it.\"\n />\n {errorOf(\"opts.ignoreFailure\") && (\n <p className=\"field-error\">{errorOf(\"opts.ignoreFailure\")}</p>\n )}\n </div>\n )}\n <div className=\"flow-node-actions\">\n <Button\n size=\"sm\"\n variant=\"ghost\"\n aria-expanded={optionsShown}\n aria-controls={optionsShown ? optionsId : undefined}\n onClick={() => setShowOptions(!optionsShown)}\n >\n Options\n <span className=\"visually-hidden\"> of {label}</span>\n </Button>\n <Button\n ref={addRef}\n size=\"sm\"\n onClick={() => editor.addChild(key)}\n disabled={blocked !== null}\n aria-describedby={blocked !== null ? reasonId : undefined}\n >\n Add child\n <span className=\"visually-hidden\"> to {label}</span>\n </Button>\n {parent !== null && (\n <Button\n size=\"sm\"\n variant=\"danger\"\n onClick={() => editor.remove(key, parent)}\n >\n Remove\n <span className=\"visually-hidden\"> {label}</span>\n </Button>\n )}\n {blocked !== null && (\n <p\n id={reasonId}\n className=\"field-hint flow-node-blocked\"\n >\n {blocked}\n </p>\n )}\n </div>\n {optionsShown && (\n <div id={optionsId}>\n <JobOptionsFields\n form={node.form}\n patch={patchForm}\n fieldError={(field) => errorOf(field)}\n onRunAtProblem={(problem) => editor.reportRunAt(key, problem)}\n />\n </div>\n )}\n </fieldset>\n {node.children.length > 0 && (\n <ol\n className=\"flow-tree flow-children\"\n aria-label={`Jobs ${label} waits on`}\n >\n {node.children.map((child, index) => (\n <FlowNodeEditor\n key={child.key}\n node={child}\n indices={[...indices, index]}\n level={level + 1}\n parent={key}\n parentQueue={queue}\n editor={editor}\n />\n ))}\n </ol>\n )}\n </li>\n );\n}\n\n/** Props of {@link FlowQueueField}. */\ninterface FlowQueueFieldProps {\n /** The child's own queue; `\"\"` for its parent's. */\n value: string;\n /** Called with a new queue (`\"\"` for its parent's). */\n onChange: (queue: string) => void;\n /** The queue its parent goes in, which `\"\"` means. */\n parentQueue: string;\n /** The queues the caller may list; `null` when it may not, and the queue is typed. */\n listed: string[] | null;\n /** The error to show under it. */\n error: string | undefined;\n}\n\n/**\n * A child's queue: its parent's by default, one the caller can list, or any\n * other name typed in (a queue need not exist before a job is added to it).\n */\nfunction FlowQueueField({\n value,\n onChange,\n parentQueue,\n listed,\n error,\n}: FlowQueueFieldProps) {\n const [typing, setTyping] = useState(\n () => value !== \"\" && listed !== null && !listed.includes(value),\n );\n if (listed === null) {\n return (\n <Field\n label=\"Queue\"\n hint={`Leave empty for its parent's, ${parentQueue}.`}\n error={error}\n >\n <TextInput\n value={value}\n onChange={onChange}\n autoComplete=\"off\"\n spellCheck={false}\n />\n </Field>\n );\n }\n const freeText = typing || (value !== \"\" && !listed.includes(value));\n return (\n <>\n <Field\n label=\"Queue\"\n error={freeText ? undefined : error}\n >\n <Select\n options={[\n { value: \"\", label: `Its parent's (${parentQueue})` },\n ...listed.map((name) => ({ value: name })),\n { value: OTHER_QUEUE, label: \"Another queue…\" },\n ]}\n value={freeText ? OTHER_QUEUE : value}\n onChange={(chosen) => {\n if (chosen === OTHER_QUEUE) {\n setTyping(true);\n onChange(\"\");\n } else {\n setTyping(false);\n onChange(chosen);\n }\n }}\n />\n </Field>\n {freeText && (\n <Field\n label=\"Another queue\"\n hint=\"A queue need not exist yet. Leave empty for its parent's.\"\n error={error}\n >\n <TextInput\n value={value}\n onChange={onChange}\n autoComplete=\"off\"\n spellCheck={false}\n />\n </Field>\n )}\n </>\n );\n}\n\n/** Props of {@link FlowResult}. */\ninterface FlowResultProps {\n /** The screen's queue, which the top job went in. */\n queue: string;\n /** What the API answered. */\n result: AddFlowResultDto;\n}\n\n/** Every job of a result in body order (each before its children). */\nfunction resultJobs(result: AddFlowResultDto): AddFlowResultDto[] {\n return [result, ...result.children.flatMap(resultJobs)];\n}\n\n/** What the flow came to: every job added, linked to its screen, or the top job that already existed. */\nfunction FlowResult({ queue, result }: FlowResultProps) {\n const ref = useRef<HTMLDivElement>(null);\n useEffect(() => ref.current?.focus(), []);\n const top = result.job;\n const jobs = resultJobs(result).filter((entry) => entry.added);\n return (\n <div\n ref={ref}\n className=\"flow-result\"\n tabIndex={-1}\n role=\"status\"\n data-testid=\"flow-result\"\n >\n {result.added ? (\n <>\n <p>\n Added {jobs.length} {jobs.length === 1 ? \"job\" : \"jobs\"}. The top\n job,{\" \"}\n <Link to={jobScreenPath(top.queue || queue, top.id)}>\n {top.name} ({top.id})\n </Link>\n , runs once the jobs it waits on have completed.\n </p>\n <ul className=\"flow-result-list\">\n {jobs.map(({ job }) => (\n <li key={`${job.queue}:${job.id}`}>\n <Link to={jobScreenPath(job.queue, job.id)}>{job.name}</Link>{\" \"}\n <span className=\"muted\">\n in <code>{job.queue}</code>, id <code>{job.id}</code>\n </span>\n </li>\n ))}\n </ul>\n </>\n ) : (\n <p>\n The top job already existed, so nothing was added:{\" \"}\n <Link to={jobScreenPath(top.queue || queue, top.id)}>\n view {top.name} ({top.id})\n </Link>\n .\n </p>\n )}\n </div>\n );\n}\n"
|
|
7
|
+
],
|
|
8
|
+
"mappings": "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",
|
|
9
|
+
"debugId": "CD129209ADBCED6164756E2164756E21",
|
|
10
|
+
"names": []
|
|
11
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
.breadcrumb ol{display:flex;gap:var(--space-1);margin:0 0 var(--space-3);list-style:none;font-size:var(--text-sm);color:var(--text-muted);flex-wrap:wrap;padding:0}.breadcrumb li+li:before{content:"/";margin-right:var(--space-1);color:var(--text-muted)}.breadcrumb [aria-current=page]{color:var(--text)}.job-header{display:flex;justify-content:space-between;align-items:flex-start;gap:var(--space-3);margin-bottom:var(--space-4);flex-wrap:wrap}.job-heading{min-width:0}.job-title{display:flex;align-items:center;gap:var(--space-2);margin-bottom:var(--space-1);flex-wrap:wrap}.job-name{overflow-wrap:anywhere}.job-id-line{display:flex;align-items:center;gap:var(--space-2);font-size:var(--text-sm);flex-wrap:wrap;margin:0}.job-id{font-family:var(--font-mono);overflow-wrap:anywhere;word-break:break-all}.job-actions{display:flex;gap:var(--space-2);flex-wrap:wrap}.job-screen .card{margin-bottom:var(--space-4)}.job-error{display:grid;gap:var(--space-2)}.job-error-line{overflow-wrap:anywhere;margin:0}.job-error-name{color:var(--danger-text)}.job-error-code{margin-left:var(--space-2);font-size:var(--text-xs);color:var(--text-muted)}.job-error-stack{padding:var(--space-2)var(--space-3);overflow-x:auto;background:var(--surface-2);border-radius:var(--radius-sm);font-family:var(--font-mono);font-size:var(--text-xs);white-space:pre;max-height:320px;margin:0}.job-error-causes{display:grid;gap:var(--space-2);padding-left:var(--space-4);border-left:2px solid var(--border);list-style:none;margin:0}.job-error-caused-by{display:block;font-size:var(--text-xs);text-transform:uppercase;letter-spacing:.04em}.job-details{margin-top:var(--space-3)}.job-details>summary{cursor:pointer;margin-bottom:var(--space-2);font-weight:600}.job-stacktraces{display:grid;gap:var(--space-3);list-style:none;margin:0;padding:0}.job-logs-controls{display:inline-flex;justify-content:flex-end;align-items:center;gap:var(--space-3);flex-wrap:wrap}.job-logs-clear{display:inline-flex;align-items:center;gap:var(--space-2);flex-wrap:wrap}.job-logs-clear-reason{font-size:var(--text-xs)}.job-logs-order{display:inline-flex;align-items:center;gap:var(--space-2);font-size:var(--text-sm)}.job-logs-follow,.job-logs-total{margin:0 0 var(--space-2);font-size:var(--text-xs)}.job-flow-summary{margin-bottom:var(--space-3)}.job-ref code{font-family:var(--font-mono);overflow-wrap:anywhere}.job-form{display:grid;gap:var(--space-3)}.job-form-options,.job-states-picker{display:grid;gap:var(--space-3);padding:var(--space-3);border:1px solid var(--border);border-radius:var(--radius);margin:0}.job-states-picker{grid-template-columns:repeat(auto-fill,minmax(150px,1fr));gap:var(--space-2)}.job-form-options>legend,.job-states-picker>legend{padding:0 var(--space-1);font-weight:600;font-size:var(--text-sm)}.job-form-error{margin:0}@media (min-width:640px){.job-form-options{grid-template-columns:repeat(2,minmax(0,1fr))}.job-form-options>legend{grid-column:1/-1}}.job-note{padding:var(--space-2)var(--space-3);border-radius:var(--radius-sm);background:var(--surface-2);font-size:var(--text-sm);margin:0}.job-note-warning{background:var(--warning-bg);color:var(--warning-text)}.job-progress{display:inline-flex;align-items:center;gap:var(--space-2)}.job-progress-bar{display:inline-block;overflow:hidden;border-radius:var(--radius-sm);background:var(--surface-2);width:8rem;height:.5rem}.job-progress-fill{display:block;background:var(--accent);height:100%}.job-progress-value{font-variant-numeric:tabular-nums}.job-processed-by{display:inline-flex;align-items:baseline;gap:var(--space-1)var(--space-2);overflow-wrap:anywhere;flex-wrap:wrap}.flow-count{font-size:var(--text-sm);margin:0}.flow-tree{display:grid;gap:var(--space-3);list-style:none;margin:0;padding:0}.flow-children{margin-top:var(--space-3);padding-left:var(--space-4);border-left:2px solid var(--border)}.flow-node{display:grid;gap:var(--space-3);padding:var(--space-3);border:1px solid var(--border);border-radius:var(--radius);min-width:0;margin:0}.flow-node>legend{padding:0 var(--space-1);font-weight:600}.flow-node[data-refused]{border-color:var(--danger)}.flow-node-queue>p{margin:0}.flow-node-actions{display:flex;align-items:center;gap:var(--space-2);flex-wrap:wrap}.flow-node-blocked{flex-basis:100%;margin:0}.flow-result{display:grid;gap:var(--space-2)}.flow-result>p,.flow-result-list{margin:0}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import{Z,n,e,t,i,Y,a,H,p,o,z,Et}from"./main-a250ffk7.js";var c=20000;function f(r,m=Date.now()){return Et(r,m)}function u(r){return["addedByState",r.kind==="preset"?{seconds:r.seconds}:{from:r.from,to:r.to}]}function y(r,m,l){let{from:s,to:d}=f(m);return r.request("GET","/overview/added",{query:{from:s,to:d},signal:l})}function b({range:r}){let m=i(),l=H(c),s=a({queryKey:u(r),queryFn:({signal:d})=>y(m,r,d),refetchInterval:l,placeholderData:Y});return t(n,{children:[t("p",{className:"range-group-title",children:["Added in range, where they are now"," ",e("span",{className:"muted",children:"(still stored)"})]}),s.data?t("dl",{className:"range-cells",children:[Z.map((d)=>t("div",{className:`range-cell state-${d}`,children:[e("dt",{children:z[d]}),e("dd",{children:o(s.data.counts[d])})]},d)),t("div",{className:"range-cell range-cell-total",children:[e("dt",{children:"Total"}),e("dd",{children:o(s.data.total)})]})]}):s.isError?t("p",{className:"range-note",children:["Could not load: ",s.error.message]}):e(p,{label:"Loading jobs added in the range"}),e("p",{className:"range-note",children:"Counted by when jobs were added, not finished, so it will not match Finished in range; queues that remove finished jobs show few completed."})]})}export{b as AddedByStateGroup};
|
|
2
|
+
|
|
3
|
+
//# debugId=639A51B3BD37E91E64756E2164756E21
|
|
4
|
+
//# sourceMappingURL=added-cbwsxv3e.js.map
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["app/jobs/api/added.ts", "app/jobs/screens/overview/added.tsx"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"import type { TimeRange } from \"../analytics/range\";\nimport type { ApiClient } from \"./client\";\nimport type { AddedByStateDto } from \"./types\";\nimport { rangeBounds } from \"../analytics/range\";\n\n/**\n * `GET /overview/added`: of the jobs added during a range, how many are in\n * each state now. Served only where `meta.features.addedByState` is `true`;\n * nothing here may be called otherwise (the route does not exist there).\n *\n * Imported only by the Overview's lazy added-by-state group, so none of it\n * reaches the entry bundle.\n */\n\n/** How often the group re-reads: it counts job records, so far slower than the Overview's 5 s (the contract asks for 15–30 s). */\nexport const ADDED_BY_STATE_POLL_MS = 20_000;\n\n/**\n * The `from`/`to` one read sends: the range resolved against `now`.\n *\n * Sent as is. The API refuses a span over `MAX_ADDED_BY_STATE_SPAN_MS`, and no\n * range the app can hold is longer: the range model caps every span at\n * `MAX_RANGE_MS`, which is not above it (`addedByState.test.ts` holds that,\n * so a contract change that splits the two fails there, not in a user's\n * Overview).\n */\nexport function addedByStateRequest(\n range: TimeRange,\n now = Date.now(),\n): {\n /** Start, epoch ms, inclusive. */\n from: number;\n /** End, epoch ms, exclusive. */\n to: number;\n} {\n return rangeBounds(range, now);\n}\n\n/**\n * The query key of one read. A preset is keyed by its length, a custom range\n * by its instants — as the analytics keys are — so a rolling window is one\n * entry that refetches. Deliberately not under `[\"overview\"]`: the live\n * updates invalidate that prefix on every job event, which would turn this\n * slow poll into one read per event.\n */\nexport function addedByStateKey(range: TimeRange) {\n return [\n \"addedByState\",\n range.kind === \"preset\"\n ? { seconds: range.seconds }\n : { from: range.from, to: range.to },\n ] as const;\n}\n\n/** `GET /overview/added?from&to`, summed over every queue the caller may see. */\nexport function getAddedByState(\n api: ApiClient,\n range: TimeRange,\n signal?: AbortSignal,\n): Promise<AddedByStateDto> {\n const { from, to } = addedByStateRequest(range);\n return api.request(\"GET\", \"/overview/added\", {\n query: { from, to },\n signal,\n });\n}\n",
|
|
6
|
+
"import type { TimeRange } from \"../../analytics/range\";\nimport { keepPreviousData, useQuery } from \"@tanstack/react-query\";\nimport {\n ADDED_BY_STATE_POLL_MS,\n addedByStateKey,\n getAddedByState,\n} from \"../../api/added\";\nimport { JOB_STATES } from \"../../api/contract\";\nimport { Spinner } from \"../../components/Spinner\";\nimport { useApiClient } from \"../../context\";\nimport { formatNumber, STATE_LABELS } from \"../../format\";\nimport { usePollInterval } from \"../../live\";\n\n/** Props of {@link AddedByStateGroup}. */\nexport interface AddedByStateGroupProps {\n /** The range the tile is read over (the Jobs section's). */\n range: TimeRange;\n}\n\n/**\n * The \"Over the range\" tile's second group: of the jobs **added** in the\n * range and still stored, how many are in each state now\n * (`GET /overview/added`). Rendered only where `features.addedByState` is\n * true, and polled every {@link ADDED_BY_STATE_POLL_MS}, relaxed further\n * while live, since each read counts job records.\n */\nexport function AddedByStateGroup({ range }: AddedByStateGroupProps) {\n const api = useApiClient();\n const refetchInterval = usePollInterval(ADDED_BY_STATE_POLL_MS);\n const added = useQuery({\n queryKey: addedByStateKey(range),\n queryFn: ({ signal }) => getAddedByState(api, range, signal),\n refetchInterval,\n placeholderData: keepPreviousData,\n });\n return (\n <>\n <p className=\"range-group-title\">\n Added in range, where they are now{\" \"}\n <span className=\"muted\">(still stored)</span>\n </p>\n {added.data ? (\n <dl className=\"range-cells\">\n {JOB_STATES.map((state) => (\n <div\n key={state}\n className={`range-cell state-${state}`}\n >\n <dt>{STATE_LABELS[state]}</dt>\n <dd>{formatNumber(added.data.counts[state])}</dd>\n </div>\n ))}\n <div className=\"range-cell range-cell-total\">\n <dt>Total</dt>\n <dd>{formatNumber(added.data.total)}</dd>\n </div>\n </dl>\n ) : added.isError ? (\n <p className=\"range-note\">Could not load: {added.error.message}</p>\n ) : (\n <Spinner label=\"Loading jobs added in the range\" />\n )}\n <p className=\"range-note\">\n Counted by when jobs were added, not finished, so it will not match\n Finished in range; queues that remove finished jobs show few completed.\n </p>\n </>\n );\n}\n"
|
|
7
|
+
],
|
|
8
|
+
"mappings": "yDAeO,IAAM,EAAyB,MAW/B,SAAS,CAAmB,CACjC,EACA,EAAM,KAAK,IAAI,EAMf,CACA,OAAO,GAAY,EAAO,CAAG,EAUxB,SAAS,CAAe,CAAC,EAAkB,CAChD,MAAO,CACL,eACA,EAAM,OAAS,SACX,CAAE,QAAS,EAAM,OAAQ,EACzB,CAAE,KAAM,EAAM,KAAM,GAAI,EAAM,EAAG,CACvC,EAIK,SAAS,CAAe,CAC7B,EACA,EACA,EAC0B,CAC1B,IAAQ,OAAM,MAAO,EAAoB,CAAK,EAC9C,OAAO,EAAI,QAAQ,MAAO,kBAAmB,CAC3C,MAAO,CAAE,OAAM,IAAG,EAClB,QACF,CAAC,ECtCI,SAAS,CAAiB,EAAG,SAAiC,CACnE,IAAM,EAAM,EAAa,EACnB,EAAkB,EAAgB,CAAsB,EACxD,EAAQ,EAAS,CACrB,SAAU,EAAgB,CAAK,EAC/B,QAAS,EAAG,YAAa,EAAgB,EAAK,EAAO,CAAM,EAC3D,kBACA,gBAAiB,CACnB,CAAC,EACD,OACE,cA8BE,CA7BA,EAAC,IAAD,CAAG,UAAU,oBAAb,SAGE,CAHF,qCACqC,IACnC,EAAC,OAAD,CAAM,UAAU,QAAhB,0BAAwC,GACxC,EACD,EAAM,KACL,EAAC,KAAD,CAAI,UAAU,cAAd,SAcE,CAbC,EAAW,IAAI,CAAC,IACf,EAAC,MAAD,CAEE,UAAW,oBAAoB,IAFjC,SAME,CAFA,EAAC,KAAD,UAAK,EAAa,GAAS,EAC3B,EAAC,KAAD,UAAK,EAAa,EAAM,KAAK,OAAO,EAAM,EAAI,IAJzC,CAKL,CACH,EACD,EAAC,MAAD,CAAK,UAAU,8BAAf,SAGE,CAFA,EAAC,KAAD,kBAAW,EACX,EAAC,KAAD,UAAK,EAAa,EAAM,KAAK,KAAK,EAAI,GACtC,GACF,EACA,EAAM,QACR,EAAC,IAAD,CAAG,UAAU,aAAb,SAAiE,CAAjE,mBAA2C,EAAM,MAAM,SAAU,EAEjE,EAAC,EAAD,CAAS,MAAM,kCAAkC,EAEnD,EAAC,IAAD,CAAG,UAAU,aAAb,uJAGE,GACF",
|
|
9
|
+
"debugId": "639A51B3BD37E91E64756E2164756E21",
|
|
10
|
+
"names": []
|
|
11
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
function l(e){if(typeof File<"u"&&e instanceof File&&e.name)return e;return new File([e],"blob",{type:e.type})}function F(e){let t={};for(let[i,n]of Object.entries(e)){if(n===void 0||n===null)continue;t[i]=Array.isArray(n)?n.filter((r)=>r!=null).map(l):l(n)}return t}function v(e,t){let i=new FormData;for(let[n,r]of e)i.append(n,r);for(let[n,r]of Object.entries(t??{}))for(let o of Array.isArray(r)?r:[r])i.append(n,o,o.name);return i}async function*f(e){let t=e.body?.getReader();if(t===void 0)return;let i=!1;try{for(;;){let n=await t.read();if(n.done){i=!0;return}yield n.value}}finally{if(!i)await t.cancel().catch(()=>{return});t.releaseLock()}}function y(e){return f(e)}function p(e,t,i,n,r){return t==="raw"?y(e):m(e,i,(o,d)=>new r(`${n} answered ${e.status} with line ${o} that is not JSON`,{key:n,cause:d}))}async function*m(e,t,i){let n=new TextDecoder,r="",o=0,d=async(a)=>{let s=a.endsWith("\r")?a.slice(0,-1):a;if(o++,s.trim()==="")return;let u;try{u=JSON.parse(s)}catch(c){throw i(o,c)}return{value:t===void 0?u:await t(u)}};for await(let a of f(e)){r+=n.decode(a,{stream:!0});let s=r.indexOf(`
|
|
2
|
+
`);while(s!==-1){let u=await d(r.slice(0,s));if(r=r.slice(s+1),u!==void 0)yield u.value;s=r.indexOf(`
|
|
3
|
+
`)}}if(r+=n.decode(),r!==""){let a=await d(r);if(a!==void 0)yield a.value}}function w(e){if(!e)return;let t=/filename\*\s*=[^']*'[^']*'([^;]+)/i.exec(e);if(t)try{return decodeURIComponent(t[1].trim())}catch{}let i=/filename\s*=\s*(?:"((?:[^"\\]|\\.)*)"|([^;]*))/i.exec(e);return(i?.[1]?.replace(/\\(.)/g,"$1")??i?.[2])?.trim()||void 0}function g(e){return/^application\/(?:x-)?(?:nd-?json|jsonl|jsonlines)\b/i.test(e)}async function x(e,t,i,n,r,o){let d=i??(g(e.headers.get("content-type")??"")?"ndjson":void 0);return d===void 0?b(e,t):{value:p(e,d,n,r,o),unread:!0}}async function b(e,t){let i=w(e.headers.get("content-disposition")),n=i===void 0?{}:{filename:i};if(t==="stream")return{value:e.body??new Blob([]).stream(),unread:!0,...n};if(t==="arrayBuffer")return{value:await e.arrayBuffer(),...n};let r=await e.blob();return{value:i===void 0||typeof File>"u"?r:new File([r],i,{type:r.type}),...n}}export{l as asFile,b as download,w as filenameOf,v as formDataOf,m as ndjsonValues,F as normaliseFiles,y as rawChunks,x as read,p as streamed};
|
|
4
|
+
|
|
5
|
+
//# debugId=2635FA1DCBB0C3D264756E2164756E21
|
|
6
|
+
//# sourceMappingURL=bodies-yyw5hmrp.js.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../bun-client/lib/bodies.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"/**\n * Bodies beyond JSON (typed-client plan, `other-transports.md` §3, §4,\n * PR-C9): a multipart body built from typed fields and files, a download\n * delivered as the call's `as` asks, and a streamed response read as an\n * `AsyncIterable`, NDJSON line by line or raw chunk by chunk.\n *\n * **Loaded lazily**, as `./validation` is: `client.ts` imports this module\n * with `import()` only when a call sends files or a multipart body, or a\n * response is a download or streams, so a JSON-only app loads none of it\n * and a splitting bundler puts it in its own chunk\n * (`__tests__/budget.test.ts`).\n *\n * Nothing here imports a Bun or Node API: it is browser code.\n */\n/** A file as a call gives it: one, or several for one field. */\nexport type FilesInput = Readonly<Record<string, Blob | readonly Blob[]>>;\n\n/**\n * A `Blob` as a `File` with a name: a `File` with a name as it is, anything\n * else a `File` of the same bytes and type named `\"blob\"`. So a check sees a\n * `File` (zod's `z.file()`), and the part goes out with a file name: Bun\n * serialises a nameless `Blob` with `filename=\"\"` where the spec says\n * `\"blob\"`, and busboy then reads a typed one as a text field\n * (oven-sh/bun#35439, still present on 1.4.4-canary.1+11c367163;\n * `docs/bun-bugs/formdata-blob-empty-filename.md`). Remove the renaming\n * once a stable Bun names it.\n */\nexport function asFile(blob: Blob): File {\n if (typeof File !== \"undefined\" && blob instanceof File && blob.name)\n return blob;\n return new File([blob], \"blob\", { type: blob.type });\n}\n\n/** A call's `files` with every `Blob` a named `File` ({@link asFile}); `undefined` and `null` entries dropped. */\nexport function normaliseFiles(\n files: FilesInput,\n): Record<string, File | File[]> {\n const out: Record<string, File | File[]> = {};\n for (const [name, value] of Object.entries(files)) {\n if (value === undefined || value === null) continue;\n out[name] = Array.isArray(value)\n ? (value as readonly Blob[]).filter((file) => file != null).map(asFile)\n : asFile(value as Blob);\n }\n return out;\n}\n\n/**\n * A multipart body: the fields as bun-common's parser reads them back (the\n * query serialiser's pairs, handed over by the client so this chunk shares\n * no module with the entry: nested objects with dots, arrays of scalars\n * repeated), then each file under its field, a list repeated.\n *\n * @param fields The body's fields as `[name, value]` pairs.\n * @param files The files, already {@link normaliseFiles normalised}.\n */\nexport function formDataOf(\n fields: readonly (readonly [string, string])[],\n files: Readonly<Record<string, File | readonly File[]>> | undefined,\n): FormData {\n const form = new FormData();\n for (const [name, value] of fields) form.append(name, value);\n for (const [name, value] of Object.entries(files ?? {})) {\n for (const file of Array.isArray(value) ? value : [value as File])\n form.append(name, file, file.name);\n }\n return form;\n}\n\n/** Reads a response body chunk by chunk; the reader is released (and the body cancelled) when iteration stops early. */\nasync function* chunksOf(\n response: Response,\n): AsyncGenerator<Uint8Array, void, undefined> {\n const reader = response.body?.getReader();\n if (reader === undefined) return;\n let done = false;\n try {\n for (;;) {\n const next = await reader.read();\n if (next.done) {\n done = true;\n return;\n }\n yield next.value;\n }\n } finally {\n if (!done) await reader.cancel().catch(() => undefined);\n reader.releaseLock();\n }\n}\n\n/**\n * A raw streamed body (`chunks()` with `framing: \"raw\"`): its bytes as they\n * arrive, one `Uint8Array` per chunk the platform delivers. Nothing is\n * checked (as on the server, a raw chunk's schema sees bytes).\n */\nexport function rawChunks(response: Response): AsyncIterable<Uint8Array> {\n return chunksOf(response);\n}\n\n/**\n * A streamed body as the call's `data` (`other-transports.md` §4): raw\n * chunks, or NDJSON values checked line by line, a line that is not JSON\n * ending the iteration with a `ClientError` naming it.\n *\n * @param response The response, its body unread.\n * @param framing How the body is cut.\n * @param check Each NDJSON line's check; `undefined` for none.\n * @param key The route key, for the error.\n * @param ClientError The client's error class, handed over so this chunk\n * shares no module with the entry.\n */\nexport function streamed(\n response: Response,\n framing: \"ndjson\" | \"raw\",\n check: LineCheck | undefined,\n key: string,\n ClientError: ClientErrorClass,\n): AsyncIterable<unknown> {\n return framing === \"raw\"\n ? rawChunks(response)\n : ndjsonValues(\n response,\n check,\n (line, cause) =>\n new ClientError(\n `${key} answered ${response.status} with line ${line} that is not JSON`,\n { key, cause },\n ),\n );\n}\n\n/** What {@link ndjsonValues} checks each line with: the value to deliver, or a throw. */\nexport type LineCheck = (value: unknown) => Promise<unknown>;\n\n/**\n * An NDJSON body (`chunks()` with `framing: \"ndjson\"`), one value per line,\n * as bun-common frames it: lines split across chunk boundaries, a trailing\n * `\\r` dropped, blank lines skipped, the last line checked when the stream\n * ends without its `\\n`. Each line is parsed and handed to `check` (the\n * status's response checks, under the call's failure mode), and what it\n * returns is yielded. A line that is not JSON ends the iteration with a\n * `ClientError`.\n *\n * @param response The response, its body unread.\n * @param check Each line's check; `undefined` delivers lines unchecked.\n * @param fail Builds the error for a line that is not JSON (`line` from 1).\n */\nexport async function* ndjsonValues(\n response: Response,\n check: LineCheck | undefined,\n fail: (line: number, cause: unknown) => Error,\n): AsyncGenerator<unknown, void, undefined> {\n const decoder = new TextDecoder();\n let buffered = \"\";\n let line = 0;\n const parse = async (\n text: string,\n ): Promise<{ value: unknown } | undefined> => {\n const trimmed = text.endsWith(\"\\r\") ? text.slice(0, -1) : text;\n line++;\n if (trimmed.trim() === \"\") return undefined;\n let value: unknown;\n try {\n value = JSON.parse(trimmed);\n } catch (error) {\n throw fail(line, error);\n }\n return { value: check === undefined ? value : await check(value) };\n };\n for await (const chunk of chunksOf(response)) {\n buffered += decoder.decode(chunk, { stream: true });\n let end = buffered.indexOf(\"\\n\");\n while (end !== -1) {\n const parsed = await parse(buffered.slice(0, end));\n buffered = buffered.slice(end + 1);\n if (parsed !== undefined) yield parsed.value;\n end = buffered.indexOf(\"\\n\");\n }\n }\n buffered += decoder.decode();\n if (buffered !== \"\") {\n const parsed = await parse(buffered);\n if (parsed !== undefined) yield parsed.value;\n }\n}\n\n/**\n * The file name a `Content-Disposition` header gives: `filename*` (RFC\n * 5987, percent-decoded) first, then `filename`, quoted or not; `undefined`\n * for none.\n */\nexport function filenameOf(header: string | null): string | undefined {\n if (!header) return undefined;\n const star = /filename\\*\\s*=[^']*'[^']*'([^;]+)/i.exec(header);\n if (star) {\n try {\n return decodeURIComponent(star[1].trim());\n } catch {\n // A malformed encoding: fall back to the plain parameter.\n }\n }\n const plain = /filename\\s*=\\s*(?:\"((?:[^\"\\\\]|\\\\.)*)\"|([^;]*))/i.exec(header);\n const name = (plain?.[1]?.replace(/\\\\(.)/g, \"$1\") ?? plain?.[2])?.trim();\n return name || undefined;\n}\n\n/** A body read: its value, whether it is left unread, and a download's file name. */\nexport interface ReadBody {\n /** What `data` or `error` holds. */\n readonly value: unknown;\n /** The body is left unread (a stream to iterate or read): no whole-body check runs on it. */\n readonly unread?: boolean;\n /** The download's file name, from `Content-Disposition`. */\n readonly filename?: string;\n}\n\n/** An NDJSON media type: `application/x-ndjson`, `application/ndjson`, `application/jsonl`, `application/jsonlines`. */\nfunction isNdjsonType(type: string): boolean {\n return /^application\\/(?:x-)?(?:nd-?json|jsonl|jsonlines)\\b/i.test(type);\n}\n\n/**\n * A body the client does not read as JSON or text: a streamed one\n * (`framing`, from the manifest's `chunks`, or an NDJSON media type) as an\n * `AsyncIterable` ({@link streamed}), anything else as a {@link download}.\n *\n * @param response The response, its body unread.\n * @param as The call's `as`.\n * @param framing The status's framing, when the manifest declares one.\n * @param check Each NDJSON line's check; `undefined` for none.\n * @param key The route key, for errors.\n * @param ClientError The client's error class (see {@link streamed}).\n */\nexport async function read(\n response: Response,\n as: \"blob\" | \"stream\" | \"arrayBuffer\" | undefined,\n framing: \"ndjson\" | \"raw\" | undefined,\n check: LineCheck | undefined,\n key: string,\n ClientError: ClientErrorClass,\n): Promise<ReadBody> {\n const cut =\n framing ??\n (isNdjsonType(response.headers.get(\"content-type\") ?? \"\")\n ? \"ndjson\"\n : undefined);\n return cut === undefined\n ? download(response, as)\n : {\n value: streamed(response, cut, check, key, ClientError),\n unread: true,\n };\n}\n\n/** The client's `ClientError` constructor, as this module calls it. */\ntype ClientErrorClass = new (\n message: string,\n options: { key: string; cause: unknown },\n) => Error;\n\n/**\n * A download (a body neither JSON nor text), delivered as `as` asks\n * (`other-transports.md` §4): a `Blob` (a `File` carrying the\n * `Content-Disposition` file name, when the header gives one), the unread\n * stream, or an `ArrayBuffer`.\n */\nexport async function download(\n response: Response,\n as: \"blob\" | \"stream\" | \"arrayBuffer\" | undefined,\n): Promise<ReadBody> {\n const filename = filenameOf(response.headers.get(\"content-disposition\"));\n const named = filename === undefined ? {} : { filename };\n if (as === \"stream\")\n return {\n value: response.body ?? new Blob([]).stream(),\n unread: true,\n ...named,\n };\n if (as === \"arrayBuffer\")\n return { value: await response.arrayBuffer(), ...named };\n const blob = await response.blob();\n return {\n value:\n filename === undefined || typeof File === \"undefined\"\n ? blob\n : new File([blob], filename, { type: blob.type }),\n ...named,\n };\n}\n"
|
|
6
|
+
],
|
|
7
|
+
"mappings": "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",
|
|
8
|
+
"debugId": "2635FA1DCBB0C3D264756E2164756E21",
|
|
9
|
+
"names": []
|
|
10
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import{j,Qe,jn,Cr,Lr,Qt}from"./main-jxre7w93.js";import{G}from"./main-a250ffk7.js";var k=Symbol.for("@kingsleyweb/bun-client"),v=["VALIDATION","UNKNOWN_MESSAGE","UNSUPPORTED_DATA"],S=[...v,"EXCEPTION"],T=[1007,1008,[4400,4499]],P={encode:({type:e,id:t,data:n},{idField:r})=>JSON.stringify(t===void 0?{type:e,data:n}:{type:e,[r]:t,data:n}),decode(e,{idField:t}){let n=JSON.parse(typeof e==="string"?e:new TextDecoder().decode(e));if(typeof n?.type!=="string")return;let r=n[t]??(n.type==="error"?n.id:void 0);if(n.type==="error"&&"code"in n)return{...n,id:typeof r==="string"?r:void 0};return typeof r==="string"?{type:n.type,id:r,data:n.data}:{type:n.type,data:n.data}}},O=(e)=>ArrayBuffer.isView(e)||e instanceof ArrayBuffer;function x(e,t){return e.map((n,r)=>typeof n==="string"&&(!Array.isArray(t)||t.includes(r))?Uint8Array.from(atob(n),(a)=>a.charCodeAt(0)):n)}var g=(e)=>e.length>1?e:e[0];function R(e){return{encode:({type:t,id:n,data:r})=>{let a=O(r),i=r;if(a){let d=ArrayBuffer.isView(r)?new Uint8Array(r.buffer,r.byteOffset,r.byteLength):new Uint8Array(r),s="";for(let o of d)s+=String.fromCharCode(o);i=btoa(s)}return JSON.stringify({type:a?5:2,namespace:"/",...n===void 0?{}:{id:/^\d+$/.test(n)?Number(n):n},data:r===void 0?[t]:[t,i],...a?{binary:[0]}:{}})},decode(t){let n=JSON.parse(typeof t==="string"?t:new TextDecoder().decode(t)),r=n?.type;if(r===4)return{type:"error",data:n.data};if(!Array.isArray(n?.data))return;let a=r===5||r===6?x(r===5?n.data.slice(1):n.data,n.binary):r===2?n.data.slice(1):n.data;if(r===3||r===6){let s=typeof n.id==="number"||typeof n.id==="string"?String(n.id):void 0,o=s===void 0?void 0:e(s);return o===void 0?void 0:{type:o,id:s,data:g(a)}}let i=n.data[0];if(r!==2&&r!==5||typeof i!=="string")return;let d=g(a);if(i==="exception"&&d!==void 0){let{ackId:s,...o}=d,l=typeof s==="number"||typeof s==="string"?String(s):void 0;if(typeof o.status==="number"&&v.includes(o.code))return{...o,type:"error",...l===void 0?{}:{id:l}};if(l!==void 0&&e(l)!==void 0)return{type:"error",id:l,code:"EXCEPTION",status:typeof o.status==="number"?o.status:500,...typeof o.message==="string"?{detail:o.message}:{}}}return{type:i,data:d}}}}var A,p=new Map;function M(e){if(e.mode!==void 0)return e.mode==="development";try{return!1}catch{return!0}}function m(e){return typeof e==="object"&&Object.entries(e).some(([t,n])=>t!=="onFailure"&&n!==!1&&n!==void 0)}var C=globalThis.requestAnimationFrame;function F(){return new Promise((e)=>typeof C==="function"?C(()=>e()):setTimeout(e,16))}class b{key;url;facts;options;logger;ws;state="connecting";handles=new Set;pending=new Map;resend=[];buffer=[];#d=0;#t=0;#e={reconnect:void 0,stable:void 0,watchdog:void 0,ping:void 0};codec;envelope;constructor(e,t,n,r,a){this.key=e;this.url=t;this.facts=n;this.options=r;this.logger=a;let i=r.codec??n.codec;this.codec=i==="bun-nest"?R((d)=>this.pending.get(d)?.reply):i===void 0||i==="bun-common"?P:i,this.envelope={idField:n.idField},this.#r()}#r(){let e=new WebSocket(this.url,this.facts.subprotocol===void 0?[]:[this.facts.subprotocol]);e.binaryType="arraybuffer",this.ws=e,e.onopen=()=>void this.#i(e),e.onmessage=(t)=>this.#o(e,t.data),e.onclose=(t)=>this.#a(e,t.code,t.reason)}#l(e){if(this.state===e)return;this.state=e;for(let t of this.handles)t.emit("$state",e)}async#i(e){if(e!==this.ws)return;this.#l("open"),this.#s();let t=this.options.reconnect===!1?0:this.options.reconnect?.stableAfterMs??60000;this.#e.stable=setTimeout(()=>{this.#t=0},t);for(let n of[...this.handles])try{await n.options.resubscribe?.(n.client)}catch(r){n.logger.error(`${this.facts.path}: resubscribe threw`,r)}if(e!==this.ws||e.readyState!==1)return;for(let n of this.resend.splice(0))this.pending.set(n.id,n),e.send(n.payload);for(let n of this.buffer.splice(0))e.send(n)}#s(){let{heartbeat:e,ping:t}=this.options;if(clearTimeout(this.#e.watchdog),clearTimeout(this.#e.ping),e!==void 0)this.#e.watchdog=setTimeout(()=>this.#n(),e.intervalMs*2+(e.slackMs??5000));if(t!==void 0){let n=()=>{if(this.ws?.readyState===1)this.ws.send(this.codec.encode({type:t.type},this.envelope));this.#e.ping=setTimeout(n,t.intervalMs)};this.#e.ping=setTimeout(n,t.intervalMs)}}#n(){let e=this.ws;if(e===void 0)return;e.onclose=null,e.close(4000,"heartbeat lost"),this.#a(e,4000,"heartbeat lost")}#o(e,t){if(e!==this.ws)return;this.#s();let n;try{n=this.codec.decode(t,this.envelope)}catch(i){this.logger.warn(`${this.facts.path}: a frame could not be decoded`,i);return}if(n===void 0)return;let r=typeof n.id==="string"?n.id:void 0,a=r===void 0?void 0:this.pending.get(r);if(n.type==="error"&&"code"in n){let{code:i,status:d,issues:s,detail:o}=n;if(!S.includes(i)||typeof d!=="number"||s!==void 0&&!Array.isArray(s)||o!==void 0&&typeof o!=="string"){this.logger.warn(`${this.facts.path}: an error frame does not have the error frame's shape; ignored`);return}if(a!==void 0){this.pending.delete(r),a.reject(new jn(this.facts.path,n,a.type));return}let l=new jn(this.facts.path,n);for(let c of this.handles)if(!c.emit("$error",l))c.logger.warn(l.message);return}if(a!==void 0&&a.reply===n.type){this.pending.delete(r),a.handle.reply(a,n.data);return}for(let i of this.handles)i.push(n)}#a(e,t,n){if(e!==this.ws)return;this.ws=void 0;for(let s of Object.values(this.#e))clearTimeout(s);for(let[s,o]of this.pending)if(this.pending.delete(s),o.retry)this.resend.push(o);else o.reject(new Lr(this.facts.path,t,n));if(this.state==="closed")return;let r=(this.options.fatalCloseCodes??T).some((s)=>typeof s==="number"?s===t:t>=s[0]&&t<=s[1]);if(this.options.reconnect===!1||r){this.end(new Qt(this.facts.path,n||"the server closed the channel",t));return}let{initialMs:a=500,maxMs:i=30000}=this.options.reconnect??{},d=Math.min(i,a*2**this.#t++);this.#l(this.state==="connecting"?"connecting":"reconnecting"),this.#e.reconnect=setTimeout(()=>this.#r(),d/2+d/2*Math.random())}end(e){p.delete(this.key);for(let r of Object.values(this.#e))clearTimeout(r);let t=e??new Qt(this.facts.path,"the channel is closed");for(let r of[...this.pending.values(),...this.resend])r.reject(t);this.buffer.length=0;let n=this.ws;this.ws=void 0,n?.close(1000);for(let r of[...this.handles])r.ended(t,e!==void 0);this.state="closed"}write(e){if(this.ws?.readyState===1){this.ws.send(e);return}let t=this.options.offline??{buffer:100};if(t==="fail"||this.buffer.length>=t.buffer)throw new Qt(this.facts.path,t==="fail"?'disconnected (offline: "fail")':`the offline buffer is full (${t.buffer} messages)`);this.buffer.push(e)}request(e,t,n,r,a,i){let d=this.facts.replies[t];if(d===void 0)throw new j(`${this.facts.path}: "${t}" has no reply declared (replies)`,{key:this.facts.path});let s=String(++this.#d),o=r.timeout??e.timeout,l,c={id:s,handle:e,type:t,reply:d,payload:this.codec.encode({type:t,id:s,data:n},this.envelope),retry:r.retry===!0,resolve:a,reject:i},h=(u)=>(E)=>{clearTimeout(l),this.pending.delete(s);let y=this.resend.indexOf(c);if(y!==-1)this.resend.splice(y,1);e.requests.delete(c),u(E)};if(c.resolve=h(a),c.reject=h(i),this.write(c.payload),this.pending.set(s,c),e.requests.add(c),o!==!1)l=setTimeout(()=>c.reject(new Cr(this.facts.path,t,s,o)),o)}}var f=Symbol("dropped");class w{conn;facts;options;config;client;#d=new Map;#t=new Set;requests=new Set;#e;#r;#l=Promise.resolve();#i=0;#s=Promise.resolve();#n;timeout;logger;constructor(e,t,n,r){this.conn=e;this.facts=t;this.options=n;this.config=r;this.timeout=r.timeout??30000,this.logger=r.logger??console;let a=M(r),i=t.contract!==void 0||r.schemas!==void 0;if(r.validate!==!1&&n.validate!==!1&&(i&&(a||m(r.validate))||m(n.validate))){let s=r.schemas;this.#r=(A??=(G("s4yz4yhw"),import("./channelChecks-s4yz4yhw.js"))).then((o)=>o.createChannelChecks({key:t.path,development:a,levels:[r.validate,n.validate],contract:t.contract?.contract,meta:t,schemas:t.contract!==void 0||s===void 0?void 0:async()=>(typeof s==="function"?await s():s).channelSchemas?.[t.path],ValidationError:Qe})).then((o)=>this.#e=o,(o)=>{this.logger.error(`${t.path}: the channel's checks failed to load; nothing is checked`,o);return})}let d=()=>this.#n===void 0?e.state:"closed";this.client={send:(s,o,l)=>{this.#o(),this.#a(s,o,l?.validate,(c)=>e.write(e.codec.encode({type:s,data:c},e.envelope)))?.catch((c)=>this.#c(c))},request:(s,o,l={})=>new Promise((c,h)=>{this.#o(),this.#a(s,o,l.validate,(u)=>e.request(this,s,u,l,c,h))?.catch(h)}),sendWhenReady:async(s,o,l={})=>{while(this.#o()&&(e.ws?.bufferedAmount??0)>=(l.highWaterMark??1048576))await F();await this.#a(s,o,l.validate,(c)=>e.write(e.codec.encode({type:s,data:c},e.envelope)))},on:(s,o)=>{let l=this.#d.get(s);if(l===void 0)this.#d.set(s,l=new Set);return l.add(o),()=>void l.delete(o)},messages:(s)=>this.#f(s),close:()=>this.#p(),get state(){return d()},get bufferedAmount(){return e.ws?.bufferedAmount??0}}}#o(){if(this.#n!==void 0)throw this.#n;return!0}#a(e,t,n,r){if(this.#r===void 0){r(t);return}let a=this.#e;if(a!==void 0&&this.#i===0){let d=a.send(e,t,n);if(!(d instanceof Promise)){r(this.#h(a,d));return}}this.#i++;let i=this.#l.then(async()=>{let d=await this.#r;if(this.#o(),d===void 0)return r(t);r(this.#h(d,await d.send(e,t,n)))});return this.#l=i.then(()=>void this.#i--,()=>void this.#i--),i}#h(e,t){if(t.error!==void 0){let n=e.onSend;if(n==="throw")throw t.error;if(n==="warn")this.logger.warn(t.error.message);else n(t.error)}return t.value}async#u(e,t,n){let r=this.#e??await this.#r;if(r===void 0)return t;let a=await r.receive(e,t),i=a.error;if(i===void 0)return a.value;let d=r.onReceive;if(d==="warn")return this.logger.warn(i.message),a.value;if(d==="drop")return this.emit("$invalid",i),n?.(i),f;try{if(d==="throw")throw i;return d(i),a.value}catch(s){if(n===void 0)this.#c(s);else n(s);return f}}reply(e,t){this.#s=this.#s.then(async()=>{let n=await this.#u(e.reply,t,e.reject);if(n!==f)e.resolve(n)})}push(e){if(this.#n!==void 0)return;this.#s=this.#s.then(async()=>{let t=await this.#u(e.type,e.data);if(t===f||this.#n!==void 0)return;let n=e.id===void 0?{type:e.type}:{type:e.type,id:e.id};this.emit(e.type,t,n);for(let r of this.#t)if(r.type===void 0)r.push({type:e.type,data:t});else if(r.type===e.type)r.push(t)})}emit(e,...t){let n=this.#d.get(e);for(let r of n??[])try{r(...t)}catch(a){this.logger.error(`${this.facts.path}: a "${e}" listener threw`,a)}return n!==void 0&&n.size>0}#c(e){if(!this.emit("$error",e))this.logger.warn(e.message??e)}#f(e){let t=[],n=this.#n!==void 0,r,a={type:e,push:(d)=>{t.push(d),r?.()},end:()=>{n=!0,r?.()}};if(!n)this.#t.add(a);let i={[Symbol.asyncIterator]:()=>i,next:async()=>{for(;;){if(t.length>0)return{value:t.shift(),done:!1};if(n)return{value:void 0,done:!0};await new Promise((d)=>r=d),r=void 0}},return:()=>(this.#t.delete(a),a.end(),t.length=0,Promise.resolve({value:void 0,done:!0}))};return i}#p(){if(this.#n!==void 0)return;if(this.ended(new Qt(this.facts.path,"the channel is closed"),!1),this.conn.handles.size===0)this.conn.end()}ended(e,t){if(this.#n!==void 0)return;this.#n=e,this.conn.handles.delete(this);for(let n of[...this.requests])n.reject(e);for(let n of this.#t)n.end();if(this.#t.clear(),t)this.#c(e);this.emit("$state","closed")}}function q(e,t,n){let r=n??{},a=e[k]??{},i;if("spec"in t){let{meta:c,spec:h}=t;for(let u of[...Object.keys(h.receives),...Object.keys(h.sends)])if(u.startsWith("$"))throw TypeError(`channel(): the message type "${u}" starts with "$", which the client's events use`);i={path:c.path,idField:c.envelope.idField,replies:c.replies,subprotocol:c.subprotocol,contract:t,codec:void 0}}else i={path:t.path,idField:t.idField??"id",replies:t.replies??{},subprotocol:t.subprotocol,contract:void 0,codec:t.codec};let d=e.url(i.path,{params:r.params,query:r.query});if(!/^[a-z][a-z\d+.-]*:/i.test(d)){let c=globalThis.location?.href;if(!c)throw new j(`${i.path}: "${d}" is relative, and there is no page to resolve it against; give the client an absolute baseUrl`,{key:i.path});d=new URL(d,c).href}d=d.replace(/^http(s?):/i,"ws$1:");let s=`${d} ${i.subprotocol??""}`,o=p.get(s);if(o===void 0)o=new b(s,d,i,r,a.logger??console),p.set(s,o);let l=new w(o,i,r,a);if(o.handles.add(l),o.state==="open")(async()=>r.resubscribe?.(l.client))().catch((c)=>l.logger.error(`${i.path}: resubscribe threw`,c));return l.client}export{q as channel};
|
|
2
|
+
|
|
3
|
+
//# debugId=8BB5707F752ACABE64756E2164756E21
|
|
4
|
+
//# sourceMappingURL=channel-tgkvqm8y.js.map
|