akanjs 3.0.2 → 3.1.0-rc.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/CHANGELOG.md +26 -0
- package/base/baseEnv.ts +0 -4
- package/base/primitiveRegistry.ts +2 -2
- package/client/cookie.ts +5 -1
- package/client/csrLocation.ts +122 -0
- package/client/csrTypes.ts +32 -4
- package/client/device.ts +2 -1
- package/client/frameConfig.ts +78 -7
- package/client/index.ts +3 -0
- package/client/makePageProto.tsx +3 -2
- package/client/native/external-view.ts +1 -0
- package/client/route/RootLayoutDefinition.ts +7 -1
- package/client/route/RouteHead.ts +108 -0
- package/client/route/index.ts +1 -0
- package/client/router.ts +36 -23
- package/client/rscNavigation.ts +8 -2
- package/client/viewportMeta.ts +60 -0
- package/client/webManifest.ts +18 -0
- package/common/Logger.ts +7 -2
- package/common/PipelineNames.ts +31 -0
- package/common/PipelinePattern.ts +70 -0
- package/common/PipelinePlanSchema.ts +490 -0
- package/common/backoff.ts +14 -0
- package/common/index.ts +39 -0
- package/common/pipelinePlan.ts +259 -0
- package/common/routeConvention.ts +1 -0
- package/common/types.ts +17 -0
- package/constant/constantRegistry.ts +26 -6
- package/dictionary/base.dictionary.ts +8 -0
- package/dictionary/trans.ts +9 -1
- package/document/into.ts +5 -0
- package/fetch/client/fetchClient.ts +135 -31
- package/fetch/client/httpClient.ts +45 -0
- package/fetch/client/wsClient.ts +125 -16
- package/fetch/fetchType/buildFetch.type.ts +1 -0
- package/index.ts +243 -10
- package/package.json +1 -5
- package/server/akanApp.ts +5 -2
- package/server/akanServer.ts +44 -8
- package/server/artifact/builderRpc.ts +8 -3
- package/server/artifact/ipcTypes.ts +5 -2
- package/server/di/diLifecycle.ts +39 -3
- package/server/di/utils.ts +28 -1
- package/server/ed25519Key.ts +48 -0
- package/server/index.ts +1 -0
- package/server/machine/index.ts +2 -0
- package/server/machine/machineAccount.ts +27 -0
- package/server/machine/machineToken.ts +151 -0
- package/server/mcp/McpApps.ts +256 -0
- package/server/mcp/McpDispatcher.ts +31 -7
- package/server/mcp/McpRouter.ts +209 -46
- package/server/ops/opsToken.ts +3 -10
- package/server/resolver/database.resolver.ts +3 -0
- package/server/resolver/signal.resolver.ts +40 -3
- package/server/routeElementComposer.tsx +78 -2
- package/server/routeState.ts +6 -0
- package/server/routeTreeBuilder.ts +26 -9
- package/server/routing/apiRouter.ts +6 -3
- package/server/routing/roomReplay.ts +88 -0
- package/server/rscClient.tsx +53 -8
- package/server/rscWorker.tsx +141 -27
- package/server/rscWorkerHost.ts +18 -5
- package/server/sitemap.ts +14 -0
- package/server/ssrFromRscRenderer.tsx +12 -6
- package/server/ssrTypes.ts +3 -1
- package/server/systemPageDocument.tsx +38 -30
- package/server/types.tsx +2 -0
- package/server/webRouter.ts +74 -5
- package/service/injectInfo.ts +9 -1
- package/service/ipcTypes.ts +2 -1
- package/service/predefinedAdaptor/database.adaptor.ts +1 -146
- package/service/predefinedAdaptor/queue.adaptor.ts +44 -6
- package/service/predefinedAdaptor/queueBackoff.ts +12 -0
- package/service/predefinedAdaptor/solidQueue.adaptor.ts +17 -13
- package/service/predefinedAdaptor/sql/SqlDocumentStore.ts +44 -2
- package/service/predefinedAdaptor/sql/dialect/postgres.ts +3 -0
- package/service/predefinedAdaptor/sql/dialect/sqlite.ts +5 -2
- package/service/predefinedAdaptor/sql/search/fts5.ts +1 -1
- package/service/predefinedAdaptor/sql/types.ts +8 -6
- package/service/predefinedAdaptor/sql/values.ts +0 -23
- package/service/serve.ts +5 -1
- package/signal/agent.signal.ts +1 -1
- package/signal/base.signal.ts +2 -2
- package/signal/endpointIdempotency.ts +53 -0
- package/signal/internalArg.ts +14 -0
- package/signal/mcp/McpDocument.ts +38 -1
- package/signal/mcp/mcpProtocol.ts +18 -0
- package/signal/signalContext.ts +58 -30
- package/signal/signalRegistry.ts +14 -4
- package/signal/types.ts +18 -3
- package/store/agent/StoreSurfaceSource.ts +1 -1
- package/store/agentic/StToolBuilder.ts +3 -0
- package/test/conformance.ts +9 -33
- package/types/base/baseEnv.d.ts +0 -4
- package/types/base/primitiveRegistry.d.ts +1 -1
- package/types/client/csrLocation.d.ts +32 -0
- package/types/client/csrTypes.d.ts +40 -4
- package/types/client/frameConfig.d.ts +14 -2
- package/types/client/index.d.ts +3 -0
- package/types/client/native/external-view.d.ts +1 -0
- package/types/client/route/RootLayoutDefinition.d.ts +2 -1
- package/types/client/route/RouteHead.d.ts +13 -0
- package/types/client/route/index.d.ts +1 -0
- package/types/client/router.d.ts +6 -0
- package/types/client/rscNavigation.d.ts +4 -0
- package/types/client/viewportMeta.d.ts +18 -0
- package/types/client/webManifest.d.ts +4 -0
- package/types/common/PipelineNames.d.ts +6 -0
- package/types/common/PipelinePattern.d.ts +14 -0
- package/types/common/PipelinePlanSchema.d.ts +9 -0
- package/types/common/backoff.d.ts +10 -0
- package/types/common/index.d.ts +5 -0
- package/types/common/pipelinePlan.d.ts +224 -0
- package/types/common/types.d.ts +16 -0
- package/types/constant/constantRegistry.d.ts +5 -2
- package/types/dictionary/base.dictionary.d.ts +1 -1
- package/types/dictionary/dictionary.d.ts +8 -8
- package/types/document/into.d.ts +9 -0
- package/types/fetch/client/fetchClient.d.ts +31 -4
- package/types/fetch/client/httpClient.d.ts +8 -0
- package/types/fetch/client/wsClient.d.ts +26 -2
- package/types/fetch/fetchType/buildFetch.type.d.ts +1 -0
- package/types/index.d.ts +230 -9
- package/types/server/akanServer.d.ts +16 -1
- package/types/server/artifact/builderRpc.d.ts +7 -2
- package/types/server/artifact/ipcTypes.d.ts +5 -1
- package/types/server/di/utils.d.ts +1 -0
- package/types/server/ed25519Key.d.ts +13 -0
- package/types/server/index.d.ts +1 -0
- package/types/server/machine/index.d.ts +2 -0
- package/types/server/machine/machineAccount.d.ts +5 -0
- package/types/server/machine/machineToken.d.ts +29 -0
- package/types/server/machine.d.ts +1 -0
- package/types/server/mcp/McpApps.d.ts +50 -0
- package/types/server/mcp/McpDispatcher.d.ts +4 -1
- package/types/server/mcp/McpRouter.d.ts +4 -0
- package/types/server/resolver/signal.resolver.d.ts +2 -1
- package/types/server/routeElementComposer.d.ts +24 -4
- package/types/server/routeState.d.ts +4 -1
- package/types/server/routing/roomReplay.d.ts +24 -0
- package/types/server/rscWorkerHost.d.ts +5 -2
- package/types/server/sitemap.d.ts +2 -0
- package/types/server/ssrFromRscRenderer.d.ts +3 -3
- package/types/server/ssrTypes.d.ts +4 -1
- package/types/server/systemPageDocument.d.ts +2 -0
- package/types/server/types.d.ts +4 -1
- package/types/server/webRouter.d.ts +3 -1
- package/types/service/ipcTypes.d.ts +2 -0
- package/types/service/predefinedAdaptor/database.adaptor.d.ts +1 -24
- package/types/service/predefinedAdaptor/queue.adaptor.d.ts +10 -3
- package/types/service/predefinedAdaptor/queueBackoff.d.ts +4 -0
- package/types/service/predefinedAdaptor/solidQueue.adaptor.d.ts +3 -3
- package/types/service/predefinedAdaptor/sql/SqlDocumentStore.d.ts +6 -1
- package/types/service/predefinedAdaptor/sql/dialect/postgres.d.ts +1 -0
- package/types/service/predefinedAdaptor/sql/dialect/sqlite.d.ts +1 -0
- package/types/service/predefinedAdaptor/sql/search/fts5.d.ts +1 -1
- package/types/service/predefinedAdaptor/sql/types.d.ts +9 -5
- package/types/service/predefinedAdaptor/sql/values.d.ts +0 -3
- package/types/service/serve.d.ts +3 -0
- package/types/signal/endpointIdempotency.d.ts +7 -0
- package/types/signal/internalArg.d.ts +10 -0
- package/types/signal/mcp/McpDocument.d.ts +8 -0
- package/types/signal/mcp/mcpProtocol.d.ts +17 -0
- package/types/signal/signalContext.d.ts +5 -3
- package/types/signal/signalRegistry.d.ts +4 -4
- package/types/signal/types.d.ts +21 -1
- package/types/store/agentic/StToolBuilder.d.ts +2 -0
- package/types/test/conformance.d.ts +7 -10
- package/types/ui/Agent/Chat.d.ts +5 -1
- package/types/ui/Agent/EmbedApproval.d.ts +1 -0
- package/types/ui/Agent/index.d.ts +3 -0
- package/types/ui/Agent/useKeyboardFrame.d.ts +2 -0
- package/types/ui/Agent/useScreenActions.d.ts +18 -0
- package/types/ui/BottomSheet.d.ts +1 -0
- package/types/ui/Constant/Doc.d.ts +11 -17
- package/types/ui/Constant/index.d.ts +7 -2
- package/types/ui/Constant/schemaDoc.d.ts +11 -0
- package/types/ui/Dialog/Modal.d.ts +6 -1
- package/types/ui/Dialog/index.d.ts +1 -1
- package/types/ui/Dialog/modalSurface.d.ts +12 -0
- package/types/ui/Layout/BottomTab.d.ts +11 -2
- package/types/ui/Layout/FrameSlotFallback.d.ts +9 -0
- package/types/ui/Layout/Navbar.d.ts +4 -1
- package/types/ui/Layout/TopInset.d.ts +1 -1
- package/types/ui/Layout/index.d.ts +3 -3
- package/types/ui/Link/Action.d.ts +5 -1
- package/types/ui/Link/Anchor.d.ts +5 -3
- package/types/ui/Link/activePath.d.ts +13 -0
- package/types/ui/Link/index.d.ts +1 -1
- package/types/ui/Modal.d.ts +6 -1
- package/types/ui/Model/EditModal.d.ts +6 -1
- package/types/ui/Model/New.d.ts +6 -1
- package/types/ui/Model/ViewEditModal.d.ts +6 -1
- package/types/ui/Model/ViewModal.d.ts +6 -1
- package/types/ui/Model/newFormOrigin.d.ts +7 -0
- package/types/ui/Portal.d.ts +3 -1
- package/types/ui/Reference/origin.d.ts +12 -0
- package/types/ui/Signal/Doc.d.ts +23 -18
- package/types/ui/Signal/Listener.d.ts +2 -6
- package/types/ui/Signal/Object.d.ts +6 -10
- package/types/ui/Signal/Response.d.ts +3 -6
- package/types/ui/Signal/RestApi.d.ts +11 -15
- package/types/ui/Signal/WebSocket.d.ts +11 -21
- package/types/ui/Signal/endpointEntries.d.ts +8 -0
- package/types/ui/Signal/index.d.ts +35 -12
- package/types/ui/Signal/signalText.d.ts +178 -0
- package/types/ui/System/CSR.d.ts +2 -2
- package/types/ui/System/Client.d.ts +4 -3
- package/types/ui/System/Common.d.ts +27 -6
- package/types/ui/System/SSR.d.ts +6 -6
- package/types/ui/Tooltip.d.ts +4 -2
- package/types/ui/index.d.ts +2 -1
- package/types/ui/overlayLayer.d.ts +6 -0
- package/types/ui/overlayPosition.d.ts +17 -0
- package/types/vendor/@akanjs/native/packages/cli/src/api.d.ts +14 -3
- package/types/vendor/@akanjs/native/packages/cli/src/commands/update.d.ts +1 -1
- package/types/vendor/@akanjs/native/packages/cli/src/config.d.ts +30 -5
- package/types/vendor/@akanjs/native/packages/cli/src/lib/compat.d.ts +8 -0
- package/types/vendor/@akanjs/native/packages/cli/src/lib/desktop-server.d.ts +5 -0
- package/types/vendor/@akanjs/native/packages/cli/src/lib/distribution.d.ts +22 -0
- package/types/vendor/@akanjs/native/packages/cli/src/lib/project.d.ts +4 -0
- package/types/vendor/@akanjs/native/packages/cli/src/lib/publish.d.ts +16 -10
- package/types/vendor/@akanjs/native/packages/cli/src/lib/updates.d.ts +51 -7
- package/types/vendor/@akanjs/native/packages/cli/src/lib/vectors.d.ts +1 -1
- package/types/vendor/@akanjs/native/packages/cli/src/platforms/windows-installer.d.ts +7 -0
- package/types/vendor/@akanjs/native/packages/cli/src/platforms/windows.d.ts +5 -0
- package/types/vendor/@akanjs/native/packages/core/src/kernel.d.ts +36 -0
- package/types/vendor/@akanjs/native/packages/desktop/src/plugin.d.ts +2 -0
- package/types/vendor/@akanjs/native/packages/desktop/src/server.d.ts +11 -0
- package/types/vendor/@akanjs/native/plugins/external-view/src/desktop.d.ts +17 -0
- package/types/vendor/@akanjs/native/plugins/external-view/src/index.d.ts +132 -0
- package/types/vendor/@akanjs/native/plugins/external-view/src.d.ts +1 -0
- package/types/vendor/@akanjs/native/plugins/updates/src/index.d.ts +8 -0
- package/types/vendor/@akanjs/native/plugins/window/src/desktop.d.ts +4 -1
- package/types/vendor/@akanjs/native/plugins/window/src/index.d.ts +49 -0
- package/types/vendor/use-agentic/types.d.ts +2 -0
- package/types/webkit/CsrStack.d.ts +1 -1
- package/types/webkit/McpAppAgent.d.ts +14 -0
- package/types/webkit/McpAppApprovals.d.ts +8 -0
- package/types/webkit/McpAppBridge.d.ts +101 -0
- package/types/webkit/McpAppView.d.ts +21 -0
- package/types/webkit/index.d.ts +1 -0
- package/types/webkit/useLocation.d.ts +1 -1
- package/types/webkit/useVisualViewportFrame.d.ts +13 -0
- package/ui/Agent/Chat.tsx +29 -10
- package/ui/Agent/Composer.tsx +2 -1
- package/ui/Agent/EmbedApproval.tsx +17 -0
- package/ui/Agent/RichInput.tsx +1 -1
- package/ui/Agent/index.ts +2 -0
- package/ui/Agent/useKeyboardFrame.ts +10 -0
- package/ui/Agent/useScreenActions.ts +45 -0
- package/ui/BottomSheet.tsx +82 -53
- package/ui/Constant/Doc.tsx +113 -60
- package/ui/Constant/index.ts +2 -2
- package/ui/Constant/schemaDoc.ts +86 -26
- package/ui/Dialog/LegacyModal.tsx +53 -52
- package/ui/Dialog/Modal.tsx +93 -49
- package/ui/Dialog/modalSurface.ts +61 -0
- package/ui/DragAction.tsx +1 -1
- package/ui/DraggableList.tsx +1 -1
- package/ui/Dropdown.tsx +4 -6
- package/ui/KeyboardAvoiding.tsx +1 -1
- package/ui/Layout/BottomInset.tsx +20 -14
- package/ui/Layout/BottomTab.tsx +42 -16
- package/ui/Layout/FrameSlotFallback.tsx +59 -0
- package/ui/Layout/Navbar.tsx +37 -11
- package/ui/Layout/Sider.tsx +1 -1
- package/ui/Layout/TopInset.tsx +15 -14
- package/ui/Link/Action.tsx +15 -5
- package/ui/Link/Anchor.tsx +25 -13
- package/ui/Link/activePath.ts +25 -0
- package/ui/Link/index.tsx +1 -1
- package/ui/Load/Pagination.tsx +1 -1
- package/ui/Load/Units.tsx +1 -1
- package/ui/Loading/ProgressBar.tsx +1 -1
- package/ui/Modal.tsx +9 -0
- package/ui/Model/EditModal.tsx +16 -1
- package/ui/Model/New.tsx +25 -2
- package/ui/Model/NewWrapper_Client.tsx +2 -0
- package/ui/Model/ViewEditModal.tsx +9 -0
- package/ui/Model/ViewModal.tsx +16 -1
- package/ui/Model/newFormOrigin.ts +45 -0
- package/ui/Popconfirm.tsx +5 -7
- package/ui/Portal.tsx +4 -7
- package/ui/Reference/origin.ts +31 -0
- package/ui/ScreenNavigator.tsx +1 -1
- package/ui/Select.tsx +2 -7
- package/ui/Signal/Arg.tsx +2 -2
- package/ui/Signal/Doc.tsx +125 -63
- package/ui/Signal/Endpoint.tsx +11 -10
- package/ui/Signal/Listener.tsx +7 -10
- package/ui/Signal/Object.tsx +7 -14
- package/ui/Signal/Response.tsx +24 -24
- package/ui/Signal/RestApi.tsx +63 -52
- package/ui/Signal/WebSocket.tsx +65 -72
- package/ui/Signal/endpointEntries.ts +25 -0
- package/ui/Signal/index.ts +23 -6
- package/ui/Signal/signalText.ts +59 -0
- package/ui/System/CSR.tsx +34 -8
- package/ui/System/Client.tsx +36 -5
- package/ui/System/Common.tsx +76 -32
- package/ui/System/SSR.tsx +73 -31
- package/ui/Toast.tsx +1 -1
- package/ui/Tooltip.tsx +84 -30
- package/ui/index.ts +11 -1
- package/ui/overlayLayer.ts +25 -2
- package/ui/overlayPosition.ts +90 -0
- package/ui/recipe/inputRecipe.ts +8 -1
- package/ui/styles.css +152 -0
- package/vendor/@akanjs/native/native/android/src/com/akanjs/runtime/AkanNativeActivity.kt +22 -2
- package/vendor/@akanjs/native/native/android/src/com/akanjs/runtime/AkanNativeKernel.kt +37 -0
- package/vendor/@akanjs/native/native/android/src/com/akanjs/runtime/AkanNativeUpdateReports.kt +169 -0
- package/vendor/@akanjs/native/native/android/src/com/akanjs/runtime/AkanNativeUpdates.kt +110 -38
- package/vendor/@akanjs/native/native/android/src/com/akanjs/runtime/AkanNativeVectors.kt +20 -0
- package/vendor/@akanjs/native/native/desktop/Cargo.lock +16 -16
- package/vendor/@akanjs/native/native/desktop/src/external.rs +651 -0
- package/vendor/@akanjs/native/native/desktop/src/json.rs +23 -0
- package/vendor/@akanjs/native/native/desktop/src/lib.rs +228 -5
- package/vendor/@akanjs/native/native/ios/Sources/AkanNativeApp.swift +2 -1
- package/vendor/@akanjs/native/native/ios/Sources/AkanNativeKernel.swift +47 -1
- package/vendor/@akanjs/native/native/ios/Sources/AkanNativeUpdateReports.swift +145 -0
- package/vendor/@akanjs/native/native/ios/Sources/AkanNativeUpdates.swift +112 -43
- package/vendor/@akanjs/native/native/ios/Sources/AkanNativeVectors.swift +18 -0
- package/vendor/@akanjs/native/native/ios/Sources/AkanNativeViewController.swift +24 -2
- package/vendor/@akanjs/native/packages/cli/src/api.ts +22 -6
- package/vendor/@akanjs/native/packages/cli/src/commands/build.ts +24 -8
- package/vendor/@akanjs/native/packages/cli/src/commands/update.ts +11 -9
- package/vendor/@akanjs/native/packages/cli/src/config.ts +35 -7
- package/vendor/@akanjs/native/packages/cli/src/lib/compat.ts +2 -0
- package/vendor/@akanjs/native/packages/cli/src/lib/configkeys.ts +6 -4
- package/vendor/@akanjs/native/packages/cli/src/lib/desktop-server.ts +11 -1
- package/vendor/@akanjs/native/packages/cli/src/lib/distribution.ts +73 -0
- package/vendor/@akanjs/native/packages/cli/src/lib/project.ts +15 -3
- package/vendor/@akanjs/native/packages/cli/src/lib/publish.ts +70 -23
- package/vendor/@akanjs/native/packages/cli/src/lib/updates.ts +159 -23
- package/vendor/@akanjs/native/packages/cli/src/lib/vectors.ts +2 -0
- package/vendor/@akanjs/native/packages/cli/src/platforms/android.ts +9 -4
- package/vendor/@akanjs/native/packages/cli/src/platforms/desktop-update.ts +1 -1
- package/vendor/@akanjs/native/packages/cli/src/platforms/desktop.ts +4 -2
- package/vendor/@akanjs/native/packages/cli/src/platforms/ios.ts +9 -4
- package/vendor/@akanjs/native/packages/cli/src/platforms/windows-installer.ts +25 -11
- package/vendor/@akanjs/native/packages/cli/src/platforms/windows.ts +14 -3
- package/vendor/@akanjs/native/packages/core/src/kernel.ts +49 -0
- package/vendor/@akanjs/native/packages/core/vectors/updates.json +31 -0
- package/vendor/@akanjs/native/packages/desktop/src/main.ts +3 -0
- package/vendor/@akanjs/native/packages/desktop/src/plugin.ts +2 -0
- package/vendor/@akanjs/native/packages/desktop/src/server.ts +70 -6
- package/vendor/@akanjs/native/plugins/external-view/native-plugin.json +23 -0
- package/vendor/@akanjs/native/plugins/external-view/src/desktop.ts +321 -0
- package/vendor/@akanjs/native/plugins/external-view/src/index.ts +106 -0
- package/vendor/@akanjs/native/plugins/updates/android/UpdatesPlugin.kt +133 -41
- package/vendor/@akanjs/native/plugins/updates/ios/UpdatesPlugin.swift +101 -36
- package/vendor/@akanjs/native/plugins/updates/native-plugin.json +4 -2
- package/vendor/@akanjs/native/plugins/updates/src/desktop.ts +15 -1
- package/vendor/@akanjs/native/plugins/updates/src/index.ts +7 -1
- package/vendor/@akanjs/native/plugins/window/native-plugin.json +4 -0
- package/vendor/@akanjs/native/plugins/window/src/desktop.ts +68 -1
- package/vendor/@akanjs/native/plugins/window/src/index.ts +47 -0
- package/vendor/use-agentic/ToolRunner.ts +8 -1
- package/vendor/use-agentic/types.ts +2 -0
- package/webkit/CsrRouteTable.ts +1 -0
- package/webkit/CsrStack.ts +9 -2
- package/webkit/McpAppAgent.ts +138 -0
- package/webkit/McpAppApprovals.ts +56 -0
- package/webkit/McpAppBridge.ts +291 -0
- package/webkit/McpAppView.ts +258 -0
- package/webkit/bootCsr.tsx +54 -5
- package/webkit/index.ts +1 -0
- package/webkit/useCsrValues.ts +16 -10
- package/webkit/useFrameRuntime.ts +10 -1
- package/webkit/useLocation.ts +4 -3
- package/webkit/useVisualViewportFrame.tsx +52 -0
- package/service/predefinedAdaptor/sql/driver/libsql.ts +0 -45
- package/types/service/predefinedAdaptor/sql/driver/libsql.d.ts +0 -20
- package/types/ui/Agent/useKeyboardInset.d.ts +0 -1
- package/types/ui/ServerPortal.d.ts +0 -15
- package/ui/Agent/useKeyboardInset.ts +0 -19
- package/ui/ServerPortal.tsx +0 -49
|
@@ -39,6 +39,7 @@ import { type Endpoint, type EndpointCls, sliceEndpoint } from "../../signal/end
|
|
|
39
39
|
import type { EndpointInfo } from "../../signal/endpointInfo";
|
|
40
40
|
import { EndpointRateLimit } from "../../signal/endpointRateLimit";
|
|
41
41
|
import type { Internal, InternalCls } from "../../signal/internal";
|
|
42
|
+
import { Machine } from "../../signal/internalArg";
|
|
42
43
|
import type { InternalInfo } from "../../signal/internalInfo";
|
|
43
44
|
import type { MiddlewareCls } from "../../signal/middleware";
|
|
44
45
|
import type { ServerSignal, ServerSignalCls } from "../../signal/serverSignal";
|
|
@@ -52,6 +53,8 @@ import type {
|
|
|
52
53
|
WebsocketMessageData,
|
|
53
54
|
WebsocketSubscribeAck,
|
|
54
55
|
} from "../../signal/types";
|
|
56
|
+
import { MachineToken } from "../machine/machineToken";
|
|
57
|
+
import type { RoomReplay } from "../routing/roomReplay";
|
|
55
58
|
import type { HttpRoutes, LocalPublish, SignalRoutes, WebsocketRoutes } from "../types";
|
|
56
59
|
|
|
57
60
|
type LiveChangeListener = (doc: unknown, type: unknown, previous?: unknown) => void;
|
|
@@ -79,8 +82,15 @@ export class SignalResolver {
|
|
|
79
82
|
const separator = roomId.indexOf("-");
|
|
80
83
|
return SignalResolver.#coalescingRooms.has(separator >= 0 ? roomId.slice(0, separator) : roomId);
|
|
81
84
|
}
|
|
82
|
-
static
|
|
85
|
+
static #roomReplay: RoomReplay | null = null;
|
|
86
|
+
static setLocalPublish(
|
|
87
|
+
localPublish: LocalPublish,
|
|
88
|
+
websocket: WebsocketAdaptor,
|
|
89
|
+
live?: LiveRegistry,
|
|
90
|
+
roomReplay: RoomReplay | null = null,
|
|
91
|
+
) {
|
|
83
92
|
SignalResolver.#localPublish = localPublish;
|
|
93
|
+
SignalResolver.#roomReplay = roomReplay;
|
|
84
94
|
websocket.setEventHandler((roomId, data) => localPublish(roomId, data as object | object[] | Uint8Array));
|
|
85
95
|
|
|
86
96
|
websocket.onRecovered?.(() => {
|
|
@@ -245,12 +255,14 @@ export class SignalResolver {
|
|
|
245
255
|
if (!internalInfo.execFn) throw new Error(`Exec function is not set for ${key}`);
|
|
246
256
|
const execFn = internalInfo.execFn.bind(internal);
|
|
247
257
|
|
|
258
|
+
const { onFailed } = internalInfo.signalOption;
|
|
248
259
|
internal.queue.registerProcessWorker(
|
|
249
260
|
key,
|
|
250
261
|
SignalResolver.#traced(
|
|
251
262
|
key,
|
|
252
263
|
async (job) => await execFn(...SignalResolver.#getJobArgs(key, internalInfo, job), job),
|
|
253
264
|
),
|
|
265
|
+
onFailed ? { onFailed: (job, error) => onFailed.call(internal, job, error) } : {},
|
|
254
266
|
);
|
|
255
267
|
break;
|
|
256
268
|
}
|
|
@@ -626,6 +638,7 @@ export class SignalResolver {
|
|
|
626
638
|
const wsRoutes: WebsocketRoutes = {};
|
|
627
639
|
const defaultPrefix = endpointCls.srv.cnst?.refName;
|
|
628
640
|
SignalResolver.#warnUnguarded(endpointMeta);
|
|
641
|
+
SignalResolver.#warnMachineKeyless(endpointMeta);
|
|
629
642
|
Object.entries(endpointMeta).forEach(([key, endpointInfo]) => {
|
|
630
643
|
const path = endpointInfo.getRoutePath(key, defaultPrefix);
|
|
631
644
|
if (endpointInfo.signalOption.globalPrefix !== undefined) {
|
|
@@ -686,7 +699,7 @@ export class SignalResolver {
|
|
|
686
699
|
);
|
|
687
700
|
break;
|
|
688
701
|
case "pubsub":
|
|
689
|
-
wsRoutes[key] = async (ws, message, event) =>
|
|
702
|
+
wsRoutes[key] = async (ws, message, event, request) =>
|
|
690
703
|
await SignalContext.run(endpoint, endpointInfo, key, "websocket", async () => {
|
|
691
704
|
const websocket = SignalResolver.#getWebsocket(registry);
|
|
692
705
|
const context = await new SignalContext(
|
|
@@ -710,14 +723,28 @@ export class SignalResolver {
|
|
|
710
723
|
roomId,
|
|
711
724
|
query: query as never,
|
|
712
725
|
fallback: liveOption.fallback,
|
|
726
|
+
payload: liveOption.payload,
|
|
713
727
|
fields: SignalResolver.#queryFieldsOf(live, liveOption.refName),
|
|
714
728
|
});
|
|
729
|
+
const replay = SignalResolver.#roomReplay;
|
|
730
|
+
replay?.watch(roomId);
|
|
715
731
|
ws.subscribe(roomId);
|
|
732
|
+
|
|
733
|
+
const missed = request?.since && replay ? replay.since(roomId, request.since) : null;
|
|
734
|
+
for (const frame of missed ?? []) ws.send(frame);
|
|
716
735
|
roomCtxMap.set(roomId, context);
|
|
717
736
|
SignalResolver.#liveWsPubsubRoomCtx.set(ws, roomCtxMap);
|
|
718
737
|
websocket.joinRoom(ws, roomId);
|
|
719
738
|
SignalResolver.logger.verbose(`WebSocket subscribed to room ${roomId}`);
|
|
720
|
-
const
|
|
739
|
+
const cursor = replay?.cursor(roomId);
|
|
740
|
+
const ack: WebsocketSubscribeAck = {
|
|
741
|
+
type: "sub",
|
|
742
|
+
roomId,
|
|
743
|
+
requestRoomId,
|
|
744
|
+
subscribe,
|
|
745
|
+
...(cursor ? { cursor } : {}),
|
|
746
|
+
...(request?.since ? { replayed: !!missed } : {}),
|
|
747
|
+
};
|
|
721
748
|
return ack;
|
|
722
749
|
}
|
|
723
750
|
if (liveOption) await context.resolveInternalArgs();
|
|
@@ -768,6 +795,16 @@ export class SignalResolver {
|
|
|
768
795
|
`${open.join(", ")} declare${open.length === 1 ? "s" : ""} no guards, so anyone can call ${open.length === 1 ? "it" : "them"} over HTTP and WebSocket. Name the guards, or \`guards: [Public]\` to keep one open on purpose.`,
|
|
769
796
|
);
|
|
770
797
|
}
|
|
798
|
+
static #warnMachineKeyless(endpointMeta: { [key: string]: EndpointInfo }) {
|
|
799
|
+
if (MachineToken.canVerify()) return;
|
|
800
|
+
const machineOnly = Object.entries(endpointMeta)
|
|
801
|
+
.filter(([, endpointInfo]) => endpointInfo.internalArgs.some((arg) => arg.argRef === Machine))
|
|
802
|
+
.map(([key]) => key);
|
|
803
|
+
if (!machineOnly.length) return;
|
|
804
|
+
SignalResolver.logger.error(
|
|
805
|
+
`${machineOnly.join(", ")} take${machineOnly.length === 1 ? "s" : ""} .with(Machine), and this server has neither AKAN_MACHINE_PUBLIC_KEY nor AKAN_MACHINE_PRIVATE_KEY to verify a machine token: every machine call to ${machineOnly.length === 1 ? "it" : "them"} is refused.`,
|
|
806
|
+
);
|
|
807
|
+
}
|
|
771
808
|
static #selectCache = new WeakMap<Cls, Record<string, true>>();
|
|
772
809
|
static #selectForConstant(constant: Cls): Record<string, true> | undefined {
|
|
773
810
|
const cached = SignalResolver.#selectCache.get(constant);
|
|
@@ -19,6 +19,14 @@ import {
|
|
|
19
19
|
} from "./routeState";
|
|
20
20
|
import { AkanSegmentOutletReference } from "./rscSegmentOutletReference";
|
|
21
21
|
|
|
22
|
+
/** How a render reached after the layouts streamed answers `router.notFound()` — in place, since its parents are out. */
|
|
23
|
+
export interface RouteNotFoundInPlace {
|
|
24
|
+
pathname: string;
|
|
25
|
+
/** Rendered when no layout above the thrower declares `.notFound()`. */
|
|
26
|
+
systemFallback: () => ReactNode;
|
|
27
|
+
onNotFound: () => void;
|
|
28
|
+
}
|
|
29
|
+
|
|
22
30
|
export class RouteElementComposer {
|
|
23
31
|
static async resolveSsrFramePathRoute({
|
|
24
32
|
pathRoute,
|
|
@@ -63,11 +71,13 @@ export class RouteElementComposer {
|
|
|
63
71
|
params,
|
|
64
72
|
searchParams,
|
|
65
73
|
navKey,
|
|
74
|
+
notFound,
|
|
66
75
|
}: {
|
|
67
76
|
pathRoute: PathRoute;
|
|
68
77
|
params: Record<string, string>;
|
|
69
78
|
searchParams: Record<string, string | string[]>;
|
|
70
79
|
navKey?: string;
|
|
80
|
+
notFound?: RouteNotFoundInPlace;
|
|
71
81
|
}): ReactNode {
|
|
72
82
|
return RouteElementComposer.composeRenders({
|
|
73
83
|
renders: RouteElementComposer.#getRenderStack(pathRoute),
|
|
@@ -75,29 +85,50 @@ export class RouteElementComposer {
|
|
|
75
85
|
params,
|
|
76
86
|
searchParams,
|
|
77
87
|
navKey,
|
|
88
|
+
notFound,
|
|
78
89
|
});
|
|
79
90
|
}
|
|
80
91
|
|
|
92
|
+
static async checkArgs({
|
|
93
|
+
pathRoute,
|
|
94
|
+
params,
|
|
95
|
+
searchParams,
|
|
96
|
+
}: {
|
|
97
|
+
pathRoute: PathRoute;
|
|
98
|
+
params: Record<string, string>;
|
|
99
|
+
searchParams: Record<string, string | string[]>;
|
|
100
|
+
}): Promise<void> {
|
|
101
|
+
await Promise.all(
|
|
102
|
+
RouteElementComposer.#getRenderStack(pathRoute).map((routeRender) =>
|
|
103
|
+
routeRender?.checkArgs?.({ params, searchParams }),
|
|
104
|
+
),
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
|
|
81
108
|
static composeSuffix({
|
|
82
109
|
pathRoute,
|
|
83
110
|
params,
|
|
84
111
|
searchParams,
|
|
85
112
|
patchStartIndex,
|
|
86
113
|
navKey,
|
|
114
|
+
notFound,
|
|
87
115
|
}: {
|
|
88
116
|
pathRoute: PathRoute;
|
|
89
117
|
params: Record<string, string>;
|
|
90
118
|
searchParams: Record<string, string | string[]>;
|
|
91
119
|
patchStartIndex: number;
|
|
92
120
|
navKey?: string;
|
|
121
|
+
notFound?: RouteNotFoundInPlace;
|
|
93
122
|
}): ReactNode | null {
|
|
94
123
|
const renders = RouteElementComposer.#getRenderStack(pathRoute);
|
|
95
124
|
if (!Number.isInteger(patchStartIndex) || patchStartIndex < 0 || patchStartIndex >= renders.length) return null;
|
|
96
125
|
return RouteElementComposer.composeRenders({
|
|
97
126
|
renders: renders.slice(patchStartIndex),
|
|
127
|
+
owners: renders.slice(0, patchStartIndex),
|
|
98
128
|
params,
|
|
99
129
|
searchParams,
|
|
100
130
|
navKey,
|
|
131
|
+
notFound,
|
|
101
132
|
});
|
|
102
133
|
}
|
|
103
134
|
|
|
@@ -174,16 +205,21 @@ export class RouteElementComposer {
|
|
|
174
205
|
|
|
175
206
|
static composeRenders({
|
|
176
207
|
renders,
|
|
208
|
+
owners = [],
|
|
177
209
|
segments,
|
|
178
210
|
params,
|
|
179
211
|
searchParams,
|
|
180
212
|
navKey,
|
|
213
|
+
notFound,
|
|
181
214
|
}: {
|
|
182
215
|
renders: RouteRender[];
|
|
216
|
+
/** Layouts above `renders` that this element leaves out (a patch) but whose `.notFound()` still applies. */
|
|
217
|
+
owners?: RouteRender[];
|
|
183
218
|
segments?: AkanRouteSegmentState[];
|
|
184
219
|
params: Record<string, string>;
|
|
185
220
|
searchParams: Record<string, string | string[]>;
|
|
186
221
|
navKey?: string;
|
|
222
|
+
notFound?: RouteNotFoundInPlace;
|
|
187
223
|
}): ReactNode {
|
|
188
224
|
let element: ReactNode = null;
|
|
189
225
|
for (let i = renders.length - 1; i >= 0; i--) {
|
|
@@ -194,7 +230,12 @@ export class RouteElementComposer {
|
|
|
194
230
|
navKey && loadingFallback != null && i === renders.length - 1 ? `akan-loading:${navKey}` : undefined;
|
|
195
231
|
element = (
|
|
196
232
|
<Suspense key={suspenseKey} fallback={loadingFallback}>
|
|
197
|
-
<RouteElementComposer.AsyncRender
|
|
233
|
+
<RouteElementComposer.AsyncRender
|
|
234
|
+
routeRender={routeRender}
|
|
235
|
+
params={params}
|
|
236
|
+
searchParams={searchParams}
|
|
237
|
+
notFound={notFound ? { ...notFound, owners: [...owners, ...renders.slice(0, i)] } : undefined}
|
|
238
|
+
>
|
|
198
239
|
{element}
|
|
199
240
|
</RouteElementComposer.AsyncRender>
|
|
200
241
|
</Suspense>
|
|
@@ -217,12 +258,47 @@ export class RouteElementComposer {
|
|
|
217
258
|
children,
|
|
218
259
|
params,
|
|
219
260
|
searchParams,
|
|
261
|
+
notFound,
|
|
220
262
|
}: {
|
|
221
263
|
routeRender: RouteRender;
|
|
222
264
|
children: ReactNode;
|
|
223
265
|
params: Record<string, string>;
|
|
224
266
|
searchParams: Record<string, string | string[]>;
|
|
225
|
-
|
|
267
|
+
notFound?: RouteNotFoundInPlace & { owners: RouteRender[] };
|
|
268
|
+
}) => {
|
|
269
|
+
try {
|
|
270
|
+
return RouteElementComposer.#normalizeReactNode(
|
|
271
|
+
await routeRender.render({ children, params, searchParams } as never),
|
|
272
|
+
);
|
|
273
|
+
} catch (error) {
|
|
274
|
+
if (!notFound || (error as { digest?: unknown } | null)?.digest !== "AKAN_NOT_FOUND") throw error;
|
|
275
|
+
notFound.onNotFound();
|
|
276
|
+
return await RouteElementComposer.#renderNotFoundInPlace(notFound, params, searchParams);
|
|
277
|
+
}
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
static async #renderNotFoundInPlace(
|
|
281
|
+
{ owners, pathname, systemFallback }: RouteNotFoundInPlace & { owners: RouteRender[] },
|
|
282
|
+
params: Record<string, string>,
|
|
283
|
+
searchParams: Record<string, string | string[]>,
|
|
284
|
+
): Promise<ReactNode> {
|
|
285
|
+
for (let index = owners.length - 1; index >= 0; index--) {
|
|
286
|
+
const fallback = await owners[index]?.resolveNotFound?.();
|
|
287
|
+
if (!fallback) continue;
|
|
288
|
+
return (
|
|
289
|
+
<>
|
|
290
|
+
<meta name="robots" content="noindex" />
|
|
291
|
+
{RouteElementComposer.#normalizeReactNode(await fallback({ params, searchParams, pathname }))}
|
|
292
|
+
</>
|
|
293
|
+
);
|
|
294
|
+
}
|
|
295
|
+
return (
|
|
296
|
+
<>
|
|
297
|
+
<meta name="robots" content="noindex" />
|
|
298
|
+
{systemFallback()}
|
|
299
|
+
</>
|
|
300
|
+
);
|
|
301
|
+
}
|
|
226
302
|
|
|
227
303
|
static #makeFallbackRouteRender({
|
|
228
304
|
kind,
|
package/server/routeState.ts
CHANGED
|
@@ -27,6 +27,8 @@ export interface AkanRouterStateV1 {
|
|
|
27
27
|
href: string;
|
|
28
28
|
routeId: string;
|
|
29
29
|
segments: AkanRouteSegmentState[];
|
|
30
|
+
/** The route's `.config({ transition })`, which the browser plays as a view transition on the way in and out. */
|
|
31
|
+
transition?: string;
|
|
30
32
|
}
|
|
31
33
|
|
|
32
34
|
export type AkanHeadSnapshotTag = "title" | "meta" | "link";
|
|
@@ -132,6 +134,7 @@ export function isAkanRouterStateV1(value: unknown): value is AkanRouterStateV1
|
|
|
132
134
|
(state.buildId === undefined || typeof state.buildId === "number") &&
|
|
133
135
|
typeof state.href === "string" &&
|
|
134
136
|
typeof state.routeId === "string" &&
|
|
137
|
+
(state.transition === undefined || typeof state.transition === "string") &&
|
|
135
138
|
Array.isArray(state.segments) &&
|
|
136
139
|
state.segments.every(isSegmentState)
|
|
137
140
|
);
|
|
@@ -207,10 +210,12 @@ export function createAkanRouterState({
|
|
|
207
210
|
pathRoute,
|
|
208
211
|
href,
|
|
209
212
|
buildId,
|
|
213
|
+
transition,
|
|
210
214
|
}: {
|
|
211
215
|
pathRoute: PathRoute;
|
|
212
216
|
href: string;
|
|
213
217
|
buildId?: number;
|
|
218
|
+
transition?: string;
|
|
214
219
|
}): AkanRouterStateV1 {
|
|
215
220
|
return {
|
|
216
221
|
version: AKAN_RSC_STATE_VERSION,
|
|
@@ -218,6 +223,7 @@ export function createAkanRouterState({
|
|
|
218
223
|
href,
|
|
219
224
|
routeId: pathRoute.path,
|
|
220
225
|
segments: createAkanRouteSegments(pathRoute),
|
|
226
|
+
...(transition && transition !== "none" ? { transition } : {}),
|
|
221
227
|
};
|
|
222
228
|
}
|
|
223
229
|
|
|
@@ -8,6 +8,8 @@ import type {
|
|
|
8
8
|
Route,
|
|
9
9
|
RouteModule,
|
|
10
10
|
RouteRender,
|
|
11
|
+
RouteViewport,
|
|
12
|
+
WebAppManifest,
|
|
11
13
|
} from "akanjs/client";
|
|
12
14
|
import {
|
|
13
15
|
assertUniqueRoutePatterns,
|
|
@@ -22,6 +24,7 @@ import {
|
|
|
22
24
|
} from "akanjs/common";
|
|
23
25
|
import { createElement } from "react";
|
|
24
26
|
import { defaultPageState, validatePageConfig } from "../client/frameConfig";
|
|
27
|
+
import { RouteHead } from "../client/route/RouteHead";
|
|
25
28
|
import {
|
|
26
29
|
type ResolvedRouteModule,
|
|
27
30
|
type RouteModuleSource,
|
|
@@ -291,7 +294,18 @@ export class RouteTreeBuilder {
|
|
|
291
294
|
},
|
|
292
295
|
resolveHead: async (props: PageProps) => {
|
|
293
296
|
const mod = await loadSynced();
|
|
294
|
-
|
|
297
|
+
try {
|
|
298
|
+
return mod.generateHead ? await mod.generateHead(props) : mod.head;
|
|
299
|
+
} catch (error) {
|
|
300
|
+
|
|
301
|
+
const digest = (error as { digest?: unknown } | null)?.digest;
|
|
302
|
+
if (digest === "AKAN_REDIRECT" || digest === "AKAN_NOT_FOUND") throw error;
|
|
303
|
+
RouteTreeBuilder.logger.warn(`${key}: .head() threw, so the route renders without it: ${String(error)}`);
|
|
304
|
+
return undefined;
|
|
305
|
+
}
|
|
306
|
+
},
|
|
307
|
+
checkArgs: async ({ params, searchParams }: PageProps) => {
|
|
308
|
+
(await loadModule()).definition?.resolveArgs({ params, searchParams });
|
|
295
309
|
},
|
|
296
310
|
};
|
|
297
311
|
if (kind === "page") {
|
|
@@ -303,6 +317,14 @@ export class RouteTreeBuilder {
|
|
|
303
317
|
const { module: mod } = await loadModule();
|
|
304
318
|
return "theme" in mod ? (mod.theme as string | undefined) : undefined;
|
|
305
319
|
};
|
|
320
|
+
routeRender.getLayoutViewport = async () => {
|
|
321
|
+
const { module: mod } = await loadModule();
|
|
322
|
+
return "viewport" in mod ? (mod.viewport as RouteViewport | undefined) : undefined;
|
|
323
|
+
};
|
|
324
|
+
routeRender.getLayoutManifest = async () => {
|
|
325
|
+
const { module: mod } = await loadModule();
|
|
326
|
+
return "manifest" in mod ? (mod.manifest as WebAppManifest | undefined) : undefined;
|
|
327
|
+
};
|
|
306
328
|
routeRender.resolveNotFound = async () => syncFallbacks((await loadModule()).module as LayoutModule).NotFound;
|
|
307
329
|
routeRender.resolveError = async () => syncFallbacks((await loadModule()).module as LayoutModule).Error;
|
|
308
330
|
}
|
|
@@ -321,15 +343,10 @@ export class RouteTreeBuilder {
|
|
|
321
343
|
};
|
|
322
344
|
}
|
|
323
345
|
|
|
346
|
+
/** `resolvers` run leaf first; their heads merge root first, so a deeper level takes over each tag it writes. */
|
|
324
347
|
static #composeHeadResolvers(...resolvers: (ResolveHead | undefined)[]): ResolveHead | undefined {
|
|
325
348
|
const chain = resolvers.filter((resolver): resolver is ResolveHead => Boolean(resolver));
|
|
326
|
-
if (chain.length
|
|
327
|
-
return async (props) =>
|
|
328
|
-
for (const resolver of chain) {
|
|
329
|
-
const head = await resolver(props);
|
|
330
|
-
if (head !== null && head !== undefined) return head;
|
|
331
|
-
}
|
|
332
|
-
return undefined;
|
|
333
|
-
};
|
|
349
|
+
if (chain.length <= 1) return chain[0];
|
|
350
|
+
return async (props) => RouteHead.merge(await Promise.all(chain.toReversed().map((resolver) => resolver(props))));
|
|
334
351
|
}
|
|
335
352
|
}
|
|
@@ -140,10 +140,13 @@ export class ApiRouter {
|
|
|
140
140
|
if (typeof message === "string" && typeof hmrHub?.handleMessage === "function") hmrHub.handleMessage(message);
|
|
141
141
|
return;
|
|
142
142
|
}
|
|
143
|
+
|
|
144
|
+
let failedKey: string | undefined;
|
|
143
145
|
try {
|
|
144
146
|
if (typeof message === "string") {
|
|
145
147
|
const msg = JSON.parse(message) as WebsocketReqData;
|
|
146
148
|
if (!msg.key) throw new Error("Message key is required");
|
|
149
|
+
failedKey = msg.key;
|
|
147
150
|
if (msg.key === websocketAuthContract.key) {
|
|
148
151
|
|
|
149
152
|
AppWsData.applyCredential(AppWsData.of(ws), websocketAuthContract.readJwt(msg.data));
|
|
@@ -159,19 +162,19 @@ export class ApiRouter {
|
|
|
159
162
|
if (!wsRoute) throw new Error(`WebSocket route "${msg.key}" is not registered`);
|
|
160
163
|
const eventType =
|
|
161
164
|
typeof msg.subscribe === "boolean" ? (msg.subscribe ? "subscribe" : "unsubscribe") : "message";
|
|
162
|
-
const result = await wsRoute(ws, msg.data, eventType);
|
|
165
|
+
const result = await wsRoute(ws, msg.data, eventType, { since: msg.since });
|
|
163
166
|
ws.send(JSON.stringify(result));
|
|
164
167
|
} else throw new Error("Message is not a string");
|
|
165
168
|
} catch (error) {
|
|
166
169
|
if (isExceptionLike(error)) {
|
|
167
|
-
ws.send(JSON.stringify({ ...error.toJSON(), timestamp: new Date().toISOString() }));
|
|
170
|
+
ws.send(JSON.stringify({ ...error.toJSON(), key: failedKey, timestamp: new Date().toISOString() }));
|
|
168
171
|
return;
|
|
169
172
|
}
|
|
170
173
|
if (!SignalContext.wasReported(error)) {
|
|
171
174
|
logger.error(error instanceof Error ? (error.stack ?? error.message) : String(error));
|
|
172
175
|
}
|
|
173
176
|
|
|
174
|
-
ws.send(JSON.stringify({ ...SignalFailure.body(error), at: dayjs() }));
|
|
177
|
+
ws.send(JSON.stringify({ ...SignalFailure.body(error), key: failedKey, at: dayjs() }));
|
|
175
178
|
}
|
|
176
179
|
},
|
|
177
180
|
close: (ws) => {
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import type { WebsocketPublishData, WebsocketRoomCursor } from "akanjs/signal";
|
|
2
|
+
|
|
3
|
+
interface RoomLog {
|
|
4
|
+
/** Names this numbering: a room log that expired and started over counts from 1 again under another epoch. */
|
|
5
|
+
epoch: string;
|
|
6
|
+
seq: number;
|
|
7
|
+
watchedAt: number;
|
|
8
|
+
frames: { seq: number; at: number; frame: string }[];
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface RoomReplayOptions {
|
|
12
|
+
/** Whether this process sees every publish to a room, so a run of sequence numbers it holds has no hole. */
|
|
13
|
+
vouches: boolean;
|
|
14
|
+
/** Frames kept per room. */
|
|
15
|
+
depth?: number;
|
|
16
|
+
/** How long a room nobody subscribes to here keeps numbering and keeping its frames. */
|
|
17
|
+
retainMs?: number;
|
|
18
|
+
/** Rooms tracked at once; the least recently watched goes first. */
|
|
19
|
+
rooms?: number;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export class RoomReplay {
|
|
23
|
+
readonly #process = crypto.randomUUID();
|
|
24
|
+
#generation = 0;
|
|
25
|
+
readonly vouches: boolean;
|
|
26
|
+
readonly #depth: number;
|
|
27
|
+
readonly #retainMs: number;
|
|
28
|
+
readonly #maxRooms: number;
|
|
29
|
+
readonly #rooms = new Map<string, RoomLog>();
|
|
30
|
+
|
|
31
|
+
constructor({ vouches, depth = 128, retainMs = 5 * 60_000, rooms = 10_000 }: RoomReplayOptions) {
|
|
32
|
+
this.vouches = vouches;
|
|
33
|
+
this.#depth = depth;
|
|
34
|
+
this.#retainMs = retainMs;
|
|
35
|
+
this.#maxRooms = rooms;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** A socket subscribed here: the room is numbered from now on, and for `retainMs` after its last subscriber left. */
|
|
39
|
+
watch(roomId: string, now = Date.now()) {
|
|
40
|
+
const room = this.#rooms.get(roomId) ?? {
|
|
41
|
+
epoch: `${this.#process}.${++this.#generation}`,
|
|
42
|
+
seq: 0,
|
|
43
|
+
watchedAt: now,
|
|
44
|
+
frames: [],
|
|
45
|
+
};
|
|
46
|
+
room.watchedAt = now;
|
|
47
|
+
this.#rooms.delete(roomId);
|
|
48
|
+
this.#rooms.set(roomId, room);
|
|
49
|
+
while (this.#rooms.size > this.#maxRooms) {
|
|
50
|
+
const oldest = this.#rooms.keys().next().value;
|
|
51
|
+
if (oldest === undefined) break;
|
|
52
|
+
this.#rooms.delete(oldest);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** The frame to send for one publish; numbered, and kept, while the room is watched. */
|
|
57
|
+
frameOf(publishData: WebsocketPublishData, subscribed: boolean, now = Date.now()): string {
|
|
58
|
+
const room = this.#rooms.get(publishData.roomId);
|
|
59
|
+
if (room && subscribed) room.watchedAt = now;
|
|
60
|
+
if (!room || now - room.watchedAt > this.#retainMs) {
|
|
61
|
+
if (room) this.#rooms.delete(publishData.roomId);
|
|
62
|
+
return JSON.stringify(publishData);
|
|
63
|
+
}
|
|
64
|
+
room.seq += 1;
|
|
65
|
+
const frame = JSON.stringify({ ...publishData, seq: room.seq });
|
|
66
|
+
room.frames.push({ seq: room.seq, at: now, frame });
|
|
67
|
+
while (room.frames.length > this.#depth || (room.frames[0] && now - room.frames[0].at > this.#retainMs))
|
|
68
|
+
room.frames.shift();
|
|
69
|
+
return frame;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** Where the room's numbering stands here, for a subscribe's ack; `null` for a room nobody watches. */
|
|
73
|
+
cursor(roomId: string): WebsocketRoomCursor | null {
|
|
74
|
+
const room = this.#rooms.get(roomId);
|
|
75
|
+
return room ? { epoch: room.epoch, seq: room.seq } : null;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** The frames numbered after the cursor, or `null` when this process cannot tell whether that is all of them. */
|
|
79
|
+
since(roomId: string, { epoch, seq }: WebsocketRoomCursor): string[] | null {
|
|
80
|
+
const room = this.#rooms.get(roomId);
|
|
81
|
+
if (!this.vouches || !room || epoch !== room.epoch || !Number.isSafeInteger(seq) || seq < 0 || seq > room.seq)
|
|
82
|
+
return null;
|
|
83
|
+
if (seq === room.seq) return [];
|
|
84
|
+
const first = room.frames[0];
|
|
85
|
+
if (!first || first.seq > seq + 1) return null;
|
|
86
|
+
return room.frames.filter((entry) => entry.seq > seq).map((entry) => entry.frame);
|
|
87
|
+
}
|
|
88
|
+
}
|
package/server/rscClient.tsx
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import * as React from "react";
|
|
1
2
|
import { createElement, type ReactNode, startTransition, type Usable, use, useLayoutEffect, useState } from "react";
|
|
2
3
|
import { hydrateRoot } from "react-dom/client";
|
|
3
4
|
import { createFromReadableStream } from "react-server-dom-webpack/client.browser";
|
|
@@ -179,8 +180,7 @@ function commitRscPatchNavigation({
|
|
|
179
180
|
rollbackPreparedAkanHeadSnapshotPatch(preparedHeadPatch);
|
|
180
181
|
return;
|
|
181
182
|
}
|
|
182
|
-
|
|
183
|
-
else window.history.pushState(null, "", target);
|
|
183
|
+
commitHistory(target, replace);
|
|
184
184
|
if (scrollToTop) bumpScrollToTop?.();
|
|
185
185
|
} catch {
|
|
186
186
|
if (headApplied) rollbackPreparedAkanHeadSnapshotPatch(preparedHeadPatch);
|
|
@@ -192,6 +192,37 @@ function commitRscPatchNavigation({
|
|
|
192
192
|
return outletCommitted && headApplied;
|
|
193
193
|
}
|
|
194
194
|
|
|
195
|
+
function akanHistoryState(replace: boolean, shallow = false) {
|
|
196
|
+
const idx = (window.history.state as { __akanRouter?: { idx?: unknown } } | null)?.__akanRouter?.idx;
|
|
197
|
+
const current = Number.isInteger(idx) ? (idx as number) : 0;
|
|
198
|
+
return { __akanRouter: { idx: replace ? current : current + 1, ...(shallow ? { shallow: true } : {}) } };
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
let renderedHref = normalizeHref(window.location.href);
|
|
202
|
+
const pathnameOf = (href: string) => new URL(href).pathname;
|
|
203
|
+
|
|
204
|
+
function commitHistory(target: string, replace: boolean | undefined) {
|
|
205
|
+
if (replace) window.history.replaceState(akanHistoryState(true), "", target);
|
|
206
|
+
else window.history.pushState(akanHistoryState(false), "", target);
|
|
207
|
+
renderedHref = target;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const addTransitionType = (React as { addTransitionType?: (type: string) => void }).addTransitionType;
|
|
211
|
+
|
|
212
|
+
function viewTransitionTypeOf(transition: string | undefined, push: boolean): string | null {
|
|
213
|
+
if (!transition || transition === "none") return null;
|
|
214
|
+
if (window.matchMedia?.("(prefers-reduced-motion: reduce)").matches) return null;
|
|
215
|
+
return `akan-${transition}-${push ? "push" : "pop"}`;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/** Writes the entry without asking the server for the page; only a change that keeps the rendered path qualifies. */
|
|
219
|
+
function navigateShallow(target: string, replace: boolean | undefined): boolean {
|
|
220
|
+
if (pathnameOf(target) !== pathnameOf(renderedHref)) return false;
|
|
221
|
+
if (replace) window.history.replaceState(akanHistoryState(true, true), "", target);
|
|
222
|
+
else window.history.pushState(akanHistoryState(false, true), "", target);
|
|
223
|
+
return true;
|
|
224
|
+
}
|
|
225
|
+
|
|
195
226
|
async function fetchRsc(
|
|
196
227
|
href: string,
|
|
197
228
|
options: {
|
|
@@ -383,6 +414,7 @@ function Root(): ReactNode {
|
|
|
383
414
|
};
|
|
384
415
|
|
|
385
416
|
globalThis.__AKAN_RSC_NAVIGATE__ = async (href, options = {}) => {
|
|
417
|
+
if (options.shallow && navigateShallow(normalizeHref(href), options.replace)) return;
|
|
386
418
|
const navId = ++navigationSeq;
|
|
387
419
|
const target = normalizeHref(href);
|
|
388
420
|
const scrollToTop = options.scrollToTop ?? true;
|
|
@@ -482,6 +514,11 @@ function Root(): ReactNode {
|
|
|
482
514
|
});
|
|
483
515
|
|
|
484
516
|
await nextNode.thenable;
|
|
517
|
+
|
|
518
|
+
const viewTransitionType = viewTransitionTypeOf(
|
|
519
|
+
options.pop ? currentRouterState?.transition : nextNode.routerState?.transition,
|
|
520
|
+
!options.pop,
|
|
521
|
+
);
|
|
485
522
|
const committed = commitLatestRscNavigation({
|
|
486
523
|
cache: rscCache,
|
|
487
524
|
href: target,
|
|
@@ -489,13 +526,16 @@ function Root(): ReactNode {
|
|
|
489
526
|
maxEntries: MAX_RSC_CACHE_ENTRIES,
|
|
490
527
|
startTransition,
|
|
491
528
|
commitThenable: (node) => {
|
|
529
|
+
if (viewTransitionType && addTransitionType) {
|
|
530
|
+
addTransitionType("akan-nav");
|
|
531
|
+
addTransitionType(viewTransitionType);
|
|
532
|
+
}
|
|
492
533
|
currentCommitFromCache = servedFromCache;
|
|
493
534
|
resetAkanSegmentOutletPatches();
|
|
494
535
|
setThenable(node.thenable);
|
|
495
536
|
},
|
|
496
537
|
updateHistory: () => {
|
|
497
|
-
|
|
498
|
-
else window.history.pushState(null, "", target);
|
|
538
|
+
commitHistory(target, options.replace);
|
|
499
539
|
},
|
|
500
540
|
scrollToTop,
|
|
501
541
|
bumpScrollToTop: () => setScrollToTopTick((tick) => tick + 1),
|
|
@@ -514,11 +554,16 @@ function Root(): ReactNode {
|
|
|
514
554
|
return use(thenable as Usable<ReactNode>);
|
|
515
555
|
}
|
|
516
556
|
|
|
517
|
-
window.addEventListener("popstate", () => {
|
|
557
|
+
window.addEventListener("popstate", (event) => {
|
|
558
|
+
const here = normalizeHref(window.location.href);
|
|
559
|
+
const shallowEntry = (event.state as { __akanRouter?: { shallow?: boolean } } | null)?.__akanRouter?.shallow;
|
|
560
|
+
|
|
561
|
+
const keepsPage = pathnameOf(here) === pathnameOf(renderedHref) && (shallowEntry || here === renderedHref);
|
|
518
562
|
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
563
|
+
if (!keepsPage)
|
|
564
|
+
void globalThis
|
|
565
|
+
.__AKAN_RSC_NAVIGATE__?.(window.location.href, { replace: true, scrollToTop: false, pop: true })
|
|
566
|
+
?.catch((error: unknown) => hardNavigateAfterRscFailure(window.location.href, true, error));
|
|
522
567
|
window.setTimeout(() => {
|
|
523
568
|
if (globalThis.__AKAN_DEV_SYNC_NAVIGATION_APPLYING__) return;
|
|
524
569
|
const href = globalThis.__AKAN_GET_SYNC_ROUTE_HREF__?.(window.location.href) ?? window.location.href;
|