@readymadeit/mcp 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +114 -2
- package/bundle/LICENSE +201 -0
- package/bundle/boards/aggregator-demo-board.json +54 -0
- package/bundle/boards/alpaca-board.json +316 -0
- package/bundle/boards/animate-board.json +159 -0
- package/bundle/boards/ascii-art-demo-board.json +33 -0
- package/bundle/boards/ascii-cam-board.json +184 -0
- package/bundle/boards/asset-demo-board.json +54 -0
- package/bundle/boards/audio-encode-demo-board.json +153 -0
- package/bundle/boards/audiolize-board.json +152 -0
- package/bundle/boards/batcher-demo-board.json +43 -0
- package/bundle/boards/buffer-demo-board.json +53 -0
- package/bundle/boards/cache-demo-board.json +59 -0
- package/bundle/boards/camera-demo-board.json +28 -0
- package/bundle/boards/canvas-demo-board.json +55 -0
- package/bundle/boards/changes-demo-board.json +60 -0
- package/bundle/boards/court-booking-browser-demo-board.json +221 -0
- package/bundle/boards/court-booking-demo-board.json +218 -0
- package/bundle/boards/current-time-board.json +32 -0
- package/bundle/boards/debounce-demo-board.json +43 -0
- package/bundle/boards/decrypt-demo-board.json +47 -0
- package/bundle/boards/delay-demo-board.json +28 -0
- package/bundle/boards/detect-demo-board.json +64 -0
- package/bundle/boards/document-extract-demo-board.json +55 -0
- package/bundle/boards/dropitapp-board.json +138 -0
- package/bundle/boards/encode-demo-board.json +39 -0
- package/bundle/boards/encrypt-board.json +185 -0
- package/bundle/boards/encrypt-demo-board.json +39 -0
- package/bundle/boards/face-alert-board.json +181 -0
- package/bundle/boards/fetcher-demo-board.json +40 -0
- package/bundle/boards/fft-demo-board.json +28 -0
- package/bundle/boards/filter-demo-board.json +55 -0
- package/bundle/boards/flat-map-demo-board.json +42 -0
- package/bundle/boards/frame-limit-test-board.json +130 -0
- package/bundle/boards/game-board.json +413 -0
- package/bundle/boards/game-of-life-board.json +139 -0
- package/bundle/boards/game-of-life-demo-board.json +34 -0
- package/bundle/boards/gather-demo-board.json +62 -0
- package/bundle/boards/group-by-demo-board.json +62 -0
- package/bundle/boards/hacker-demo-board.json +45 -0
- package/bundle/boards/hacker-news-board.json +741 -0
- package/bundle/boards/hash-demo-board.json +40 -0
- package/bundle/boards/health-log-browser-demo-board.json +537 -0
- package/bundle/boards/health-log-demo-board.json +498 -0
- package/bundle/boards/helloworld-board.json +57 -0
- package/bundle/boards/hold-demo-board.json +67 -0
- package/bundle/boards/hold-scopes-demo-board.json +141 -0
- package/bundle/boards/http-client-browser-demo-board.json +379 -0
- package/bundle/boards/http-client-demo-board.json +380 -0
- package/bundle/boards/image-picker-demo-board.json +22 -0
- package/bundle/boards/imap-email-demo-board.json +35 -0
- package/bundle/boards/injector-demo-board.json +55 -0
- package/bundle/boards/iterator-demo-board.json +55 -0
- package/bundle/boards/limit-demo-board.json +66 -0
- package/bundle/boards/link-debugger.json +32 -0
- package/bundle/boards/live-location-demo-board.json +93 -0
- package/bundle/boards/live-radio-cloud-demo-board.json +216 -0
- package/bundle/boards/live-radio-cloud-direct-demo-board.json +311 -0
- package/bundle/boards/live-radio-demo-board.json +211 -0
- package/bundle/boards/live-radio-members-demo-board.json +314 -0
- package/bundle/boards/local-storage-demo-board.json +30 -0
- package/bundle/boards/lz-compress-demo-board.json +52 -0
- package/bundle/boards/map-demo-board.json +46 -0
- package/bundle/boards/match-filter-demo-board.json +43 -0
- package/bundle/boards/meeting-poll-browser-demo-board.json +649 -0
- package/bundle/boards/meeting-poll-demo-board.json +650 -0
- package/bundle/boards/microphone-monitor-demo-board.json +28 -0
- package/bundle/boards/microphone-speaker-demo-board.json +86 -0
- package/bundle/boards/monitor-demo-board.json +52 -0
- package/bundle/boards/mounted-endpoint-demo-board-hkp-rt.json +84 -0
- package/bundle/boards/mounted-endpoint-demo-board.json +84 -0
- package/bundle/boards/nested-rhythm-demo-board.json +1160 -0
- package/bundle/boards/next-bus-board.json +52 -0
- package/bundle/boards/next-bus-switch-board.json +79 -0
- package/bundle/boards/night-groove-demo-board.json +742 -0
- package/bundle/boards/noise-alert-board.json +146 -0
- package/bundle/boards/offer-followup-demo-board.json +291 -0
- package/bundle/boards/offer-intake-demo-board.json +289 -0
- package/bundle/boards/offer-intake-hetzner-demo-board.json +286 -0
- package/bundle/boards/offer-intake-imap-demo-board.json +289 -0
- package/bundle/boards/offer-intake-imap-hetzner-demo-board.json +354 -0
- package/bundle/boards/p2p-receiver-demo-board.json +65 -0
- package/bundle/boards/p2p-sender-demo-board.json +94 -0
- package/bundle/boards/pad-groove-demo-board.json +644 -0
- package/bundle/boards/peer-chat-board.json +109 -0
- package/bundle/boards/peer-chat-node-board.json +97 -0
- package/bundle/boards/qr-code-demo-board.json +22 -0
- package/bundle/boards/queue-demo-board.json +239 -0
- package/bundle/boards/reduce-demo-board.json +54 -0
- package/bundle/boards/remote-named-demo-board-hkp-rt.json +61 -0
- package/bundle/boards/remote-named-demo-board.json +61 -0
- package/bundle/boards/rss-demo-board.json +530 -0
- package/bundle/boards/rss-radio-board.json +515 -0
- package/bundle/boards/rss-radio-brief-one-board.json +237 -0
- package/bundle/boards/rss-radio-editor-unit-board.json +555 -0
- package/bundle/boards/rss-radio-library-unit-board.json +293 -0
- package/bundle/boards/rss-radio-unit-board.json +809 -0
- package/bundle/boards/rss-radio-voice-unit-board.json +146 -0
- package/bundle/boards/runtime-configs/hold-example.json +41 -0
- package/bundle/boards/runtime-configs/injector-example.json +23 -0
- package/bundle/boards/runtime-configs/speech-to-text-example.json +35 -0
- package/bundle/boards/runtime-configs/text-generation-example.json +31 -0
- package/bundle/boards/runtime-configs/text-to-speech-example.json +28 -0
- package/bundle/boards/runtime-configs/text-to-speech-inflect-example.json +28 -0
- package/bundle/boards/runtime-configs/ws-audio-playback-example.json +23 -0
- package/bundle/boards/select-demo-board.json +32 -0
- package/bundle/boards/send-ntfy-board.json +52 -0
- package/bundle/boards/shared-costs-demo-board.json +361 -0
- package/bundle/boards/sign-demo-board.json +41 -0
- package/bundle/boards/skill-router-demo-board.json +210 -0
- package/bundle/boards/smooth-demo-board.json +91 -0
- package/bundle/boards/smtp-email-demo-board.json +34 -0
- package/bundle/boards/sort-demo-board.json +55 -0
- package/bundle/boards/sound-demo-board.json +65 -0
- package/bundle/boards/spectral-modifier-board.json +98 -0
- package/bundle/boards/speech-synth-demo-board.json +37 -0
- package/bundle/boards/speech-to-text-demo-board.json +146 -0
- package/bundle/boards/spotify-board.json +75 -0
- package/bundle/boards/spread-demo-board.json +35 -0
- package/bundle/boards/sql-demo-board.json +56 -0
- package/bundle/boards/sql-explorer-board.json +447 -0
- package/bundle/boards/sql-explorer-node-board.json +474 -0
- package/bundle/boards/sql-import-board.json +156 -0
- package/bundle/boards/state-demo-board.json +65 -0
- package/bundle/boards/stopper-demo-board.json +40 -0
- package/bundle/boards/storage-demo-board.json +193 -0
- package/bundle/boards/store-demo-board.json +63 -0
- package/bundle/boards/swimlanes-demo-board.json +197 -0
- package/bundle/boards/switch-demo-board.json +51 -0
- package/bundle/boards/syn-board.json +19 -0
- package/bundle/boards/syn-booking-unit-board.json +998 -0
- package/bundle/boards/syn-conversations-anthropic-demo-board.json +925 -0
- package/bundle/boards/syn-conversations-demo-board.json +925 -0
- package/bundle/boards/syn-hotels-unit-board.json +287 -0
- package/bundle/boards/syn-mail-desk-board.json +25 -0
- package/bundle/boards/syn-mail-desk-unit-board.json +254 -0
- package/bundle/boards/telegram-listener-demo-board.json +31 -0
- package/bundle/boards/text-generation-anthropic-demo-board.json +66 -0
- package/bundle/boards/text-generation-demo-board.json +51 -0
- package/bundle/boards/text-generation-local-demo-board.json +80 -0
- package/bundle/boards/text-to-speech-demo-board.json +58 -0
- package/bundle/boards/timeline-demo-board.json +477 -0
- package/bundle/boards/timer-demo-board.json +35 -0
- package/bundle/boards/tracks-demo-board.json +95 -0
- package/bundle/boards/trigger-pad-demo-board.json +47 -0
- package/bundle/boards/triggerpad-board.json +31 -0
- package/bundle/boards/triggerpad-recorder-board.json +56 -0
- package/bundle/boards/uuid-generator-demo-board.json +166 -0
- package/bundle/boards/voice-assistant-demo-board.json +230 -0
- package/bundle/boards/voice-assistant-hkp-rt-demo-board.json +239 -0
- package/bundle/boards/voice-assistant-local-demo-board.json +232 -0
- package/bundle/boards/voice-assistant-skills-demo-board.json +316 -0
- package/bundle/boards/voice-assistant-skills-local-demo-board.json +316 -0
- package/bundle/boards/xy-pad-demo-board.json +36 -0
- package/bundle/docs/content/board-json.md +277 -0
- package/bundle/docs/content/boards/alpaca-board.md +56 -0
- package/bundle/docs/content/boards/animate-board.md +40 -0
- package/bundle/docs/content/boards/ascii-cam-board.md +37 -0
- package/bundle/docs/content/boards/audiolize-board.md +47 -0
- package/bundle/docs/content/boards/court-booking-browser-demo-board.md +43 -0
- package/bundle/docs/content/boards/court-booking-demo-board.md +120 -0
- package/bundle/docs/content/boards/current-time-board.md +32 -0
- package/bundle/docs/content/boards/dropitapp-board.md +57 -0
- package/bundle/docs/content/boards/encrypt-board.md +36 -0
- package/bundle/docs/content/boards/face-alert-board.md +50 -0
- package/bundle/docs/content/boards/game-board.md +69 -0
- package/bundle/docs/content/boards/game-of-life-board.md +47 -0
- package/bundle/docs/content/boards/hacker-news-board.md +76 -0
- package/bundle/docs/content/boards/health-log-browser-demo-board.md +56 -0
- package/bundle/docs/content/boards/health-log-demo-board.md +138 -0
- package/bundle/docs/content/boards/helloworld-board.md +40 -0
- package/bundle/docs/content/boards/link-debugger.md +31 -0
- package/bundle/docs/content/boards/live-radio-cloud-demo-board.md +70 -0
- package/bundle/docs/content/boards/live-radio-cloud-direct-demo-board.md +89 -0
- package/bundle/docs/content/boards/live-radio-demo-board.md +102 -0
- package/bundle/docs/content/boards/live-radio-members-demo-board.md +91 -0
- package/bundle/docs/content/boards/meeting-poll-browser-demo-board.md +45 -0
- package/bundle/docs/content/boards/meeting-poll-demo-board.md +121 -0
- package/bundle/docs/content/boards/mounted-endpoint-demo-board-hkp-rt.md +50 -0
- package/bundle/docs/content/boards/nested-rhythm-demo-board.md +165 -0
- package/bundle/docs/content/boards/next-bus-board.md +38 -0
- package/bundle/docs/content/boards/next-bus-switch-board.md +44 -0
- package/bundle/docs/content/boards/night-groove-demo-board.md +110 -0
- package/bundle/docs/content/boards/noise-alert-board.md +44 -0
- package/bundle/docs/content/boards/pad-groove-demo-board.md +93 -0
- package/bundle/docs/content/boards/peer-chat-board.md +49 -0
- package/bundle/docs/content/boards/peer-chat-node-board.md +47 -0
- package/bundle/docs/content/boards/rss-radio-board.md +221 -0
- package/bundle/docs/content/boards/send-ntfy-board.md +33 -0
- package/bundle/docs/content/boards/shared-costs-demo-board.md +65 -0
- package/bundle/docs/content/boards/spectral-modifier-board.md +55 -0
- package/bundle/docs/content/boards/spotify-board.md +46 -0
- package/bundle/docs/content/boards/sql-explorer-board.md +70 -0
- package/bundle/docs/content/boards/sql-explorer-node-board.md +76 -0
- package/bundle/docs/content/boards/sql-import-board.md +37 -0
- package/bundle/docs/content/boards/swimlanes-demo-board.md +148 -0
- package/bundle/docs/content/boards/syn-mail-desk-board.md +52 -0
- package/bundle/docs/content/boards/timeline-demo-board.md +115 -0
- package/bundle/docs/content/boards/triggerpad-board.md +32 -0
- package/bundle/docs/content/boards/triggerpad-recorder-board.md +52 -0
- package/bundle/docs/content/boards/voice-assistant-demo-board.md +51 -0
- package/bundle/docs/content/concepts/assets.md +245 -0
- package/bundle/docs/content/concepts/blocks.md +300 -0
- package/bundle/docs/content/concepts/board-assistant.md +307 -0
- package/bundle/docs/content/concepts/board.md +265 -0
- package/bundle/docs/content/concepts/cloud-boards.md +803 -0
- package/bundle/docs/content/concepts/coordinator.md +297 -0
- package/bundle/docs/content/concepts/logging.md +293 -0
- package/bundle/docs/content/concepts/mounts.md +314 -0
- package/bundle/docs/content/concepts/presets.md +360 -0
- package/bundle/docs/content/concepts/remotes.md +423 -0
- package/bundle/docs/content/concepts/runtime.md +664 -0
- package/bundle/docs/content/concepts/scopes.md +256 -0
- package/bundle/docs/content/concepts/service.md +227 -0
- package/bundle/docs/content/concepts/units.md +321 -0
- package/bundle/docs/content/introduction.md +163 -0
- package/bundle/docs/content/llm/circle-text-board.json +55 -0
- package/bundle/docs/content/llm/howto-workflow.md +369 -0
- package/bundle/docs/content/repository.md +161 -0
- package/bundle/docs/content/services/aggregator.md +76 -0
- package/bundle/docs/content/services/analyzer.md +44 -0
- package/bundle/docs/content/services/ascii-art.md +58 -0
- package/bundle/docs/content/services/asset.md +88 -0
- package/bundle/docs/content/services/audio-editor.md +57 -0
- package/bundle/docs/content/services/audio-encode.md +115 -0
- package/bundle/docs/content/services/audio-io.md +173 -0
- package/bundle/docs/content/services/batcher.md +49 -0
- package/bundle/docs/content/services/board-service.md +48 -0
- package/bundle/docs/content/services/browser-sub-service.md +75 -0
- package/bundle/docs/content/services/buffer.md +70 -0
- package/bundle/docs/content/services/cache.md +92 -0
- package/bundle/docs/content/services/camera.md +85 -0
- package/bundle/docs/content/services/canvas.md +196 -0
- package/bundle/docs/content/services/changes.md +87 -0
- package/bundle/docs/content/services/chunked-file-provider.md +63 -0
- package/bundle/docs/content/services/communication-dispatcher.md +190 -0
- package/bundle/docs/content/services/configurator.md +66 -0
- package/bundle/docs/content/services/conversations.md +244 -0
- package/bundle/docs/content/services/debounce.md +45 -0
- package/bundle/docs/content/services/decrypt.md +53 -0
- package/bundle/docs/content/services/delay.md +55 -0
- package/bundle/docs/content/services/detect.md +101 -0
- package/bundle/docs/content/services/document-extract.md +174 -0
- package/bundle/docs/content/services/download.md +67 -0
- package/bundle/docs/content/services/encode.md +63 -0
- package/bundle/docs/content/services/encrypt.md +53 -0
- package/bundle/docs/content/services/fetcher.md +122 -0
- package/bundle/docs/content/services/fft.md +96 -0
- package/bundle/docs/content/services/file-source.md +67 -0
- package/bundle/docs/content/services/filesystem.md +157 -0
- package/bundle/docs/content/services/filter.md +133 -0
- package/bundle/docs/content/services/flat-map.md +62 -0
- package/bundle/docs/content/services/game-of-life.md +93 -0
- package/bundle/docs/content/services/gather.md +135 -0
- package/bundle/docs/content/services/gif-encoder.md +56 -0
- package/bundle/docs/content/services/github.md +85 -0
- package/bundle/docs/content/services/group-by.md +54 -0
- package/bundle/docs/content/services/hacker.md +79 -0
- package/bundle/docs/content/services/hash.md +59 -0
- package/bundle/docs/content/services/hold.md +182 -0
- package/bundle/docs/content/services/http-relay-client.md +48 -0
- package/bundle/docs/content/services/http-uploader.md +62 -0
- package/bundle/docs/content/services/http.md +424 -0
- package/bundle/docs/content/services/if.md +79 -0
- package/bundle/docs/content/services/ifft.md +46 -0
- package/bundle/docs/content/services/image-picker.md +50 -0
- package/bundle/docs/content/services/imap-email.md +104 -0
- package/bundle/docs/content/services/injector.md +129 -0
- package/bundle/docs/content/services/input.md +90 -0
- package/bundle/docs/content/services/iterator.md +186 -0
- package/bundle/docs/content/services/join.md +153 -0
- package/bundle/docs/content/services/limit.md +47 -0
- package/bundle/docs/content/services/local-storage.md +47 -0
- package/bundle/docs/content/services/looper.md +62 -0
- package/bundle/docs/content/services/lz-compress.md +52 -0
- package/bundle/docs/content/services/map.md +257 -0
- package/bundle/docs/content/services/match-filter.md +38 -0
- package/bundle/docs/content/services/microphone-monitor.md +66 -0
- package/bundle/docs/content/services/monitor.md +67 -0
- package/bundle/docs/content/services/ollama.md +116 -0
- package/bundle/docs/content/services/openai-prompt.md +74 -0
- package/bundle/docs/content/services/output.md +107 -0
- package/bundle/docs/content/services/peer-socket.md +61 -0
- package/bundle/docs/content/services/qr-code.md +48 -0
- package/bundle/docs/content/services/queue.md +201 -0
- package/bundle/docs/content/services/reduce.md +51 -0
- package/bundle/docs/content/services/rss.md +232 -0
- package/bundle/docs/content/services/select.md +47 -0
- package/bundle/docs/content/services/sign.md +53 -0
- package/bundle/docs/content/services/skill-router.md +154 -0
- package/bundle/docs/content/services/smooth.md +59 -0
- package/bundle/docs/content/services/smtp-email.md +129 -0
- package/bundle/docs/content/services/sort.md +48 -0
- package/bundle/docs/content/services/sound.md +87 -0
- package/bundle/docs/content/services/speech-synth.md +46 -0
- package/bundle/docs/content/services/speech-to-text.md +172 -0
- package/bundle/docs/content/services/spotify.md +77 -0
- package/bundle/docs/content/services/spread.md +111 -0
- package/bundle/docs/content/services/sql.md +491 -0
- package/bundle/docs/content/services/state.md +57 -0
- package/bundle/docs/content/services/static.md +47 -0
- package/bundle/docs/content/services/stopper.md +55 -0
- package/bundle/docs/content/services/storage.md +155 -0
- package/bundle/docs/content/services/store.md +255 -0
- package/bundle/docs/content/services/sub-service.md +152 -0
- package/bundle/docs/content/services/switch.md +67 -0
- package/bundle/docs/content/services/telegram-listener.md +76 -0
- package/bundle/docs/content/services/text-generation.md +390 -0
- package/bundle/docs/content/services/text-to-speech.md +130 -0
- package/bundle/docs/content/services/timeline.md +333 -0
- package/bundle/docs/content/services/timer.md +113 -0
- package/bundle/docs/content/services/tracks.md +207 -0
- package/bundle/docs/content/services/trigger-pad.md +68 -0
- package/bundle/docs/content/services/uuid-generator.md +48 -0
- package/bundle/docs/content/services/wav-reader.md +66 -0
- package/bundle/docs/content/services/websocket.md +189 -0
- package/bundle/docs/content/services/workflow-board-builder.md +65 -0
- package/bundle/docs/content/services/xy-pad.md +60 -0
- package/bundle/docs/content/targets.md +434 -0
- package/bundle/docs/content/testing.md +263 -0
- package/bundle/docs/content/vocabulary.md +171 -0
- package/bundle/hkp-frontend/src/runtime/browser/BrowserRegistry.tsx +141 -0
- package/bundle/hkp-frontend/src/runtime/browser/BrowserRuntime.tsx +202 -0
- package/bundle/hkp-frontend/src/runtime/browser/BrowserRuntimeApi.ts +306 -0
- package/bundle/hkp-frontend/src/runtime/browser/BrowserRuntimeApp.ts +310 -0
- package/bundle/hkp-frontend/src/runtime/browser/BrowserRuntimeScope.ts +500 -0
- package/bundle/hkp-frontend/src/runtime/browser/UIRegistry.ts +150 -0
- package/bundle/hkp-frontend/src/runtime/browser/assetResolver.ts +123 -0
- package/bundle/hkp-frontend/src/runtime/browser/pipelineNames.ts +65 -0
- package/bundle/hkp-frontend/src/runtime/browser/registry/Default.ts +237 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/Aggregator.ts +127 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/AggregatorUI.tsx +83 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/Analyzer.tsx +43 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/AnalyzerUI/Bars.tsx +33 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/AnalyzerUI/HorizontalSpectrogram.tsx +53 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/AnalyzerUI/VerticalSpectogram.tsx +56 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/AnalyzerUI/Waveform.tsx +109 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/AnalyzerUI/index.tsx +99 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/AsciiArt.ts +119 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/AsciiArtUI.tsx +131 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/Asset.ts +125 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/AssetUI.tsx +37 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/AudioDeviceSettings.tsx +56 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/AudioEditor.ts +129 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/AudioEditorUI.tsx +549 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/AudioInput.ts +298 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/AudioInputUI.tsx +257 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/AudioOutput.ts +189 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/AudioOutputUI.tsx +63 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/Batcher.ts +11 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/BoardService.tsx +86 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/BoardServiceUI.tsx +410 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/BrowserGather.ts +311 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/BrowserIterator.ts +167 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/BrowserSubService.tsx +583 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/BrowserSubServiceUI.tsx +187 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/BrowserTracks.tsx +477 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/Buffer.ts +11 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/Cache.ts +134 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/CacheUI.tsx +125 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/Camera.ts +90 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/CameraUI.tsx +321 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/Canvas.ts +139 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/CanvasUI/canvasDraw.ts +543 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/CanvasUI/canvasUtils.ts +39 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/CanvasUI/index.tsx +287 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/CanvasUI/tests/canvasDraw.test.ts +355 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/CanvasUI/tests/canvasUtils.test.ts +89 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/CanvasUI/tests/fullscreen.test.tsx +26 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/Changes.ts +201 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/ChangesUI.tsx +85 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/ChunkedFileProvider.tsx +183 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/Configurator.ts +258 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/ConfiguratorUI.tsx +83 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/ConsideredHacker.tsx +28 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/Debounce.ts +75 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/DebounceUI.tsx +70 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/Decrypt.ts +127 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/DecryptUI.tsx +39 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/Delay.tsx +110 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/Detect.ts +214 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/DetectUI.tsx +86 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/Download.ts +120 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/DownloadUI.tsx +61 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/Encode.ts +138 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/EncodeUI.tsx +52 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/Encrypt.ts +125 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/EncryptUI.tsx +39 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/FFT.tsx +238 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/FFTUI.tsx +112 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/FeedbackService.ts +247 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/FeedbackServiceUI.tsx +35 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/Fetcher.tsx +232 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/FetcherUI.tsx +154 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/FileSource.tsx +171 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/Filter.tsx +18 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/FilterUI.tsx +89 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/FlatMap.ts +68 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/FlatMapUI.tsx +38 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/GameOfLife.ts +210 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/GameOfLifeRenderer.ts +182 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/GameOfLifeRendererUI.tsx +48 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/GameOfLifeUI.tsx +58 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/GatherUI.tsx +84 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/GifEncoder.tsx +651 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/GithubAPI.ts +294 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/GithubComponents.tsx +389 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/GithubSink.tsx +109 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/GithubSinkUI.tsx +143 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/GithubSource.tsx +86 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/GithubSourceUI.tsx +162 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/GroupBy.ts +11 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/HTTPUploader.ts +102 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/Hacker.ts +73 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/HackerUI.tsx +134 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/Hash.ts +134 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/HashUI.tsx +45 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/Hold.ts +288 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/HoldUI.tsx +43 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/HttpClient.ts +558 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/HttpClientUI.tsx +212 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/HttpUploaderTokenInjector.ts +149 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/IfService.ts +231 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/IfServiceUI.tsx +50 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/ImagePicker.tsx +118 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/Injector.ts +61 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/InjectorUI.tsx +389 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/Input.ts +214 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/InputUI.tsx +71 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/IteratorUI.tsx +59 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/LZCompress.ts +63 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/Limit.ts +39 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/LimitUI.tsx +41 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/LocalStorage.ts +65 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/LocalStorageUI.tsx +72 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/Map.ts +14 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/MapUI.tsx +334 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/MapUIModeAssist.tsx +61 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/MatchFilter.ts +11 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/MicrophoneMonitor.ts +190 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/MicrophoneMonitorUI.tsx +94 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/Monitor.tsx +15 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/MonitorUI.tsx +281 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/OllamaPrompt.ts +237 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/OllamaPromptUI/BusyIndicator.tsx +18 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/OllamaPromptUI/PromptInput.tsx +71 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/OllamaPromptUI/StreamingPreview.tsx +24 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/OllamaPromptUI/index.tsx +156 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/OpenAIPrompt.ts +254 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/OpenAIPromptUI.tsx +189 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/Output.tsx +145 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/OutputUI.tsx +118 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/PeerChatTemplate.json +43 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/PeerConnection.ts +260 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/PeerSocket.tsx +268 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/PeerSocketReceiverTemplate.json +29 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/PeerSocketUI.tsx +341 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/PlaceholderUI.tsx +72 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/QrCode.ts +63 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/QrCodeUI.tsx +197 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/Select.ts +40 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/SelectUI.tsx +41 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/ServiceBase.ts +50 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/SetRuntimeVariable.ts +65 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/SetRuntimeVariableUI.tsx +50 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/Sign.ts +182 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/SignUI.tsx +55 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/Smooth.ts +101 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/SmoothUI.tsx +50 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/Sort.ts +59 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/SortUI.tsx +61 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/Sound.ts +541 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/SoundUI.tsx +183 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/SpeechSynth.ts +56 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/SpeechSynthUI.tsx +108 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/Spotify.tsx +111 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/SpotifyAPI.ts +113 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/SpotifyComponents.tsx +104 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/SpotifyUI.tsx +186 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/Spread.ts +117 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/SpreadUI.tsx +38 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/Sql.ts +448 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/SqlInspector.tsx +332 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/SqlUI.tsx +226 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/State.ts +149 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/Stopper.ts +29 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/Switch.tsx +419 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/SwitchUI.tsx +171 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/TickRuleDelta.ts +101 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/TickRuleDeltaUI.tsx +56 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/Timeline.ts +709 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/TimelineUI/TimelinePanel.tsx +79 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/TimelineUI/TimelineRows.tsx +574 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/TimelineUI/Transport.tsx +141 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/TimelineUI/index.tsx +119 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/TimelineUI/model.ts +425 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/TimelineUI/parts.tsx +708 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/Timer.ts +15 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/TimerUI.tsx +213 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/TracksUI.tsx +81 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/TriggerPad.ts +108 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/TriggerPadUI.tsx +160 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/UuidGenerator.ts +74 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/UuidGeneratorUI.tsx +59 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/WaveformDisplay.tsx +104 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/WebsocketClient.ts +114 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/WebsocketClientUI.tsx +42 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/WorkflowBoardBuilder.ts +499 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/WorkflowBoardBuilderUI.tsx +521 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/XYPad.ts +68 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/XYPadUI.tsx +274 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/audio/audioHelper.ts +36 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/base/Batcher.ts +76 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/base/Buffer.ts +142 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/base/Filter.ts +125 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/base/GroupBy.ts +101 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/base/Map.ts +543 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/base/MatchFilter.ts +49 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/base/Monitor.ts +75 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/base/Reduce.ts +74 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/base/Timer.ts +342 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/base/bundle.js +7375 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/base/eval.ts +208 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/base/expressionEval.ts +223 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/base/helpers.ts +10 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/base/index.ts +37 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/camera/videoCapture.ts +78 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/changes-modes.ts +9 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/colorpalette.ts +26 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/detect/DetectionPreview.tsx +76 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/detect/mediapipe.ts +34 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/detect/models/README.md +14 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/detect/models/blaze_face_short_range.tflite +0 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/detect-modes.ts +7 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/encode-modes.ts +10 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/helpers.ts +300 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/http-client-methods.ts +10 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/sql-database.ts +594 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/sql-dump.ts +192 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/sql-inspect.ts +130 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/sql-inspector.css +155 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/sql-modes.ts +27 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/TESTING_APPROACH.md +169 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/__snapshots__/service-ui.future-enhancements.test.tsx.snap +5 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/audio-input-permissions.test.ts +42 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/audiolizeLanes.test.ts +70 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/browser-scopes.test.ts +305 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/cache-service.test.ts +53 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/camera-video-capture.test.ts +24 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/changes-service.test.ts +133 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/crypto-secrets.test.ts +196 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/detect-service.test.ts +119 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/download.test.ts +100 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/expressionEval.test.ts +117 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/fetcher-panel.test.tsx +35 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/fetcher-service.test.ts +80 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/filter-service.test.ts +37 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/github-secrets.test.ts +108 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/group-by-board.test.ts +42 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/hold-service.test.ts +199 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/http-client-service.test.ts +474 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/http-uploader-token-injector.test.ts +152 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/iterator-gather-service.test.ts +277 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/map-service.test.ts +1047 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/map-ui.test.tsx +70 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/nested-rhythm-board.test.ts +311 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/night-groove-board.test.ts +290 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/pad-groove-board.test.ts +230 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/peer-host-discovery.test.ts +77 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/peer-socket-discovery.test.ts +69 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/pipeline-names.test.ts +63 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/secret-canary.test.ts +121 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/service-modules.contract.test.ts +229 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/service-ui.advanced.test.tsx +670 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/service-ui.future-enhancements.test.tsx +219 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/service-ui.integration.test.tsx +432 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/service-ui.smoke.test.tsx +140 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/sound-service.test.ts +293 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/spread-service.test.ts +137 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/sql-dump.test.ts +172 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/sql-inspect.test.ts +163 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/sql-inspector.test.tsx +121 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/sql.test.ts +702 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/sub-service-in-block.test.tsx +84 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/switch-service.test.ts +254 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/timeline-board.test.ts +218 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/timeline-service.test.ts +1002 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/timeline-ui-model.test.ts +230 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/timeline-ui.test.tsx +371 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/timer-service.test.ts +902 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/tracks-service.test.ts +275 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/tracks-ui.test.tsx +126 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/triggerpad-recorder-board.test.ts +100 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/tests/uuid-generator-service.test.ts +214 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/timeline-core.ts +410 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/workflow-prompt/DefaultWorkflowBoard.ts +90 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/workflow-prompt/RefinementSession.ts +121 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/workflow-prompt/SystemPromptCatalog.ts +245 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/workflow-prompt/catalog/browser-service-specs.json +110 -0
- package/bundle/hkp-frontend/src/runtime/browser/services/workflow-prompt/catalog/runtime-capabilities.json +37 -0
- package/bundle/hkp-frontend/src/runtime/browser/tests/asset-service.test.ts +73 -0
- package/bundle/hkp-frontend/src/runtime/browser/tests/board-persistence.integration.test.tsx +391 -0
- package/bundle/hkp-frontend/src/runtime/browser/tests/browser-runtime.identity-binding.test.tsx +81 -0
- package/bundle/hkp-frontend/src/runtime/browser/tests/demo-boards.regression.test.tsx +386 -0
- package/bundle/hkp-frontend/src/runtime/browser/tests/failed-call-lifecycle.test.ts +56 -0
- package/bundle/hkp-frontend/src/runtime/browser/tests/http-client.integration.test.ts +146 -0
- package/bundle/hkp-frontend/src/runtime/browser/tests/nested-run.integration.test.ts +415 -0
- package/bundle/hkp-frontend/src/runtime/browser/tests/replay-next.test.ts +391 -0
- package/bundle/hkp-frontend/src/runtime/browser/tests/rerun.test.ts +71 -0
- package/bundle/hkp-frontend/src/runtime/browser/tests/run-no-input.test.ts +72 -0
- package/bundle/hkp-frontend/src/runtime/browser/tests/runtime-add.integration.test.tsx +256 -0
- package/bundle/hkp-frontend/src/runtime/browser/tests/runtime-operations.integration.test.tsx +310 -0
- package/bundle/hkp-frontend/src/runtime/browser/tests/runtime-processing.integration.test.ts +311 -0
- package/bundle/hkp-frontend/src/runtime/browser/tests/runtime-removal.integration.test.tsx +454 -0
- package/bundle/hkp-frontend/src/runtime/browser/tests/runtime-update.identity.integration.test.tsx +145 -0
- package/bundle/hkp-frontend/src/runtime/browser/tests/service-operations.integration.test.tsx +511 -0
- package/bundle/hkp-node/src/address.ts +72 -0
- package/bundle/hkp-node/src/assets.ts +582 -0
- package/bundle/hkp-node/src/auth.ts +369 -0
- package/bundle/hkp-node/src/callReport.ts +281 -0
- package/bundle/hkp-node/src/coordinator/auth.ts +85 -0
- package/bundle/hkp-node/src/coordinator/binaryFrame.ts +189 -0
- package/bundle/hkp-node/src/coordinator/boardStore.ts +0 -0
- package/bundle/hkp-node/src/coordinator/bridge.ts +242 -0
- package/bundle/hkp-node/src/coordinator/bridgeProtocol.ts +200 -0
- package/bundle/hkp-node/src/coordinator/coordinator.ts +594 -0
- package/bundle/hkp-node/src/coordinator/facadeAccess.ts +426 -0
- package/bundle/hkp-node/src/coordinator/fileBoardStore.ts +159 -0
- package/bundle/hkp-node/src/coordinator/frameLimits.ts +41 -0
- package/bundle/hkp-node/src/coordinator/index.ts +12 -0
- package/bundle/hkp-node/src/coordinator/join.ts +62 -0
- package/bundle/hkp-node/src/coordinator/logStore.ts +335 -0
- package/bundle/hkp-node/src/coordinator/members.ts +133 -0
- package/bundle/hkp-node/src/coordinator/mount.ts +124 -0
- package/bundle/hkp-node/src/coordinator/participantProtocol.ts +169 -0
- package/bundle/hkp-node/src/coordinator/participants.ts +666 -0
- package/bundle/hkp-node/src/coordinator/router.ts +416 -0
- package/bundle/hkp-node/src/coordinator/session.ts +1610 -0
- package/bundle/hkp-node/src/coordinator/types.ts +95 -0
- package/bundle/hkp-node/src/coordinatorLinks.ts +712 -0
- package/bundle/hkp-node/src/credentialedFetch.ts +122 -0
- package/bundle/hkp-node/src/egress.ts +344 -0
- package/bundle/hkp-node/src/expressionEval.ts +223 -0
- package/bundle/hkp-node/src/index.ts +470 -0
- package/bundle/hkp-node/src/instance.ts +172 -0
- package/bundle/hkp-node/src/mounts.ts +296 -0
- package/bundle/hkp-node/src/origins.ts +201 -0
- package/bundle/hkp-node/src/reset.ts +187 -0
- package/bundle/hkp-node/src/runtime.ts +1345 -0
- package/bundle/hkp-node/src/secrets.ts +292 -0
- package/bundle/hkp-node/src/server.ts +2120 -0
- package/bundle/hkp-node/src/services/asset.ts +150 -0
- package/bundle/hkp-node/src/services/claudeModels.ts +370 -0
- package/bundle/hkp-node/src/services/communication-dispatcher.ts +735 -0
- package/bundle/hkp-node/src/services/conversations.ts +914 -0
- package/bundle/hkp-node/src/services/database.ts +386 -0
- package/bundle/hkp-node/src/services/document-extract.ts +575 -0
- package/bundle/hkp-node/src/services/expression.ts +417 -0
- package/bundle/hkp-node/src/services/fileStore.ts +369 -0
- package/bundle/hkp-node/src/services/filesystem.ts +280 -0
- package/bundle/hkp-node/src/services/gather.ts +335 -0
- package/bundle/hkp-node/src/services/hold.ts +275 -0
- package/bundle/hkp-node/src/services/http-client.ts +525 -0
- package/bundle/hkp-node/src/services/http-server.ts +1222 -0
- package/bundle/hkp-node/src/services/imap-email.ts +627 -0
- package/bundle/hkp-node/src/services/injector.ts +135 -0
- package/bundle/hkp-node/src/services/iterator.ts +203 -0
- package/bundle/hkp-node/src/services/join.ts +226 -0
- package/bundle/hkp-node/src/services/map.ts +632 -0
- package/bundle/hkp-node/src/services/monitor.ts +128 -0
- package/bundle/hkp-node/src/services/nested-pipeline.ts +405 -0
- package/bundle/hkp-node/src/services/peer-server.ts +268 -0
- package/bundle/hkp-node/src/services/queue-store.ts +301 -0
- package/bundle/hkp-node/src/services/queue.ts +316 -0
- package/bundle/hkp-node/src/services/recordStore.ts +341 -0
- package/bundle/hkp-node/src/services/rss-parse.ts +281 -0
- package/bundle/hkp-node/src/services/rss.ts +392 -0
- package/bundle/hkp-node/src/services/smtp-email.ts +398 -0
- package/bundle/hkp-node/src/services/spread.ts +153 -0
- package/bundle/hkp-node/src/services/sql-dump.ts +192 -0
- package/bundle/hkp-node/src/services/sql.ts +429 -0
- package/bundle/hkp-node/src/services/stopper.ts +69 -0
- package/bundle/hkp-node/src/services/storage.ts +332 -0
- package/bundle/hkp-node/src/services/store.ts +609 -0
- package/bundle/hkp-node/src/services/stream.ts +367 -0
- package/bundle/hkp-node/src/services/sub-service.ts +600 -0
- package/bundle/hkp-node/src/services/telegram-listener.ts +263 -0
- package/bundle/hkp-node/src/services/telegram-sender.ts +136 -0
- package/bundle/hkp-node/src/services/text-generation.ts +1249 -0
- package/bundle/hkp-node/src/services/timer.ts +273 -0
- package/bundle/hkp-node/src/services/tracks.ts +381 -0
- package/bundle/hkp-node/src/services/websocket-reader.ts +364 -0
- package/bundle/hkp-node/src/types.ts +465 -0
- package/bundle/hkp-node/src/wsTickets.ts +77 -0
- package/bundle/hkp-node/src/yas.ts +95 -0
- package/bundle/hkp-python/src/hkp/__init__.py +1 -0
- package/bundle/hkp-python/src/hkp/__main__.py +241 -0
- package/bundle/hkp-python/src/hkp/address.py +56 -0
- package/bundle/hkp-python/src/hkp/assets.py +439 -0
- package/bundle/hkp-python/src/hkp/auth.py +252 -0
- package/bundle/hkp-python/src/hkp/binary_frame.py +100 -0
- package/bundle/hkp-python/src/hkp/call_report.py +201 -0
- package/bundle/hkp-python/src/hkp/coordinator_links.py +667 -0
- package/bundle/hkp-python/src/hkp/data.py +118 -0
- package/bundle/hkp-python/src/hkp/egress.py +310 -0
- package/bundle/hkp-python/src/hkp/mount.py +46 -0
- package/bundle/hkp-python/src/hkp/mounts.py +288 -0
- package/bundle/hkp-python/src/hkp/origins.py +169 -0
- package/bundle/hkp-python/src/hkp/runtime.py +1131 -0
- package/bundle/hkp-python/src/hkp/secrets.py +261 -0
- package/bundle/hkp-python/src/hkp/server.py +1714 -0
- package/bundle/hkp-python/src/hkp/services/__init__.py +0 -0
- package/bundle/hkp-python/src/hkp/services/asset.py +123 -0
- package/bundle/hkp-python/src/hkp/services/audio_encode.py +190 -0
- package/bundle/hkp-python/src/hkp/services/expression.py +942 -0
- package/bundle/hkp-python/src/hkp/services/gather.py +266 -0
- package/bundle/hkp-python/src/hkp/services/hold.py +246 -0
- package/bundle/hkp-python/src/hkp/services/http_client.py +444 -0
- package/bundle/hkp-python/src/hkp/services/http_server.py +790 -0
- package/bundle/hkp-python/src/hkp/services/iterator.py +141 -0
- package/bundle/hkp-python/src/hkp/services/join.py +173 -0
- package/bundle/hkp-python/src/hkp/services/map_service.py +452 -0
- package/bundle/hkp-python/src/hkp/services/monitor.py +99 -0
- package/bundle/hkp-python/src/hkp/services/nested_pipeline.py +278 -0
- package/bundle/hkp-python/src/hkp/services/skill_router.py +388 -0
- package/bundle/hkp-python/src/hkp/services/speech_to_text.py +186 -0
- package/bundle/hkp-python/src/hkp/services/spread.py +114 -0
- package/bundle/hkp-python/src/hkp/services/stopper.py +59 -0
- package/bundle/hkp-python/src/hkp/services/sub_service.py +417 -0
- package/bundle/hkp-python/src/hkp/services/text_generation.py +498 -0
- package/bundle/hkp-python/src/hkp/services/text_to_speech.py +237 -0
- package/bundle/hkp-python/src/hkp/services/timer.py +267 -0
- package/bundle/hkp-python/src/hkp/services/tracks.py +251 -0
- package/bundle/hkp-python/src/hkp/types.py +412 -0
- package/bundle/hkp-python/src/hkp/ws_tickets.py +54 -0
- package/bundle/hkp-python/src/hkp/yas.py +192 -0
- package/bundle/hkp-rt/lib/CMakeLists.txt +193 -0
- package/bundle/hkp-rt/lib/hkp-bundles/CMakeLists.txt +55 -0
- package/bundle/hkp-rt/lib/hkp-bundles/plugin_register.cpp +14 -0
- package/bundle/hkp-rt/lib/include/app.h +201 -0
- package/bundle/hkp-rt/lib/include/assets.h +239 -0
- package/bundle/hkp-rt/lib/include/auth.h +167 -0
- package/bundle/hkp-rt/lib/include/call_report.h +197 -0
- package/bundle/hkp-rt/lib/include/coordinator_links.h +127 -0
- package/bundle/hkp-rt/lib/include/discovery_hooks.h +29 -0
- package/bundle/hkp-rt/lib/include/egress.h +379 -0
- package/bundle/hkp-rt/lib/include/files.h +275 -0
- package/bundle/hkp-rt/lib/include/log_entry.h +131 -0
- package/bundle/hkp-rt/lib/include/mounts.h +139 -0
- package/bundle/hkp-rt/lib/include/origins.h +363 -0
- package/bundle/hkp-rt/lib/include/registry.h +119 -0
- package/bundle/hkp-rt/lib/include/secrets.h +414 -0
- package/bundle/hkp-rt/lib/include/server.h +122 -0
- package/bundle/hkp-rt/lib/include/service.h +190 -0
- package/bundle/hkp-rt/lib/include/slot_store.h +41 -0
- package/bundle/hkp-rt/lib/include/standalone_config.h +253 -0
- package/bundle/hkp-rt/lib/include/types/audio.h +3 -0
- package/bundle/hkp-rt/lib/include/types/data.h +152 -0
- package/bundle/hkp-rt/lib/include/types/message.h +56 -0
- package/bundle/hkp-rt/lib/include/types/models.h +154 -0
- package/bundle/hkp-rt/lib/include/types/owns_me.h +19 -0
- package/bundle/hkp-rt/lib/include/types/ringbuffer.h +86 -0
- package/bundle/hkp-rt/lib/include/types/types.h +33 -0
- package/bundle/hkp-rt/lib/include/types/validation.h +205 -0
- package/bundle/hkp-rt/lib/src/address.h +88 -0
- package/bundle/hkp-rt/lib/src/app.cpp +591 -0
- package/bundle/hkp-rt/lib/src/assets.cpp +686 -0
- package/bundle/hkp-rt/lib/src/binary_frame.h +154 -0
- package/bundle/hkp-rt/lib/src/common/expression.h +1481 -0
- package/bundle/hkp-rt/lib/src/common/inja.h +36 -0
- package/bundle/hkp-rt/lib/src/common/link_socket.cpp +437 -0
- package/bundle/hkp-rt/lib/src/common/link_socket.h +97 -0
- package/bundle/hkp-rt/lib/src/common/realtime_wakeup.h +120 -0
- package/bundle/hkp-rt/lib/src/common/stream_broadcast.h +250 -0
- package/bundle/hkp-rt/lib/src/common/string_util.h +31 -0
- package/bundle/hkp-rt/lib/src/common/websocket_client_session.cpp +217 -0
- package/bundle/hkp-rt/lib/src/common/websocket_client_session.h +37 -0
- package/bundle/hkp-rt/lib/src/common/websocket_config.h +119 -0
- package/bundle/hkp-rt/lib/src/common/websocket_protocol.h +32 -0
- package/bundle/hkp-rt/lib/src/common/websocket_server.cpp +82 -0
- package/bundle/hkp-rt/lib/src/common/websocket_server.h +57 -0
- package/bundle/hkp-rt/lib/src/common/websocket_server_listener.cpp +115 -0
- package/bundle/hkp-rt/lib/src/common/websocket_server_listener.h +35 -0
- package/bundle/hkp-rt/lib/src/common/websocket_server_session.cpp +172 -0
- package/bundle/hkp-rt/lib/src/common/websocket_server_session.h +53 -0
- package/bundle/hkp-rt/lib/src/common.h +13 -0
- package/bundle/hkp-rt/lib/src/coordinator_links.cpp +1142 -0
- package/bundle/hkp-rt/lib/src/discovery/discovery.cpp +707 -0
- package/bundle/hkp-rt/lib/src/discovery/discovery.h +89 -0
- package/bundle/hkp-rt/lib/src/http/auth.cpp +569 -0
- package/bundle/hkp-rt/lib/src/http/front_door.cpp +518 -0
- package/bundle/hkp-rt/lib/src/http/front_door.h +131 -0
- package/bundle/hkp-rt/lib/src/http/server.cpp +1539 -0
- package/bundle/hkp-rt/lib/src/http/ws_tickets.h +125 -0
- package/bundle/hkp-rt/lib/src/mount.h +63 -0
- package/bundle/hkp-rt/lib/src/process_context.h +335 -0
- package/bundle/hkp-rt/lib/src/process_depth.h +20 -0
- package/bundle/hkp-rt/lib/src/registry.cpp +233 -0
- package/bundle/hkp-rt/lib/src/runtime.cpp +986 -0
- package/bundle/hkp-rt/lib/src/runtime.h +225 -0
- package/bundle/hkp-rt/lib/src/runtime_host.h +180 -0
- package/bundle/hkp-rt/lib/src/service.cpp +315 -0
- package/bundle/hkp-rt/lib/src/services/asset.h +185 -0
- package/bundle/hkp-rt/lib/src/services/audio_encode.cpp +372 -0
- package/bundle/hkp-rt/lib/src/services/audio_encode.h +97 -0
- package/bundle/hkp-rt/lib/src/services/cache.h +362 -0
- package/bundle/hkp-rt/lib/src/services/cache_subservices.h +463 -0
- package/bundle/hkp-rt/lib/src/services/core_audio.cpp +384 -0
- package/bundle/hkp-rt/lib/src/services/core_audio.h +22 -0
- package/bundle/hkp-rt/lib/src/services/core_input.h +337 -0
- package/bundle/hkp-rt/lib/src/services/core_output.h +370 -0
- package/bundle/hkp-rt/lib/src/services/ffmpeg/detail.cpp +749 -0
- package/bundle/hkp-rt/lib/src/services/ffmpeg/detail.h +29 -0
- package/bundle/hkp-rt/lib/src/services/ffmpeg/ffmpeg.cpp +193 -0
- package/bundle/hkp-rt/lib/src/services/ffmpeg/ffmpeg.h +40 -0
- package/bundle/hkp-rt/lib/src/services/ffmpeg/types.h +120 -0
- package/bundle/hkp-rt/lib/src/services/fft_service.h +190 -0
- package/bundle/hkp-rt/lib/src/services/filesystem.h +309 -0
- package/bundle/hkp-rt/lib/src/services/filter.h +208 -0
- package/bundle/hkp-rt/lib/src/services/gather.h +272 -0
- package/bundle/hkp-rt/lib/src/services/hold.h +304 -0
- package/bundle/hkp-rt/lib/src/services/http_client.cpp +347 -0
- package/bundle/hkp-rt/lib/src/services/http_client.h +67 -0
- package/bundle/hkp-rt/lib/src/services/http_client_impl.h +45 -0
- package/bundle/hkp-rt/lib/src/services/http_client_template_examples.md +202 -0
- package/bundle/hkp-rt/lib/src/services/http_server/http_listener.cpp +130 -0
- package/bundle/hkp-rt/lib/src/services/http_server/http_listener.h +33 -0
- package/bundle/hkp-rt/lib/src/services/http_server/http_server.cpp +351 -0
- package/bundle/hkp-rt/lib/src/services/http_server/http_server.h +79 -0
- package/bundle/hkp-rt/lib/src/services/http_server/http_server_impl.cpp +230 -0
- package/bundle/hkp-rt/lib/src/services/http_server/http_server_impl.h +64 -0
- package/bundle/hkp-rt/lib/src/services/http_server/http_server_subservices.cpp +950 -0
- package/bundle/hkp-rt/lib/src/services/http_server/http_server_subservices.h +253 -0
- package/bundle/hkp-rt/lib/src/services/http_server/http_session.cpp +495 -0
- package/bundle/hkp-rt/lib/src/services/http_server/http_session.h +169 -0
- package/bundle/hkp-rt/lib/src/services/http_server/http_stream_listener.cpp +270 -0
- package/bundle/hkp-rt/lib/src/services/http_server/http_stream_listener.h +104 -0
- package/bundle/hkp-rt/lib/src/services/http_server/request_decode.h +164 -0
- package/bundle/hkp-rt/lib/src/services/if_service.h +210 -0
- package/bundle/hkp-rt/lib/src/services/ifft_service.h +198 -0
- package/bundle/hkp-rt/lib/src/services/injector.h +162 -0
- package/bundle/hkp-rt/lib/src/services/iterator.h +201 -0
- package/bundle/hkp-rt/lib/src/services/map.h +641 -0
- package/bundle/hkp-rt/lib/src/services/monitor.h +161 -0
- package/bundle/hkp-rt/lib/src/services/mp3_frames.h +119 -0
- package/bundle/hkp-rt/lib/src/services/mp4_to_wav.cpp +281 -0
- package/bundle/hkp-rt/lib/src/services/mp4_to_wav.h +263 -0
- package/bundle/hkp-rt/lib/src/services/peer_server/peer_registry.cpp +88 -0
- package/bundle/hkp-rt/lib/src/services/peer_server/peer_registry.h +43 -0
- package/bundle/hkp-rt/lib/src/services/peer_server/peer_server.h +163 -0
- package/bundle/hkp-rt/lib/src/services/peer_server/peer_server_listener.cpp +133 -0
- package/bundle/hkp-rt/lib/src/services/peer_server/peer_server_listener.h +46 -0
- package/bundle/hkp-rt/lib/src/services/peer_server/peer_server_session.cpp +165 -0
- package/bundle/hkp-rt/lib/src/services/peer_server/peer_server_session.h +44 -0
- package/bundle/hkp-rt/lib/src/services/peer_server/peer_ws_session.cpp +184 -0
- package/bundle/hkp-rt/lib/src/services/peer_server/peer_ws_session.h +55 -0
- package/bundle/hkp-rt/lib/src/services/receiver_session.h +172 -0
- package/bundle/hkp-rt/lib/src/services/root_certificates.h +3959 -0
- package/bundle/hkp-rt/lib/src/services/sha256_openssl.h +49 -0
- package/bundle/hkp-rt/lib/src/services/speech_http.h +306 -0
- package/bundle/hkp-rt/lib/src/services/speech_to_text.cpp +595 -0
- package/bundle/hkp-rt/lib/src/services/speech_to_text.h +69 -0
- package/bundle/hkp-rt/lib/src/services/static.cpp +45 -0
- package/bundle/hkp-rt/lib/src/services/static.h +41 -0
- package/bundle/hkp-rt/lib/src/services/stopper.h +72 -0
- package/bundle/hkp-rt/lib/src/services/sub_service.h +286 -0
- package/bundle/hkp-rt/lib/src/services/text_generation.cpp +1132 -0
- package/bundle/hkp-rt/lib/src/services/text_generation.h +71 -0
- package/bundle/hkp-rt/lib/src/services/text_to_speech.cpp +853 -0
- package/bundle/hkp-rt/lib/src/services/text_to_speech.h +97 -0
- package/bundle/hkp-rt/lib/src/services/timer.h +300 -0
- package/bundle/hkp-rt/lib/src/services/tracks.h +397 -0
- package/bundle/hkp-rt/lib/src/services/tts_inflect.cpp +149 -0
- package/bundle/hkp-rt/lib/src/services/tts_inflect.h +82 -0
- package/bundle/hkp-rt/lib/src/services/wav_reader.h +213 -0
- package/bundle/hkp-rt/lib/src/services/websocket_client_service.h +182 -0
- package/bundle/hkp-rt/lib/src/services/websocket_reader.h +148 -0
- package/bundle/hkp-rt/lib/src/services/websocket_server_service.h +196 -0
- package/bundle/hkp-rt/lib/src/services/websocket_writer.cpp +641 -0
- package/bundle/hkp-rt/lib/src/services/websocket_writer.h +117 -0
- package/bundle/hkp-rt/lib/src/sub_runtime.cpp +234 -0
- package/bundle/hkp-rt/lib/src/sub_runtime.h +160 -0
- package/bundle/hkp-rt/lib/src/types/data.cpp +237 -0
- package/bundle/hkp-rt/lib/src/types/message.cpp +148 -0
- package/bundle/hkp-rt/lib/src/types/models.cpp +83 -0
- package/bundle/hkp-rt/lib/src/types/ringbuffer.cpp +336 -0
- package/bundle/hkp-rt/lib/src/uuid.h +12 -0
- package/bundle/readymade-mcp-commits.json +5 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +79 -0
- package/dist/git.d.ts +8 -0
- package/dist/git.d.ts.map +1 -0
- package/dist/git.js +53 -0
- package/dist/http.d.ts +17 -0
- package/dist/http.d.ts.map +1 -0
- package/dist/http.js +130 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +6 -0
- package/dist/repository.d.ts +49 -0
- package/dist/repository.d.ts.map +1 -0
- package/dist/repository.js +452 -0
- package/dist/schemas.d.ts +156 -0
- package/dist/schemas.d.ts.map +1 -0
- package/dist/schemas.js +43 -0
- package/dist/server.d.ts +4 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +83 -0
- package/dist/types.d.ts +63 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +20 -0
- package/dist/validation.d.ts +9 -0
- package/dist/validation.d.ts.map +1 -0
- package/dist/validation.js +101 -0
- package/package.json +52 -4
|
@@ -0,0 +1,1222 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Service Documentation
|
|
3
|
+
* Service ID: http-server-subservices
|
|
4
|
+
* Service Name: HttpServerSubservices
|
|
5
|
+
* Runtime: hkp-node
|
|
6
|
+
* Modes: none — the entry points a board declares say what it is for
|
|
7
|
+
* Key Config: bypass/onProcess/onRequest (the endpoint is assigned, not configured)
|
|
8
|
+
* IO: in=request envelope -> out=response envelope
|
|
9
|
+
* Arrays: not primary
|
|
10
|
+
* Binary: depends on endpoint + nested services
|
|
11
|
+
* MixedData: not native in runtime
|
|
12
|
+
*
|
|
13
|
+
* **There are two ways in, and a board names the ones it uses.** `onRequest` is
|
|
14
|
+
* a caller arriving; `onProcess` is a pass of the board's own chain. Each is a
|
|
15
|
+
* pipeline of its own, because they are different jobs:
|
|
16
|
+
*
|
|
17
|
+
* { "onRequest": [ … ] } requests; a pass goes through
|
|
18
|
+
* { "onProcess": [ … ] } passes; the board answers
|
|
19
|
+
* { "onProcess": [ … ], "onRequest": [ … ] } both, separately
|
|
20
|
+
* { "pipeline": [ … ] } one pipeline, entered from both
|
|
21
|
+
*
|
|
22
|
+
* **Declaring `onRequest` is what takes the answer away from the chain.** With
|
|
23
|
+
* one, that pipeline is the handler and what it returns is what the caller
|
|
24
|
+
* gets; the services after this one still run — that is where a board acts on
|
|
25
|
+
* having served a request — but after the answer is decided. Without one, the
|
|
26
|
+
* request flows into the services after this one and whatever they return is
|
|
27
|
+
* the answer, which is the inversion of control this service is built around
|
|
28
|
+
* and what makes a board able to answer an endpoint at all.
|
|
29
|
+
*
|
|
30
|
+
* So an endpoint can have something to run on a pass without silently becoming
|
|
31
|
+
* an HTTP handler, which is what a single unnamed pipeline could not express:
|
|
32
|
+
* having one at all decided who answered.
|
|
33
|
+
*
|
|
34
|
+
* **A value does not survive between the two on its own.** They are separate
|
|
35
|
+
* pipelines, and a pass ends where it ends — so an endpoint that publishes what
|
|
36
|
+
* the board last handed it holds that value in a slot (see `hold`), in cells
|
|
37
|
+
* this service owns and lends to both of its pipelines. Legacy boards say the
|
|
38
|
+
* same thing as `mode: "process_on_data"`, which is this arrangement built in
|
|
39
|
+
* and unnamed; `entryFor` is where the older spellings are read.
|
|
40
|
+
*
|
|
41
|
+
* **What the handler answers with is the envelope, read backwards.** A value
|
|
42
|
+
* carrying `meta.status` beside `body` or `binary` sets the status, the content
|
|
43
|
+
* type and the headers — the status being what distinguishes an answer from a
|
|
44
|
+
* request passed through unchanged, which is the same shape. Anything else is
|
|
45
|
+
* answered by its own type, which is JSON for everything a board returned
|
|
46
|
+
* before this existed. That is what lets one board
|
|
47
|
+
* serve a feed as XML and the next serve an audio file as audio — without which
|
|
48
|
+
* an endpoint can only ever say `application/json`, whatever it is holding.
|
|
49
|
+
*
|
|
50
|
+
* A `body` that is an `hkp-asset://<id>` reference is answered with that
|
|
51
|
+
* asset's content, resolved from the runtime's asset store as the answer is
|
|
52
|
+
* written, and with the asset's media type unless `meta` names one. A page an
|
|
53
|
+
* endpoint serves is therefore an asset of the board rather than a string in
|
|
54
|
+
* this service's state, and editing it changes the next response.
|
|
55
|
+
*
|
|
56
|
+
* Byte answers are seekable: `Range` is honoured against the bytes the handler
|
|
57
|
+
* produced, because a player dragging a scrubber asks for one and a server that
|
|
58
|
+
* ignores it re-sends the whole file each time.
|
|
59
|
+
*
|
|
60
|
+
* **A stream is the one answer that keeps coming** (see `./stream`). With
|
|
61
|
+
* `stream: { path, … }` declared, a GET on that path is a listener to keep, and
|
|
62
|
+
* what every pass produces is written to all of them. What a pass is — the
|
|
63
|
+
* previous service's output, a message a `websocket-reader` received, the
|
|
64
|
+
* result of the runtime before — is the board's business, not the endpoint's.
|
|
65
|
+
*/
|
|
66
|
+
import { IncomingMessage, ServerResponse } from "node:http";
|
|
67
|
+
|
|
68
|
+
import { newRun } from "../runtime";
|
|
69
|
+
import { MountContext, MountHandle } from "../mounts";
|
|
70
|
+
import { parseAssetRef } from "../assets";
|
|
71
|
+
import {
|
|
72
|
+
HostedService,
|
|
73
|
+
JsonRecord,
|
|
74
|
+
ProcessContext,
|
|
75
|
+
RuntimeHost,
|
|
76
|
+
RuntimeScope,
|
|
77
|
+
ServiceConfiguration,
|
|
78
|
+
ServiceCreator,
|
|
79
|
+
ServiceRegistryEntry,
|
|
80
|
+
SlotStore,
|
|
81
|
+
} from "../types";
|
|
82
|
+
import { NestedPipeline } from "./nested-pipeline";
|
|
83
|
+
import {
|
|
84
|
+
StreamBroadcast,
|
|
85
|
+
StreamConfig,
|
|
86
|
+
StreamListener,
|
|
87
|
+
answerRangeProbe,
|
|
88
|
+
boundedRange,
|
|
89
|
+
parseStreamConfig,
|
|
90
|
+
sameStream,
|
|
91
|
+
streamBytes,
|
|
92
|
+
streamState,
|
|
93
|
+
} from "./stream";
|
|
94
|
+
|
|
95
|
+
export const httpServerSubservicesDescriptor: ServiceRegistryEntry = {
|
|
96
|
+
serviceId: "http-server-subservices",
|
|
97
|
+
serviceName: "HttpServerSubservices",
|
|
98
|
+
capabilities: ["subservices"],
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* How a board that predates named entry points says which side enters the one
|
|
103
|
+
* pipeline it declares. Still accepted, and still reported back to a board that
|
|
104
|
+
* arrived carrying it; `entryFor` is the whole of what it means now.
|
|
105
|
+
*
|
|
106
|
+
* - `process_on_session` — requests only; data from the outer chain passes
|
|
107
|
+
* through untouched.
|
|
108
|
+
* - `process_on_data` — data from the outer chain is stored and served back to
|
|
109
|
+
* requests verbatim; the nested pipeline is not used.
|
|
110
|
+
* - `process_on_both` — both entry points run the one pipeline, which is why a
|
|
111
|
+
* service inside it can only tell a request from a pass by looking at what it
|
|
112
|
+
* was given.
|
|
113
|
+
*/
|
|
114
|
+
type HttpServerMode =
|
|
115
|
+
| "process_on_session"
|
|
116
|
+
| "process_on_data"
|
|
117
|
+
| "process_on_both";
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* The two ways into this service, named.
|
|
121
|
+
*
|
|
122
|
+
* `onProcess` is a pass of the board's own chain arriving; `onRequest` is a
|
|
123
|
+
* caller. They are declared as separate pipelines because they are separate
|
|
124
|
+
* jobs — which is what the `mode` flag above was standing in for, badly: one
|
|
125
|
+
* unnamed list could not say what it was for, so a flag beside it had to.
|
|
126
|
+
*/
|
|
127
|
+
type EntryName = "onProcess" | "onRequest";
|
|
128
|
+
|
|
129
|
+
const ENTRY_NAMES: EntryName[] = ["onProcess", "onRequest"];
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* An incoming request as MixedData: JSON metadata plus the raw body. Mirrors
|
|
133
|
+
* hkp-rt's body-carrying HTTP service so the same pipeline works on either.
|
|
134
|
+
*/
|
|
135
|
+
type MixedRequest = {
|
|
136
|
+
meta: JsonRecord;
|
|
137
|
+
/**
|
|
138
|
+
* The raw body, for content whose type does not say what the bytes mean.
|
|
139
|
+
* Absent once the body has been decoded into `body` — keeping both would
|
|
140
|
+
* double the payload to restate what the decoded value already carries — and
|
|
141
|
+
* absent entirely when the request had no body.
|
|
142
|
+
*/
|
|
143
|
+
binary?: Uint8Array;
|
|
144
|
+
/**
|
|
145
|
+
* The body decoded according to its content type. Present for the cases a
|
|
146
|
+
* board can act on directly — a JSON webhook, a form post — instead of
|
|
147
|
+
* needing bytes decoded by hand. Absent when the type is not textual, when
|
|
148
|
+
* there is no body, or when it did not parse.
|
|
149
|
+
*/
|
|
150
|
+
body?: unknown;
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
/** Content type with any parameters (`; charset=…`) stripped, lower-cased. */
|
|
154
|
+
function mediaType(contentType: string | undefined): string {
|
|
155
|
+
return (contentType ?? "").split(";")[0].trim().toLowerCase();
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Decode a body for the content types where a board would otherwise be stuck
|
|
160
|
+
* with raw bytes. Returns undefined when there is nothing sensible to produce,
|
|
161
|
+
* which includes malformed input: a public endpoint receives whatever it is
|
|
162
|
+
* given, and a parse failure should leave the raw bytes to inspect rather than
|
|
163
|
+
* fail the request.
|
|
164
|
+
*/
|
|
165
|
+
function decodeBody(
|
|
166
|
+
binary: Uint8Array,
|
|
167
|
+
contentType: string | undefined,
|
|
168
|
+
): unknown {
|
|
169
|
+
if (binary.length === 0) {
|
|
170
|
+
return undefined;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const type = mediaType(contentType);
|
|
174
|
+
const asText = () => Buffer.from(binary).toString("utf8");
|
|
175
|
+
|
|
176
|
+
if (type === "application/json" || type.endsWith("+json")) {
|
|
177
|
+
try {
|
|
178
|
+
return JSON.parse(asText());
|
|
179
|
+
} catch {
|
|
180
|
+
return undefined;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (type === "application/x-www-form-urlencoded") {
|
|
185
|
+
const fields: JsonRecord = {};
|
|
186
|
+
for (const [key, value] of new URLSearchParams(asText())) {
|
|
187
|
+
fields[key] = value;
|
|
188
|
+
}
|
|
189
|
+
return fields;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (type.startsWith("text/")) {
|
|
193
|
+
return asText();
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
return undefined;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
class RequestTooLargeError extends Error {
|
|
200
|
+
constructor() {
|
|
201
|
+
super("Request body too large");
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function header(req: IncomingMessage, name: string): string | undefined {
|
|
206
|
+
const value = req.headers[name];
|
|
207
|
+
return Array.isArray(value) ? value[0] : value;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/** Extracts `filename="…"` from a content-disposition header, if present. */
|
|
211
|
+
function filenameFromDisposition(
|
|
212
|
+
disposition: string | undefined,
|
|
213
|
+
): string | undefined {
|
|
214
|
+
if (!disposition) {
|
|
215
|
+
return undefined;
|
|
216
|
+
}
|
|
217
|
+
const match = disposition.match(/filename\*?=(?:UTF-8'')?"?([^";]+)"?/i);
|
|
218
|
+
return match ? decodeURIComponent(match[1]) : undefined;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* What a pipeline may answer with, as against what it was given.
|
|
223
|
+
*
|
|
224
|
+
* The same envelope a request arrives in — `meta` beside `body` or `binary` —
|
|
225
|
+
* read in the other direction, which is also the shape `http-client` hands back
|
|
226
|
+
* for a call it made. So a board that proxies one endpoint to another passes
|
|
227
|
+
* the response along untouched, and a board that builds one writes the shape it
|
|
228
|
+
* already reads.
|
|
229
|
+
*
|
|
230
|
+
* Without an envelope the answer is JSON, which is what every board written
|
|
231
|
+
* before this got and still gets. The single exception is raw bytes, which are
|
|
232
|
+
* sent as bytes: JSON encoding them produced an object of numbered keys, and no
|
|
233
|
+
* board wanted that.
|
|
234
|
+
*/
|
|
235
|
+
type MixedResponse = {
|
|
236
|
+
meta?: JsonRecord;
|
|
237
|
+
binary?: Uint8Array;
|
|
238
|
+
body?: unknown;
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
/** A response, reduced to the three things writing one needs. */
|
|
242
|
+
type Answer = {
|
|
243
|
+
status: number;
|
|
244
|
+
headers: JsonRecord;
|
|
245
|
+
/** The bytes to send; text and JSON are encoded before they get here. */
|
|
246
|
+
payload: Uint8Array;
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
function isBytes(value: unknown): value is Uint8Array {
|
|
250
|
+
return value instanceof Uint8Array;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Reads a value as a response envelope, or returns null when it is not one.
|
|
255
|
+
*
|
|
256
|
+
* **A status is what tells the two apart.** A request envelope and a response
|
|
257
|
+
* envelope are the same shape, and a pipeline that passes its input through
|
|
258
|
+
* returns the request — so reading any `meta` as a response would answer the
|
|
259
|
+
* caller with the content type they sent, and silently change what every board
|
|
260
|
+
* written before this did. A request has no status and a response always has
|
|
261
|
+
* one, which makes `meta.status` the one field that cannot be a coincidence.
|
|
262
|
+
*
|
|
263
|
+
* It is also what a proxied answer already carries: `http-client` reports a
|
|
264
|
+
* response as `{meta: {status, headers, contentType}, …}`, so a board that
|
|
265
|
+
* calls one endpoint and answers with what it got does nothing special.
|
|
266
|
+
*/
|
|
267
|
+
function asResponseEnvelope(value: unknown): MixedResponse | null {
|
|
268
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
269
|
+
return null;
|
|
270
|
+
}
|
|
271
|
+
const candidate = value as MixedResponse;
|
|
272
|
+
const meta = candidate.meta;
|
|
273
|
+
if (!meta || typeof meta !== "object" || Array.isArray(meta)) {
|
|
274
|
+
return null;
|
|
275
|
+
}
|
|
276
|
+
if (typeof meta.status !== "number") {
|
|
277
|
+
return null;
|
|
278
|
+
}
|
|
279
|
+
const carries = "binary" in candidate || "body" in candidate;
|
|
280
|
+
return carries ? candidate : null;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/** Header map from an envelope's `meta.headers`, names lower-cased. */
|
|
284
|
+
function envelopeHeaders(meta: JsonRecord): JsonRecord {
|
|
285
|
+
const declared = meta.headers;
|
|
286
|
+
if (!declared || typeof declared !== "object" || Array.isArray(declared)) {
|
|
287
|
+
return {};
|
|
288
|
+
}
|
|
289
|
+
const headers: JsonRecord = {};
|
|
290
|
+
for (const [name, value] of Object.entries(declared as JsonRecord)) {
|
|
291
|
+
if (value !== null && value !== undefined) {
|
|
292
|
+
headers[name.toLowerCase()] = String(value);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
return headers;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* The answer a value stands for: an envelope's status, headers and payload, or
|
|
300
|
+
* the type-led default for a bare value.
|
|
301
|
+
*/
|
|
302
|
+
function toAnswer(value: unknown): Answer {
|
|
303
|
+
const envelope = asResponseEnvelope(value);
|
|
304
|
+
if (envelope) {
|
|
305
|
+
const meta = envelope.meta ?? {};
|
|
306
|
+
const headers = envelopeHeaders(meta);
|
|
307
|
+
const status = typeof meta.status === "number" ? meta.status : 200;
|
|
308
|
+
const declaredType =
|
|
309
|
+
typeof meta.contentType === "string" ? meta.contentType : undefined;
|
|
310
|
+
|
|
311
|
+
if (isBytes(envelope.binary)) {
|
|
312
|
+
headers["content-type"] =
|
|
313
|
+
declaredType ?? headers["content-type"] ?? "application/octet-stream";
|
|
314
|
+
return { status, headers, payload: envelope.binary };
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
const body = envelope.body;
|
|
318
|
+
if (typeof body === "string") {
|
|
319
|
+
headers["content-type"] =
|
|
320
|
+
declaredType ?? headers["content-type"] ?? "text/plain; charset=utf-8";
|
|
321
|
+
return {
|
|
322
|
+
status,
|
|
323
|
+
headers,
|
|
324
|
+
payload: new Uint8Array(Buffer.from(body, "utf8")),
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
headers["content-type"] =
|
|
329
|
+
declaredType ?? headers["content-type"] ?? "application/json";
|
|
330
|
+
return {
|
|
331
|
+
status,
|
|
332
|
+
headers,
|
|
333
|
+
payload: new Uint8Array(
|
|
334
|
+
Buffer.from(JSON.stringify(body ?? null), "utf8"),
|
|
335
|
+
),
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// Bytes are the one value whose own type says what to do with it: JSON
|
|
340
|
+
// encoding a Uint8Array produces an object of numbered keys, which is nothing
|
|
341
|
+
// anybody asked for. Everything else stays JSON, including a bare string —
|
|
342
|
+
// that is what a board serving a held value already answers with, and a board
|
|
343
|
+
// that means text says so in an envelope.
|
|
344
|
+
if (isBytes(value)) {
|
|
345
|
+
return {
|
|
346
|
+
status: 200,
|
|
347
|
+
headers: { "content-type": "application/octet-stream" },
|
|
348
|
+
payload: value,
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
return {
|
|
353
|
+
status: 200,
|
|
354
|
+
headers: { "content-type": "application/json" },
|
|
355
|
+
payload: new Uint8Array(Buffer.from(JSON.stringify(value ?? null), "utf8")),
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* The range a `Range: bytes=…` header asks for, clamped to what there is, or
|
|
361
|
+
* null when the header asks for nothing this can serve — no header, a unit
|
|
362
|
+
* other than bytes, more than one range, or a start past the end.
|
|
363
|
+
*
|
|
364
|
+
* A player seeking inside an audio file sends one of these, and a server that
|
|
365
|
+
* ignores it answers the whole file every time somebody drags the scrubber.
|
|
366
|
+
* Slicing an answer already in hand is the whole of it: the pipeline produced
|
|
367
|
+
* the bytes, and which of them travel is the server's business.
|
|
368
|
+
*/
|
|
369
|
+
function requestedRange(
|
|
370
|
+
value: string | undefined,
|
|
371
|
+
length: number,
|
|
372
|
+
): { start: number; end: number } | null {
|
|
373
|
+
if (!value) {
|
|
374
|
+
return null;
|
|
375
|
+
}
|
|
376
|
+
const match = value.match(/^bytes=(\d*)-(\d*)$/);
|
|
377
|
+
if (!match) {
|
|
378
|
+
return null;
|
|
379
|
+
}
|
|
380
|
+
const [, rawStart, rawEnd] = match;
|
|
381
|
+
if (rawStart === "" && rawEnd === "") {
|
|
382
|
+
return null;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// "bytes=-500" is the last 500 bytes, not a range starting at zero.
|
|
386
|
+
if (rawStart === "") {
|
|
387
|
+
const span = Number(rawEnd);
|
|
388
|
+
if (!span) {
|
|
389
|
+
return null;
|
|
390
|
+
}
|
|
391
|
+
return { start: Math.max(0, length - span), end: length - 1 };
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
const start = Number(rawStart);
|
|
395
|
+
if (start >= length) {
|
|
396
|
+
return null;
|
|
397
|
+
}
|
|
398
|
+
const end = rawEnd === "" ? length - 1 : Math.min(Number(rawEnd), length - 1);
|
|
399
|
+
return end < start ? null : { start, end };
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
type HttpServerSubservicesState = JsonRecord & {
|
|
403
|
+
bypass: boolean;
|
|
404
|
+
mode: HttpServerMode;
|
|
405
|
+
/** Name this endpoint is known by; see the field on the service. */
|
|
406
|
+
mountName: string;
|
|
407
|
+
/** Public endpoint assigned by the runtime; empty while bypassed. Reserved
|
|
408
|
+
* name: generic board machinery reads and rewrites it (see the frontend's
|
|
409
|
+
* runtime/board/mount). */
|
|
410
|
+
__hkpMount: string;
|
|
411
|
+
/** Which request headers reach the pipeline; see the field on the service. */
|
|
412
|
+
forwardHeaders: string[] | null;
|
|
413
|
+
pipeline: Array<{
|
|
414
|
+
serviceId: string;
|
|
415
|
+
instanceId: string;
|
|
416
|
+
state: JsonRecord;
|
|
417
|
+
}>;
|
|
418
|
+
};
|
|
419
|
+
|
|
420
|
+
export class HttpServerSubservicesService implements HostedService {
|
|
421
|
+
readonly serviceId = httpServerSubservicesDescriptor.serviceId;
|
|
422
|
+
readonly serviceName = httpServerSubservicesDescriptor.serviceName;
|
|
423
|
+
readonly capabilities = httpServerSubservicesDescriptor.capabilities;
|
|
424
|
+
readonly uuid: string;
|
|
425
|
+
|
|
426
|
+
private bypass = true;
|
|
427
|
+
private mode: HttpServerMode = "process_on_session";
|
|
428
|
+
/**
|
|
429
|
+
* What this endpoint is called, which is what its public address is derived
|
|
430
|
+
* from. Empty falls back to the service's uuid, which is stable in a board
|
|
431
|
+
* file too — so an address only changes when a board deliberately renames it.
|
|
432
|
+
*/
|
|
433
|
+
private mountName = "";
|
|
434
|
+
/**
|
|
435
|
+
* Which of a request's headers the pipeline is shown, or null for all.
|
|
436
|
+
*
|
|
437
|
+
* Headers are where a caller puts a credential, and `meta` goes wherever the
|
|
438
|
+
* pipeline takes it — including into a board, if a service is wired to write
|
|
439
|
+
* it there. Naming the ones a board actually reads is how it stops carrying
|
|
440
|
+
* the ones it does not: an empty list forwards none, and no list at all
|
|
441
|
+
* forwards everything, which is what a board that has not thought about it
|
|
442
|
+
* gets.
|
|
443
|
+
*/
|
|
444
|
+
private forwardHeaders: string[] | null = null;
|
|
445
|
+
private latestData: unknown = null;
|
|
446
|
+
|
|
447
|
+
private mount: MountHandle | null = null;
|
|
448
|
+
/**
|
|
449
|
+
* Which way the board declared its pipelines; see the header.
|
|
450
|
+
*
|
|
451
|
+
* Kept because state reports what was declared rather than a canonical form:
|
|
452
|
+
* a board saved after being loaded has to come back out the way it went in,
|
|
453
|
+
* or every board on the older spelling rewrites itself the first time
|
|
454
|
+
* somebody saves it.
|
|
455
|
+
*/
|
|
456
|
+
private form: "legacy" | "entries" = "legacy";
|
|
457
|
+
/** The one pipeline a legacy board declares, entered from whichever side its
|
|
458
|
+
* `mode` says. */
|
|
459
|
+
private legacy: NestedPipeline | null = null;
|
|
460
|
+
private readonly entries: Record<EntryName, NestedPipeline | null> = {
|
|
461
|
+
onProcess: null,
|
|
462
|
+
onRequest: null,
|
|
463
|
+
};
|
|
464
|
+
/**
|
|
465
|
+
* The cells this endpoint's pipelines hold values in.
|
|
466
|
+
*
|
|
467
|
+
* Owned here because the two entry points are pipelines that never meet: a
|
|
468
|
+
* value one of them produces has nowhere to live until the other runs. One
|
|
469
|
+
* store per endpoint is also what keeps the names in it private, so two
|
|
470
|
+
* endpoints on a runtime may both call a slot `document`.
|
|
471
|
+
*/
|
|
472
|
+
private readonly slotStore: SlotStore = new Map<string, unknown>();
|
|
473
|
+
private readonly createService: ServiceCreator;
|
|
474
|
+
private host: RuntimeHost | null = null;
|
|
475
|
+
|
|
476
|
+
private stream: StreamConfig | null = null;
|
|
477
|
+
private readonly broadcast = new StreamBroadcast((listeners) =>
|
|
478
|
+
this.notify({ listeners }, this.uuid),
|
|
479
|
+
);
|
|
480
|
+
|
|
481
|
+
constructor(
|
|
482
|
+
config: ServiceConfiguration,
|
|
483
|
+
createService: ServiceCreator,
|
|
484
|
+
// Upper bound on a request body, in bytes; 0 disables the limit. Supplied by
|
|
485
|
+
// the server because the endpoint is public and shared.
|
|
486
|
+
private readonly maxBodyBytes = 0,
|
|
487
|
+
) {
|
|
488
|
+
this.uuid = config.uuid;
|
|
489
|
+
this.createService = createService;
|
|
490
|
+
|
|
491
|
+
if (config.state) {
|
|
492
|
+
this.configure(config.state);
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
configure(config: JsonRecord): JsonRecord {
|
|
497
|
+
// `port` is accepted and ignored: the endpoint is served by the shared
|
|
498
|
+
// runtime server under an assigned path, so a service no longer picks a
|
|
499
|
+
// port. Older boards still carry the field, and rejecting it would fail
|
|
500
|
+
// them on load for a setting that no longer means anything.
|
|
501
|
+
|
|
502
|
+
// An array is a decision, including an empty one. Anything else — absent,
|
|
503
|
+
// null, a string — leaves the default of forwarding all of them.
|
|
504
|
+
if (config.forwardHeaders !== undefined) {
|
|
505
|
+
this.forwardHeaders = Array.isArray(config.forwardHeaders)
|
|
506
|
+
? config.forwardHeaders
|
|
507
|
+
.filter((name): name is string => typeof name === "string")
|
|
508
|
+
.map((name) => name.toLowerCase())
|
|
509
|
+
: null;
|
|
510
|
+
}
|
|
511
|
+
if (typeof config.mountName === "string") {
|
|
512
|
+
// Renaming rotates this endpoint's address, so an already-claimed mount
|
|
513
|
+
// is released and claimed again under the new name rather than left
|
|
514
|
+
// answering on the old one.
|
|
515
|
+
const renamed = config.mountName !== this.mountName;
|
|
516
|
+
this.mountName = config.mountName;
|
|
517
|
+
if (renamed && this.mount) {
|
|
518
|
+
this.releaseMount();
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
// A different stream, or `null` / anything that names no path, ends the
|
|
523
|
+
// one there was: its listeners were answered under the old path and
|
|
524
|
+
// content type, and what it kept for late joiners and probes belongs to
|
|
525
|
+
// it. The same declaration again — a board re-sending its state — changes
|
|
526
|
+
// nothing.
|
|
527
|
+
if (config.stream !== undefined) {
|
|
528
|
+
const declared = parseStreamConfig(config.stream);
|
|
529
|
+
if (this.stream && !sameStream(this.stream, declared)) {
|
|
530
|
+
this.endStream();
|
|
531
|
+
}
|
|
532
|
+
this.stream = declared;
|
|
533
|
+
this.broadcast.setBurstBytes(declared?.burstBytes ?? 0);
|
|
534
|
+
this.notify(this.streamAnnouncement(), this.uuid);
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
if (
|
|
538
|
+
config.mode === "process_on_session" ||
|
|
539
|
+
config.mode === "process_on_data" ||
|
|
540
|
+
config.mode === "process_on_both"
|
|
541
|
+
) {
|
|
542
|
+
this.mode = config.mode;
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
// Declaring an entry point by name is what puts this endpoint in the newer
|
|
546
|
+
// form, and from then on its state is reported that way.
|
|
547
|
+
for (const name of ENTRY_NAMES) {
|
|
548
|
+
if (Array.isArray(config[name])) {
|
|
549
|
+
this.form = "entries";
|
|
550
|
+
this.entryPipeline(name).setPipeline(config[name]);
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
// An edit aimed at one named entry. The unscoped verbs below cannot say
|
|
555
|
+
// which pipeline they mean once there is more than one.
|
|
556
|
+
if (isJsonRecord(config.configurePipeline)) {
|
|
557
|
+
const payload = config.configurePipeline;
|
|
558
|
+
const name = payload.entry;
|
|
559
|
+
if (name === "onProcess" || name === "onRequest") {
|
|
560
|
+
this.form = "entries";
|
|
561
|
+
this.editPipeline(this.entryPipeline(name), payload);
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
if (
|
|
566
|
+
Array.isArray(config.pipeline) ||
|
|
567
|
+
isJsonRecord(config.appendService) ||
|
|
568
|
+
typeof config.removeService === "string" ||
|
|
569
|
+
isJsonRecord(config.configureService)
|
|
570
|
+
) {
|
|
571
|
+
// The unscoped verbs belong to the one pipeline a legacy board declares.
|
|
572
|
+
// Left working rather than redirected at an entry, because which entry
|
|
573
|
+
// they would mean is exactly what the older form cannot say.
|
|
574
|
+
this.editPipeline(this.legacyPipeline(), config);
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
if (typeof config.bypass === "boolean" && config.bypass !== this.bypass) {
|
|
578
|
+
this.bypass = config.bypass;
|
|
579
|
+
if (this.bypass) {
|
|
580
|
+
this.releaseMount();
|
|
581
|
+
} else {
|
|
582
|
+
this.claimMount();
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
// Anything above may have left this without an endpoint it should have —
|
|
587
|
+
// coming out of bypass, or a rename that released the old address. One
|
|
588
|
+
// check covers them rather than one per cause.
|
|
589
|
+
if (!this.bypass && !this.mount) {
|
|
590
|
+
this.claimMount();
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
return this.getState();
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
/**
|
|
597
|
+
* The pipeline behind one entry point, built on first use.
|
|
598
|
+
*
|
|
599
|
+
* Built lazily because an endpoint declaring only `onRequest` should not
|
|
600
|
+
* carry an empty runtime for the side it never uses — and because an empty
|
|
601
|
+
* pipeline and an absent one differ here: only the second leaves the board
|
|
602
|
+
* answering.
|
|
603
|
+
*/
|
|
604
|
+
private entryPipeline(name: EntryName): NestedPipeline {
|
|
605
|
+
const existing = this.entries[name];
|
|
606
|
+
if (existing) {
|
|
607
|
+
return existing;
|
|
608
|
+
}
|
|
609
|
+
const created = this.newPipeline(name);
|
|
610
|
+
this.entries[name] = created;
|
|
611
|
+
return created;
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
private legacyPipeline(): NestedPipeline {
|
|
615
|
+
if (!this.legacy) {
|
|
616
|
+
this.legacy = this.newPipeline("pipeline");
|
|
617
|
+
}
|
|
618
|
+
return this.legacy;
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
private newPipeline(label: string): NestedPipeline {
|
|
622
|
+
const pipeline = new NestedPipeline(
|
|
623
|
+
`${this.uuid}:${label}`,
|
|
624
|
+
this.createService,
|
|
625
|
+
this.uuid,
|
|
626
|
+
);
|
|
627
|
+
// Both entries hold in the same cells: that they can is the whole reason
|
|
628
|
+
// for declaring them separately.
|
|
629
|
+
pipeline.shareSlots(this.slotStore);
|
|
630
|
+
if (this.host) {
|
|
631
|
+
pipeline.attach(this.host);
|
|
632
|
+
}
|
|
633
|
+
return pipeline;
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
/** The four edit verbs, applied to whichever pipeline was named. */
|
|
637
|
+
private editPipeline(pipeline: NestedPipeline, payload: JsonRecord): void {
|
|
638
|
+
if (Array.isArray(payload.pipeline)) {
|
|
639
|
+
pipeline.setPipeline(payload.pipeline);
|
|
640
|
+
} else if (isJsonRecord(payload.appendService)) {
|
|
641
|
+
pipeline.append(payload.appendService);
|
|
642
|
+
} else if (typeof payload.removeService === "string") {
|
|
643
|
+
pipeline.remove(payload.removeService);
|
|
644
|
+
} else if (isJsonRecord(payload.configureService)) {
|
|
645
|
+
const edit = payload.configureService;
|
|
646
|
+
if (typeof edit.instanceId === "string" && isJsonRecord(edit.state)) {
|
|
647
|
+
pipeline.configureService(edit.instanceId, edit.state);
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
/**
|
|
653
|
+
* The nested service a scoped address names, searching every pipeline this
|
|
654
|
+
* endpoint owns. The two entries are separate pipelines rather than branches
|
|
655
|
+
* of one, so an instanceId used in both resolves to whichever `pipelines()`
|
|
656
|
+
* lists first.
|
|
657
|
+
*/
|
|
658
|
+
findNested(instanceId: string): HostedService | undefined {
|
|
659
|
+
for (const pipeline of this.pipelines()) {
|
|
660
|
+
const found = pipeline.find(instanceId);
|
|
661
|
+
if (found) {
|
|
662
|
+
return found;
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
return undefined;
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
getState(): JsonRecord {
|
|
669
|
+
const common: JsonRecord = {
|
|
670
|
+
bypass: this.bypass,
|
|
671
|
+
mountName: this.mountName,
|
|
672
|
+
__hkpMount: this.mount?.url ?? "",
|
|
673
|
+
forwardHeaders: this.forwardHeaders,
|
|
674
|
+
};
|
|
675
|
+
if (this.stream) {
|
|
676
|
+
common.stream = streamState(this.stream);
|
|
677
|
+
Object.assign(common, this.streamAnnouncement());
|
|
678
|
+
common.listenerDetails = this.broadcast.details();
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
// What was declared, not what it was understood as. A board that named its
|
|
682
|
+
// entries gets them back; one that carries a `mode` keeps it, because that
|
|
683
|
+
// is the version an older runtime can still load.
|
|
684
|
+
if (this.form === "entries") {
|
|
685
|
+
const state: JsonRecord = { ...common };
|
|
686
|
+
for (const name of ENTRY_NAMES) {
|
|
687
|
+
const pipeline = this.entries[name];
|
|
688
|
+
if (pipeline) {
|
|
689
|
+
state[name] = pipeline.state();
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
return state;
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
const state: HttpServerSubservicesState = {
|
|
696
|
+
...(common as Pick<
|
|
697
|
+
HttpServerSubservicesState,
|
|
698
|
+
"bypass" | "mountName" | "__hkpMount" | "forwardHeaders"
|
|
699
|
+
>),
|
|
700
|
+
mode: this.mode,
|
|
701
|
+
pipeline: this.legacy?.state() ?? [],
|
|
702
|
+
};
|
|
703
|
+
// The stream fields ride along when declared; see getState's top.
|
|
704
|
+
return { ...common, ...state };
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
/** Passes the scope on to the nested pipelines; see SubService.setScope. */
|
|
708
|
+
setScope(scope: RuntimeScope): void {
|
|
709
|
+
for (const pipeline of this.pipelines()) {
|
|
710
|
+
pipeline.setScope(scope);
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
/** Every pipeline this endpoint owns, each one only once. */
|
|
715
|
+
private pipelines(): NestedPipeline[] {
|
|
716
|
+
const all = [this.legacy, this.entries.onProcess, this.entries.onRequest];
|
|
717
|
+
return [...new Set(all.filter((p): p is NestedPipeline => !!p))];
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
/**
|
|
721
|
+
* The pipeline one side enters through, or null where that side has none.
|
|
722
|
+
*
|
|
723
|
+
* This is where a legacy `mode` is read, and the only place it is: a board
|
|
724
|
+
* that names its entries never reaches the table below.
|
|
725
|
+
*
|
|
726
|
+
* | declared | onProcess | onRequest |
|
|
727
|
+
* |--------------------------------|-----------|-----------|
|
|
728
|
+
* | `process_on_session` | — | the one |
|
|
729
|
+
* | `process_on_both` | the one | the one |
|
|
730
|
+
* | `process_on_data` | — | — |
|
|
731
|
+
*
|
|
732
|
+
* `process_on_both` answers with *the same instance* on both sides, never a
|
|
733
|
+
* second copy of the configuration: a pipeline holding a Hold, a timer or a
|
|
734
|
+
* mount is one running thing, and duplicating it would give a board two of
|
|
735
|
+
* each and a slot that never reaches itself.
|
|
736
|
+
*/
|
|
737
|
+
private entryFor(name: EntryName): NestedPipeline | null {
|
|
738
|
+
if (this.form === "entries") {
|
|
739
|
+
return this.entries[name];
|
|
740
|
+
}
|
|
741
|
+
if (this.mode === "process_on_data") {
|
|
742
|
+
return null;
|
|
743
|
+
}
|
|
744
|
+
if (name === "onProcess" && this.mode !== "process_on_both") {
|
|
745
|
+
return null;
|
|
746
|
+
}
|
|
747
|
+
return this.legacy;
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
setHost(host: RuntimeHost): void {
|
|
751
|
+
this.host = host;
|
|
752
|
+
// State is applied in the constructor, before the host exists, so a service
|
|
753
|
+
// configured as already-active has nothing to claim its mount from until
|
|
754
|
+
// now. Claiming here is what makes a board load into a live endpoint.
|
|
755
|
+
if (!this.bypass && !this.mount) {
|
|
756
|
+
this.claimMount();
|
|
757
|
+
}
|
|
758
|
+
// A pipeline built in the constructor was built before there was a host to
|
|
759
|
+
// ask what board it belongs to, or to report through.
|
|
760
|
+
for (const pipeline of this.pipelines()) {
|
|
761
|
+
pipeline.attach(host);
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
process(
|
|
766
|
+
input: unknown,
|
|
767
|
+
_notify: (payload: unknown, instanceId?: string) => void,
|
|
768
|
+
): unknown {
|
|
769
|
+
// The legacy built-in slot: what the board hands this endpoint is what a
|
|
770
|
+
// caller gets back. Expressible now as an `onProcess` that writes a slot
|
|
771
|
+
// and an `onRequest` that reads it, and kept because boards carry the older
|
|
772
|
+
// spelling and a board is a document people keep.
|
|
773
|
+
if (this.form === "legacy" && this.mode === "process_on_data") {
|
|
774
|
+
this.latestData = input;
|
|
775
|
+
return input;
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
const entry = this.bypass ? null : this.entryFor("onProcess");
|
|
779
|
+
if (!entry) {
|
|
780
|
+
this.publish(input);
|
|
781
|
+
return input;
|
|
782
|
+
}
|
|
783
|
+
// Routing only: what the pass's own pipeline returns carries on down the
|
|
784
|
+
// chain. Whatever has to survive until a request arrives — a value this
|
|
785
|
+
// side produces and the other reads — belongs in a slot, which is a
|
|
786
|
+
// service's job and not this one's. It is also what the stream carries.
|
|
787
|
+
return this.runEntry(entry, input).then((output) => {
|
|
788
|
+
this.publish(output);
|
|
789
|
+
return output;
|
|
790
|
+
});
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
/** What a pass produced, to whoever is listening, where it is bytes. */
|
|
794
|
+
private publish(value: unknown): void {
|
|
795
|
+
if (!this.stream || this.bypass) {
|
|
796
|
+
return;
|
|
797
|
+
}
|
|
798
|
+
const bytes = streamBytes(value);
|
|
799
|
+
if (bytes) {
|
|
800
|
+
this.broadcast.publish(bytes);
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
/** Where a player connects, and how many have: what changes as it runs. */
|
|
805
|
+
private streamAnnouncement(): JsonRecord {
|
|
806
|
+
return {
|
|
807
|
+
streamUrl:
|
|
808
|
+
this.stream && this.mount ? `${this.mount.url}${this.stream.path}` : "",
|
|
809
|
+
listeners: this.broadcast.count,
|
|
810
|
+
};
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
private endStream(): void {
|
|
814
|
+
this.broadcast.closeAll();
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
/** A GET on the stream path: a listener to keep, or a probe to answer. */
|
|
818
|
+
private handleListener(req: IncomingMessage, res: ServerResponse): void {
|
|
819
|
+
const stream = this.stream!;
|
|
820
|
+
const range = boundedRange(header(req, "range"));
|
|
821
|
+
if (range && this.broadcast.last) {
|
|
822
|
+
answerRangeProbe(res, stream.contentType, range, this.broadcast.last);
|
|
823
|
+
return;
|
|
824
|
+
}
|
|
825
|
+
const listener: StreamListener = new StreamListener(
|
|
826
|
+
req,
|
|
827
|
+
res,
|
|
828
|
+
stream.contentType,
|
|
829
|
+
stream.maxQueueBytes,
|
|
830
|
+
stream.stallTimeoutMs,
|
|
831
|
+
(closed) => this.broadcast.leave(closed),
|
|
832
|
+
);
|
|
833
|
+
this.broadcast.join(listener);
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
destroy(): void {
|
|
837
|
+
this.releaseMount();
|
|
838
|
+
// Nested services hold the same things top-level ones do — timers, sockets,
|
|
839
|
+
// mounts — and nothing else will ever reach them once this service is gone.
|
|
840
|
+
for (const pipeline of this.pipelines()) {
|
|
841
|
+
pipeline.destroy();
|
|
842
|
+
}
|
|
843
|
+
this.legacy = null;
|
|
844
|
+
this.entries.onProcess = null;
|
|
845
|
+
this.entries.onRequest = null;
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
/** See HostedService.remount: the runtime can serve one now. */
|
|
849
|
+
remount(): void {
|
|
850
|
+
// Bypassed is the same answer it gives at setHost: an endpoint switched
|
|
851
|
+
// off holds no address, and gaining somewhere to claim one does not switch
|
|
852
|
+
// it back on.
|
|
853
|
+
if (!this.bypass) {
|
|
854
|
+
this.claimMount();
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
private claimMount(): void {
|
|
859
|
+
if (this.mount || !this.host?.mount) {
|
|
860
|
+
return;
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
this.mount = this.host.mount(
|
|
864
|
+
this.uuid,
|
|
865
|
+
{
|
|
866
|
+
request: (req, res, context) => {
|
|
867
|
+
if (
|
|
868
|
+
this.isStreamPath(context) &&
|
|
869
|
+
req.method === "GET" &&
|
|
870
|
+
!this.bypass
|
|
871
|
+
) {
|
|
872
|
+
this.handleListener(req, res);
|
|
873
|
+
return;
|
|
874
|
+
}
|
|
875
|
+
void this.handleRequest(req, res, context);
|
|
876
|
+
},
|
|
877
|
+
},
|
|
878
|
+
{ mountName: this.mountName },
|
|
879
|
+
);
|
|
880
|
+
|
|
881
|
+
// A board reads the assigned endpoint from here (or from state), since it
|
|
882
|
+
// is not knowable at design time.
|
|
883
|
+
this.notify(
|
|
884
|
+
{
|
|
885
|
+
__hkpMount: this.mount?.url ?? "",
|
|
886
|
+
...(this.stream ? this.streamAnnouncement() : {}),
|
|
887
|
+
},
|
|
888
|
+
this.uuid,
|
|
889
|
+
);
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
private isStreamPath(context: MountContext): boolean {
|
|
893
|
+
if (!this.stream) {
|
|
894
|
+
return false;
|
|
895
|
+
}
|
|
896
|
+
return (
|
|
897
|
+
new URL(context.subPath, "http://localhost").pathname === this.stream.path
|
|
898
|
+
);
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
private releaseMount(): void {
|
|
902
|
+
this.mount?.release();
|
|
903
|
+
this.mount = null;
|
|
904
|
+
this.endStream();
|
|
905
|
+
if (this.stream) {
|
|
906
|
+
this.notify({ __hkpMount: "", ...this.streamAnnouncement() }, this.uuid);
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
/**
|
|
911
|
+
* Build the MixedData an incoming request becomes: JSON `meta` describing it,
|
|
912
|
+
* plus the body in whichever single form is useful — decoded as `body` when
|
|
913
|
+
* the content type says what the bytes mean, raw as `binary` otherwise. The
|
|
914
|
+
* meta/binary pair is the shape hkp-rt's body-carrying HTTP service produces,
|
|
915
|
+
* so pipelines handling uploads can be written once against it.
|
|
916
|
+
*
|
|
917
|
+
* `meta.path` stays the URL path this service has always reported. A filename
|
|
918
|
+
* from content-disposition is surfaced separately as `meta.filename` rather
|
|
919
|
+
* than overloading `path`, which would silently change what existing
|
|
920
|
+
* pipelines match on.
|
|
921
|
+
*/
|
|
922
|
+
private async readRequest(
|
|
923
|
+
req: IncomingMessage,
|
|
924
|
+
context: MountContext,
|
|
925
|
+
): Promise<MixedRequest> {
|
|
926
|
+
// The mount prefix is transport addressing, not part of the route the
|
|
927
|
+
// pipeline matches on, so the pipeline sees the path below the mount.
|
|
928
|
+
const url = new URL(context.subPath, "http://localhost");
|
|
929
|
+
const query: JsonRecord = {};
|
|
930
|
+
for (const [key, value] of url.searchParams) {
|
|
931
|
+
query[key] = value;
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
const contentType = header(req, "content-type");
|
|
935
|
+
const meta: JsonRecord = {
|
|
936
|
+
method: req.method ?? "GET",
|
|
937
|
+
path: url.pathname,
|
|
938
|
+
query,
|
|
939
|
+
headers: this.requestHeaders(req),
|
|
940
|
+
};
|
|
941
|
+
if (contentType) {
|
|
942
|
+
meta.contentType = contentType;
|
|
943
|
+
}
|
|
944
|
+
const filename = filenameFromDisposition(
|
|
945
|
+
header(req, "content-disposition"),
|
|
946
|
+
);
|
|
947
|
+
if (filename) {
|
|
948
|
+
meta.filename = filename;
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
const binary = await this.readBody(req);
|
|
952
|
+
|
|
953
|
+
// Exactly one representation of the body, or neither when there was none.
|
|
954
|
+
const body = decodeBody(binary, contentType);
|
|
955
|
+
if (body !== undefined) {
|
|
956
|
+
return { meta, body };
|
|
957
|
+
}
|
|
958
|
+
return binary.length > 0 ? { meta, binary } : { meta };
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
/**
|
|
962
|
+
* The headers this pipeline is shown, lower-cased as HTTP names compare.
|
|
963
|
+
*
|
|
964
|
+
* A caller that has to prove who it is does so in a header — a shared secret,
|
|
965
|
+
* a signature, a bearer token — so a pipeline that cannot see them cannot
|
|
966
|
+
* check one. What a board does not name, it does not receive.
|
|
967
|
+
*/
|
|
968
|
+
private requestHeaders(req: IncomingMessage): JsonRecord {
|
|
969
|
+
const headers: JsonRecord = {};
|
|
970
|
+
for (const [name, value] of Object.entries(req.headers)) {
|
|
971
|
+
if (value === undefined) {
|
|
972
|
+
continue;
|
|
973
|
+
}
|
|
974
|
+
if (this.forwardHeaders && !this.forwardHeaders.includes(name)) {
|
|
975
|
+
continue;
|
|
976
|
+
}
|
|
977
|
+
headers[name] = Array.isArray(value) ? value.join(", ") : value;
|
|
978
|
+
}
|
|
979
|
+
return headers;
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
/**
|
|
983
|
+
* Read the request body, refusing anything past the configured cap.
|
|
984
|
+
*
|
|
985
|
+
* A mount is reachable without a token by design, so an unbounded read is a
|
|
986
|
+
* way for anyone holding the URL to exhaust the host — which on a shared
|
|
987
|
+
* instance is everyone else's problem too. The cap is enforced while reading
|
|
988
|
+
* rather than from content-length, which a client controls.
|
|
989
|
+
*/
|
|
990
|
+
private readBody(req: IncomingMessage): Promise<Uint8Array> {
|
|
991
|
+
return new Promise((resolve, reject) => {
|
|
992
|
+
const chunks: Buffer[] = [];
|
|
993
|
+
let total = 0;
|
|
994
|
+
let rejected = false;
|
|
995
|
+
|
|
996
|
+
req.on("data", (chunk: Buffer) => {
|
|
997
|
+
if (rejected) {
|
|
998
|
+
// Keep draining but stop accumulating: memory is what the limit
|
|
999
|
+
// protects, and tearing the socket down here would lose the 413 the
|
|
1000
|
+
// caller is about to write.
|
|
1001
|
+
return;
|
|
1002
|
+
}
|
|
1003
|
+
total += chunk.length;
|
|
1004
|
+
if (this.maxBodyBytes > 0 && total > this.maxBodyBytes) {
|
|
1005
|
+
rejected = true;
|
|
1006
|
+
chunks.length = 0;
|
|
1007
|
+
reject(new RequestTooLargeError());
|
|
1008
|
+
return;
|
|
1009
|
+
}
|
|
1010
|
+
chunks.push(chunk);
|
|
1011
|
+
});
|
|
1012
|
+
|
|
1013
|
+
req.on("end", () => {
|
|
1014
|
+
if (!rejected) {
|
|
1015
|
+
resolve(new Uint8Array(Buffer.concat(chunks)));
|
|
1016
|
+
}
|
|
1017
|
+
});
|
|
1018
|
+
|
|
1019
|
+
req.on("error", (err) => {
|
|
1020
|
+
if (!rejected) {
|
|
1021
|
+
reject(err);
|
|
1022
|
+
}
|
|
1023
|
+
});
|
|
1024
|
+
});
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
private async handleRequest(
|
|
1028
|
+
req: IncomingMessage,
|
|
1029
|
+
res: ServerResponse,
|
|
1030
|
+
context: MountContext,
|
|
1031
|
+
): Promise<void> {
|
|
1032
|
+
if (this.bypass) {
|
|
1033
|
+
res.statusCode = 503;
|
|
1034
|
+
res.setHeader("content-type", "application/json");
|
|
1035
|
+
res.end(JSON.stringify({ error: "http-server-subservices is bypassed" }));
|
|
1036
|
+
return;
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
// Serving a request is one run, however many pipelines it passes through:
|
|
1040
|
+
// the nested handler below descends from it, and the outer chain afterwards
|
|
1041
|
+
// continues it. Minting one here rather than letting each leg mint its own
|
|
1042
|
+
// is what keeps a request's trace joined up instead of arriving as two
|
|
1043
|
+
// unrelated runs that happen to share a timestamp.
|
|
1044
|
+
const runContext = newRun("mount");
|
|
1045
|
+
|
|
1046
|
+
let output: unknown;
|
|
1047
|
+
let processInput: unknown;
|
|
1048
|
+
// Whether the answer is already decided here, or is whatever the rest of
|
|
1049
|
+
// the outer chain makes of what this service emitted.
|
|
1050
|
+
let answeredHere = false;
|
|
1051
|
+
if (this.form === "legacy" && this.mode === "process_on_data") {
|
|
1052
|
+
processInput = this.latestData;
|
|
1053
|
+
output = processInput;
|
|
1054
|
+
// The mode's whole contract: what the board handed this endpoint is what
|
|
1055
|
+
// a caller gets back, verbatim. The services after it still run — having
|
|
1056
|
+
// served a request is something a board may want to act on — but what
|
|
1057
|
+
// they make of it is theirs, not the answer. Letting the chain's tail
|
|
1058
|
+
// answer instead would mean an endpoint could only ever be the last
|
|
1059
|
+
// service in its runtime, so a runtime could publish only one document.
|
|
1060
|
+
answeredHere = true;
|
|
1061
|
+
} else {
|
|
1062
|
+
let request: MixedRequest;
|
|
1063
|
+
try {
|
|
1064
|
+
request = await this.readRequest(req, context);
|
|
1065
|
+
} catch (error) {
|
|
1066
|
+
if (error instanceof RequestTooLargeError) {
|
|
1067
|
+
res.statusCode = 413;
|
|
1068
|
+
res.setHeader("content-type", "application/json");
|
|
1069
|
+
res.end(JSON.stringify({ error: "Request body too large" }));
|
|
1070
|
+
return;
|
|
1071
|
+
}
|
|
1072
|
+
throw error;
|
|
1073
|
+
}
|
|
1074
|
+
processInput = request;
|
|
1075
|
+
// A declared handler is what takes the answer away from the chain — not
|
|
1076
|
+
// the presence of a pipeline, which says only that this endpoint has
|
|
1077
|
+
// something to run, possibly on the other side. Without one the board
|
|
1078
|
+
// answers, which is the inversion of control this service is built
|
|
1079
|
+
// around.
|
|
1080
|
+
const handler = this.entryFor("onRequest");
|
|
1081
|
+
answeredHere = !!handler && !handler.isEmpty();
|
|
1082
|
+
output = handler
|
|
1083
|
+
? await this.runEntry(handler, processInput, runContext)
|
|
1084
|
+
: processInput;
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
// What the nested pipeline produced, before the outer runtime sees it.
|
|
1088
|
+
const answer = output;
|
|
1089
|
+
|
|
1090
|
+
// processFrom reports this service's own call-process pair, so there is no
|
|
1091
|
+
// manual pair here — emitting one too would double every request in the UI.
|
|
1092
|
+
// It also reports the right value: what this service emitted, rather than
|
|
1093
|
+
// what the whole downstream chain finally returned.
|
|
1094
|
+
if (this.host) {
|
|
1095
|
+
// No-op: the runtime already fans these out to its notification targets.
|
|
1096
|
+
// Re-notifying through the host would deliver every one twice.
|
|
1097
|
+
output = await this.host.processFrom(
|
|
1098
|
+
this.uuid,
|
|
1099
|
+
output,
|
|
1100
|
+
() => {},
|
|
1101
|
+
runContext,
|
|
1102
|
+
);
|
|
1103
|
+
this.host.emitResult(output);
|
|
1104
|
+
}
|
|
1105
|
+
|
|
1106
|
+
this.sendAnswer(
|
|
1107
|
+
req,
|
|
1108
|
+
res,
|
|
1109
|
+
await this.resolveAssetBody(answeredHere ? answer : output),
|
|
1110
|
+
);
|
|
1111
|
+
}
|
|
1112
|
+
|
|
1113
|
+
/**
|
|
1114
|
+
* An answer whose `body` is an `hkp-asset://` reference, with the asset's
|
|
1115
|
+
* content in its place.
|
|
1116
|
+
*
|
|
1117
|
+
* Resolved as the answer is written, from the runtime's asset store, so the
|
|
1118
|
+
* page an endpoint serves is whatever the asset holds now: editing it changes
|
|
1119
|
+
* the next response and nothing is reconfigured. The content type is the
|
|
1120
|
+
* asset's own unless the envelope named one.
|
|
1121
|
+
*
|
|
1122
|
+
* A reference that does not resolve is answered as a 500 naming the asset and
|
|
1123
|
+
* why, and reported. Sending the reference itself as the page would be a
|
|
1124
|
+
* quieter failure, and a worse one.
|
|
1125
|
+
*/
|
|
1126
|
+
private async resolveAssetBody(value: unknown): Promise<unknown> {
|
|
1127
|
+
const envelope = asResponseEnvelope(value);
|
|
1128
|
+
if (!envelope || !parseAssetRef(envelope.body)) {
|
|
1129
|
+
return value;
|
|
1130
|
+
}
|
|
1131
|
+
const meta = envelope.meta ?? {};
|
|
1132
|
+
const store = this.host?.assets?.();
|
|
1133
|
+
const { asset, problem } = store
|
|
1134
|
+
? await store.resolve(envelope.body as string)
|
|
1135
|
+
: { asset: null, problem: "this runtime has no assets" };
|
|
1136
|
+
if (!asset) {
|
|
1137
|
+
this.notify({ error: problem });
|
|
1138
|
+
return {
|
|
1139
|
+
meta: { status: 500, contentType: "application/json" },
|
|
1140
|
+
body: { error: problem },
|
|
1141
|
+
};
|
|
1142
|
+
}
|
|
1143
|
+
const { body: _reference, ...rest } = envelope;
|
|
1144
|
+
return {
|
|
1145
|
+
...rest,
|
|
1146
|
+
meta: {
|
|
1147
|
+
...meta,
|
|
1148
|
+
contentType:
|
|
1149
|
+
typeof meta.contentType === "string" && meta.contentType
|
|
1150
|
+
? meta.contentType
|
|
1151
|
+
: asset.mediaType,
|
|
1152
|
+
},
|
|
1153
|
+
binary: asset.bytes,
|
|
1154
|
+
};
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1157
|
+
/**
|
|
1158
|
+
* Writes what the handler produced, honouring a range request when the
|
|
1159
|
+
* answer is bytes a caller can seek inside.
|
|
1160
|
+
*/
|
|
1161
|
+
private sendAnswer(
|
|
1162
|
+
req: IncomingMessage,
|
|
1163
|
+
res: ServerResponse,
|
|
1164
|
+
value: unknown,
|
|
1165
|
+
): void {
|
|
1166
|
+
const answer = toAnswer(value);
|
|
1167
|
+
for (const [name, headerValue] of Object.entries(answer.headers)) {
|
|
1168
|
+
res.setHeader(name, String(headerValue));
|
|
1169
|
+
}
|
|
1170
|
+
|
|
1171
|
+
const seekable = answer.status === 200;
|
|
1172
|
+
if (seekable) {
|
|
1173
|
+
res.setHeader("accept-ranges", "bytes");
|
|
1174
|
+
}
|
|
1175
|
+
|
|
1176
|
+
const range = seekable
|
|
1177
|
+
? requestedRange(header(req, "range"), answer.payload.length)
|
|
1178
|
+
: null;
|
|
1179
|
+
if (range) {
|
|
1180
|
+
const slice = answer.payload.subarray(range.start, range.end + 1);
|
|
1181
|
+
res.statusCode = 206;
|
|
1182
|
+
res.setHeader(
|
|
1183
|
+
"content-range",
|
|
1184
|
+
`bytes ${range.start}-${range.end}/${answer.payload.length}`,
|
|
1185
|
+
);
|
|
1186
|
+
res.setHeader("content-length", String(slice.length));
|
|
1187
|
+
res.end(Buffer.from(slice));
|
|
1188
|
+
return;
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1191
|
+
res.statusCode = answer.status;
|
|
1192
|
+
res.setHeader("content-length", String(answer.payload.length));
|
|
1193
|
+
res.end(Buffer.from(answer.payload));
|
|
1194
|
+
}
|
|
1195
|
+
|
|
1196
|
+
/**
|
|
1197
|
+
* Runs one entry's pipeline as a run descended from `parent`.
|
|
1198
|
+
*
|
|
1199
|
+
* Both entry points land here, and they differ only in what they descend
|
|
1200
|
+
* from: a request brings the run its caller minted for the whole exchange,
|
|
1201
|
+
* while data from the outer chain arrives mid-call and descends from whatever
|
|
1202
|
+
* that call is running as.
|
|
1203
|
+
*/
|
|
1204
|
+
private async runEntry(
|
|
1205
|
+
pipeline: NestedPipeline,
|
|
1206
|
+
input: unknown,
|
|
1207
|
+
parent?: ProcessContext | null,
|
|
1208
|
+
): Promise<unknown> {
|
|
1209
|
+
return await pipeline.process(
|
|
1210
|
+
input,
|
|
1211
|
+
parent ?? this.host?.currentContext() ?? null,
|
|
1212
|
+
);
|
|
1213
|
+
}
|
|
1214
|
+
|
|
1215
|
+
private notify(payload: unknown, instanceId?: string): void {
|
|
1216
|
+
this.host?.notify(payload, instanceId ?? this.uuid);
|
|
1217
|
+
}
|
|
1218
|
+
}
|
|
1219
|
+
|
|
1220
|
+
function isJsonRecord(value: unknown): value is JsonRecord {
|
|
1221
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1222
|
+
}
|