@abide/abide 0.49.0 → 0.50.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (325) hide show
  1. package/AGENTS.md +564 -437
  2. package/CHANGELOG.md +183 -0
  3. package/README.md +165 -202
  4. package/package.json +14 -5
  5. package/src/abideLsp.ts +5 -6
  6. package/src/abideResolverPlugin.ts +197 -169
  7. package/src/build.ts +79 -56
  8. package/src/buildDisconnected.ts +0 -2
  9. package/src/checkAbide.ts +12 -2
  10. package/src/devEntry.ts +113 -28
  11. package/src/discoveryEntry.ts +3 -2
  12. package/src/lib/bundle/disconnected.abide +69 -73
  13. package/src/lib/bundle/infoPlist.ts +13 -7
  14. package/src/lib/bundle/spawnEmbeddedServer.ts +13 -4
  15. package/src/lib/bundle/waitForServer.ts +6 -1
  16. package/src/lib/mcp/mcpTools.ts +15 -6
  17. package/src/lib/server/error.ts +6 -6
  18. package/src/lib/server/json.ts +17 -3
  19. package/src/lib/server/rpc/defineRpc.ts +58 -24
  20. package/src/lib/server/rpc/parseArgs.ts +98 -13
  21. package/src/lib/server/rpc/resolveRpcJsonSchema.ts +28 -0
  22. package/src/lib/server/rpc/types/RpcHelper.ts +75 -105
  23. package/src/lib/server/rpc/types/RpcRegistryEntry.ts +24 -9
  24. package/src/lib/server/rpc/validationError.ts +1 -1
  25. package/src/lib/server/runtime/DEV_RELOAD_CLIENT_SCRIPT.ts +0 -15
  26. package/src/lib/server/runtime/buildCacheSnapshot.ts +10 -9
  27. package/src/lib/server/runtime/buildInspectorSurface.ts +6 -4
  28. package/src/lib/server/runtime/buildOpenApiSpec.ts +55 -11
  29. package/src/lib/server/runtime/buildPreloadManifest.ts +12 -10
  30. package/src/lib/server/runtime/createAppAssetServer.ts +18 -15
  31. package/src/lib/server/runtime/createAppRouteResolver.ts +145 -0
  32. package/src/lib/server/runtime/createPlumbingRouter.ts +212 -0
  33. package/src/lib/server/runtime/createRouteDispatcher.ts +4 -10
  34. package/src/lib/server/runtime/createServer.ts +120 -324
  35. package/src/lib/server/runtime/createUiPageRenderer.ts +137 -26
  36. package/src/lib/server/runtime/devClientFingerprint.ts +19 -34
  37. package/src/lib/server/runtime/installAmbientScopeStore.ts +30 -9
  38. package/src/lib/server/runtime/logExposedSurfaces.ts +8 -7
  39. package/src/lib/server/runtime/pathStore.ts +15 -0
  40. package/src/lib/server/runtime/registryManifests.ts +1 -1
  41. package/src/lib/server/runtime/renderCellBarrierStore.ts +15 -0
  42. package/src/lib/server/runtime/runWithRequestScope.ts +3 -0
  43. package/src/lib/server/runtime/serializeCacheSnapshot.ts +6 -4
  44. package/src/lib/server/runtime/snapshotEntryFromCache.ts +14 -14
  45. package/src/lib/server/runtime/streamCacheResolutions.ts +14 -7
  46. package/src/lib/server/runtime/streamFromIterator.ts +30 -2
  47. package/src/lib/server/runtime/textResponse.ts +23 -0
  48. package/src/lib/server/runtime/types/DevReloadStamp.ts +6 -10
  49. package/src/lib/server/runtime/types/InspectorCacheEntry.ts +1 -1
  50. package/src/lib/server/runtime/types/RequestStore.ts +27 -0
  51. package/src/lib/server/runtime/warnUnguardedMcp.ts +15 -5
  52. package/src/lib/server/sockets/createSocketDispatcher.ts +9 -11
  53. package/src/lib/server/sockets/defineSocket.ts +5 -4
  54. package/src/lib/server/sse.ts +5 -1
  55. package/src/lib/shared/ASYNC_CELL.ts +5 -0
  56. package/src/lib/shared/DEV_REBUILD_PATH.ts +6 -0
  57. package/src/lib/shared/HTTP_METHODS.ts +6 -0
  58. package/src/lib/shared/HttpError.ts +1 -5
  59. package/src/lib/shared/MCP_PATH.ts +6 -0
  60. package/src/lib/shared/PROXIED_SERVER_SUBDIRS.ts +15 -0
  61. package/src/lib/shared/REF_JSON_TAGS.ts +13 -1
  62. package/src/lib/shared/RPC_SHIM_GLOBALS.ts +9 -0
  63. package/src/lib/shared/activeCacheStore.ts +1 -0
  64. package/src/lib/shared/activePage.ts +1 -0
  65. package/src/lib/shared/buildArtifact.ts +4 -1
  66. package/src/lib/shared/buildSocketOverChannel.ts +4 -3
  67. package/src/lib/shared/bundleGraphFromMetafile.ts +68 -0
  68. package/src/lib/shared/cache.ts +156 -131
  69. package/src/lib/shared/changeAffectsClient.ts +12 -7
  70. package/src/lib/shared/createCacheStore.ts +31 -8
  71. package/src/lib/shared/createLifecycleChannel.ts +7 -1
  72. package/src/lib/shared/createReachable.ts +47 -78
  73. package/src/lib/shared/createRemoteFunction.ts +52 -30
  74. package/src/lib/shared/createRpcServerProgram.ts +820 -0
  75. package/src/lib/shared/decodeRefJson.ts +4 -4
  76. package/src/lib/shared/decodeResponse.ts +2 -2
  77. package/src/lib/shared/decodeWireBody.ts +35 -0
  78. package/src/lib/shared/detectRpcMethod.ts +8 -1
  79. package/src/lib/shared/done.ts +8 -2
  80. package/src/lib/shared/encodeRefJson.ts +4 -4
  81. package/src/lib/shared/encodeWireBody.ts +18 -0
  82. package/src/lib/{server/rpc → shared}/fieldErrorsFromIssues.ts +5 -1
  83. package/src/lib/shared/generateDeclarations.ts +72 -0
  84. package/src/lib/shared/hasSeedableRequest.ts +25 -0
  85. package/src/lib/shared/hydrationWindow.ts +53 -0
  86. package/src/lib/shared/isAsyncCell.ts +11 -0
  87. package/src/lib/shared/isAsyncIterable.ts +8 -0
  88. package/src/lib/shared/isSubscribable.ts +3 -3
  89. package/src/lib/shared/isThenable.ts +9 -0
  90. package/src/lib/shared/jsonSchemaForType.ts +258 -0
  91. package/src/lib/shared/lenientDecode.ts +15 -0
  92. package/src/lib/shared/loadProjectTsConfig.ts +28 -0
  93. package/src/lib/shared/markFrameworkSourcesIgnored.ts +1 -1
  94. package/src/lib/shared/matchRoute.ts +4 -14
  95. package/src/lib/shared/peek.ts +14 -0
  96. package/src/lib/shared/pending.ts +9 -6
  97. package/src/lib/shared/pendingAsyncCellsSlot.ts +13 -0
  98. package/src/lib/shared/prepareRpcModule.ts +91 -93
  99. package/src/lib/shared/prepareSocketModule.ts +3 -2
  100. package/src/lib/shared/probeRegistries.ts +5 -18
  101. package/src/lib/shared/reachable.ts +7 -10
  102. package/src/lib/shared/refresh.ts +12 -2
  103. package/src/lib/shared/refreshing.ts +8 -2
  104. package/src/lib/shared/resolvedCellsSlot.ts +13 -0
  105. package/src/lib/shared/reviveWireField.ts +49 -0
  106. package/src/lib/shared/reviveWireOutput.ts +36 -0
  107. package/src/lib/shared/rpcServerForRoot.ts +25 -0
  108. package/src/lib/shared/scanPages.ts +25 -0
  109. package/src/lib/shared/snapshotShippable.ts +8 -7
  110. package/src/lib/shared/streamedCellsSlot.ts +14 -0
  111. package/src/lib/shared/subscribableFromResponse.ts +4 -4
  112. package/src/lib/shared/subscribableProbes.ts +1 -1
  113. package/src/lib/shared/tailProbeSlot.ts +1 -1
  114. package/src/lib/shared/types/AsyncComputed.ts +20 -0
  115. package/src/lib/shared/types/AsyncState.ts +13 -0
  116. package/src/lib/shared/types/CacheEntry.ts +7 -7
  117. package/src/lib/shared/types/CacheOptions.ts +23 -37
  118. package/src/lib/shared/types/CachePolicy.ts +25 -0
  119. package/src/lib/shared/types/CacheSnapshotEntry.ts +6 -5
  120. package/src/lib/shared/types/ErrorJsonSchemas.ts +8 -0
  121. package/src/lib/shared/types/HttpMethod.ts +3 -1
  122. package/src/lib/shared/types/InputCoercion.ts +17 -0
  123. package/src/lib/shared/types/{Subscribable.ts → NamedAsyncIterable.ts} +1 -1
  124. package/src/lib/shared/types/OutputWirePlan.ts +17 -0
  125. package/src/lib/shared/types/PagesScan.ts +7 -0
  126. package/src/lib/shared/types/PendingAsyncCells.ts +10 -0
  127. package/src/lib/shared/types/RawRemoteFunction.ts +6 -0
  128. package/src/lib/shared/types/RemoteCallable.ts +8 -7
  129. package/src/lib/shared/types/RemoteFunction.ts +16 -15
  130. package/src/lib/shared/types/ResolvedCells.ts +11 -0
  131. package/src/lib/shared/types/ReturnBody.ts +15 -0
  132. package/src/lib/shared/types/RpcBuildStamps.ts +23 -0
  133. package/src/lib/shared/types/RpcErrorGuard.ts +3 -8
  134. package/src/lib/shared/types/Socket.ts +4 -4
  135. package/src/lib/shared/types/SsrPayload.ts +5 -1
  136. package/src/lib/shared/types/StreamPolicy.ts +11 -0
  137. package/src/lib/shared/types/StreamedCells.ts +19 -0
  138. package/src/lib/shared/types/StreamedResolution.ts +24 -6
  139. package/src/lib/shared/types/TailHooks.ts +1 -1
  140. package/src/lib/shared/types/WireKind.ts +11 -0
  141. package/src/lib/shared/validationHttpError.ts +33 -0
  142. package/src/lib/shared/warmSeedKey.ts +16 -0
  143. package/src/lib/shared/wireJsonReplacer.ts +30 -0
  144. package/src/lib/shared/writeRpcDts.ts +11 -1
  145. package/src/lib/shared/writeTestSocketsDts.ts +1 -1
  146. package/src/lib/test/createTestApp.ts +19 -1
  147. package/src/lib/ui/README.md +1 -1
  148. package/src/lib/ui/activePendingCells.ts +14 -0
  149. package/src/lib/ui/compile/BLOCK_KEYWORDS.ts +21 -0
  150. package/src/lib/ui/compile/SSR_ESCAPE.ts +4 -1
  151. package/src/lib/ui/compile/UI_RUNTIME_IMPORTS.ts +35 -8
  152. package/src/lib/ui/compile/abideUiPlugin.ts +19 -2
  153. package/src/lib/ui/compile/analyzeComponent.ts +89 -6
  154. package/src/lib/ui/compile/assignmentTargetNames.ts +54 -0
  155. package/src/lib/ui/compile/asyncInterpolationFields.ts +152 -0
  156. package/src/lib/ui/compile/asyncValuePositionError.ts +21 -0
  157. package/src/lib/ui/compile/asyncValuePositionInterpolations.ts +64 -0
  158. package/src/lib/ui/compile/attrLiftPosition.ts +18 -0
  159. package/src/lib/ui/compile/cachedSourceFile.ts +40 -0
  160. package/src/lib/ui/compile/catchBinding.ts +7 -5
  161. package/src/lib/ui/compile/classifyInterpolationType.ts +46 -0
  162. package/src/lib/ui/compile/collectAbideDiagnostics.ts +96 -0
  163. package/src/lib/ui/compile/compileComponent.ts +19 -3
  164. package/src/lib/ui/compile/compileModule.ts +27 -58
  165. package/src/lib/ui/compile/compileSSR.ts +51 -12
  166. package/src/lib/ui/compile/compileShadow.ts +145 -18
  167. package/src/lib/ui/compile/composeProps.ts +12 -2
  168. package/src/lib/ui/compile/createShadowLanguageService.ts +116 -68
  169. package/src/lib/ui/compile/createShadowProgram.ts +35 -6
  170. package/src/lib/ui/compile/declaredNames.ts +36 -0
  171. package/src/lib/ui/compile/desugarSignals.ts +418 -36
  172. package/src/lib/ui/compile/expressionIsPrefixEvaluable.ts +19 -0
  173. package/src/lib/ui/compile/generateBuild.ts +52 -9
  174. package/src/lib/ui/compile/generateSSR.ts +242 -43
  175. package/src/lib/ui/compile/hoistableAwaits.ts +193 -0
  176. package/src/lib/ui/compile/hoistableChildRenders.ts +112 -0
  177. package/src/lib/ui/compile/interpolatedTemplateLiteral.ts +3 -1
  178. package/src/lib/ui/compile/interpolationClassifierForRoot.ts +37 -0
  179. package/src/lib/ui/compile/isSpuriousAsyncReadDiagnostic.ts +174 -0
  180. package/src/lib/ui/compile/liftAsyncSubExpressions.ts +166 -0
  181. package/src/lib/ui/compile/lowerAsyncInterpolations.ts +92 -0
  182. package/src/lib/ui/compile/lowerContext.ts +10 -0
  183. package/src/lib/ui/compile/lowerDocAccess.ts +26 -23
  184. package/src/lib/ui/compile/lowerScript.ts +40 -4
  185. package/src/lib/ui/compile/nodeAtShadowOffset.ts +28 -0
  186. package/src/lib/ui/compile/parseTemplate.ts +18 -1092
  187. package/src/lib/ui/compile/parseTemplateRecovering.ts +1385 -0
  188. package/src/lib/ui/compile/referencedIdentifiers.ts +35 -0
  189. package/src/lib/ui/compile/renameSignalRefs.ts +21 -22
  190. package/src/lib/ui/compile/seedTypeClassifierForRoot.ts +65 -0
  191. package/src/lib/ui/compile/shadowInterpolationClassifier.ts +47 -0
  192. package/src/lib/ui/compile/sourceFileOptionsSignature.ts +20 -0
  193. package/src/lib/ui/compile/structuralHeadTokens.ts +107 -0
  194. package/src/lib/ui/compile/templateSemanticTokens.ts +21 -0
  195. package/src/lib/ui/compile/templateStartOffset.ts +15 -0
  196. package/src/lib/ui/compile/tryPlan.ts +4 -3
  197. package/src/lib/ui/compile/types/AnalyzedComponent.ts +4 -0
  198. package/src/lib/ui/compile/types/AsyncInterpolationField.ts +19 -0
  199. package/src/lib/ui/compile/types/InterpolationClassifier.ts +12 -0
  200. package/src/lib/ui/compile/types/InterpolationKind.ts +7 -0
  201. package/src/lib/ui/compile/types/ParseDiagnostic.ts +8 -0
  202. package/src/lib/ui/compile/types/SeedTypeClassifier.ts +15 -0
  203. package/src/lib/ui/compile/types/TemplateNode.ts +36 -4
  204. package/src/lib/ui/compile/types/ValuePositionInterpolation.ts +13 -0
  205. package/src/lib/ui/compile/writtenTemplateNames.ts +84 -0
  206. package/src/lib/ui/computed.ts +28 -1
  207. package/src/lib/ui/createScope.ts +35 -68
  208. package/src/lib/ui/dom/anchoredBranch.ts +142 -0
  209. package/src/lib/ui/dom/appendText.ts +8 -3
  210. package/src/lib/ui/dom/awaitBlock.ts +43 -94
  211. package/src/lib/ui/dom/bindProp.ts +22 -0
  212. package/src/lib/ui/dom/bindableProp.ts +47 -0
  213. package/src/lib/ui/dom/cellPending.ts +24 -0
  214. package/src/lib/ui/dom/disposeRange.ts +2 -1
  215. package/src/lib/ui/dom/each.ts +26 -4
  216. package/src/lib/ui/dom/eachAsync.ts +39 -3
  217. package/src/lib/ui/dom/fillBoundary.ts +2 -3
  218. package/src/lib/ui/dom/fillRange.ts +4 -4
  219. package/src/lib/ui/dom/hydrate.ts +6 -12
  220. package/src/lib/ui/dom/isComment.ts +1 -1
  221. package/src/lib/ui/dom/matchingRangeClose.ts +29 -0
  222. package/src/lib/ui/dom/mount.ts +1 -2
  223. package/src/lib/ui/dom/mountChild.ts +9 -40
  224. package/src/lib/ui/dom/mountRange.ts +2 -3
  225. package/src/lib/ui/dom/mountStreamedChild.ts +84 -0
  226. package/src/lib/ui/dom/mountSwappableRange.ts +46 -4
  227. package/src/lib/ui/dom/mutateDocContainer.ts +65 -0
  228. package/src/lib/ui/dom/on.ts +2 -2
  229. package/src/lib/ui/dom/readCell.ts +40 -0
  230. package/src/lib/ui/dom/switchBlock.ts +30 -7
  231. package/src/lib/ui/dom/tryBlock.ts +230 -66
  232. package/src/lib/ui/dom/types/SwitchCase.ts +5 -1
  233. package/src/lib/ui/dom/when.ts +12 -2
  234. package/src/lib/ui/dom/withScope.ts +2 -2
  235. package/src/lib/ui/finalizeStreamedChildren.ts +89 -0
  236. package/src/lib/ui/flight.ts +56 -0
  237. package/src/lib/ui/html.ts +12 -1
  238. package/src/lib/ui/isolateCellBarrier.ts +17 -0
  239. package/src/lib/ui/linked.ts +48 -2
  240. package/src/lib/ui/remoteProxy.ts +84 -159
  241. package/src/lib/ui/renderChain.ts +49 -13
  242. package/src/lib/ui/renderToStream.ts +22 -18
  243. package/src/lib/ui/resumeSeedScript.ts +1 -1
  244. package/src/lib/ui/router.ts +63 -36
  245. package/src/lib/ui/runtime/AsyncCellError.ts +20 -0
  246. package/src/lib/ui/runtime/CELL_SEED.ts +14 -0
  247. package/src/lib/ui/runtime/CURRENT_BOUNDARY.ts +11 -0
  248. package/src/lib/ui/runtime/CURRENT_PATH.ts +29 -0
  249. package/src/lib/ui/runtime/RENDER.ts +10 -7
  250. package/src/lib/ui/runtime/RESUME.ts +4 -4
  251. package/src/lib/ui/runtime/STREAMED_CELLS.ts +57 -0
  252. package/src/lib/ui/runtime/ambientPathBacking.ts +32 -0
  253. package/src/lib/ui/runtime/applyPatchToTree.ts +1 -1
  254. package/src/lib/ui/runtime/blockId.ts +31 -0
  255. package/src/lib/ui/runtime/boundaryFor.ts +11 -0
  256. package/src/lib/ui/runtime/cellBarrierBacking.ts +30 -0
  257. package/src/lib/ui/runtime/createAsyncCell.ts +300 -0
  258. package/src/lib/ui/runtime/createDoc.ts +3 -42
  259. package/src/lib/ui/runtime/createEffectNode.ts +9 -0
  260. package/src/lib/ui/runtime/enterRenderPass.ts +5 -4
  261. package/src/lib/ui/runtime/flushEffects.ts +16 -6
  262. package/src/lib/ui/runtime/isAsyncFunction.ts +14 -0
  263. package/src/lib/ui/runtime/nextBlockId.ts +8 -7
  264. package/src/lib/ui/runtime/renderPath.ts +16 -0
  265. package/src/lib/ui/runtime/types/Boundary.ts +9 -0
  266. package/src/lib/ui/runtime/types/RenderContext.ts +9 -7
  267. package/src/lib/ui/runtime/types/SsrRender.ts +9 -2
  268. package/src/lib/ui/runtime/types/UiComponent.ts +1 -5
  269. package/src/lib/ui/runtime/withOptionalPath.ts +8 -0
  270. package/src/lib/ui/runtime/withPath.ts +16 -0
  271. package/src/lib/ui/runtime/withPathFrom.ts +20 -0
  272. package/src/lib/ui/seedStreamedResolution.ts +31 -6
  273. package/src/lib/ui/settleAsyncCells.ts +24 -0
  274. package/src/lib/ui/socketProxy.ts +1 -1
  275. package/src/lib/ui/startClient.ts +16 -31
  276. package/src/lib/ui/trackedComputed.ts +68 -0
  277. package/src/lib/ui/types/Scope.ts +8 -24
  278. package/src/lib/ui/watch.ts +3 -3
  279. package/src/serverEntry.ts +14 -0
  280. package/template/src/server/rpc/getHello.ts +15 -13
  281. package/template/test/app.test.ts +1 -1
  282. package/src/lib/server/runtime/devHotModuleResponse.ts +0 -41
  283. package/src/lib/shared/DEV_HOT_PREFIX.ts +0 -7
  284. package/src/lib/shared/UNREACHABLE_STATUSES.ts +0 -13
  285. package/src/lib/shared/hasReplayableRequest.ts +0 -17
  286. package/src/lib/shared/hydratingSlot.ts +0 -12
  287. package/src/lib/shared/outboxProbeSlot.ts +0 -20
  288. package/src/lib/shared/types/Outbox.ts +0 -9
  289. package/src/lib/shared/types/OutboxEntry.ts +0 -27
  290. package/src/lib/shared/types/SmartReadOptions.ts +0 -36
  291. package/src/lib/shared/wakeHydrationPeeks.ts +0 -20
  292. package/src/lib/ui/COMPONENT_WRAPPER_PREFIX.ts +0 -5
  293. package/src/lib/ui/compile/REACTIVE_CALLEES.ts +0 -11
  294. package/src/lib/ui/compile/assertRuntimeHelpersBound.ts +0 -80
  295. package/src/lib/ui/compile/markupTokens.ts +0 -313
  296. package/src/lib/ui/compile/structuralBlockTokens.ts +0 -100
  297. package/src/lib/ui/dom/applyResolved.ts +0 -87
  298. package/src/lib/ui/dom/mutateDocArray.ts +0 -38
  299. package/src/lib/ui/dom/scopeLabel.ts +0 -21
  300. package/src/lib/ui/dom/text.ts +0 -20
  301. package/src/lib/ui/history.ts +0 -108
  302. package/src/lib/ui/installHotBridge.ts +0 -95
  303. package/src/lib/ui/installInspectorBridge.ts +0 -140
  304. package/src/lib/ui/outbox.ts +0 -35
  305. package/src/lib/ui/persist.ts +0 -115
  306. package/src/lib/ui/rpcOutbox/createOutboxQueue.ts +0 -281
  307. package/src/lib/ui/rpcOutbox/outboxRegistry.ts +0 -69
  308. package/src/lib/ui/runtime/PATCH_BUS.ts +0 -28
  309. package/src/lib/ui/runtime/captureModelDoc.ts +0 -43
  310. package/src/lib/ui/runtime/hotInstances.ts +0 -10
  311. package/src/lib/ui/runtime/hotReloadEnabled.ts +0 -8
  312. package/src/lib/ui/runtime/hotReplace.ts +0 -38
  313. package/src/lib/ui/runtime/liveScopes.ts +0 -15
  314. package/src/lib/ui/runtime/localStoragePersistence.ts +0 -47
  315. package/src/lib/ui/runtime/registerHotInstance.ts +0 -23
  316. package/src/lib/ui/runtime/seedModelDoc.ts +0 -26
  317. package/src/lib/ui/runtime/types/HotInstance.ts +0 -22
  318. package/src/lib/ui/runtime/types/PatchEvent.ts +0 -16
  319. package/src/lib/ui/seedResolved.ts +0 -28
  320. package/src/lib/ui/sync.ts +0 -48
  321. package/src/lib/ui/types/History.ts +0 -14
  322. package/src/lib/ui/types/PersistHandle.ts +0 -11
  323. package/src/lib/ui/types/PersistenceStore.ts +0 -12
  324. package/src/lib/ui/types/ResolvedFrame.ts +0 -15
  325. package/src/lib/ui/types/SyncTransport.ts +0 -13
