akanjs 3.0.0-alpha.2 → 3.0.0-alpha.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.ko.md +1 -1
- package/README.md +1 -1
- package/base/symbols.ts +4 -0
- package/client/clientRuntime.ts +8 -0
- package/client/cn.ts +7 -0
- package/client/csrTypes.ts +2 -0
- package/client/frameDebug.ts +3 -2
- package/common/Logger.ts +4 -0
- package/common/deepObjectify.ts +21 -7
- package/common/index.ts +14 -0
- package/common/mcpExposure.ts +99 -0
- package/common/pathSet.ts +17 -5
- package/common/routeConvention.ts +30 -0
- package/constant/crystalize.ts +3 -1
- package/constant/fieldInfo.ts +6 -0
- package/constant/getDefault.ts +37 -9
- package/constant/immerify.ts +1 -1
- package/constant/index.ts +2 -0
- package/constant/labelOf.ts +17 -0
- package/constant/mask.ts +60 -0
- package/constant/purify.ts +17 -11
- package/constant/types.ts +14 -12
- package/dictionary/DictionaryLookup.ts +30 -0
- package/dictionary/agent.dictionary.ts +31 -0
- package/dictionary/agentTurn.dictionary.ts +29 -0
- package/dictionary/base.dictionary.ts +8 -0
- package/dictionary/dictInfo.ts +8 -0
- package/dictionary/dictionary.ts +9 -3
- package/dictionary/index.ts +1 -0
- package/document/database.ts +21 -0
- package/document/filterMeta.ts +7 -0
- package/document/index.ts +1 -0
- package/document/into.ts +7 -1
- package/document/noDocumentError.ts +12 -0
- package/fetch/agentTurn.ts +19 -0
- package/fetch/client/fetchClient.ts +71 -13
- package/fetch/client/wsClient.ts +17 -2
- package/fetch/fetchType/endpointFetch.type.ts +10 -4
- package/fetch/index.ts +1 -0
- package/local/apps/serverLifecycle/serverLifecycle-local.db +0 -0
- package/local/apps/serverLifecycle/serverLifecycle-local.db-shm +0 -0
- package/local/apps/serverLifecycle/serverLifecycle-local.db-wal +0 -0
- package/local/apps/serverLifecycle/serverLifecycle-local_solid.db +0 -0
- package/local/apps/serverLifecycle/serverLifecycle-local_solid.db-shm +0 -0
- package/local/apps/serverLifecycle/serverLifecycle-local_solid.db-wal +0 -0
- package/package.json +11 -9
- package/server/akanApp.ts +30 -34
- package/server/akanOption.ts +65 -7
- package/server/akanServer.ts +172 -7
- package/server/animatedImage.ts +83 -0
- package/server/artifact/routeClientCache.ts +14 -0
- package/server/assetEncoding.ts +47 -0
- package/server/devtools/signalSerializer.ts +2 -2
- package/server/devtools/types.ts +2 -2
- package/server/di/diLifecycle.ts +31 -3
- package/server/di/predefinedAdaptor.ts +6 -0
- package/server/imageOptimizer.ts +33 -46
- package/server/index.ts +1 -2
- package/server/mcp/McpAuth.ts +177 -0
- package/server/mcp/McpDispatcher.ts +245 -0
- package/server/mcp/McpEventStream.ts +76 -0
- package/server/mcp/McpExecutionContext.ts +105 -0
- package/server/mcp/McpRouter.ts +591 -0
- package/server/mcp/index.ts +5 -0
- package/server/resolver/CascadeRunner.ts +1 -1
- package/server/resolver/database.resolver.ts +42 -5
- package/server/resolver/service.resolver.ts +29 -0
- package/server/resolver/signal.resolver.ts +16 -1
- package/server/routeTreeBuilder.ts +2 -44
- package/server/rscWorker.tsx +6 -2
- package/server/subRouteIndexDocument.tsx +61 -0
- package/server/systemPageDocument.tsx +2 -2
- package/server/systemPages.tsx +49 -16
- package/server/vendor/akanjs-fetch.ts +1 -0
- package/server/webRouter.ts +159 -35
- package/service/agent.service.ts +14 -0
- package/service/base.service.ts +2 -1
- package/service/index.ts +1 -0
- package/service/predefinedAdaptor/database.adaptor.ts +78 -24
- package/service/predefinedAdaptor/deepseekLlm.ts +230 -0
- package/service/predefinedAdaptor/index.ts +3 -0
- package/service/predefinedAdaptor/insightQuery.ts +183 -0
- package/service/predefinedAdaptor/llm.adaptor.ts +76 -0
- package/service/predefinedAdaptor/role.adaptor.ts +2 -0
- package/service/predefinedAdaptor/schedule.adaptor.ts +12 -5
- package/service/serviceModule.ts +17 -0
- package/service/types.ts +4 -0
- package/signal/agent/AgentCatalogue.ts +165 -0
- package/signal/agent/index.ts +1 -0
- package/signal/agent.signal.ts +42 -0
- package/signal/agentTurn.ts +8 -0
- package/signal/agentTurnStream.ts +48 -0
- package/signal/base.signal.ts +2 -1
- package/signal/endpointInfo.ts +38 -2
- package/signal/guard.ts +13 -1
- package/signal/guards.ts +52 -1
- package/signal/index.ts +6 -0
- package/signal/mcp/McpDocument.ts +286 -0
- package/signal/mcp/McpProgress.ts +106 -0
- package/signal/mcp/McpUriTemplate.ts +85 -0
- package/signal/mcp/Msg.ts +368 -0
- package/signal/mcp/index.ts +5 -0
- package/signal/mcp/mcpProtocol.ts +120 -0
- package/signal/middleware.ts +7 -3
- package/signal/openapi/openapi.ts +32 -171
- package/signal/schema/JsonSchemaBuilder.ts +211 -0
- package/signal/schema/index.ts +1 -0
- package/signal/signalContext.ts +85 -9
- package/signal/slice.ts +9 -9
- package/signal/types.ts +1 -1
- package/store/action.ts +11 -22
- package/store/actionTag.ts +33 -0
- package/store/agent/AgentBridge.ts +323 -0
- package/store/agent/AgentContext.ts +74 -0
- package/store/agent/AgentPrompts.ts +82 -0
- package/store/agent/AgentVisibility.ts +68 -0
- package/store/agent/ScreenReader.ts +208 -0
- package/store/agent/StoreCatalogue.ts +351 -0
- package/store/agent/StoreSurfaceSource.ts +122 -0
- package/store/agent/index.ts +9 -0
- package/store/agent/storeSurface.ts +28 -0
- package/store/agent/types.ts +50 -0
- package/store/agentic/StToolBuilder.ts +199 -0
- package/store/agentic/attachAgentic.ts +22 -0
- package/store/agentic/index.ts +5 -0
- package/store/agentic/readableValue.ts +17 -0
- package/store/agentic/useStExpose.ts +18 -0
- package/store/agentic/useStState.ts +35 -0
- package/store/baseSt.ts +7 -0
- package/store/databaseStateNames.ts +31 -0
- package/store/formSetterNames.ts +21 -0
- package/store/index.ts +8 -0
- package/store/rootStore.ts +3 -1
- package/store/sliceRole.ts +36 -0
- package/store/state.ts +2 -12
- package/store/store.ts +19 -1
- package/store/storeInstance.ts +165 -20
- package/store/storeRegistry.ts +16 -1
- package/store/types.ts +18 -0
- package/store/withSelector.ts +7 -8
- package/test/registerDom.ts +4 -0
- package/types/base/symbols.d.ts +4 -0
- package/types/client/cn.d.ts +5 -0
- package/types/client/csrTypes.d.ts +2 -0
- package/types/common/Logger.d.ts +2 -0
- package/types/common/deepObjectify.d.ts +4 -2
- package/types/common/index.d.ts +2 -1
- package/types/common/mcpExposure.d.ts +60 -0
- package/types/common/routeConvention.d.ts +8 -0
- package/types/constant/fieldInfo.d.ts +1 -0
- package/types/constant/getDefault.d.ts +5 -0
- package/types/constant/index.d.ts +2 -0
- package/types/constant/labelOf.d.ts +6 -0
- package/types/constant/mask.d.ts +34 -0
- package/types/constant/purify.d.ts +2 -2
- package/types/constant/types.d.ts +2 -2
- package/types/dictionary/DictionaryLookup.d.ts +14 -0
- package/types/dictionary/agent.dictionary.d.ts +1 -0
- package/types/dictionary/agentTurn.dictionary.d.ts +1 -0
- package/types/dictionary/base.dictionary.d.ts +1 -1
- package/types/dictionary/dictInfo.d.ts +6 -0
- package/types/dictionary/dictionary.d.ts +11 -9
- package/types/dictionary/index.d.ts +1 -0
- package/types/document/database.d.ts +20 -0
- package/types/document/filterMeta.d.ts +1 -0
- package/types/document/index.d.ts +1 -0
- package/types/document/into.d.ts +6 -1
- package/types/document/noDocumentError.d.ts +12 -0
- package/types/fetch/agentTurn.d.ts +593 -0
- package/types/fetch/client/fetchClient.d.ts +10 -0
- package/types/fetch/fetchType/endpointFetch.type.d.ts +5 -3
- package/types/fetch/index.d.ts +1 -0
- package/types/server/akanOption.d.ts +35 -7
- package/types/server/akanServer.d.ts +44 -1
- package/types/server/animatedImage.d.ts +7 -0
- package/types/server/artifact/routeClientCache.d.ts +6 -0
- package/types/server/assetEncoding.d.ts +7 -0
- package/types/server/devtools/types.d.ts +2 -2
- package/types/server/di/predefinedAdaptor.d.ts +4 -1
- package/types/server/index.d.ts +0 -2
- package/types/server/mcp/McpAuth.d.ts +62 -0
- package/types/server/mcp/McpDispatcher.d.ts +54 -0
- package/types/server/mcp/McpEventStream.d.ts +18 -0
- package/types/server/mcp/McpExecutionContext.d.ts +44 -0
- package/types/server/mcp/McpRouter.d.ts +60 -0
- package/types/server/mcp/index.d.ts +5 -0
- package/types/server/mcp.d.ts +1 -0
- package/types/server/subRouteIndexDocument.d.ts +8 -0
- package/types/server/systemPageDocument.d.ts +1 -0
- package/types/server/systemPages.d.ts +5 -0
- package/types/server/vendor/akanjs-fetch.d.ts +1 -0
- package/types/service/agent.service.d.ts +12 -0
- package/types/service/base.service.d.ts +4 -0
- package/types/service/index.d.ts +1 -0
- package/types/service/predefinedAdaptor/database.adaptor.d.ts +24 -2
- package/types/service/predefinedAdaptor/deepseekLlm.d.ts +64 -0
- package/types/service/predefinedAdaptor/index.d.ts +3 -0
- package/types/service/predefinedAdaptor/insightQuery.d.ts +50 -0
- package/types/service/predefinedAdaptor/llm.adaptor.d.ts +68 -0
- package/types/service/predefinedAdaptor/role.adaptor.d.ts +2 -0
- package/types/service/predefinedAdaptor/schedule.adaptor.d.ts +10 -6
- package/types/service/types.d.ts +4 -1
- package/types/signal/agent/AgentCatalogue.d.ts +100 -0
- package/types/signal/agent/index.d.ts +1 -0
- package/types/signal/agent.d.ts +1 -0
- package/types/signal/agent.signal.d.ts +29 -0
- package/types/signal/agentTurn.d.ts +9 -0
- package/types/signal/agentTurnStream.d.ts +16 -0
- package/types/signal/base.signal.d.ts +1 -1
- package/types/signal/endpointInfo.d.ts +21 -3
- package/types/signal/guard.d.ts +11 -0
- package/types/signal/guards.d.ts +22 -1
- package/types/signal/index.d.ts +6 -0
- package/types/signal/mcp/McpDocument.d.ts +65 -0
- package/types/signal/mcp/McpProgress.d.ts +40 -0
- package/types/signal/mcp/McpUriTemplate.d.ts +26 -0
- package/types/signal/mcp/Msg.d.ts +143 -0
- package/types/signal/mcp/index.d.ts +5 -0
- package/types/signal/mcp/mcpProtocol.d.ts +107 -0
- package/types/signal/mcp.d.ts +1 -0
- package/types/signal/openapi/openapi.d.ts +3 -3
- package/types/signal/schema/JsonSchemaBuilder.d.ts +47 -0
- package/types/signal/schema/index.d.ts +1 -0
- package/types/signal/schema.d.ts +1 -0
- package/types/signal/signalContext.d.ts +16 -1
- package/types/signal/slice.d.ts +8 -8
- package/types/signal/types.d.ts +1 -1
- package/types/store/actionTag.d.ts +21 -0
- package/types/store/agent/AgentBridge.d.ts +77 -0
- package/types/store/agent/AgentContext.d.ts +16 -0
- package/types/store/agent/AgentPrompts.d.ts +31 -0
- package/types/store/agent/AgentVisibility.d.ts +21 -0
- package/types/store/agent/ScreenReader.d.ts +11 -0
- package/types/store/agent/StoreCatalogue.d.ts +26 -0
- package/types/store/agent/StoreSurfaceSource.d.ts +18 -0
- package/types/store/agent/index.d.ts +9 -0
- package/types/store/agent/storeSurface.d.ts +14 -0
- package/types/store/agent/types.d.ts +49 -0
- package/types/store/agent.d.ts +1 -0
- package/types/store/agentic/StToolBuilder.d.ts +41 -0
- package/types/store/agentic/attachAgentic.d.ts +14 -0
- package/types/store/agentic/index.d.ts +5 -0
- package/types/store/agentic/readableValue.d.ts +3 -0
- package/types/store/agentic/useStExpose.d.ts +8 -0
- package/types/store/agentic/useStState.d.ts +12 -0
- package/types/store/agentic.d.ts +1 -0
- package/types/store/baseSt.d.ts +29 -22
- package/types/store/databaseStateNames.d.ts +25 -0
- package/types/store/formSetterNames.d.ts +16 -0
- package/types/store/index.d.ts +7 -0
- package/types/store/rootStore.d.ts +5 -1
- package/types/store/sliceRole.d.ts +25 -0
- package/types/store/store.d.ts +7 -2
- package/types/store/storeInstance.d.ts +33 -1
- package/types/store/storeRegistry.d.ts +5 -0
- package/types/store/types.d.ts +15 -0
- package/types/store/withSelector.d.ts +7 -8
- package/types/test/registerDom.d.ts +1 -0
- package/types/ui/Agent/Approval.d.ts +7 -0
- package/types/ui/Agent/Bubble.d.ts +13 -0
- package/types/ui/Agent/Chat.d.ts +24 -0
- package/types/ui/Agent/Context.d.ts +9 -0
- package/types/ui/Agent/Dock.d.ts +16 -0
- package/types/ui/Agent/Guide.d.ts +9 -0
- package/types/ui/Agent/Section.d.ts +10 -0
- package/types/ui/Agent/StateKey.d.ts +16 -0
- package/types/ui/Agent/Tool.d.ts +15 -0
- package/types/ui/Agent/Transcript.d.ts +13 -0
- package/types/ui/Agent/Zone.d.ts +23 -0
- package/types/ui/Agent/fetchRunner.d.ts +12 -0
- package/types/ui/Agent/index.d.ts +17 -0
- package/types/ui/Agent/index_.d.ts +1 -0
- package/types/ui/Agent/sessionHistory.d.ts +12 -0
- package/types/ui/Agent.d.ts +1 -0
- package/types/ui/Badge.d.ts +1 -1
- package/types/ui/Button.d.ts +1 -1
- package/types/ui/CsrImage.d.ts +1 -1
- package/types/ui/Dropdown.d.ts +2 -0
- package/types/ui/Image.d.ts +3 -3
- package/types/ui/Layout/BottomInset.d.ts +2 -1
- package/types/ui/Layout/index.d.ts +1 -1
- package/types/ui/Signal/style.d.ts +4 -1
- package/types/ui/UiOverride/context.d.ts +14 -2
- package/types/ui/agentAttrs.d.ts +14 -0
- package/types/ui/index.d.ts +4 -1
- package/types/ui/overlayLayer.d.ts +24 -0
- package/types/ui/recipe/badgeRecipe.d.ts +7 -3
- package/types/ui/recipe/buttonRecipe.d.ts +7 -3
- package/types/ui/recipe/inputRecipe.d.ts +5 -1
- package/types/vendor/use-agentic/AgentProvider.d.ts +18 -0
- package/types/vendor/use-agentic/AgentScope.d.ts +9 -0
- package/types/vendor/use-agentic/AgentSession.d.ts +50 -0
- package/types/vendor/use-agentic/Agentic.d.ts +21 -0
- package/types/vendor/use-agentic/AgenticSurface.d.ts +44 -0
- package/types/vendor/use-agentic/httpRunner.d.ts +15 -0
- package/types/vendor/use-agentic/index.d.ts +13 -0
- package/types/vendor/use-agentic/surfaceContext.d.ts +5 -0
- package/types/vendor/use-agentic/types.d.ts +133 -0
- package/types/vendor/use-agentic/useAgent.d.ts +4 -0
- package/types/vendor/use-agentic/useAgentGuide.d.ts +6 -0
- package/types/vendor/use-agentic/useAgentResource.d.ts +7 -0
- package/types/vendor/use-agentic/useAgentState.d.ts +13 -0
- package/types/vendor/use-agentic/useAgentTool.d.ts +16 -0
- package/types/vendor/use-agentic.d.ts +1 -0
- package/types/webkit/index.d.ts +3 -0
- package/types/webkit/lazy.d.ts +12 -0
- package/types/webkit/useCsrValues.d.ts +3 -3
- package/types/webkit/useEscapeKey.d.ts +5 -0
- package/types/webkit/useFrameRuntime.d.ts +6 -1
- package/types/webkit/useScreenScope.d.ts +18 -0
- package/ui/Agent/Approval.tsx +29 -0
- package/ui/Agent/Bubble.tsx +74 -0
- package/ui/Agent/Chat.tsx +225 -0
- package/ui/Agent/Context.tsx +38 -0
- package/ui/Agent/Dock.tsx +72 -0
- package/ui/Agent/Guide.tsx +16 -0
- package/ui/Agent/Section.tsx +24 -0
- package/ui/Agent/StateKey.tsx +44 -0
- package/ui/Agent/Tool.tsx +66 -0
- package/ui/Agent/Transcript.tsx +33 -0
- package/ui/Agent/Zone.tsx +48 -0
- package/ui/Agent/fetchRunner.ts +30 -0
- package/ui/Agent/index.ts +12 -0
- package/ui/Agent/index_.tsx +4 -0
- package/ui/Agent/sessionHistory.ts +33 -0
- package/ui/Badge.tsx +3 -3
- package/ui/BottomSheet.tsx +5 -0
- package/ui/Button.tsx +6 -2
- package/ui/Constant/Doc.tsx +1 -1
- package/ui/CsrImage.tsx +1 -1
- package/ui/Data/ListContainer.tsx +1 -1
- package/ui/DatePicker.tsx +5 -4
- package/ui/Dialog/Modal.tsx +12 -15
- package/ui/DraggableList.tsx +3 -1
- package/ui/Dropdown.tsx +33 -11
- package/ui/Field.tsx +30 -16
- package/ui/Image.tsx +3 -3
- package/ui/Input.tsx +14 -5
- package/ui/Layout/BottomInset.tsx +6 -1
- package/ui/Load/Units.tsx +11 -2
- package/ui/Load/View.tsx +7 -2
- package/ui/Loading/ProgressBar.tsx +8 -1
- package/ui/Menu.tsx +7 -8
- package/ui/Model/EditModal.tsx +37 -2
- package/ui/Model/SureToRemove.tsx +2 -1
- package/ui/Model/index_.tsx +42 -15
- package/ui/ObjectId.tsx +3 -4
- package/ui/Pagination.tsx +3 -4
- package/ui/Popconfirm.tsx +8 -7
- package/ui/Select.tsx +6 -4
- package/ui/Signal/RestApi.tsx +43 -16
- package/ui/Signal/WebSocket.tsx +1 -1
- package/ui/Signal/style.ts +6 -1
- package/ui/Switch.tsx +2 -0
- package/ui/System/CSR.tsx +6 -2
- package/ui/System/ThemeToggle.tsx +55 -33
- package/ui/ToggleSelect.tsx +4 -3
- package/ui/Tooltip.tsx +2 -1
- package/ui/UiOverride/context.ts +14 -2
- package/ui/agentAttrs.ts +19 -0
- package/ui/index.ts +10 -1
- package/ui/overlayLayer.ts +39 -0
- package/ui/recipe/badgeRecipe.ts +22 -3
- package/ui/recipe/buttonRecipe.ts +51 -2
- package/ui/recipe/factory.ts +2 -2
- package/ui/recipe/inputRecipe.ts +18 -4
- package/vendor/use-agentic/AgentProvider.tsx +39 -0
- package/vendor/use-agentic/AgentScope.tsx +20 -0
- package/vendor/use-agentic/AgentSession.ts +292 -0
- package/vendor/use-agentic/Agentic.tsx +44 -0
- package/vendor/use-agentic/AgenticSurface.ts +294 -0
- package/vendor/use-agentic/WIRE.md +57 -0
- package/vendor/use-agentic/httpRunner.ts +115 -0
- package/vendor/use-agentic/index.ts +14 -0
- package/vendor/use-agentic/surfaceContext.ts +9 -0
- package/vendor/use-agentic/types.ts +144 -0
- package/vendor/use-agentic/useAgent.ts +17 -0
- package/vendor/use-agentic/useAgentGuide.ts +16 -0
- package/vendor/use-agentic/useAgentResource.ts +30 -0
- package/vendor/use-agentic/useAgentState.ts +61 -0
- package/vendor/use-agentic/useAgentTool.ts +55 -0
- package/webkit/bootCsr.tsx +2 -33
- package/webkit/index.ts +3 -0
- package/webkit/lazy.tsx +27 -3
- package/webkit/useCsrValues.ts +121 -4
- package/webkit/useEscapeKey.tsx +42 -0
- package/webkit/useFrameRuntime.ts +65 -32
- package/webkit/useScreenScope.tsx +49 -0
package/server/webRouter.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
|
+
import { pathToFileURL } from "node:url";
|
|
3
4
|
import { getEnv } from "akanjs/base";
|
|
4
5
|
import {
|
|
5
6
|
type AkanI18nConfig,
|
|
@@ -20,6 +21,7 @@ import {
|
|
|
20
21
|
RouteSeedIndexStore,
|
|
21
22
|
RoutesManifestStore,
|
|
22
23
|
} from "./artifact";
|
|
24
|
+
import { resolveEncodedSidecar } from "./assetEncoding";
|
|
23
25
|
import {
|
|
24
26
|
getClientFacingOrigin,
|
|
25
27
|
hasRouteCacheInvalidationScope,
|
|
@@ -51,7 +53,7 @@ import { type RscRedirectMethod, type RscRedirectStatus, type RscRenderResult, R
|
|
|
51
53
|
import { createDefaultSitemapXml, getSitemapBasePath } from "./sitemap";
|
|
52
54
|
import { SsrFromRscRenderer } from "./ssrFromRscRenderer";
|
|
53
55
|
import type { RscTraceMetadata, SsrManifest } from "./ssrTypes";
|
|
54
|
-
import { createSystemPageResponse, getSystemPageHomeHref } from "./systemPages";
|
|
56
|
+
import { createSubRouteIndexResponse, createSystemPageResponse, getSystemPageHomeHref } from "./systemPages";
|
|
55
57
|
import type { BaseBuildArtifact, HttpRoutes, RenderState } from "./types";
|
|
56
58
|
|
|
57
59
|
const CLIENT_CLOSED_REQUEST_STATUS = 499;
|
|
@@ -60,6 +62,18 @@ export const DEFAULT_HTML_RESULT_CACHE_MAX_BODY_BYTES = 2 * 1024 * 1024;
|
|
|
60
62
|
const ROUTE_CACHE_SWEEP_INTERVAL_MS = 60_000;
|
|
61
63
|
const APPLE_APP_SITE_ASSOCIATION_PATH = "/.well-known/apple-app-site-association";
|
|
62
64
|
const ANDROID_ASSET_LINKS_PATH = "/.well-known/assetlinks.json";
|
|
65
|
+
const FIREBASE_MESSAGING_SW_PATH = "/firebase-messaging-sw.js";
|
|
66
|
+
const FIREBASE_WEB_SDK_VERSION = "12.13.0";
|
|
67
|
+
|
|
68
|
+
interface FirebaseClientEnvConfig {
|
|
69
|
+
apiKey: string;
|
|
70
|
+
authDomain?: string;
|
|
71
|
+
projectId: string;
|
|
72
|
+
storageBucket?: string;
|
|
73
|
+
messagingSenderId: string;
|
|
74
|
+
appId: string;
|
|
75
|
+
vapidKey?: string;
|
|
76
|
+
}
|
|
63
77
|
|
|
64
78
|
export function createRscRedirectResponse(
|
|
65
79
|
location: string,
|
|
@@ -273,7 +287,13 @@ export class WebRouter {
|
|
|
273
287
|
#subRoutes: Record<string, string[]>;
|
|
274
288
|
#rsc: RscWorker;
|
|
275
289
|
#hub: HmrWsHub | null = null;
|
|
276
|
-
|
|
290
|
+
/**
|
|
291
|
+
* `akan start` is the dev server whatever the environment claims. Its artifact directory carries no routes
|
|
292
|
+
* manifest — only `akan build` writes one — so the production branch cannot build a route on demand and
|
|
293
|
+
* throws on every request instead. `NODE_ENV` arrives by accident often enough (a workspace `.env` Bun loads
|
|
294
|
+
* on its own, a CI image default) that the command which started this process has to outrank it.
|
|
295
|
+
*/
|
|
296
|
+
#prodMode = process.env.NODE_ENV === "production" && process.env.AKAN_COMMAND_TYPE !== "start";
|
|
277
297
|
#builderRpc: BuilderRpc | null;
|
|
278
298
|
#routeCache: RouteClientCache;
|
|
279
299
|
#devHmr: DevHmrController | null = null;
|
|
@@ -297,10 +317,13 @@ export class WebRouter {
|
|
|
297
317
|
#htmlCacheHits = 0;
|
|
298
318
|
#htmlCacheMisses = 0;
|
|
299
319
|
#htmlCacheBypass = 0;
|
|
320
|
+
#runtimeManifest: { revision: number; manifest: MergedManifest } | null = null;
|
|
300
321
|
renderState: RenderState;
|
|
301
322
|
#seedIndex: RouteSeedIndex;
|
|
302
323
|
constructor({ artifact, cssBytesByUrl, rsc, seedIndex, upgradeHmrWs }: WebRouterOptions) {
|
|
303
324
|
this.#logger.verbose(`[SSR] loaded ${Object.keys(cssBytesByUrl).length} CSS assets`);
|
|
325
|
+
if (process.env.NODE_ENV === "production" && !this.#prodMode)
|
|
326
|
+
this.#logger.warn("[SSR] NODE_ENV=production ignored under `akan start`; serving in dev mode");
|
|
304
327
|
this.#artifact = artifact;
|
|
305
328
|
const { subRoutes, ignoredBasePaths } = resolveSubRouteHosts({
|
|
306
329
|
subRoutes: artifact.subRoutes,
|
|
@@ -340,7 +363,7 @@ export class WebRouter {
|
|
|
340
363
|
if (prebuilt) {
|
|
341
364
|
this.#routeCache.seed(prebuilt);
|
|
342
365
|
await this.#rsc.reload({
|
|
343
|
-
clientManifest: this.#mergeRuntimeManifest(
|
|
366
|
+
clientManifest: this.#mergeRuntimeManifest().clientManifest,
|
|
344
367
|
cssAssets: this.renderState.cssAssets,
|
|
345
368
|
buildId: this.renderState.buildId,
|
|
346
369
|
});
|
|
@@ -486,6 +509,16 @@ export class WebRouter {
|
|
|
486
509
|
WebRouter.#deepLinkAssociationResponse(ANDROID_ASSET_LINKS_PATH, this.#artifact, {
|
|
487
510
|
cacheControl: this.#prodMode ? "public, max-age=3600" : "no-store",
|
|
488
511
|
}) ?? new Response("Not Found", { status: 404 }),
|
|
512
|
+
[FIREBASE_MESSAGING_SW_PATH]: async () => {
|
|
513
|
+
this.#requestStats.staticAsset += 1;
|
|
514
|
+
const firebaseConfig = await WebRouter.#resolveFirebaseClientConfig();
|
|
515
|
+
return new Response(WebRouter.#createFirebaseMessagingServiceWorker(firebaseConfig), {
|
|
516
|
+
headers: {
|
|
517
|
+
"Content-Type": "application/javascript; charset=utf-8",
|
|
518
|
+
"Cache-Control": "no-store",
|
|
519
|
+
},
|
|
520
|
+
});
|
|
521
|
+
},
|
|
489
522
|
"/*": async (req) => {
|
|
490
523
|
const url = new URL(req.url);
|
|
491
524
|
if (WebRouter.#isImageOptimizerPath(url.pathname)) {
|
|
@@ -553,6 +586,9 @@ export class WebRouter {
|
|
|
553
586
|
);
|
|
554
587
|
}
|
|
555
588
|
|
|
589
|
+
const subRouteIndex = this.#localSubRouteIndexResponse(req, url);
|
|
590
|
+
if (subRouteIndex) return await subRouteIndex;
|
|
591
|
+
|
|
556
592
|
try {
|
|
557
593
|
this.#requestStats.fullSsr += 1;
|
|
558
594
|
const manifest = await this.#ensureRoute(url);
|
|
@@ -813,15 +849,27 @@ export class WebRouter {
|
|
|
813
849
|
this.#logger.verbose(
|
|
814
850
|
`[route-cache] ensure pathname=${url.pathname} routeId=${matched?.entry.routeId ?? "(none)"} in ${Date.now() - started}ms`,
|
|
815
851
|
);
|
|
816
|
-
return this.#mergeRuntimeManifest(
|
|
852
|
+
return this.#mergeRuntimeManifest();
|
|
817
853
|
}
|
|
818
854
|
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
855
|
+
/**
|
|
856
|
+
* Memoized on the route cache's revision. Both the snapshot and the runtime merge copy the whole client manifest
|
|
857
|
+
* and SSR module map, which is a few hundred KB of structure for a small app — and this ran on every request. In
|
|
858
|
+
* production the revision never moves after `seed`, so the manifest is built once; in dev a rebuild bumps it and
|
|
859
|
+
* the next request pays for one fresh copy. The cached object is still a copy, so an in-flight request keeps
|
|
860
|
+
* consuming a stable manifest across an invalidate exactly as the per-request snapshot made it.
|
|
861
|
+
*/
|
|
862
|
+
#mergeRuntimeManifest(): MergedManifest {
|
|
863
|
+
const revision = this.#routeCache.revision;
|
|
864
|
+
if (this.#runtimeManifest?.revision === revision) return this.#runtimeManifest.manifest;
|
|
865
|
+
const snapshot = this.#routeCache.snapshot();
|
|
866
|
+
const manifest: MergedManifest = {
|
|
867
|
+
...snapshot,
|
|
868
|
+
clientManifest: WebRouter.#mergeClientManifest(this.#artifact.rscRuntimeClientManifest, snapshot.clientManifest),
|
|
869
|
+
ssrManifest: WebRouter.#mergeSsrManifest(this.#artifact.rscRuntimeSsrManifest, snapshot.ssrManifest),
|
|
824
870
|
};
|
|
871
|
+
this.#runtimeManifest = { revision, manifest };
|
|
872
|
+
return manifest;
|
|
825
873
|
}
|
|
826
874
|
#renderSystemNotFoundFallbackResponse(req: Request, url: URL): Promise<Response> {
|
|
827
875
|
return createSystemPageResponse({
|
|
@@ -834,6 +882,29 @@ export class WebRouter {
|
|
|
834
882
|
});
|
|
835
883
|
}
|
|
836
884
|
|
|
885
|
+
/**
|
|
886
|
+
* A build with subRoutes must keep every route file under `page/<basePath>`, so the site root owns no page and
|
|
887
|
+
* answers 404 — including the URL `akan start` opens a browser on. Locally that reads as a broken app, so serve a
|
|
888
|
+
* picker of the basePaths this build carries instead. Deployed hosts never reach it: `HostBasePathWebProxy`
|
|
889
|
+
* rewrites the root onto the basePath its host maps to before the router sees it.
|
|
890
|
+
*/
|
|
891
|
+
#localSubRouteIndexResponse(req: Request, url: URL): Promise<Response> | null {
|
|
892
|
+
if (process.env.AKAN_PUBLIC_ENV !== "local" || !this.#artifact.basePaths.length) return null;
|
|
893
|
+
if (!WebRouter.#isSiteRootPathname(url.pathname, this.#artifact.i18n)) return null;
|
|
894
|
+
return createSubRouteIndexResponse({
|
|
895
|
+
method: req.method,
|
|
896
|
+
locale: WebRouter.#getLocale(url.pathname, this.#artifact.i18n),
|
|
897
|
+
basePaths: this.#artifact.basePaths,
|
|
898
|
+
subRoutes: this.#subRoutes,
|
|
899
|
+
});
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
static #isSiteRootPathname(pathname: string, i18n: AkanI18nConfig): boolean {
|
|
903
|
+
const segments = pathname.split("/").filter(Boolean);
|
|
904
|
+
if (!segments.length) return true;
|
|
905
|
+
return segments.length === 1 && i18n.locales.includes(segments[0] ?? "");
|
|
906
|
+
}
|
|
907
|
+
|
|
837
908
|
#renderErrorResponse(req: Request, scope: string, err: unknown): Promise<Response> {
|
|
838
909
|
if (WebRouter.#isExpectedRequestAbort(err)) return Promise.resolve(WebRouter.#clientClosedResponse());
|
|
839
910
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -994,6 +1065,79 @@ export class WebRouter {
|
|
|
994
1065
|
return process.env.AKAN_APP_DIR ?? path.dirname(Bun.main);
|
|
995
1066
|
}
|
|
996
1067
|
|
|
1068
|
+
static async #resolveFirebaseClientConfig(): Promise<FirebaseClientEnvConfig | null> {
|
|
1069
|
+
const envPath = path.join(WebRouter.#resolveAppDir(), "env", "env.client.ts");
|
|
1070
|
+
if (!fs.existsSync(envPath)) return null;
|
|
1071
|
+
try {
|
|
1072
|
+
const envUrl = pathToFileURL(envPath);
|
|
1073
|
+
envUrl.searchParams.set("t", String(Date.now()));
|
|
1074
|
+
const envModule = (await import(envUrl.href)) as { env?: { firebase?: unknown } };
|
|
1075
|
+
return WebRouter.#normalizeFirebaseClientConfig(envModule.env?.firebase);
|
|
1076
|
+
} catch {
|
|
1077
|
+
return null;
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1081
|
+
static #normalizeFirebaseClientConfig(config: unknown): FirebaseClientEnvConfig | null {
|
|
1082
|
+
if (!config || typeof config !== "object") return null;
|
|
1083
|
+
const value = config as Partial<Record<keyof FirebaseClientEnvConfig, unknown>>;
|
|
1084
|
+
if (
|
|
1085
|
+
typeof value.apiKey !== "string" ||
|
|
1086
|
+
typeof value.projectId !== "string" ||
|
|
1087
|
+
typeof value.messagingSenderId !== "string" ||
|
|
1088
|
+
typeof value.appId !== "string"
|
|
1089
|
+
) {
|
|
1090
|
+
return null;
|
|
1091
|
+
}
|
|
1092
|
+
return {
|
|
1093
|
+
apiKey: value.apiKey,
|
|
1094
|
+
...(typeof value.authDomain === "string" ? { authDomain: value.authDomain } : {}),
|
|
1095
|
+
projectId: value.projectId,
|
|
1096
|
+
...(typeof value.storageBucket === "string" ? { storageBucket: value.storageBucket } : {}),
|
|
1097
|
+
messagingSenderId: value.messagingSenderId,
|
|
1098
|
+
appId: value.appId,
|
|
1099
|
+
};
|
|
1100
|
+
}
|
|
1101
|
+
|
|
1102
|
+
static #createFirebaseMessagingServiceWorker(config: FirebaseClientEnvConfig | null): string {
|
|
1103
|
+
const configJson = JSON.stringify(config);
|
|
1104
|
+
return `/* Generated by Akan.js. Do not edit. */
|
|
1105
|
+
const firebaseConfig = ${configJson};
|
|
1106
|
+
|
|
1107
|
+
if (firebaseConfig) {
|
|
1108
|
+
importScripts("https://www.gstatic.com/firebasejs/${FIREBASE_WEB_SDK_VERSION}/firebase-app-compat.js");
|
|
1109
|
+
importScripts("https://www.gstatic.com/firebasejs/${FIREBASE_WEB_SDK_VERSION}/firebase-messaging-compat.js");
|
|
1110
|
+
|
|
1111
|
+
firebase.initializeApp(firebaseConfig);
|
|
1112
|
+
const messaging = firebase.messaging();
|
|
1113
|
+
|
|
1114
|
+
const notificationUrl = (payload) =>
|
|
1115
|
+
payload?.data?.url || payload?.fcmOptions?.link || payload?.notification?.click_action;
|
|
1116
|
+
|
|
1117
|
+
messaging.onBackgroundMessage((payload) => {
|
|
1118
|
+
const title = payload?.notification?.title || "";
|
|
1119
|
+
const options = {
|
|
1120
|
+
body: payload?.notification?.body,
|
|
1121
|
+
icon: payload?.notification?.icon,
|
|
1122
|
+
image: payload?.notification?.image,
|
|
1123
|
+
data: {
|
|
1124
|
+
url: notificationUrl(payload),
|
|
1125
|
+
FCM_MSG: payload,
|
|
1126
|
+
},
|
|
1127
|
+
};
|
|
1128
|
+
self.registration.showNotification(title, options);
|
|
1129
|
+
});
|
|
1130
|
+
}
|
|
1131
|
+
|
|
1132
|
+
self.addEventListener("notificationclick", (event) => {
|
|
1133
|
+
const url = event.notification?.data?.url || event.notification?.data?.FCM_MSG?.data?.url;
|
|
1134
|
+
event.notification?.close();
|
|
1135
|
+
if (!url) return;
|
|
1136
|
+
event.waitUntil(clients.openWindow(url));
|
|
1137
|
+
});
|
|
1138
|
+
`;
|
|
1139
|
+
}
|
|
1140
|
+
|
|
997
1141
|
static #normalizeArtifact(artifact: BaseBuildArtifact, artifactDir: string): BaseBuildArtifact {
|
|
998
1142
|
const normalizedArtifactDir = path.resolve(artifactDir);
|
|
999
1143
|
const pagesBundlePath = WebRouter.#resolveArtifactPath(artifact.pagesBundlePath, normalizedArtifactDir);
|
|
@@ -1101,16 +1245,12 @@ export class WebRouter {
|
|
|
1101
1245
|
headers.set("Last-Modified", new Date(lastModifiedMs).toUTCString());
|
|
1102
1246
|
if (WebRouter.#isNotModified(req, etag, lastModifiedMs)) return new Response(null, { status: 304, headers });
|
|
1103
1247
|
|
|
1104
|
-
const
|
|
1105
|
-
if (
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
headers.set("Content-Length", String(gzipBytes.byteLength));
|
|
1111
|
-
headers.set("Vary", "Accept-Encoding");
|
|
1112
|
-
return new Response(WebRouter.#toArrayBuffer(gzipBytes), { headers });
|
|
1113
|
-
}
|
|
1248
|
+
const sidecar = await resolveEncodedSidecar(req, filePath, options.contentType);
|
|
1249
|
+
if (sidecar) {
|
|
1250
|
+
headers.set("Content-Encoding", sidecar.encoding);
|
|
1251
|
+
headers.set("Content-Length", String(sidecar.bytes.byteLength));
|
|
1252
|
+
headers.set("Vary", "Accept-Encoding");
|
|
1253
|
+
return new Response(sidecar.bytes, { headers });
|
|
1114
1254
|
}
|
|
1115
1255
|
|
|
1116
1256
|
return new Response(file.stream(), { headers });
|
|
@@ -1197,22 +1337,6 @@ export class WebRouter {
|
|
|
1197
1337
|
return Number.isFinite(sinceMs) && sinceMs >= lastModifiedMs;
|
|
1198
1338
|
}
|
|
1199
1339
|
|
|
1200
|
-
static #acceptsGzip(req: Request): boolean {
|
|
1201
|
-
const acceptEncoding = req.headers.get("accept-encoding") ?? "";
|
|
1202
|
-
return /\bgzip\b/.test(acceptEncoding);
|
|
1203
|
-
}
|
|
1204
|
-
|
|
1205
|
-
static #isCompressible(contentType: string): boolean {
|
|
1206
|
-
const type = contentType.split(";")[0]?.trim().toLowerCase() ?? "";
|
|
1207
|
-
return (
|
|
1208
|
-
type.startsWith("text/") ||
|
|
1209
|
-
type === "application/javascript" ||
|
|
1210
|
-
type === "application/json" ||
|
|
1211
|
-
type === "application/manifest+json" ||
|
|
1212
|
-
type === "image/svg+xml"
|
|
1213
|
-
);
|
|
1214
|
-
}
|
|
1215
|
-
|
|
1216
1340
|
static #safeResolve(baseDir: string, urlPath: string): string | null {
|
|
1217
1341
|
let decoded: string;
|
|
1218
1342
|
try {
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Err } from "akanjs/dictionary";
|
|
2
|
+
import type { LlmTurnRequest } from "./predefinedAdaptor/llm.adaptor";
|
|
3
|
+
import { LlmAdaptorRole } from "./predefinedAdaptor/role.adaptor";
|
|
4
|
+
import { serve } from "./serve";
|
|
5
|
+
|
|
6
|
+
export class AgentService extends serve("agent" as const, ({ plug }) => ({
|
|
7
|
+
llm: plug(LlmAdaptorRole),
|
|
8
|
+
})) {
|
|
9
|
+
async runTurn(request: LlmTurnRequest, onDelta?: (delta: string) => void) {
|
|
10
|
+
const answer = await this.llm.chat(request, onDelta);
|
|
11
|
+
if (!answer) throw new Err("agent.error.llmUnavailable");
|
|
12
|
+
return { text: answer.text ?? "", toolCalls: answer.toolCalls ?? [], stop: answer.stop };
|
|
13
|
+
}
|
|
14
|
+
}
|
package/service/base.service.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Base } from "akanjs/signal";
|
|
2
|
+
import { AgentService } from "./agent.service";
|
|
2
3
|
import { serve } from "./serve";
|
|
3
4
|
import { ServiceModel } from "./serviceModule";
|
|
4
5
|
|
|
@@ -11,4 +12,4 @@ export class BaseService extends serve("base" as const, ({ env, signal, memory }
|
|
|
11
12
|
}
|
|
12
13
|
}
|
|
13
14
|
|
|
14
|
-
export const srv = { base: new ServiceModel(BaseService) };
|
|
15
|
+
export const srv = { base: new ServiceModel(BaseService), agent: new ServiceModel(AgentService) };
|
package/service/index.ts
CHANGED
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
documentQueryHelper,
|
|
20
20
|
encodeDocumentValue,
|
|
21
21
|
isDocumentUpdateNode,
|
|
22
|
+
NoDocumentError,
|
|
22
23
|
resolveDocumentUpdate,
|
|
23
24
|
type SchemaOf,
|
|
24
25
|
sanitizeJson,
|
|
@@ -98,6 +99,9 @@ export interface DocumentStore {
|
|
|
98
99
|
removeManyByQuery(
|
|
99
100
|
query: DocumentQuery,
|
|
100
101
|
): Promise<{ acknowledged: boolean; matchedCount: number; modifiedCount: number }>;
|
|
102
|
+
removeOneByQuery(
|
|
103
|
+
query: DocumentQuery,
|
|
104
|
+
): Promise<{ acknowledged: boolean; matchedCount: number; modifiedCount: number; upsertedId: string | null }>;
|
|
101
105
|
bulkWrite(
|
|
102
106
|
operations: { updateOne: { filter: DocumentQuery; update: DocumentUpdateInput; upsert?: boolean } }[],
|
|
103
107
|
): Promise<{ acknowledged: boolean; matchedCount: number; modifiedCount: number; upsertedId: string | null }>;
|
|
@@ -116,7 +120,7 @@ export interface DocumentStore {
|
|
|
116
120
|
exists(query?: DocumentQuery): Promise<string | null>;
|
|
117
121
|
count(query?: DocumentQuery): Promise<number>;
|
|
118
122
|
insight(query?: DocumentQuery): Promise<any>;
|
|
119
|
-
hydrate(data: DocumentRecord, originalData?: DocumentRecord): any;
|
|
123
|
+
hydrate(data: DocumentRecord, originalData?: DocumentRecord, options?: { track?: boolean }): any;
|
|
120
124
|
}
|
|
121
125
|
|
|
122
126
|
export interface SqlResultRows<Row = Record<string, unknown>> {
|
|
@@ -957,6 +961,17 @@ class UpdateCompiler {
|
|
|
957
961
|
}
|
|
958
962
|
}
|
|
959
963
|
|
|
964
|
+
/**
|
|
965
|
+
* Per-document modification state, attached only when a document is hydrated for writing.
|
|
966
|
+
* Non-enumerable so `{ ...doc }` in `toRow` and `Object.entries` in `sanitizeJson` never see it.
|
|
967
|
+
*/
|
|
968
|
+
const MODIFICATION_STATE = Symbol("akan.document.modificationState");
|
|
969
|
+
|
|
970
|
+
interface ModificationState {
|
|
971
|
+
isNew: boolean;
|
|
972
|
+
original: Record<string, unknown>;
|
|
973
|
+
}
|
|
974
|
+
|
|
960
975
|
export class SqlDocumentStore {
|
|
961
976
|
readonly schema: DocumentSchema;
|
|
962
977
|
readonly table: string;
|
|
@@ -964,6 +979,7 @@ export class SqlDocumentStore {
|
|
|
964
979
|
readonly updateCompiler: UpdateCompiler;
|
|
965
980
|
#insertStmt: AkanSqlStatement | null = null;
|
|
966
981
|
#readStmtCache = new Map<string, AkanSqlStatement>();
|
|
982
|
+
#docPrototype: object | null = null;
|
|
967
983
|
|
|
968
984
|
constructor(
|
|
969
985
|
private readonly owner: DocumentDatabaseOwner,
|
|
@@ -1086,6 +1102,11 @@ export class SqlDocumentStore {
|
|
|
1086
1102
|
return this.updateManyByQuery(query, { removedAt: dayjs() });
|
|
1087
1103
|
}
|
|
1088
1104
|
|
|
1105
|
+
async removeOneByQuery(query: DocumentQuery) {
|
|
1106
|
+
|
|
1107
|
+
return this.updateOneByQuery(query, { removedAt: dayjs() });
|
|
1108
|
+
}
|
|
1109
|
+
|
|
1089
1110
|
private compiledUpdate(update: DocumentUpdate) {
|
|
1090
1111
|
const compiled = this.updateCompiler.compile(update);
|
|
1091
1112
|
return {
|
|
@@ -1125,14 +1146,14 @@ export class SqlDocumentStore {
|
|
|
1125
1146
|
const rows = await this.prepareReadStmt(
|
|
1126
1147
|
`SELECT ${this.projectionSql(projection)} FROM ${quoteIdent(this.table)}${join} WHERE ${where} ${order}${limit}${offset}`,
|
|
1127
1148
|
).all<ProjectedSqliteDocumentRow>(...args);
|
|
1128
|
-
return rows.map((row) => this.hydrate(this.fromProjectedRow(row, projection)));
|
|
1149
|
+
return rows.map((row) => this.hydrate(this.fromProjectedRow(row, projection), undefined, { track: false }));
|
|
1129
1150
|
}
|
|
1130
1151
|
|
|
1131
1152
|
const star = joins.length ? `${quoteIdent(this.table)}.*` : "*";
|
|
1132
1153
|
const rows = await this.prepareReadStmt(
|
|
1133
1154
|
`SELECT ${star} FROM ${quoteIdent(this.table)}${join} WHERE ${where} ${order}${limit}${offset}`,
|
|
1134
1155
|
).all<SqliteDocumentRow>(...args);
|
|
1135
|
-
return rows.map((row) => this.hydrate(this.fromRow(row)));
|
|
1156
|
+
return rows.map((row) => this.hydrate(this.fromRow(row), undefined, { track: false }));
|
|
1136
1157
|
}
|
|
1137
1158
|
|
|
1138
1159
|
async findIds(
|
|
@@ -1162,13 +1183,13 @@ export class SqlDocumentStore {
|
|
|
1162
1183
|
|
|
1163
1184
|
async pickOne(query?: DocumentQuery, options: FindOneOptions = {}) {
|
|
1164
1185
|
const doc = await this.findOne(query, options);
|
|
1165
|
-
if (!doc) throw new
|
|
1186
|
+
if (!doc) throw new NoDocumentError(`No Document (${this.table}): ${JSON.stringify(query)}`);
|
|
1166
1187
|
return doc;
|
|
1167
1188
|
}
|
|
1168
1189
|
|
|
1169
1190
|
async pickById(id: string) {
|
|
1170
1191
|
const doc = await this.findOne({ id } as DocumentQuery);
|
|
1171
|
-
if (!doc) throw new
|
|
1192
|
+
if (!doc) throw new NoDocumentError(`No Document (${this.table}): ${id}`);
|
|
1172
1193
|
return doc;
|
|
1173
1194
|
}
|
|
1174
1195
|
|
|
@@ -1450,7 +1471,7 @@ export class SqlDocumentStore {
|
|
|
1450
1471
|
|
|
1451
1472
|
private async pickByIdForWrite(id: string) {
|
|
1452
1473
|
const doc = await this.findOneForWrite({ id } as DocumentQuery);
|
|
1453
|
-
if (!doc) throw new
|
|
1474
|
+
if (!doc) throw new NoDocumentError(`No Document (${this.table}): ${id}`);
|
|
1454
1475
|
return doc;
|
|
1455
1476
|
}
|
|
1456
1477
|
|
|
@@ -1580,49 +1601,82 @@ export class SqlDocumentStore {
|
|
|
1580
1601
|
return result;
|
|
1581
1602
|
}
|
|
1582
1603
|
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1604
|
+
/**
|
|
1605
|
+
* `track` buys `isModified()` and costs a deep clone of the row, so it is decided per call site rather than
|
|
1606
|
+
* paid everywhere. Only the read paths (`find`, and the projected read behind it) opt out — that clone was 42%
|
|
1607
|
+
* of a list query, and a listed document is never the one a save hook runs on. Everything else keeps it, so an
|
|
1608
|
+
* external caller of `hydrate` sees no change.
|
|
1609
|
+
*/
|
|
1610
|
+
hydrate(data: DocumentRecord, originalData: DocumentRecord = data, { track = true }: { track?: boolean } = {}) {
|
|
1586
1611
|
const isNew = !originalData.id;
|
|
1587
1612
|
const hydratedData = isNew ? this.prepareDocument(data) : data;
|
|
1588
|
-
const doc = Object.assign(Object.create(this
|
|
1589
|
-
|
|
1613
|
+
const doc = Object.assign(Object.create(this.#documentPrototype()), hydratedData);
|
|
1614
|
+
if (!track) return doc;
|
|
1615
|
+
Object.defineProperty(doc, MODIFICATION_STATE, {
|
|
1616
|
+
value: {
|
|
1617
|
+
isNew,
|
|
1618
|
+
|
|
1619
|
+
original: JSON.parse(JSON.stringify(sanitizeJson(originalData) ?? {})) as Record<string, unknown>,
|
|
1620
|
+
} satisfies ModificationState,
|
|
1621
|
+
});
|
|
1622
|
+
return doc;
|
|
1623
|
+
}
|
|
1624
|
+
|
|
1625
|
+
/**
|
|
1626
|
+
* One prototype per store instead of six closures per document. It extends the model's own document prototype,
|
|
1627
|
+
* so declared chain methods and `instanceof` are unaffected, and every method here is non-enumerable exactly as
|
|
1628
|
+
* the previous per-document `defineProperties` made them.
|
|
1629
|
+
*/
|
|
1630
|
+
#documentPrototype() {
|
|
1631
|
+
if (this.#docPrototype) return this.#docPrototype;
|
|
1632
|
+
const store = this;
|
|
1633
|
+
this.#docPrototype = Object.create(this.database.doc.prototype, {
|
|
1590
1634
|
set: {
|
|
1591
|
-
value(patch: DocumentRecord) {
|
|
1635
|
+
value(this: DocumentRecord, patch: DocumentRecord) {
|
|
1592
1636
|
Object.assign(this, patch);
|
|
1593
1637
|
return this;
|
|
1594
1638
|
},
|
|
1595
1639
|
},
|
|
1596
1640
|
save: {
|
|
1597
|
-
async value() {
|
|
1598
|
-
return this.id ? store.update(this.id, this) : store.create(this);
|
|
1641
|
+
async value(this: DocumentRecord) {
|
|
1642
|
+
return this.id ? store.update(this.id as string, this) : store.create(this);
|
|
1599
1643
|
},
|
|
1600
1644
|
},
|
|
1601
1645
|
refresh: {
|
|
1602
|
-
async value() {
|
|
1603
|
-
Object.assign(this, await store.pickById(this.id));
|
|
1646
|
+
async value(this: DocumentRecord) {
|
|
1647
|
+
Object.assign(this, await store.pickById(this.id as string));
|
|
1604
1648
|
return this;
|
|
1605
1649
|
},
|
|
1606
1650
|
},
|
|
1607
1651
|
isModified: {
|
|
1608
|
-
value(field?: string) {
|
|
1609
|
-
|
|
1610
|
-
if (!
|
|
1611
|
-
|
|
1652
|
+
value(this: DocumentRecord & { [MODIFICATION_STATE]?: ModificationState }, field?: string) {
|
|
1653
|
+
const state = this[MODIFICATION_STATE];
|
|
1654
|
+
if (!state) throw new Error(SqlDocumentStore.#untrackedModificationMessage(store.table));
|
|
1655
|
+
if (state.isNew) return true;
|
|
1656
|
+
if (!field) return JSON.stringify(sanitizeJson(this)) !== JSON.stringify(state.original);
|
|
1657
|
+
return JSON.stringify(sanitizeJson(this[field])) !== JSON.stringify(state.original[field]);
|
|
1612
1658
|
},
|
|
1613
1659
|
},
|
|
1614
1660
|
toJSON: {
|
|
1615
|
-
value() {
|
|
1661
|
+
value(this: DocumentRecord) {
|
|
1616
1662
|
return sanitizeJson(this);
|
|
1617
1663
|
},
|
|
1618
1664
|
},
|
|
1619
1665
|
toObject: {
|
|
1620
|
-
value() {
|
|
1666
|
+
value(this: DocumentRecord) {
|
|
1621
1667
|
return sanitizeJson(this);
|
|
1622
1668
|
},
|
|
1623
1669
|
},
|
|
1624
|
-
});
|
|
1625
|
-
return
|
|
1670
|
+
}) as object;
|
|
1671
|
+
return this.#docPrototype;
|
|
1672
|
+
}
|
|
1673
|
+
|
|
1674
|
+
static #untrackedModificationMessage(table: string) {
|
|
1675
|
+
return (
|
|
1676
|
+
`isModified() is unavailable on this ${table} document: it was loaded through a read query, which does not ` +
|
|
1677
|
+
`snapshot the row. Call it inside a save hook, or re-load the document through the write path (\`save()\`, ` +
|
|
1678
|
+
`\`update()\`) before comparing.`
|
|
1679
|
+
);
|
|
1626
1680
|
}
|
|
1627
1681
|
|
|
1628
1682
|
private async runHooks(
|