decantr 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +868 -0
- package/CHANGELOG.md +255 -0
- package/CLAUDE.md +178 -0
- package/LICENSE +21 -0
- package/README.md +229 -0
- package/cli/art.js +127 -0
- package/cli/commands/a11y.js +61 -0
- package/cli/commands/audit.js +225 -0
- package/cli/commands/build.js +38 -0
- package/cli/commands/dev.js +18 -0
- package/cli/commands/doctor.js +197 -0
- package/cli/commands/figma-sync.js +48 -0
- package/cli/commands/figma-tokens.js +55 -0
- package/cli/commands/generate.js +26 -0
- package/cli/commands/init.js +116 -0
- package/cli/commands/lint.js +209 -0
- package/cli/commands/mcp.js +530 -0
- package/cli/commands/migrate.js +175 -0
- package/cli/commands/test.js +38 -0
- package/cli/commands/validate.js +354 -0
- package/cli/index.js +113 -0
- package/package.json +95 -0
- package/reference/atoms.md +517 -0
- package/reference/behaviors.md +384 -0
- package/reference/build-tooling.md +275 -0
- package/reference/color-guidelines.md +965 -0
- package/reference/component-lifecycle.md +137 -0
- package/reference/compound-spacing.md +95 -0
- package/reference/decantation-process.md +499 -0
- package/reference/dev-server-routes.md +93 -0
- package/reference/form-system.md +253 -0
- package/reference/i18n.md +336 -0
- package/reference/icons.md +576 -0
- package/reference/llm-primer.md +953 -0
- package/reference/plugins.md +252 -0
- package/reference/registry-consumption.md +76 -0
- package/reference/router.md +217 -0
- package/reference/shells.md +116 -0
- package/reference/spatial-guidelines.md +541 -0
- package/reference/ssr.md +234 -0
- package/reference/state-data.md +215 -0
- package/reference/state-patterns.md +166 -0
- package/reference/state.md +194 -0
- package/reference/style-system.md +110 -0
- package/reference/tokens.md +460 -0
- package/src/app.js +19 -0
- package/src/chart/_animate.js +266 -0
- package/src/chart/_base.js +109 -0
- package/src/chart/_data.js +209 -0
- package/src/chart/_format.js +106 -0
- package/src/chart/_interact.js +364 -0
- package/src/chart/_palette.js +105 -0
- package/src/chart/_renderer.js +52 -0
- package/src/chart/_scene.js +262 -0
- package/src/chart/_shared.js +371 -0
- package/src/chart/index.js +637 -0
- package/src/chart/layouts/_layout-base.js +328 -0
- package/src/chart/layouts/cartesian.js +148 -0
- package/src/chart/layouts/hierarchy.js +562 -0
- package/src/chart/layouts/polar.js +101 -0
- package/src/chart/renderers/canvas.js +179 -0
- package/src/chart/renderers/svg.js +256 -0
- package/src/chart/renderers/webgpu.js +715 -0
- package/src/chart/types/_type-base.js +26 -0
- package/src/chart/types/area.js +134 -0
- package/src/chart/types/bar.js +173 -0
- package/src/chart/types/box-plot.js +125 -0
- package/src/chart/types/bubble.js +63 -0
- package/src/chart/types/candlestick.js +115 -0
- package/src/chart/types/chord.js +85 -0
- package/src/chart/types/combination.js +108 -0
- package/src/chart/types/funnel.js +68 -0
- package/src/chart/types/gauge.js +163 -0
- package/src/chart/types/heatmap.js +98 -0
- package/src/chart/types/histogram.js +71 -0
- package/src/chart/types/line.js +111 -0
- package/src/chart/types/org-chart.js +93 -0
- package/src/chart/types/pie.js +81 -0
- package/src/chart/types/radar.js +96 -0
- package/src/chart/types/radial.js +68 -0
- package/src/chart/types/range-area.js +55 -0
- package/src/chart/types/range-bar.js +61 -0
- package/src/chart/types/sankey.js +73 -0
- package/src/chart/types/scatter.js +66 -0
- package/src/chart/types/sparkline.js +81 -0
- package/src/chart/types/sunburst.js +69 -0
- package/src/chart/types/swimlane.js +88 -0
- package/src/chart/types/treemap.js +62 -0
- package/src/chart/types/waterfall.js +100 -0
- package/src/components/_base.js +1658 -0
- package/src/components/_behaviors.js +1140 -0
- package/src/components/_primitives.js +534 -0
- package/src/components/_qr-encoder.js +539 -0
- package/src/components/accordion.js +207 -0
- package/src/components/affix.js +62 -0
- package/src/components/alert-dialog.js +75 -0
- package/src/components/alert.js +47 -0
- package/src/components/aspect-ratio.js +24 -0
- package/src/components/avatar-group.js +55 -0
- package/src/components/avatar.js +38 -0
- package/src/components/back-top.js +75 -0
- package/src/components/badge.js +74 -0
- package/src/components/banner.js +68 -0
- package/src/components/breadcrumb.js +162 -0
- package/src/components/button.js +115 -0
- package/src/components/calendar.js +131 -0
- package/src/components/card.js +192 -0
- package/src/components/carousel.js +98 -0
- package/src/components/cascader.js +261 -0
- package/src/components/checkbox.js +80 -0
- package/src/components/chip.js +81 -0
- package/src/components/code-block.js +82 -0
- package/src/components/collapsible.js +50 -0
- package/src/components/color-palette.js +438 -0
- package/src/components/color-picker.js +314 -0
- package/src/components/combobox.js +181 -0
- package/src/components/command.js +174 -0
- package/src/components/comment.js +206 -0
- package/src/components/context-menu.js +76 -0
- package/src/components/data-table.js +724 -0
- package/src/components/date-picker.js +217 -0
- package/src/components/date-range-picker.js +244 -0
- package/src/components/datetime-picker.js +271 -0
- package/src/components/descriptions.js +68 -0
- package/src/components/drawer.js +179 -0
- package/src/components/dropdown.js +88 -0
- package/src/components/empty.js +41 -0
- package/src/components/float-button.js +90 -0
- package/src/components/form.js +106 -0
- package/src/components/hover-card.js +49 -0
- package/src/components/icon.js +87 -0
- package/src/components/image.js +97 -0
- package/src/components/index.js +117 -0
- package/src/components/input-group.js +75 -0
- package/src/components/input-number.js +155 -0
- package/src/components/input-otp.js +178 -0
- package/src/components/input.js +91 -0
- package/src/components/kbd.js +36 -0
- package/src/components/label.js +25 -0
- package/src/components/list.js +118 -0
- package/src/components/masked-input.js +236 -0
- package/src/components/mentions.js +165 -0
- package/src/components/menu.js +259 -0
- package/src/components/message.js +80 -0
- package/src/components/modal.js +147 -0
- package/src/components/navigation-menu.js +166 -0
- package/src/components/notification.js +84 -0
- package/src/components/pagination.js +104 -0
- package/src/components/placeholder.js +132 -0
- package/src/components/popconfirm.js +70 -0
- package/src/components/popover.js +58 -0
- package/src/components/progress.js +61 -0
- package/src/components/qrcode.js +251 -0
- package/src/components/radiogroup.js +120 -0
- package/src/components/range-slider.js +176 -0
- package/src/components/rate.js +186 -0
- package/src/components/resizable.js +83 -0
- package/src/components/result.js +57 -0
- package/src/components/scroll-area.js +43 -0
- package/src/components/segmented.js +97 -0
- package/src/components/select.js +165 -0
- package/src/components/separator.js +31 -0
- package/src/components/shell.js +407 -0
- package/src/components/skeleton.js +39 -0
- package/src/components/slider.js +141 -0
- package/src/components/sortable-list.js +176 -0
- package/src/components/space.js +42 -0
- package/src/components/spinner.js +112 -0
- package/src/components/splitter.js +147 -0
- package/src/components/statistic.js +136 -0
- package/src/components/steps.js +99 -0
- package/src/components/switch.js +95 -0
- package/src/components/table.js +44 -0
- package/src/components/tabs.js +216 -0
- package/src/components/tag.js +115 -0
- package/src/components/textarea.js +82 -0
- package/src/components/time-picker.js +153 -0
- package/src/components/time-range-picker.js +170 -0
- package/src/components/timeline.js +226 -0
- package/src/components/toast.js +71 -0
- package/src/components/toggle.js +213 -0
- package/src/components/tooltip.js +57 -0
- package/src/components/tour.js +159 -0
- package/src/components/transfer.js +163 -0
- package/src/components/tree-select.js +274 -0
- package/src/components/tree.js +141 -0
- package/src/components/typography.js +136 -0
- package/src/components/upload.js +118 -0
- package/src/components/visually-hidden.js +20 -0
- package/src/components/watermark.js +124 -0
- package/src/core/index.js +539 -0
- package/src/core/lifecycle.js +69 -0
- package/src/css/atoms.js +651 -0
- package/src/css/components.js +940 -0
- package/src/css/derive.js +1296 -0
- package/src/css/index.js +265 -0
- package/src/css/runtime.js +268 -0
- package/src/css/styles/addons/bioluminescent.js +93 -0
- package/src/css/styles/addons/clay.js +70 -0
- package/src/css/styles/addons/clean.js +57 -0
- package/src/css/styles/addons/command-center.js +143 -0
- package/src/css/styles/addons/dopamine.js +83 -0
- package/src/css/styles/addons/editorial.js +80 -0
- package/src/css/styles/addons/glassmorphism.js +99 -0
- package/src/css/styles/addons/liquid-glass.js +105 -0
- package/src/css/styles/addons/prismatic.js +100 -0
- package/src/css/styles/addons/retro.js +63 -0
- package/src/css/styles/auradecantism.js +96 -0
- package/src/css/theme-registry.js +444 -0
- package/src/data/entity.js +281 -0
- package/src/data/index.js +13 -0
- package/src/data/persist.js +225 -0
- package/src/data/query.js +839 -0
- package/src/data/realtime.js +299 -0
- package/src/data/url.js +177 -0
- package/src/data/worker.js +134 -0
- package/src/explorer/archetypes.js +243 -0
- package/src/explorer/atoms.js +228 -0
- package/src/explorer/charts.js +497 -0
- package/src/explorer/components.js +129 -0
- package/src/explorer/foundations.js +949 -0
- package/src/explorer/icons.js +178 -0
- package/src/explorer/patterns.js +247 -0
- package/src/explorer/recipes.js +194 -0
- package/src/explorer/shared/pattern-examples.js +1337 -0
- package/src/explorer/shared/showcase-renderer.js +958 -0
- package/src/explorer/shared/spec-table.js +41 -0
- package/src/explorer/shared/usage-links.js +87 -0
- package/src/explorer/shell-config.js +10 -0
- package/src/explorer/shells.js +551 -0
- package/src/explorer/styles.js +161 -0
- package/src/explorer/tokens.js +262 -0
- package/src/explorer/tools.js +525 -0
- package/src/form/index.js +804 -0
- package/src/i18n/index.js +251 -0
- package/src/icons/essential.js +479 -0
- package/src/icons/index.js +53 -0
- package/src/plugins/index.js +282 -0
- package/src/registry/archetypes/content-site.json +71 -0
- package/src/registry/archetypes/docs-explorer.json +23 -0
- package/src/registry/archetypes/ecommerce.json +104 -0
- package/src/registry/archetypes/financial-dashboard.json +77 -0
- package/src/registry/archetypes/index.json +41 -0
- package/src/registry/archetypes/portfolio.json +82 -0
- package/src/registry/archetypes/recipe-community.json +159 -0
- package/src/registry/archetypes/saas-dashboard.json +86 -0
- package/src/registry/architect/cross-cutting.json +45 -0
- package/src/registry/architect/domains/ecommerce.json +294 -0
- package/src/registry/architect/domains/financial-services.json +302 -0
- package/src/registry/architect/index.json +26 -0
- package/src/registry/architect/traits.json +379 -0
- package/src/registry/atoms.json +16 -0
- package/src/registry/chart-showcase.json +160 -0
- package/src/registry/chart.json +136 -0
- package/src/registry/components.json +8616 -0
- package/src/registry/core.json +216 -0
- package/src/registry/css.json +319 -0
- package/src/registry/data.json +135 -0
- package/src/registry/foundations.json +11 -0
- package/src/registry/icons.json +463 -0
- package/src/registry/index.json +101 -0
- package/src/registry/patterns/activity-feed.json +37 -0
- package/src/registry/patterns/article-content.json +27 -0
- package/src/registry/patterns/auth-form.json +37 -0
- package/src/registry/patterns/author-card.json +20 -0
- package/src/registry/patterns/card-grid.json +127 -0
- package/src/registry/patterns/category-nav.json +26 -0
- package/src/registry/patterns/chart-grid.json +36 -0
- package/src/registry/patterns/chat-interface.json +37 -0
- package/src/registry/patterns/checklist-card.json +55 -0
- package/src/registry/patterns/comparison-panel.json +27 -0
- package/src/registry/patterns/component-showcase.json +24 -0
- package/src/registry/patterns/contact-form.json +31 -0
- package/src/registry/patterns/cta-section.json +20 -0
- package/src/registry/patterns/data-table.json +37 -0
- package/src/registry/patterns/detail-header.json +83 -0
- package/src/registry/patterns/detail-panel.json +27 -0
- package/src/registry/patterns/explorer-shell.json +22 -0
- package/src/registry/patterns/filter-bar.json +33 -0
- package/src/registry/patterns/filter-sidebar.json +27 -0
- package/src/registry/patterns/form-sections.json +110 -0
- package/src/registry/patterns/goal-tracker.json +27 -0
- package/src/registry/patterns/hero.json +107 -0
- package/src/registry/patterns/index.json +47 -0
- package/src/registry/patterns/kpi-grid.json +36 -0
- package/src/registry/patterns/media-gallery.json +20 -0
- package/src/registry/patterns/order-history.json +20 -0
- package/src/registry/patterns/pagination.json +19 -0
- package/src/registry/patterns/photo-to-recipe.json +36 -0
- package/src/registry/patterns/pipeline-tracker.json +28 -0
- package/src/registry/patterns/post-list.json +27 -0
- package/src/registry/patterns/pricing-table.json +32 -0
- package/src/registry/patterns/scorecard.json +28 -0
- package/src/registry/patterns/search-bar.json +20 -0
- package/src/registry/patterns/specimen-grid.json +19 -0
- package/src/registry/patterns/stat-card.json +55 -0
- package/src/registry/patterns/stats-bar.json +55 -0
- package/src/registry/patterns/steps-card.json +55 -0
- package/src/registry/patterns/table-of-contents.json +19 -0
- package/src/registry/patterns/testimonials.json +21 -0
- package/src/registry/patterns/timeline.json +27 -0
- package/src/registry/patterns/token-inspector.json +21 -0
- package/src/registry/patterns/wizard.json +27 -0
- package/src/registry/recipe-auradecantism.json +69 -0
- package/src/registry/recipe-clean.json +65 -0
- package/src/registry/recipe-command-center.json +78 -0
- package/src/registry/router.json +73 -0
- package/src/registry/schema/README.md +197 -0
- package/src/registry/skeletons.json +259 -0
- package/src/registry/state.json +137 -0
- package/src/registry/tokens.json +40 -0
- package/src/router/hash.js +17 -0
- package/src/router/history.js +18 -0
- package/src/router/index.js +598 -0
- package/src/ssr/index.js +922 -0
- package/src/state/arrays.js +181 -0
- package/src/state/devtools.js +647 -0
- package/src/state/index.js +498 -0
- package/src/state/middleware.js +288 -0
- package/src/state/scheduler.js +206 -0
- package/src/state/store.js +300 -0
- package/src/tags/index.js +19 -0
- package/src/tannins/auth.js +396 -0
- package/src/test/dom.js +352 -0
- package/src/test/index.js +62 -0
- package/src/test/state.js +306 -0
- package/tools/a11y-audit.js +487 -0
- package/tools/analyzer.js +315 -0
- package/tools/audit.js +706 -0
- package/tools/builder.js +1422 -0
- package/tools/css-extract.js +188 -0
- package/tools/dev-server.js +316 -0
- package/tools/dts-gen.js +1260 -0
- package/tools/figma-components.js +329 -0
- package/tools/figma-patterns.js +516 -0
- package/tools/figma-plugin/code.js +453 -0
- package/tools/figma-plugin/manifest.json +14 -0
- package/tools/figma-plugin/ui.html +268 -0
- package/tools/figma-render.js +293 -0
- package/tools/figma-tokens.js +712 -0
- package/tools/figma-upload.js +318 -0
- package/tools/generate.js +738 -0
- package/tools/icons.js +133 -0
- package/tools/init-templates.js +265 -0
- package/tools/install-hooks.sh +5 -0
- package/tools/migrations/0.5.0.js +53 -0
- package/tools/migrations/0.6.0.js +95 -0
- package/tools/minify.js +170 -0
- package/tools/pre-commit +4 -0
- package/tools/registry.js +662 -0
- package/tools/reset-playground.js +61 -0
- package/tools/starter-templates/content-site/app.js +49 -0
- package/tools/starter-templates/content-site/essence.js +19 -0
- package/tools/starter-templates/content-site/pages.js +31 -0
- package/tools/starter-templates/ecommerce/app.js +50 -0
- package/tools/starter-templates/ecommerce/essence.js +19 -0
- package/tools/starter-templates/ecommerce/pages.js +31 -0
- package/tools/starter-templates/landing-page/app.js +38 -0
- package/tools/starter-templates/landing-page/essence.js +18 -0
- package/tools/starter-templates/landing-page/pages.js +21 -0
- package/tools/starter-templates/portfolio/app.js +45 -0
- package/tools/starter-templates/portfolio/essence.js +19 -0
- package/tools/starter-templates/portfolio/pages.js +33 -0
- package/tools/starter-templates/saas-dashboard/app.js +70 -0
- package/tools/starter-templates/saas-dashboard/essence.js +19 -0
- package/tools/starter-templates/saas-dashboard/pages.js +31 -0
- package/tools/verify-pack.js +203 -0
- package/types/chart.d.ts +77 -0
- package/types/components.d.ts +587 -0
- package/types/core.d.ts +89 -0
- package/types/css.d.ts +149 -0
- package/types/data.d.ts +238 -0
- package/types/form.d.ts +164 -0
- package/types/i18n.d.ts +51 -0
- package/types/icons.d.ts +27 -0
- package/types/index.d.ts +13 -0
- package/types/router.d.ts +116 -0
- package/types/ssr.d.ts +102 -0
- package/types/state.d.ts +83 -0
- package/types/tags.d.ts +62 -0
- package/types/tannins.d.ts +63 -0
- package/types/test.d.ts +48 -0
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Decantr Realtime Primitives
|
|
3
|
+
* WebSocket and EventSource wrappers built on Decantr signals.
|
|
4
|
+
*
|
|
5
|
+
* @module decantr/data/realtime
|
|
6
|
+
*
|
|
7
|
+
* Exports:
|
|
8
|
+
* createWebSocket(url, options?) — Reactive WebSocket with auto-reconnect
|
|
9
|
+
* createEventSource(url, options?) — Reactive Server-Sent Events wrapper
|
|
10
|
+
*/
|
|
11
|
+
import { createSignal, batch } from '../state/index.js';
|
|
12
|
+
|
|
13
|
+
// ─── CONSTANTS ──────────────────────────────────────────────────
|
|
14
|
+
|
|
15
|
+
const MAX_MESSAGES = 100;
|
|
16
|
+
const MAX_BACKOFF = 30000;
|
|
17
|
+
|
|
18
|
+
// ─── createWebSocket ────────────────────────────────────────────
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Create a reactive WebSocket connection with auto-reconnect and message buffering.
|
|
22
|
+
*
|
|
23
|
+
* @param {string} url — WebSocket endpoint (ws:// or wss://)
|
|
24
|
+
* @param {object} [options]
|
|
25
|
+
* @param {number} [options.reconnectDelay=1000] — Initial reconnect delay in ms
|
|
26
|
+
* @param {number} [options.maxRetries=5] — Maximum reconnect attempts
|
|
27
|
+
* @param {boolean} [options.buffer=true] — Buffer sends while disconnected
|
|
28
|
+
* @param {(data: any) => any} [options.parse] — Message parser (default: identity)
|
|
29
|
+
* @param {string[]} [options.protocols] — WebSocket sub-protocols
|
|
30
|
+
* @returns {{
|
|
31
|
+
* lastMessage: () => any,
|
|
32
|
+
* messages: () => any[],
|
|
33
|
+
* status: () => 'connecting'|'open'|'closed'|'reconnecting',
|
|
34
|
+
* send: (data: any) => void,
|
|
35
|
+
* close: () => void,
|
|
36
|
+
* reconnect: () => void,
|
|
37
|
+
* on: (handler: (msg: any) => void) => () => void
|
|
38
|
+
* }}
|
|
39
|
+
*/
|
|
40
|
+
export function createWebSocket(url, options = {}) {
|
|
41
|
+
const {
|
|
42
|
+
reconnectDelay = 1000,
|
|
43
|
+
maxRetries = 5,
|
|
44
|
+
buffer = true,
|
|
45
|
+
parse = (d) => d,
|
|
46
|
+
protocols
|
|
47
|
+
} = options;
|
|
48
|
+
|
|
49
|
+
const [lastMessage, setLastMessage] = createSignal(null);
|
|
50
|
+
const [messages, setMessages] = createSignal([]);
|
|
51
|
+
const [status, setStatus] = createSignal('connecting');
|
|
52
|
+
|
|
53
|
+
/** @type {((msg: any) => void)[]} */
|
|
54
|
+
const handlers = [];
|
|
55
|
+
/** @type {any[]} */
|
|
56
|
+
const sendBuffer = [];
|
|
57
|
+
/** @type {{ ws: WebSocket|null, attempts: number, timer: number|null, stopped: boolean }} */
|
|
58
|
+
const state = { ws: null, attempts: 0, timer: null, stopped: false };
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Flush the send buffer once the socket is open.
|
|
62
|
+
* @param {WebSocket} ws
|
|
63
|
+
*/
|
|
64
|
+
function flushBuffer(ws) {
|
|
65
|
+
while (sendBuffer.length > 0) {
|
|
66
|
+
ws.send(sendBuffer.shift());
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Connect (or reconnect) to the WebSocket server.
|
|
72
|
+
*/
|
|
73
|
+
function connect() {
|
|
74
|
+
if (state.stopped) return;
|
|
75
|
+
const ws = protocols
|
|
76
|
+
? new WebSocket(url, protocols)
|
|
77
|
+
: new WebSocket(url);
|
|
78
|
+
|
|
79
|
+
state.ws = ws;
|
|
80
|
+
|
|
81
|
+
ws.addEventListener('open', () => {
|
|
82
|
+
batch(() => {
|
|
83
|
+
state.attempts = 0;
|
|
84
|
+
setStatus('open');
|
|
85
|
+
});
|
|
86
|
+
flushBuffer(ws);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
ws.addEventListener('message', (ev) => {
|
|
90
|
+
const parsed = parse(ev.data);
|
|
91
|
+
batch(() => {
|
|
92
|
+
setLastMessage(parsed);
|
|
93
|
+
setMessages((prev) => {
|
|
94
|
+
const next = prev.concat(parsed);
|
|
95
|
+
return next.length > MAX_MESSAGES ? next.slice(next.length - MAX_MESSAGES) : next;
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
for (let i = 0; i < handlers.length; i++) handlers[i](parsed);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
ws.addEventListener('close', () => {
|
|
102
|
+
if (state.stopped) {
|
|
103
|
+
setStatus('closed');
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
scheduleReconnect();
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
ws.addEventListener('error', () => {
|
|
110
|
+
// Error is always followed by close — reconnect logic lives there.
|
|
111
|
+
// Explicitly close to ensure the close event fires consistently.
|
|
112
|
+
ws.close();
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Schedule a reconnect attempt with exponential backoff + jitter.
|
|
118
|
+
*/
|
|
119
|
+
function scheduleReconnect() {
|
|
120
|
+
if (state.stopped) return;
|
|
121
|
+
if (state.attempts >= maxRetries) {
|
|
122
|
+
setStatus('closed');
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
setStatus('reconnecting');
|
|
126
|
+
const delay = Math.min(
|
|
127
|
+
reconnectDelay * Math.pow(2, state.attempts) + Math.random() * 1000,
|
|
128
|
+
MAX_BACKOFF
|
|
129
|
+
);
|
|
130
|
+
state.attempts++;
|
|
131
|
+
state.timer = setTimeout(() => {
|
|
132
|
+
state.timer = null;
|
|
133
|
+
connect();
|
|
134
|
+
}, delay);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Send data through the WebSocket. Buffers if disconnected and `buffer` is enabled.
|
|
139
|
+
* @param {any} data
|
|
140
|
+
*/
|
|
141
|
+
function send(data) {
|
|
142
|
+
if (state.ws && state.ws.readyState === WebSocket.OPEN) {
|
|
143
|
+
state.ws.send(data);
|
|
144
|
+
} else if (buffer) {
|
|
145
|
+
sendBuffer.push(data);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Close the connection and stop all reconnection attempts.
|
|
151
|
+
*/
|
|
152
|
+
function close() {
|
|
153
|
+
state.stopped = true;
|
|
154
|
+
if (state.timer !== null) {
|
|
155
|
+
clearTimeout(state.timer);
|
|
156
|
+
state.timer = null;
|
|
157
|
+
}
|
|
158
|
+
if (state.ws) {
|
|
159
|
+
state.ws.close();
|
|
160
|
+
state.ws = null;
|
|
161
|
+
}
|
|
162
|
+
setStatus('closed');
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Manually trigger a reconnect. Resets attempt counter.
|
|
167
|
+
*/
|
|
168
|
+
function reconnect() {
|
|
169
|
+
if (state.timer !== null) {
|
|
170
|
+
clearTimeout(state.timer);
|
|
171
|
+
state.timer = null;
|
|
172
|
+
}
|
|
173
|
+
if (state.ws) {
|
|
174
|
+
state.stopped = true; // prevent auto-reconnect from close handler
|
|
175
|
+
state.ws.close();
|
|
176
|
+
state.ws = null;
|
|
177
|
+
}
|
|
178
|
+
state.stopped = false;
|
|
179
|
+
state.attempts = 0;
|
|
180
|
+
setStatus('connecting');
|
|
181
|
+
connect();
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Register a message handler. Returns an unsubscribe function.
|
|
186
|
+
* @param {(msg: any) => void} handler
|
|
187
|
+
* @returns {() => void}
|
|
188
|
+
*/
|
|
189
|
+
function on(handler) {
|
|
190
|
+
handlers.push(handler);
|
|
191
|
+
return () => {
|
|
192
|
+
const idx = handlers.indexOf(handler);
|
|
193
|
+
if (idx !== -1) handlers.splice(idx, 1);
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Kick off initial connection
|
|
198
|
+
connect();
|
|
199
|
+
|
|
200
|
+
return { lastMessage, messages, status, send, close, reconnect, on };
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// ─── createEventSource ──────────────────────────────────────────
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Create a reactive EventSource (Server-Sent Events) wrapper.
|
|
207
|
+
*
|
|
208
|
+
* @param {string} url — SSE endpoint
|
|
209
|
+
* @param {object} [options]
|
|
210
|
+
* @param {string[]} [options.events] — Event types to listen for (default: ['message'])
|
|
211
|
+
* @param {boolean} [options.withCredentials=false] — Send credentials with request
|
|
212
|
+
* @returns {{
|
|
213
|
+
* lastEvent: () => { type: string, data: string }|null,
|
|
214
|
+
* status: () => 'connecting'|'open'|'closed',
|
|
215
|
+
* close: () => void,
|
|
216
|
+
* on: (eventType: string, handler: (ev: { type: string, data: string }) => void) => () => void
|
|
217
|
+
* }}
|
|
218
|
+
*/
|
|
219
|
+
export function createEventSource(url, options = {}) {
|
|
220
|
+
const { events, withCredentials = false } = options;
|
|
221
|
+
|
|
222
|
+
const [lastEvent, setLastEvent] = createSignal(null);
|
|
223
|
+
const [status, setStatus] = createSignal('connecting');
|
|
224
|
+
|
|
225
|
+
/** @type {Map<string, ((ev: {type: string, data: string}) => void)[]>} */
|
|
226
|
+
const handlerMap = new Map();
|
|
227
|
+
|
|
228
|
+
const source = new EventSource(url, { withCredentials });
|
|
229
|
+
|
|
230
|
+
source.addEventListener('open', () => {
|
|
231
|
+
setStatus('open');
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
source.addEventListener('error', () => {
|
|
235
|
+
// EventSource auto-reconnects; reflect readyState
|
|
236
|
+
if (source.readyState === EventSource.CLOSED) {
|
|
237
|
+
setStatus('closed');
|
|
238
|
+
} else {
|
|
239
|
+
setStatus('connecting');
|
|
240
|
+
}
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Internal handler factory for a given event type.
|
|
245
|
+
* @param {string} type
|
|
246
|
+
* @returns {(ev: MessageEvent) => void}
|
|
247
|
+
*/
|
|
248
|
+
function makeListener(type) {
|
|
249
|
+
return (ev) => {
|
|
250
|
+
const record = { type, data: ev.data };
|
|
251
|
+
setLastEvent(record);
|
|
252
|
+
const list = handlerMap.get(type);
|
|
253
|
+
if (list) {
|
|
254
|
+
for (let i = 0; i < list.length; i++) list[i](record);
|
|
255
|
+
}
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// Attach listeners for requested event types
|
|
260
|
+
const types = events && events.length > 0 ? events : ['message'];
|
|
261
|
+
for (let i = 0; i < types.length; i++) {
|
|
262
|
+
source.addEventListener(types[i], makeListener(types[i]));
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Close the EventSource connection.
|
|
267
|
+
*/
|
|
268
|
+
function close() {
|
|
269
|
+
source.close();
|
|
270
|
+
setStatus('closed');
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Register a handler for a specific event type. Returns an unsubscribe function.
|
|
275
|
+
* If the event type was not in the initial `events` list, it is dynamically added.
|
|
276
|
+
*
|
|
277
|
+
* @param {string} eventType
|
|
278
|
+
* @param {(ev: { type: string, data: string }) => void} handler
|
|
279
|
+
* @returns {() => void}
|
|
280
|
+
*/
|
|
281
|
+
function on(eventType, handler) {
|
|
282
|
+
if (!handlerMap.has(eventType)) {
|
|
283
|
+
handlerMap.set(eventType, []);
|
|
284
|
+
// Attach a native listener if this type wasn't in the initial set
|
|
285
|
+
if (!types.includes(eventType)) {
|
|
286
|
+
source.addEventListener(eventType, makeListener(eventType));
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
handlerMap.get(eventType).push(handler);
|
|
290
|
+
return () => {
|
|
291
|
+
const list = handlerMap.get(eventType);
|
|
292
|
+
if (!list) return;
|
|
293
|
+
const idx = list.indexOf(handler);
|
|
294
|
+
if (idx !== -1) list.splice(idx, 1);
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
return { lastEvent, status, close, on };
|
|
299
|
+
}
|
package/src/data/url.js
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import { createSignal, batch, untrack } from '../state/index.js';
|
|
2
|
+
|
|
3
|
+
/* Built-in parsers -------------------------------------------------------- */
|
|
4
|
+
|
|
5
|
+
/** @type {{ parse: (v: string) => string, serialize: (v: string) => string }} */
|
|
6
|
+
const string = { parse: v => v, serialize: v => v };
|
|
7
|
+
/** @type {{ parse: (v: string) => number, serialize: (v: number) => string }} */
|
|
8
|
+
const integer = { parse: v => parseInt(v, 10), serialize: v => String(v) };
|
|
9
|
+
/** @type {{ parse: (v: string) => number, serialize: (v: number) => string }} */
|
|
10
|
+
const float = { parse: v => parseFloat(v), serialize: v => String(v) };
|
|
11
|
+
/** @type {{ parse: (v: string) => boolean, serialize: (v: boolean) => string }} */
|
|
12
|
+
const boolean = { parse: v => v === 'true', serialize: v => v ? 'true' : 'false' };
|
|
13
|
+
/** @type {{ parse: (v: string) => any, serialize: (v: any) => string }} */
|
|
14
|
+
const json = { parse: v => JSON.parse(v), serialize: v => JSON.stringify(v) };
|
|
15
|
+
/** @type {{ parse: (v: string) => Date, serialize: (v: Date) => string }} */
|
|
16
|
+
const date = { parse: v => new Date(v), serialize: v => v.toISOString() };
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Factory parser that validates against an allowed set of values.
|
|
20
|
+
* @template {string} T
|
|
21
|
+
* @param {T[]} values
|
|
22
|
+
* @returns {{ parse: (v: string) => T | undefined, serialize: (v: T) => string }}
|
|
23
|
+
*/
|
|
24
|
+
function enumParser(values) {
|
|
25
|
+
const allowed = new Set(values);
|
|
26
|
+
return { parse: v => allowed.has(v) ? /** @type {T} */ (v) : undefined, serialize: v => v };
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export const parsers = { string, integer, float, boolean, json, date, enum: enumParser };
|
|
30
|
+
|
|
31
|
+
/* Internal helpers -------------------------------------------------------- */
|
|
32
|
+
|
|
33
|
+
/** Detect hash-based routing (`#/...`). */
|
|
34
|
+
function isHashMode() {
|
|
35
|
+
return typeof window !== 'undefined' && window.location.hash.startsWith('#/');
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Read current URL search params, respecting routing mode.
|
|
40
|
+
* @returns {URLSearchParams}
|
|
41
|
+
*/
|
|
42
|
+
function readParams() {
|
|
43
|
+
if (isHashMode()) {
|
|
44
|
+
const hash = window.location.hash.slice(1);
|
|
45
|
+
const qi = hash.indexOf('?');
|
|
46
|
+
return new URLSearchParams(qi >= 0 ? hash.slice(qi + 1) : '');
|
|
47
|
+
}
|
|
48
|
+
return new URLSearchParams(window.location.search);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Write search params back to URL, preserving routing mode.
|
|
53
|
+
* @param {URLSearchParams} params
|
|
54
|
+
* @param {boolean} push — true = pushState, false = replaceState
|
|
55
|
+
*/
|
|
56
|
+
function writeParams(params, push) {
|
|
57
|
+
const qs = params.toString();
|
|
58
|
+
const nav = push ? 'pushState' : 'replaceState';
|
|
59
|
+
if (isHashMode()) {
|
|
60
|
+
let hash = window.location.hash.slice(1);
|
|
61
|
+
const qi = hash.indexOf('?');
|
|
62
|
+
const path = qi >= 0 ? hash.slice(0, qi) : hash;
|
|
63
|
+
window.history[nav](null, '', window.location.pathname + window.location.search + '#' + path + (qs ? '?' + qs : ''));
|
|
64
|
+
} else {
|
|
65
|
+
window.history[nav](null, '', window.location.pathname + (qs ? '?' + qs : ''));
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/* Throttled URL writer (shared across all signals) ------------------------ */
|
|
70
|
+
|
|
71
|
+
/** @type {number | null} */
|
|
72
|
+
let _flushTimer = null;
|
|
73
|
+
/** @type {Array<{ key: string, value: string | null, push: boolean }>} */
|
|
74
|
+
let _pendingWrites = [];
|
|
75
|
+
|
|
76
|
+
function scheduleFlush() {
|
|
77
|
+
if (_flushTimer !== null) return;
|
|
78
|
+
_flushTimer = setTimeout(() => {
|
|
79
|
+
_flushTimer = null;
|
|
80
|
+
const writes = _pendingWrites;
|
|
81
|
+
_pendingWrites = [];
|
|
82
|
+
if (writes.length === 0) return;
|
|
83
|
+
const params = readParams();
|
|
84
|
+
let push = false;
|
|
85
|
+
for (const w of writes) {
|
|
86
|
+
if (w.value === null) params.delete(w.key);
|
|
87
|
+
else params.set(w.key, w.value);
|
|
88
|
+
if (w.push) push = true;
|
|
89
|
+
}
|
|
90
|
+
writeParams(params, push);
|
|
91
|
+
}, 50);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/* createURLSignal --------------------------------------------------------- */
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Create a reactive signal backed by a URL search parameter.
|
|
98
|
+
* @template T
|
|
99
|
+
* @param {string} key — URL param name
|
|
100
|
+
* @param {{ parse: (v: string) => T, serialize: (v: T) => string }} parser
|
|
101
|
+
* @param {{ defaultValue?: T, push?: boolean }} [options]
|
|
102
|
+
* @returns {[() => T, (v: T | ((prev: T) => T)) => void]}
|
|
103
|
+
*/
|
|
104
|
+
export function createURLSignal(key, parser, options = {}) {
|
|
105
|
+
const { defaultValue, push = false } = options;
|
|
106
|
+
|
|
107
|
+
function fromURL() {
|
|
108
|
+
const raw = readParams().get(key);
|
|
109
|
+
if (raw === null) return defaultValue;
|
|
110
|
+
const parsed = parser.parse(raw);
|
|
111
|
+
return parsed === undefined ? defaultValue : parsed;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const [get, set] = createSignal(fromURL());
|
|
115
|
+
|
|
116
|
+
// URL -> signal: sync on browser navigation
|
|
117
|
+
const onNav = () => {
|
|
118
|
+
const next = fromURL();
|
|
119
|
+
if (!Object.is(untrack(get), next)) set(next);
|
|
120
|
+
};
|
|
121
|
+
if (typeof window !== 'undefined') {
|
|
122
|
+
window.addEventListener('popstate', onNav);
|
|
123
|
+
window.addEventListener('hashchange', onNav);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Update signal value and schedule a throttled URL write.
|
|
128
|
+
* @param {T | ((prev: T) => T)} v
|
|
129
|
+
*/
|
|
130
|
+
function setter(v) {
|
|
131
|
+
const prev = untrack(get);
|
|
132
|
+
const next = typeof v === 'function' ? /** @type {Function} */ (v)(prev) : v;
|
|
133
|
+
set(next);
|
|
134
|
+
const serialized = Object.is(next, defaultValue) ? null : parser.serialize(next);
|
|
135
|
+
_pendingWrites.push({ key, value: serialized, push });
|
|
136
|
+
scheduleFlush();
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return [get, setter];
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/* createURLStore ---------------------------------------------------------- */
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Create a reactive store backed by multiple URL search parameters.
|
|
146
|
+
* @template {Record<string, { parser: { parse: (v: string) => any, serialize: (v: any) => string }, defaultValue?: any }>} S
|
|
147
|
+
* @param {S} schema
|
|
148
|
+
* @returns {Record<string, any>} — getter/setter pairs, plus values() and reset()
|
|
149
|
+
*/
|
|
150
|
+
export function createURLStore(schema) {
|
|
151
|
+
const keys = Object.keys(schema);
|
|
152
|
+
/** @type {Record<string, [Function, Function]>} */
|
|
153
|
+
const signals = {};
|
|
154
|
+
const store = {};
|
|
155
|
+
|
|
156
|
+
for (const key of keys) {
|
|
157
|
+
const { parser, defaultValue, ...rest } = schema[key];
|
|
158
|
+
const [get, set] = createURLSignal(key, parser, { defaultValue, ...rest });
|
|
159
|
+
signals[key] = [get, set];
|
|
160
|
+
store[key] = get;
|
|
161
|
+
store['set' + key.charAt(0).toUpperCase() + key.slice(1)] = set;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/** @returns {Record<string, any>} All current values as a plain object. */
|
|
165
|
+
store.values = () => {
|
|
166
|
+
const out = {};
|
|
167
|
+
for (const key of keys) out[key] = signals[key][0]();
|
|
168
|
+
return out;
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
/** Reset all params to their defaults. */
|
|
172
|
+
store.reset = () => {
|
|
173
|
+
batch(() => { for (const key of keys) signals[key][1](schema[key].defaultValue); });
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
return store;
|
|
177
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { createSignal, createEffect } from '../state/index.js';
|
|
2
|
+
|
|
3
|
+
/** @type {number} */
|
|
4
|
+
let nextId = 1;
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Persistent bridge to a Web Worker with reactive state.
|
|
8
|
+
*
|
|
9
|
+
* Sends messages using `{ id, payload }` protocol and expects
|
|
10
|
+
* `{ id, result }` or `{ id, error }` responses.
|
|
11
|
+
*
|
|
12
|
+
* @param {Worker} worker
|
|
13
|
+
* @returns {{
|
|
14
|
+
* result: () => any,
|
|
15
|
+
* busy: () => boolean,
|
|
16
|
+
* error: () => any,
|
|
17
|
+
* send: (data: any) => number,
|
|
18
|
+
* terminate: () => void
|
|
19
|
+
* }}
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```js
|
|
23
|
+
* const ws = createWorkerSignal(new Worker('./compute.js'));
|
|
24
|
+
* ws.send({ type: 'process', data: [1,2,3] });
|
|
25
|
+
* // ws.result() — last result from worker
|
|
26
|
+
* // ws.busy() — true while worker is processing
|
|
27
|
+
* // ws.error() — last error (if any)
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
export function createWorkerSignal(worker) {
|
|
31
|
+
const [result, setResult] = createSignal(undefined);
|
|
32
|
+
const [busy, setBusy] = createSignal(false);
|
|
33
|
+
const [error, setError] = createSignal(undefined);
|
|
34
|
+
|
|
35
|
+
/** @type {Set<number>} */
|
|
36
|
+
const pending = new Set();
|
|
37
|
+
|
|
38
|
+
function onMessage(e) {
|
|
39
|
+
const msg = e.data;
|
|
40
|
+
if (msg && typeof msg.id === 'number') {
|
|
41
|
+
pending.delete(msg.id);
|
|
42
|
+
if ('error' in msg) {
|
|
43
|
+
setError(msg.error);
|
|
44
|
+
} else {
|
|
45
|
+
setResult(msg.result);
|
|
46
|
+
setError(undefined);
|
|
47
|
+
}
|
|
48
|
+
if (pending.size === 0) setBusy(false);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function onError(e) {
|
|
53
|
+
setError(e.message || e);
|
|
54
|
+
pending.clear();
|
|
55
|
+
setBusy(false);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
worker.addEventListener('message', onMessage);
|
|
59
|
+
worker.addEventListener('error', onError);
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Send a message to the worker.
|
|
63
|
+
* @param {any} data
|
|
64
|
+
* @returns {number} message id
|
|
65
|
+
*/
|
|
66
|
+
function send(data) {
|
|
67
|
+
const id = nextId++;
|
|
68
|
+
pending.add(id);
|
|
69
|
+
setBusy(true);
|
|
70
|
+
setError(undefined);
|
|
71
|
+
worker.postMessage({ id, payload: data });
|
|
72
|
+
return id;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** Terminate the worker and clean up listeners. */
|
|
76
|
+
function terminate() {
|
|
77
|
+
worker.removeEventListener('message', onMessage);
|
|
78
|
+
worker.removeEventListener('error', onError);
|
|
79
|
+
worker.terminate();
|
|
80
|
+
pending.clear();
|
|
81
|
+
setBusy(false);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return { result, busy, error, send, terminate };
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* One-shot reactive computation via a Web Worker.
|
|
89
|
+
*
|
|
90
|
+
* When `input` is a signal getter, re-sends to the worker whenever
|
|
91
|
+
* the value changes. Returns a resource-like object.
|
|
92
|
+
*
|
|
93
|
+
* @param {Worker} worker
|
|
94
|
+
* @param {(() => any) | any} input - Signal getter or plain value
|
|
95
|
+
* @returns {{
|
|
96
|
+
* data: () => any,
|
|
97
|
+
* loading: () => boolean,
|
|
98
|
+
* error: () => any,
|
|
99
|
+
* refetch: () => void
|
|
100
|
+
* }}
|
|
101
|
+
*
|
|
102
|
+
* @example
|
|
103
|
+
* ```js
|
|
104
|
+
* const result = createWorkerQuery(new Worker('./heavy.js'), () => ({ data: largeArray() }));
|
|
105
|
+
* // result.data() — computed result
|
|
106
|
+
* // result.loading() — true while computing
|
|
107
|
+
* // result.error() — last error
|
|
108
|
+
* // result.refetch() — re-run with current input
|
|
109
|
+
* ```
|
|
110
|
+
*/
|
|
111
|
+
export function createWorkerQuery(worker, input) {
|
|
112
|
+
const ws = createWorkerSignal(worker);
|
|
113
|
+
const isGetter = typeof input === 'function';
|
|
114
|
+
|
|
115
|
+
/** Resolve current input value and send to worker. */
|
|
116
|
+
function run() {
|
|
117
|
+
const value = isGetter ? input() : input;
|
|
118
|
+
ws.send(value);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// If input is reactive, track changes and re-send automatically.
|
|
122
|
+
if (isGetter) {
|
|
123
|
+
createEffect(() => { run(); });
|
|
124
|
+
} else {
|
|
125
|
+
run();
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return {
|
|
129
|
+
data: ws.result,
|
|
130
|
+
loading: ws.busy,
|
|
131
|
+
error: ws.error,
|
|
132
|
+
refetch: run
|
|
133
|
+
};
|
|
134
|
+
}
|