@@ -1,95 +0,0 @@
1
- import { snippet } from '../shared/snippet.ts'
2
- import { anchorCursor } from './dom/anchorCursor.ts'
3
- import { appendSnippet } from './dom/appendSnippet.ts'
4
- import { appendStatic } from './dom/appendStatic.ts'
5
- import { appendText } from './dom/appendText.ts'
6
- import { appendTextAt } from './dom/appendTextAt.ts'
7
- import { attach } from './dom/attach.ts'
8
- import { attr } from './dom/attr.ts'
9
- import { awaitBlock } from './dom/awaitBlock.ts'
10
- import { bindSelectValue } from './dom/bindSelectValue.ts'
11
- import { cloneStatic } from './dom/cloneStatic.ts'
12
- import { each } from './dom/each.ts'
13
- import { eachAsync } from './dom/eachAsync.ts'
14
- import { hydrate } from './dom/hydrate.ts'
15
- import { mergeProps } from './dom/mergeProps.ts'
16
- import { mount } from './dom/mount.ts'
17
- import { mountChild } from './dom/mountChild.ts'
18
- import { mountSlot } from './dom/mountSlot.ts'
19
- import { mutateDocArray } from './dom/mutateDocArray.ts'
20
- import { on } from './dom/on.ts'
21
- import { outlet } from './dom/outlet.ts'
22
- import { readCall } from './dom/readCall.ts'
23
- import { restProps } from './dom/restProps.ts'
24
- import { skeleton } from './dom/skeleton.ts'
25
- import { spreadAttrs } from './dom/spreadAttrs.ts'
26
- import { spreadProps } from './dom/spreadProps.ts'
27
- import { switchBlock } from './dom/switchBlock.ts'
28
- import { tryBlock } from './dom/tryBlock.ts'
29
- import { when } from './dom/when.ts'
30
- import { effect } from './effect.ts'
31
- import { enterScope } from './enterScope.ts'
32
- import { exitScope } from './exitScope.ts'
33
- import { enterRenderPass } from './runtime/enterRenderPass.ts'
34
- import { escapeKey } from './runtime/escapeKey.ts'
35
- import { exitRenderPass } from './runtime/exitRenderPass.ts'
36
- import { hotReloadEnabled } from './runtime/hotReloadEnabled.ts'
37
- import { hotReplace } from './runtime/hotReplace.ts'
38
- import { nextBlockId } from './runtime/nextBlockId.ts'
39
- import { scope } from './scope.ts'
40
- import { watch } from './watch.ts'
41
-
42
- /*
43
- Dev-only: exposes the abide-ui runtime plus `hotReplace` on `window.__abide`, and
44
- flips on `hotReloadEnabled` so `mountChild` records every instance it mounts. A
45
- hot module (the dev server's standalone recompile of one edited `.abide`) reads
46
- its runtime from this object instead of importing fresh copies — so it shares the
47
- one reactive graph and instance registry — then calls `hotReplace` to swap every
48
- live instance in place. The keys mirror `UI_RUNTIME_IMPORTS` (+ `hotReplace`); a
49
- test guards that pairing. `startClient` calls this when the server marks the page
50
- dev (the live-reload script sets `window.__abideDev`).
51
- */
52
- export function installHotBridge(): void {
53
- ;(globalThis as { __abide?: Record<string, unknown> }).__abide = {
54
- snippet,
55
- scope,
56
- enterScope,
57
- exitScope,
58
- effect,
59
- watch,
60
- mount,
61
- appendText,
62
- appendTextAt,
63
- appendSnippet,
64
- appendStatic,
65
- cloneStatic,
66
- skeleton,
67
- anchorCursor,
68
- attr,
69
- on,
70
- attach,
71
- bindSelectValue,
72
- each,
73
- eachAsync,
74
- when,
75
- awaitBlock,
76
- tryBlock,
77
- switchBlock,
78
- mountSlot,
79
- outlet,
80
- mountChild,
81
- mergeProps,
82
- spreadProps,
83
- restProps,
84
- spreadAttrs,
85
- readCall,
86
- mutateDocArray,
87
- hydrate,
88
- escapeKey,
89
- nextBlockId,
90
- enterRenderPass,
91
- exitRenderPass,
92
- hotReplace,
93
- }
94
- hotReloadEnabled.current = true
95
- }
@@ -1,140 +0,0 @@
1
- import { effect } from './effect.ts'
2
- import { clientPage } from './runtime/clientPage.ts'
3
- import { historyEntries } from './runtime/historyEntries.ts'
4
- import { liveScopes } from './runtime/liveScopes.ts'
5
- import { PATCH_BUS } from './runtime/PATCH_BUS.ts'
6
- import { runtimePath } from './runtime/runtimePath.ts'
7
-
8
- /* Same-origin cross-tab channel the inspector page listens on. The app tab
9
- publishes scope + router state; the inspector subscribes. Web-standard, so no
10
- server route or buffer is involved — core publishes, the package consumes. */
11
- const CHANNEL = 'abide:inspector'
12
-
13
- /* Coalesce a burst of patches into one snapshot — the post is the cost, the read is cheap. */
14
- const SNAPSHOT_DEBOUNCE_MS = 60
15
-
16
- /* JSON round-trip to strip anything structured-clone can't carry (functions,
17
- class instances, DOM nodes) before postMessage, degrading to undefined rather
18
- than throwing into the channel. */
19
- function cloneable(value: unknown): unknown {
20
- try {
21
- return JSON.parse(JSON.stringify(value))
22
- } catch {
23
- return undefined
24
- }
25
- }
26
-
27
- /* The live scope forest as a flat list — the inspector rebuilds the tree from
28
- each node's `parent` id (the Scope surface exposes no children accessor). */
29
- function scopeNodes(): Array<{
30
- id: string
31
- label: string | undefined
32
- parent: string | undefined
33
- state: unknown
34
- recorded: boolean
35
- }> {
36
- return Array.from(liveScopes.scopes, (scope) => ({
37
- id: scope.id,
38
- label: scope.label,
39
- parent: scope.parent?.id,
40
- state: cloneable(scope.snapshot()),
41
- recorded: scope.canUndo() || scope.canRedo(),
42
- }))
43
- }
44
-
45
- /* Router state off the existing reactive holders — read, not subscribed, here;
46
- the nav effect drives re-reads. */
47
- function routerState() {
48
- const page = clientPage.value
49
- return {
50
- path: runtimePath.value,
51
- route: page.route,
52
- /* `page.params` is a reactive Proxy — structuredClone (postMessage) can't carry
53
- a Proxy and throws, dropping every router frame. Spread to a plain object. */
54
- params: { ...page.params },
55
- navigating: page.navigating,
56
- url: page.url.href,
57
- entry: historyEntries.current,
58
- }
59
- }
60
-
61
- /*
62
- Installs the client→inspector bridge: a BroadcastChannel the inspector page reads
63
- to render its Reactive + Router tabs. Gated by `__abideInspect` (server-injected
64
- only when the inspector is enabled) and called from startClient before the router
65
- builds any scope — so registration is armed before the first scope exists. A
66
- no-op where BroadcastChannel is absent (SSR, old runtimes). Mirrors
67
- installHotBridge: dev instrumentation, not public surface.
68
- */
69
- export function installInspectorBridge(): void {
70
- /* No BroadcastChannel means no consumer for the scope registry — bail before
71
- arming `liveScopes.enabled`, so createScope never pays scope tracking for a
72
- set nothing reads. */
73
- if (typeof BroadcastChannel === 'undefined') {
74
- return
75
- }
76
- liveScopes.enabled = true
77
- const channel = new BroadcastChannel(CHANNEL)
78
- const tab = typeof crypto !== 'undefined' ? crypto.randomUUID() : String(performance.now())
79
- const post = (message: object) => {
80
- try {
81
- channel.postMessage({ tab, ...message })
82
- } catch {
83
- /* A non-cloneable slipped through despite the JSON pass — drop the frame
84
- rather than throw into a patch/nav handler. */
85
- }
86
- }
87
-
88
- const announce = () =>
89
- post({
90
- kind: 'announce',
91
- url: typeof location !== 'undefined' ? location.href : '',
92
- app: typeof document !== 'undefined' ? document.title : '',
93
- })
94
- const sendSnapshot = () =>
95
- post({ kind: 'snapshot', scopes: scopeNodes(), router: routerState() })
96
-
97
- let snapshotTimer: ReturnType<typeof setTimeout> | undefined
98
- const scheduleSnapshot = () => {
99
- clearTimeout(snapshotTimer)
100
- snapshotTimer = setTimeout(sendSnapshot, SNAPSHOT_DEBOUNCE_MS)
101
- }
102
-
103
- /* Inspector handshakes: `hello` (it just opened — everyone re-announce + push)
104
- and `request` (it wants a fresh snapshot of this tab). */
105
- channel.onmessage = (event) => {
106
- const message = event.data as { kind?: string; tab?: string }
107
- if (message.kind === 'hello') {
108
- announce()
109
- sendSnapshot()
110
- } else if (message.kind === 'request' && message.tab === tab) {
111
- sendSnapshot()
112
- }
113
- }
114
-
115
- /* Every doc mutation flows through PATCH_BUS; forward the patch as a live
116
- mutation event and re-snapshot (debounced) so values stay current. */
117
- PATCH_BUS.subscribe((patchEvent) => {
118
- post({ kind: 'patch', op: patchEvent.patch.op, path: patchEvent.patch.path })
119
- scheduleSnapshot()
120
- })
121
-
122
- /* Reading the page + path States tracks them, so this re-runs on navigation. */
123
- effect(() => {
124
- clientPage.value
125
- runtimePath.value
126
- post({ kind: 'nav', router: routerState() })
127
- })
128
-
129
- if (typeof addEventListener === 'function') {
130
- addEventListener('pagehide', () => {
131
- post({ kind: 'bye' })
132
- channel.close()
133
- })
134
- }
135
-
136
- announce()
137
- /* First snapshot once the router has built the initial scope tree (this runs
138
- before router()). */
139
- setTimeout(sendSnapshot, 0)
140
- }
@@ -1,35 +0,0 @@
1
- import type { OutboxEntry } from '../shared/types/OutboxEntry.ts'
2
- import type { RemoteFunction } from '../shared/types/RemoteFunction.ts'
3
- import { outboxRegistry } from './rpcOutbox/outboxRegistry.ts'
4
-
5
- /* One entry in the global outbox, tagged with the RPC it belongs to. */
6
- export type GlobalOutboxEntry = OutboxEntry<unknown> & { rpc: RemoteFunction<unknown, unknown> }
7
-
8
- /* The global outbox: callable for the flat entry list, awaitable `retry()` to drain every
9
- queue (resolves when every replay settles). */
10
- export type GlobalOutbox = (() => GlobalOutboxEntry[]) & { retry: () => Promise<void> }
11
-
12
- function list(): GlobalOutboxEntry[] {
13
- return outboxRegistry.all().flatMap(({ rpc, queue }) =>
14
- queue.entries().map((entry) => ({
15
- ...entry,
16
- rpc: rpc as RemoteFunction<unknown, unknown>,
17
- })),
18
- )
19
- }
20
-
21
- /*
22
- The global, reactive view of every durable RPC's outbox — a flat list of undelivered
23
- entries across all `outbox` rpcs, each tagged with its `rpc`. Reactive: reading it in a
24
- template/effect subscribes to each registered queue, so it updates as writes park, drain,
25
- fail, or cancel. Use it for an app-wide "N unsynced" badge or a sync panel; cancel a
26
- pending write through `entry.controller.abort()`, retry one through `entry.retry()`, or
27
- drain everything through `outbox.retry()`. A single rpc's slice is `rpc.outbox`. Server-
28
- side there are no client queues, so it returns an empty list.
29
- */
30
- // @documentation ui
31
- export const outbox: GlobalOutbox = Object.assign(list, {
32
- async retry(): Promise<void> {
33
- await Promise.all(outboxRegistry.all().map(({ queue }) => queue.retry()))
34
- },
35
- })
@@ -1,115 +0,0 @@
1
- import { escapeKey } from './runtime/escapeKey.ts'
2
- import { localStoragePersistence } from './runtime/localStoragePersistence.ts'
3
- import { PATCH_BUS } from './runtime/PATCH_BUS.ts'
4
- import type { Doc } from './runtime/types/Doc.ts'
5
- import type { PersistenceStore } from './types/PersistenceStore.ts'
6
- import type { PersistHandle } from './types/PersistHandle.ts'
7
-
8
- /*
9
- Makes a document durable: seeds it from the saved snapshot on boot, then writes
10
- its snapshot back (debounced) on every change the patch bus announces — so the
11
- state survives a reload. The bus is the single tap; `persist` adds no bookkeeping
12
- to the doc. A store-less environment (the server, or a browser without
13
- localStorage and no injected store) returns an inert handle, so the same call is
14
- safe to write isomorphically.
15
-
16
- Restore is a per-top-level-key overlay (saved wins, current keeps keys saved
17
- lacks), so a doc that boots empty fills from the snapshot while a key added in a
18
- newer app version keeps its fresh default. Writes coalesce into one per `debounce`
19
- window and flush when the tab is hidden, so the tail of a burst isn't lost.
20
- */
21
- // @documentation plumbing
22
- export function persist(
23
- doc: Doc,
24
- key: string,
25
- {
26
- store = localStoragePersistence(),
27
- debounce = 200,
28
- }: { store?: PersistenceStore; debounce?: number } = {},
29
- ): PersistHandle {
30
- /* No durable store → inert: server render, or a browser without localStorage. */
31
- if (store === undefined) {
32
- return { flush: () => undefined, clear: () => undefined, dispose: () => undefined }
33
- }
34
-
35
- const saved = store.load(key)
36
- if (saved !== undefined) {
37
- restore(doc, saved)
38
- }
39
-
40
- /* Coalesce a burst into one write: the timer is armed by the first patch and
41
- writes the latest snapshot when it fires, bounding staleness to `debounce`. */
42
- let timer: ReturnType<typeof setTimeout> | undefined
43
- const flush = (): void => {
44
- if (timer !== undefined) {
45
- clearTimeout(timer)
46
- timer = undefined
47
- }
48
- store.save(key, doc.snapshot())
49
- }
50
- const schedule = (): void => {
51
- if (timer === undefined) {
52
- timer = setTimeout(flush, debounce)
53
- }
54
- }
55
-
56
- const unsubscribe = PATCH_BUS.subscribe((event) => {
57
- if (event.doc === doc) {
58
- schedule()
59
- }
60
- })
61
-
62
- /* Flush the tail before the tab goes away, so a pending debounced write survives. */
63
- const onHidden = (): void => {
64
- if (typeof document !== 'undefined' && document.visibilityState === 'hidden') {
65
- flush()
66
- }
67
- }
68
- const canListen = typeof window !== 'undefined' && typeof window.addEventListener === 'function'
69
- if (canListen) {
70
- window.addEventListener('pagehide', flush)
71
- document.addEventListener('visibilitychange', onHidden)
72
- }
73
-
74
- return {
75
- flush,
76
- clear: () => {
77
- if (timer !== undefined) {
78
- clearTimeout(timer)
79
- timer = undefined
80
- }
81
- store.remove(key)
82
- },
83
- dispose: () => {
84
- unsubscribe()
85
- if (timer !== undefined) {
86
- clearTimeout(timer)
87
- timer = undefined
88
- }
89
- if (canListen) {
90
- window.removeEventListener('pagehide', flush)
91
- document.removeEventListener('visibilitychange', onHidden)
92
- }
93
- },
94
- }
95
- }
96
-
97
- /* Load `saved` into `doc`: a per-key overlay for object roots (so the doc's own
98
- keys that the snapshot lacks keep their value), else a wholesale root replace
99
- (a primitive or array root). */
100
- function restore(doc: Doc, saved: unknown): void {
101
- const current = doc.snapshot()
102
- if (isPlainObject(saved) && isPlainObject(current)) {
103
- /* `replace` takes a `/`-delimited escaped path, so a top-level key containing
104
- `/` or `~` must be escaped to a single segment or it'd be mis-routed. */
105
- for (const key of Object.keys(saved)) {
106
- doc.replace(escapeKey(key), saved[key])
107
- }
108
- return
109
- }
110
- doc.replace('', saved)
111
- }
112
-
113
- function isPlainObject(value: unknown): value is Record<string, unknown> {
114
- return value !== null && typeof value === 'object' && !Array.isArray(value)
115
- }
@@ -1,281 +0,0 @@
1
- import { decodeResponse } from '../../shared/decodeResponse.ts'
2
- import type { OutboxEntry, OutboxStatus } from '../../shared/types/OutboxEntry.ts'
3
- import { UNREACHABLE_STATUSES } from '../../shared/UNREACHABLE_STATUSES.ts'
4
- import { persist } from '../persist.ts'
5
- import { createDoc as doc } from '../runtime/createDoc.ts'
6
- import type { PersistenceStore } from '../types/PersistenceStore.ts'
7
-
8
- /* Persisted form of an entry — the Request reduced to its replayable parts (a live
9
- Request + AbortController don't serialize). `body`/`contentType` are captured
10
- asynchronously just after park (Request.text() is async); they seed empty and fill
11
- in before any reload, so a restored entry replays with its original body. */
12
- type StoredEntry<Args> = {
13
- id: string
14
- args: Args
15
- method: string
16
- url: string
17
- body: string
18
- contentType: string
19
- status: OutboxStatus
20
- }
21
-
22
- /* Non-serializable identity for a live entry: its abort handle, the in-session Request
23
- (body intact), and the per-entry retry trigger. Rebuilt from the persisted item on a
24
- cold read after reload. */
25
- type Identity = { controller: AbortController; request: Request; retry: () => Promise<void> }
26
-
27
- export type OutboxQueue<Args> = {
28
- park: (args: Args, request: Request, reason?: unknown) => OutboxEntry<Args>
29
- entries: () => OutboxEntry<Args>[]
30
- /* Undelivered-entry count — the same reactive read as `entries()`, without building
31
- the entry objects. A caller parks straight to the tail when this is non-empty. */
32
- size: () => number
33
- retry: () => Promise<void>
34
- drain: () => Promise<void>
35
- dispose: () => void
36
- }
37
-
38
- /* Persistence key per RPC url — the queue's durable identity. */
39
- const keyFor = (url: string): string => `abide:outbox:${url}`
40
-
41
- /*
42
- A durable, app-owned FIFO retry queue for one RPC. A call fetches directly and throws as
43
- before while the queue is empty; when the server can't be reached — a transport failure or
44
- a 502/503/504/520–527/530 — the caller `park`s the request here (a side-effect; the call still
45
- throws), and once a backlog exists the caller parks every later call at the tail too, so
46
- FIFO order is preserved on replay. Built on `doc` + `persist`, so the queue IS a persisted
47
- document that survives a reload. Draining is manual — the app calls `retry()` (per-entry or
48
- whole-queue) when it decides to replay; nothing drains automatically. The drain re-sends
49
- head-first: any server response removes the entry — a 2xx (delivered) AND a real 4xx/500
50
- (the server handled+rejected it; retrying won't help, so it leaves the queue) — while a
51
- still-unreachable result keeps it `queued` for the next `retry()`, and an abort removes it.
52
- Resends run under the entry's OWN abort signal alone — scope-abort and the client timeout
53
- are deliberately not composed in, so a parked write survives an unmount and waits out the
54
- outage.
55
- */
56
- export function createOutboxQueue<Args>(opts: {
57
- url: string
58
- send: (request: Request) => Promise<Response>
59
- store?: PersistenceStore
60
- }): OutboxQueue<Args> {
61
- const stored = doc({ items: [] as StoredEntry<Args>[] })
62
- const persistence = persist(stored, keyFor(opts.url), { store: opts.store, debounce: 0 })
63
- /* Live identity (controller + Request + retry) by id, rebuilt from persisted items on
64
- a cold read; `errors` is transient last-failure state (never persisted). */
65
- const live = new Map<string, Identity>()
66
- const errors = new Map<string, unknown>()
67
- /* Per-entry `settled` deferreds, created lazily on first read of `entry.settled` (so a
68
- never-awaited refusal can't become an unhandled rejection) and resolved/rejected once
69
- at the drain exit. Transient — a reload starts fresh. */
70
- type Settler = {
71
- promise: Promise<unknown>
72
- resolve: (value: unknown) => void
73
- reject: (error: unknown) => void
74
- }
75
- const settlers = new Map<string, Settler>()
76
-
77
- /* The lazily-armed `settled` promise for an id: the outcome of this write as if the
78
- original call had reached the server. */
79
- const settledFor = (id: string): Promise<unknown> => {
80
- const existing = settlers.get(id)
81
- if (existing !== undefined) {
82
- return existing.promise
83
- }
84
- let resolve!: (value: unknown) => void
85
- let reject!: (error: unknown) => void
86
- const promise = new Promise<unknown>((res, rej) => {
87
- resolve = res
88
- reject = rej
89
- })
90
- settlers.set(id, { promise, resolve, reject })
91
- return promise
92
- }
93
-
94
- /* Settle an id's deferred (if armed) and drop it — single-shot. */
95
- const settle = (id: string, run: (settler: Settler) => void): void => {
96
- const settler = settlers.get(id)
97
- if (settler !== undefined) {
98
- run(settler)
99
- settlers.delete(id)
100
- }
101
- }
102
-
103
- const items = (): StoredEntry<Args>[] => stored.read('items') as StoredEntry<Args>[]
104
-
105
- const remove = (id: string): void => {
106
- const index = items().findIndex((item) => item.id === id)
107
- if (index !== -1) {
108
- stored.remove(`items/${index}`)
109
- persistence.flush()
110
- }
111
- live.delete(id)
112
- errors.delete(id)
113
- /* A cancel/supersede removal with nobody having delivered first → reject `settled`
114
- as a canceled call would. Delivery/refusal settle before calling remove, so this
115
- is a no-op for them (single-shot). */
116
- settle(id, (s) => s.reject(new DOMException('The outbox entry was canceled', 'AbortError')))
117
- }
118
-
119
- const setStatus = (id: string, status: OutboxStatus): void => {
120
- const index = items().findIndex((item) => item.id === id)
121
- if (index !== -1) {
122
- stored.replace(`items/${index}/status`, status)
123
- persistence.flush()
124
- }
125
- }
126
-
127
- /* The non-serializable identity for an id: reuse the live one (Request body intact in
128
- session), else rebuild from the persisted parts (fresh controller, cold reload). */
129
- const identityFor = (item: StoredEntry<Args>): Identity => {
130
- const existing = live.get(item.id)
131
- if (existing !== undefined) {
132
- return existing
133
- }
134
- const controller = new AbortController()
135
- controller.signal.addEventListener('abort', () => remove(item.id), { once: true })
136
- /* `body || undefined` so a bodyless method rebuilds without one (a Request with a
137
- body-carrying method + no body is fine; GET/DELETE with a body throws). */
138
- const request = new Request(item.url, {
139
- method: item.method,
140
- body: item.body || undefined,
141
- headers: item.contentType ? { 'content-type': item.contentType } : undefined,
142
- })
143
- /* Per-entry `retry()` kicks a FIFO drain — every entry is `queued`, so it resends
144
- head-first and reaches this one in order. */
145
- const identity: Identity = { controller, request, retry: () => drain() }
146
- live.set(item.id, identity)
147
- return identity
148
- }
149
-
150
- const toLive = (item: StoredEntry<Args>): OutboxEntry<Args> => {
151
- const { controller, request, retry } = identityFor(item)
152
- return {
153
- id: item.id,
154
- controller,
155
- request,
156
- args: item.args,
157
- status: item.status,
158
- error: errors.get(item.id),
159
- retry,
160
- /* Lazy: the deferred is only armed when someone reads `settled`, so an
161
- unawaited refusal can't surface as an unhandled rejection. */
162
- get settled() {
163
- return settledFor(item.id)
164
- },
165
- }
166
- }
167
-
168
- /* Persist the request body + content type just after park (Request.text() is async).
169
- The in-session drain replays the LIVE request (body intact) regardless; this fills
170
- the persisted form before any reload. A bodyless method leaves the empty seed. */
171
- const captureRequest = async (id: string, request: Request): Promise<void> => {
172
- const body = request.body === null ? '' : await request.clone().text()
173
- const contentType = request.headers.get('content-type') ?? ''
174
- const index = items().findIndex((item) => item.id === id)
175
- if (index === -1) {
176
- return
177
- }
178
- stored.replace(`items/${index}/body`, body)
179
- stored.replace(`items/${index}/contentType`, contentType)
180
- persistence.flush()
181
- }
182
-
183
- /* Resends run under the entry's OWN signal only — a parked write survives unmount +
184
- waits out the outage, so scope-abort + client-timeout are NOT composed in. */
185
- const sendable = (entry: OutboxEntry<Args>): Request =>
186
- new Request(entry.request, { signal: entry.controller.signal })
187
-
188
- let draining = false
189
- let settled: Promise<void> = Promise.resolve()
190
- const drain = (): Promise<void> => {
191
- if (draining) {
192
- return settled
193
- }
194
- draining = true
195
- settled = (async () => {
196
- try {
197
- while (true) {
198
- const head = items()[0]
199
- if (head === undefined) {
200
- break
201
- }
202
- const entry = toLive(head)
203
- if (entry.controller.signal.aborted) {
204
- remove(head.id) // canceled while queued
205
- continue
206
- }
207
- setStatus(head.id, 'sending')
208
- try {
209
- const response = await opts.send(sendable(entry))
210
- if (UNREACHABLE_STATUSES.has(response.status)) {
211
- setStatus(head.id, 'queued') // still down → wait for the next trigger
212
- break
213
- }
214
- /* The server responded — 2xx OR a real 4xx/500. Either way it
215
- HANDLED the request (the outbox only holds the unreachable), so
216
- the entry leaves the queue and the next proceeds in order. Decode
217
- it exactly as a live call would so `settled` resolves with the
218
- result, or rejects with the real HttpError on a refusal. */
219
- try {
220
- const value = await decodeResponse(response)
221
- settle(head.id, (s) => s.resolve(value))
222
- } catch (responseError) {
223
- settle(head.id, (s) => s.reject(responseError))
224
- }
225
- remove(head.id)
226
- continue
227
- } catch {
228
- if (entry.controller.signal.aborted) {
229
- remove(head.id) // canceled mid-send
230
- continue
231
- }
232
- /* Transport failure on resend — still unreachable, keep queued. */
233
- setStatus(head.id, 'queued')
234
- break
235
- }
236
- }
237
- } finally {
238
- draining = false
239
- }
240
- })()
241
- return settled
242
- }
243
-
244
- return {
245
- park(args, request, reason) {
246
- const id = crypto.randomUUID()
247
- const controller = new AbortController()
248
- controller.signal.addEventListener('abort', () => remove(id), { once: true })
249
- live.set(id, { controller, request, retry: () => drain() })
250
- if (reason !== undefined) {
251
- errors.set(id, reason)
252
- }
253
- stored.add('items/-', {
254
- id,
255
- args,
256
- method: request.method,
257
- url: request.url,
258
- body: '',
259
- contentType: '',
260
- status: 'queued',
261
- })
262
- persistence.flush()
263
- void captureRequest(id, request)
264
- const item = items().find((entry) => entry.id === id) as StoredEntry<Args>
265
- return toLive(item)
266
- },
267
- entries() {
268
- return items().map(toLive)
269
- },
270
- size() {
271
- return items().length
272
- },
273
- retry() {
274
- return drain()
275
- },
276
- drain,
277
- dispose() {
278
- persistence.dispose()
279
- },
280
- }
281
- }