akanjs 3.1.0-rc.2 → 3.1.0-rc.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/base/baseEnv.ts +5 -2
- package/client/device.ts +1 -1
- package/client/frameConfig.ts +2 -2
- package/client/href.ts +35 -0
- package/client/index.ts +1 -0
- package/client/makePageProto.tsx +3 -2
- package/client/router.ts +6 -4
- package/client/rscNavigation.ts +1 -1
- package/client/transitionTypes.ts +0 -4
- package/client/translator.ts +16 -0
- package/client/types.ts +3 -2
- package/client/viewportMeta.ts +23 -8
- package/client/webManifest.ts +5 -3
- package/common/TrustedProxy.ts +14 -0
- package/common/codeAgentWire.ts +8 -1
- package/common/index.ts +1 -0
- package/common/interpolateTranslation.ts +17 -3
- package/common/isEmail.ts +1 -1
- package/common/localeConfig.ts +10 -4
- package/common/markdownSpans.ts +1 -1
- package/common/restClient.ts +3 -1
- package/common/routeConvention.ts +4 -4
- package/common/trimSlashes.ts +11 -0
- package/common/types.ts +1 -1
- package/constant/fieldInfo.ts +2 -2
- package/constant/fieldPlan.ts +80 -0
- package/constant/index.ts +3 -0
- package/constant/inputParser.ts +148 -0
- package/constant/invalidInputError.ts +13 -0
- package/constant/serialize.ts +36 -20
- package/constant/types.ts +1 -1
- package/constant/via.ts +2 -0
- package/dictionary/base.dictionary.ts +4 -0
- package/dictionary/trans.ts +8 -0
- package/document/database.ts +3 -6
- package/document/documentSchema.ts +1 -1
- package/document/noDocumentError.ts +6 -1
- package/fetch/client/fetchClient.ts +2 -1
- package/fetch/client/sliceInitHandle.ts +2 -3
- package/fetch/fetchType/appliedReturn.type.ts +3 -1
- package/fetch/types.ts +4 -2
- package/index.ts +2 -0
- package/package.json +31 -21
- package/server/akanApp.ts +15 -3
- package/server/akanServer.ts +34 -14
- package/server/artifact/routeClientCache.ts +4 -5
- package/server/artifact/routeSeedIndexStore.ts +3 -20
- package/server/artifact/routeSeedMatcher.ts +70 -0
- package/server/console.ts +1 -1
- package/server/di/diLifecycle.ts +16 -7
- package/server/imageOptimizer.ts +1 -1
- package/server/index.ts +1 -0
- package/server/machine/machineAccount.ts +28 -3
- package/server/machine/machineToken.ts +27 -3
- package/server/mcp/McpDispatcher.ts +2 -3
- package/server/mcp/McpExecutionContext.ts +9 -3
- package/server/migration/docPath.ts +121 -0
- package/server/migration/index.ts +15 -0
- package/server/migration/migrationChangeLog.ts +86 -0
- package/server/migration/migrationChanges.ts +70 -0
- package/server/migration/migrationCommand.ts +179 -0
- package/server/migration/migrationContext.ts +53 -0
- package/server/migration/migrationDefinition.ts +87 -0
- package/server/migration/migrationError.ts +45 -0
- package/server/migration/migrationLedger.ts +172 -0
- package/server/migration/migrationLock.ts +103 -0
- package/server/migration/migrationParams.ts +122 -0
- package/server/migration/migrationPulse.ts +39 -0
- package/server/migration/migrationRunner.ts +525 -0
- package/server/migration/migrationScope.ts +93 -0
- package/server/migration/migrationTable.ts +258 -0
- package/server/migration/migrations.ts +57 -0
- package/server/ops/opsCommand.ts +30 -1
- package/server/resolver/CascadeRunner.ts +15 -2
- package/server/resolver/signal.resolver.ts +35 -55
- package/server/routeState.ts +0 -6
- package/server/rscClient.tsx +22 -37
- package/server/rscNavigationState.ts +3 -5
- package/server/rscScrollRestoration.ts +116 -0
- package/server/rscWorker.tsx +12 -19
- package/server/sitemap.ts +4 -4
- package/server/tunnel/TunnelAgent.ts +9 -1
- package/server/webRouter.ts +17 -2
- package/service/agent.service.ts +21 -1
- package/service/endpointCacheKeys.ts +35 -0
- package/service/index.ts +1 -0
- package/service/injectInfo.ts +2 -2
- package/service/predefinedAdaptor/anthropicLlm.ts +37 -17
- package/service/predefinedAdaptor/database.adaptor.ts +15 -0
- package/service/predefinedAdaptor/documentTransfer.ts +50 -0
- package/service/predefinedAdaptor/endpointCacheControl.adaptor.ts +19 -0
- package/service/predefinedAdaptor/index.ts +1 -0
- package/service/predefinedAdaptor/llm.adaptor.ts +7 -0
- package/service/predefinedAdaptor/openaiDialect.ts +53 -17
- package/service/predefinedAdaptor/openaiLlm.ts +1 -1
- package/service/predefinedAdaptor/sql/MigrationTables.ts +90 -0
- package/service/predefinedAdaptor/sql/RawDocumentTable.ts +200 -0
- package/service/predefinedAdaptor/sql/SqlDocumentStore.ts +6 -2
- package/service/predefinedAdaptor/sql/SqliteInsightSession.ts +4 -1
- package/service/predefinedAdaptor/sql/dialect/postgres.ts +0 -1
- package/service/predefinedAdaptor/sql/dialect/sqlite.ts +0 -1
- package/service/predefinedAdaptor/sql/types.ts +3 -1
- package/service/predefinedAdaptor/storage.adaptor.ts +19 -0
- package/signal/endpointCache.ts +23 -7
- package/signal/endpointErrorContract.ts +37 -0
- package/signal/endpointIdempotency.ts +5 -3
- package/signal/endpointInfo.ts +4 -1
- package/signal/endpointRateLimit.ts +6 -2
- package/signal/exception.ts +4 -1
- package/signal/index.ts +1 -0
- package/signal/mcp/McpDocument.ts +16 -1
- package/signal/mcp/McpUriTemplate.ts +1 -1
- package/signal/middlewareChain.ts +105 -0
- package/signal/openapi/openapi.ts +46 -2
- package/signal/serializer/fetch.serializer.ts +1 -0
- package/signal/signalContext.ts +110 -82
- package/signal/slice.ts +2 -2
- package/signal/trace.ts +8 -2
- package/signal/types.ts +13 -0
- package/store/action.ts +102 -42
- package/store/agent/ScreenImage.ts +166 -0
- package/store/agent/ScreenReader.ts +2 -1
- package/store/agent/StoreSurfaceSource.ts +41 -2
- package/store/agent/index.ts +2 -1
- package/store/databaseStateNames.ts +1 -0
- package/store/formUploadOf.ts +41 -0
- package/store/index.ts +1 -0
- package/store/liveInsert.ts +2 -2
- package/store/state.ts +9 -1
- package/store/stateBuilder.ts +8 -2
- package/store/storeInstance.ts +12 -3
- package/store/withSelector.ts +2 -2
- package/types/client/href.d.ts +11 -0
- package/types/client/index.d.ts +1 -0
- package/types/client/rscNavigation.d.ts +1 -1
- package/types/client/transitionTypes.d.ts +0 -2
- package/types/client/translator.d.ts +2 -0
- package/types/common/codeAgentWire.d.ts +7 -0
- package/types/common/index.d.ts +1 -0
- package/types/common/trimSlashes.d.ts +2 -0
- package/types/common/types.d.ts +1 -1
- package/types/constant/fieldInfo.d.ts +2 -2
- package/types/constant/fieldPlan.d.ts +33 -0
- package/types/constant/index.d.ts +3 -0
- package/types/constant/inputParser.d.ts +11 -0
- package/types/constant/invalidInputError.d.ts +8 -0
- package/types/constant/types.d.ts +1 -1
- package/types/dictionary/base.dictionary.d.ts +1 -1
- package/types/dictionary/dictionary.d.ts +3 -3
- package/types/dictionary/trans.d.ts +3 -0
- package/types/document/database.d.ts +2 -3
- package/types/document/documentSchema.d.ts +1 -1
- package/types/document/noDocumentError.d.ts +6 -1
- package/types/fetch/fetchType/appliedReturn.type.d.ts +3 -1
- package/types/fetch/types.d.ts +2 -2
- package/types/index.d.ts +5 -0
- package/types/server/akanServer.d.ts +1 -1
- package/types/server/artifact/routeSeedMatcher.d.ts +8 -0
- package/types/server/di/diLifecycle.d.ts +1 -0
- package/types/server/index.d.ts +1 -0
- package/types/server/machine/machineAccount.d.ts +1 -0
- package/types/server/machine/machineToken.d.ts +9 -0
- package/types/server/migration/docPath.d.ts +20 -0
- package/types/server/migration/index.d.ts +15 -0
- package/types/server/migration/migrationChangeLog.d.ts +27 -0
- package/types/server/migration/migrationChanges.d.ts +27 -0
- package/types/server/migration/migrationCommand.d.ts +33 -0
- package/types/server/migration/migrationContext.d.ts +28 -0
- package/types/server/migration/migrationDefinition.d.ts +42 -0
- package/types/server/migration/migrationError.d.ts +15 -0
- package/types/server/migration/migrationLedger.d.ts +51 -0
- package/types/server/migration/migrationLock.d.ts +34 -0
- package/types/server/migration/migrationParams.d.ts +41 -0
- package/types/server/migration/migrationPulse.d.ts +15 -0
- package/types/server/migration/migrationRunner.d.ts +93 -0
- package/types/server/migration/migrationScope.d.ts +42 -0
- package/types/server/migration/migrationTable.d.ts +52 -0
- package/types/server/migration/migrations.d.ts +12 -0
- package/types/server/migration.d.ts +1 -0
- package/types/server/ops/opsCommand.d.ts +10 -1
- package/types/server/routeState.d.ts +1 -4
- package/types/server/rscNavigationState.d.ts +2 -3
- package/types/server/rscScrollRestoration.d.ts +14 -0
- package/types/server/sitemap.d.ts +1 -1
- package/types/service/agent.service.d.ts +2 -0
- package/types/service/endpointCacheKeys.d.ts +8 -0
- package/types/service/index.d.ts +1 -0
- package/types/service/injectInfo.d.ts +2 -2
- package/types/service/predefinedAdaptor/anthropicLlm.d.ts +9 -4
- package/types/service/predefinedAdaptor/database.adaptor.d.ts +8 -1
- package/types/service/predefinedAdaptor/documentTransfer.d.ts +1 -0
- package/types/service/predefinedAdaptor/endpointCacheControl.adaptor.d.ts +9 -0
- package/types/service/predefinedAdaptor/index.d.ts +1 -0
- package/types/service/predefinedAdaptor/llm.adaptor.d.ts +7 -0
- package/types/service/predefinedAdaptor/openaiDialect.d.ts +20 -5
- package/types/service/predefinedAdaptor/sql/MigrationTables.d.ts +17 -0
- package/types/service/predefinedAdaptor/sql/RawDocumentTable.d.ts +38 -0
- package/types/service/predefinedAdaptor/sql/types.d.ts +1 -0
- package/types/service/predefinedAdaptor/storage.adaptor.d.ts +8 -0
- package/types/signal/endpointCache.d.ts +2 -0
- package/types/signal/endpointErrorContract.d.ts +10 -0
- package/types/signal/endpointIdempotency.d.ts +1 -0
- package/types/signal/endpointRateLimit.d.ts +1 -1
- package/types/signal/exception.d.ts +4 -1
- package/types/signal/index.d.ts +1 -0
- package/types/signal/middlewareChain.d.ts +11 -0
- package/types/signal/signalContext.d.ts +2 -6
- package/types/signal/slice.d.ts +1 -1
- package/types/signal/trace.d.ts +1 -1
- package/types/signal/types.d.ts +12 -0
- package/types/store/action.d.ts +2 -0
- package/types/store/agent/ScreenImage.d.ts +27 -0
- package/types/store/agent/StoreSurfaceSource.d.ts +1 -1
- package/types/store/agent/index.d.ts +2 -1
- package/types/store/databaseStateNames.d.ts +1 -0
- package/types/store/formUploadOf.d.ts +9 -0
- package/types/store/index.d.ts +1 -0
- package/types/store/state.d.ts +7 -1
- package/types/store/stateBuilder.d.ts +2 -1
- package/types/store/storeInstance.d.ts +2 -2
- package/types/store/withSelector.d.ts +1 -1
- package/types/ui/Agent/Composer.d.ts +2 -0
- package/types/ui/Agent/Menu.d.ts +2 -0
- package/types/ui/CsrImage.d.ts +2 -0
- package/types/ui/Data/CardList.d.ts +1 -1
- package/types/ui/Data/Dashboard.d.ts +1 -1
- package/types/ui/Data/Insight.d.ts +1 -1
- package/types/ui/Data/Item.d.ts +2 -2
- package/types/ui/Data/ListContainer.d.ts +1 -1
- package/types/ui/Data/Pagination.d.ts +1 -1
- package/types/ui/Data/TableList.d.ts +1 -1
- package/types/ui/Field/Relation.d.ts +13 -10
- package/types/ui/Field/index.d.ts +4 -4
- package/types/ui/Field/useRelationOptions.d.ts +3 -1
- package/types/ui/Field/useRelationWindow.d.ts +14 -0
- package/types/ui/Image.d.ts +3 -1
- package/types/ui/Load/View.d.ts +1 -1
- package/types/ui/Pagination.d.ts +1 -1
- package/types/ui/Signal/Arg.d.ts +1 -1
- package/types/ui/System/CSR.d.ts +1 -1
- package/types/ui/System/Client.d.ts +1 -1
- package/types/ui/System/SSR.d.ts +1 -1
- package/types/vendor/@akanjs/native/packages/core/src/plugin.d.ts +3 -3
- package/types/vendor/@akanjs/native/packages/desktop/src/page-veto.d.ts +1 -1
- package/types/vendor/@akanjs/native/packages/desktop/src/plugin.d.ts +1 -1
- package/types/vendor/@akanjs/native/plugins/auth-session/src/desktop.d.ts +2 -2
- package/types/vendor/@akanjs/native/plugins/auth-session/src/index.d.ts +1 -1
- package/types/vendor/@akanjs/native/plugins/autostart/src/desktop.d.ts +2 -2
- package/types/vendor/@akanjs/native/plugins/autostart/src/index.d.ts +1 -1
- package/types/vendor/@akanjs/native/plugins/badge/src/desktop.d.ts +1 -1
- package/types/vendor/@akanjs/native/plugins/badge/src/index.d.ts +1 -1
- package/types/vendor/@akanjs/native/plugins/badge/src/web.d.ts +1 -1
- package/types/vendor/@akanjs/native/plugins/biometric/src/index.d.ts +1 -1
- package/types/vendor/@akanjs/native/plugins/camera/src/desktop.d.ts +1 -1
- package/types/vendor/@akanjs/native/plugins/camera/src/index.d.ts +1 -1
- package/types/vendor/@akanjs/native/plugins/camera/src/web.d.ts +1 -1
- package/types/vendor/@akanjs/native/plugins/clipboard/src/desktop.d.ts +2 -2
- package/types/vendor/@akanjs/native/plugins/clipboard/src/index.d.ts +1 -1
- package/types/vendor/@akanjs/native/plugins/clipboard/src/web.d.ts +1 -1
- package/types/vendor/@akanjs/native/plugins/contacts/src/index.d.ts +1 -1
- package/types/vendor/@akanjs/native/plugins/device/src/desktop.d.ts +2 -2
- package/types/vendor/@akanjs/native/plugins/device/src/index.d.ts +1 -1
- package/types/vendor/@akanjs/native/plugins/device/src/web.d.ts +1 -1
- package/types/vendor/@akanjs/native/plugins/dialog/src/desktop.d.ts +1 -1
- package/types/vendor/@akanjs/native/plugins/dialog/src/index.d.ts +1 -1
- package/types/vendor/@akanjs/native/plugins/dialog/src/web.d.ts +1 -1
- package/types/vendor/@akanjs/native/plugins/dock/src/desktop.d.ts +1 -1
- package/types/vendor/@akanjs/native/plugins/dock/src/index.d.ts +1 -1
- package/types/vendor/@akanjs/native/plugins/file-picker/src/desktop.d.ts +2 -2
- package/types/vendor/@akanjs/native/plugins/file-picker/src/web.d.ts +1 -1
- package/types/vendor/@akanjs/native/plugins/filesystem/src/desktop.d.ts +2 -2
- package/types/vendor/@akanjs/native/plugins/filesystem/src/web.d.ts +1 -1
- package/types/vendor/@akanjs/native/plugins/haptics/src/index.d.ts +1 -1
- package/types/vendor/@akanjs/native/plugins/haptics/src/web.d.ts +1 -1
- package/types/vendor/@akanjs/native/plugins/http/src/desktop.d.ts +2 -2
- package/types/vendor/@akanjs/native/plugins/http/src/web.d.ts +1 -1
- package/types/vendor/@akanjs/native/plugins/keep-awake/src/desktop.d.ts +2 -2
- package/types/vendor/@akanjs/native/plugins/keep-awake/src/index.d.ts +1 -1
- package/types/vendor/@akanjs/native/plugins/keep-awake/src/web.d.ts +1 -1
- package/types/vendor/@akanjs/native/plugins/opener/src/desktop.d.ts +2 -2
- package/types/vendor/@akanjs/native/plugins/opener/src/index.d.ts +1 -1
- package/types/vendor/@akanjs/native/plugins/opener/src/web.d.ts +1 -1
- package/types/vendor/@akanjs/native/plugins/preferences/src/desktop.d.ts +1 -1
- package/types/vendor/@akanjs/native/plugins/preferences/src/index.d.ts +1 -1
- package/types/vendor/@akanjs/native/plugins/preferences/src/web.d.ts +1 -1
- package/types/vendor/@akanjs/native/plugins/secure-storage/src/desktop.d.ts +2 -2
- package/types/vendor/@akanjs/native/plugins/secure-storage/src/index.d.ts +1 -1
- package/types/vendor/@akanjs/native/plugins/share/src/index.d.ts +1 -1
- package/types/vendor/@akanjs/native/plugins/share/src/web.d.ts +1 -1
- package/types/vendor/@akanjs/native/plugins/single-instance/src/desktop.d.ts +2 -2
- package/types/vendor/@akanjs/native/plugins/splash-screen/src/index.d.ts +1 -1
- package/types/vendor/@akanjs/native/plugins/splash-screen/src/web.d.ts +1 -1
- package/types/vendor/@akanjs/native/plugins/sqlite/src/desktop.d.ts +2 -2
- package/types/vendor/@akanjs/native/plugins/toast/src/index.d.ts +1 -1
- package/types/vendor/@akanjs/native/plugins/toast/src/web.d.ts +1 -1
- package/types/vendor/@akanjs/native/plugins/window/src/index.d.ts +2 -2
- package/types/vendor/@akanjs/native/plugins/window-state/src/desktop.d.ts +2 -2
- package/types/vendor/@akanjs/native/plugins/window-state/src/index.d.ts +1 -1
- package/types/vendor/use-agentic/Compaction.d.ts +3 -1
- package/types/vendor/use-agentic/Reference.d.ts +9 -1
- package/types/vendor/use-agentic/ToolOutput.d.ts +6 -1
- package/types/vendor/use-agentic/ToolReturn.d.ts +18 -0
- package/types/vendor/use-agentic/Transcript.d.ts +7 -0
- package/types/vendor/use-agentic/index.d.ts +1 -0
- package/types/vendor/use-agentic/types.d.ts +6 -0
- package/types/webkit/useDebounce.d.ts +1 -1
- package/ui/Agent/Chat.tsx +1 -0
- package/ui/Agent/Composer.tsx +2 -0
- package/ui/Agent/Menu.tsx +5 -1
- package/ui/Agent/agentSessionOf.ts +15 -8
- package/ui/Agent/mentionDraft.ts +2 -3
- package/ui/Agent/useReferenceMenu.ts +1 -0
- package/ui/Agent/useSlashMenu.ts +1 -0
- package/ui/Clipboard.tsx +1 -0
- package/ui/CsrImage.tsx +3 -1
- package/ui/Data/CardList.tsx +1 -1
- package/ui/Data/Dashboard.tsx +1 -1
- package/ui/Data/Insight.tsx +1 -1
- package/ui/Data/Item.tsx +5 -2
- package/ui/Data/ListContainer.tsx +2 -2
- package/ui/Data/Pagination.tsx +1 -1
- package/ui/Data/TableList.tsx +1 -1
- package/ui/DraggableList.tsx +1 -0
- package/ui/Field/Collection.tsx +4 -0
- package/ui/Field/Relation.tsx +33 -14
- package/ui/Field/useRelationOptions.ts +33 -5
- package/ui/Field/useRelationWindow.ts +50 -0
- package/ui/Image.tsx +4 -4
- package/ui/Input.tsx +1 -0
- package/ui/Layout/Sider.tsx +1 -1
- package/ui/Link/Anchor.tsx +7 -5
- package/ui/Load/Units.tsx +17 -7
- package/ui/Load/View.tsx +2 -2
- package/ui/Model/Edit.tsx +1 -1
- package/ui/Model/EditModal.tsx +11 -5
- package/ui/Model/New.tsx +1 -1
- package/ui/Model/SureToRemove.tsx +1 -0
- package/ui/Model/ViewEditModal.tsx +17 -5
- package/ui/Model/ViewModal.tsx +2 -2
- package/ui/ObjectId.tsx +5 -1
- package/ui/Pagination.tsx +1 -1
- package/ui/Signal/Arg.tsx +1 -3
- package/ui/Signal/Doc.tsx +5 -1
- package/ui/System/CSR.tsx +1 -1
- package/ui/System/Client.tsx +1 -1
- package/ui/System/Reconnect.tsx +1 -1
- package/ui/System/SSR.tsx +3 -36
- package/ui/ToggleSelect.tsx +2 -0
- package/ui/styles.css +10 -103
- package/vendor/@akanjs/native/native/ios/Sources/AkanNativeBridge.swift +3 -2
- package/vendor/@akanjs/native/packages/cli/src/lib/addons.ts +4 -1
- package/vendor/@akanjs/native/packages/cli/src/lib/apk.ts +2 -2
- package/vendor/@akanjs/native/packages/cli/src/lib/codegen.ts +1 -1
- package/vendor/@akanjs/native/packages/cli/src/lib/csp.ts +4 -3
- package/vendor/@akanjs/native/packages/cli/src/lib/googleservices.ts +7 -1
- package/vendor/@akanjs/native/packages/cli/src/lib/hmr.ts +8 -5
- package/vendor/@akanjs/native/packages/cli/src/lib/html.ts +18 -3
- package/vendor/@akanjs/native/packages/cli/src/lib/manifestmerge.ts +1 -1
- package/vendor/@akanjs/native/packages/cli/src/lib/plist.ts +2 -4
- package/vendor/@akanjs/native/packages/cli/src/lib/toolchains.ts +1 -1
- package/vendor/@akanjs/native/packages/cli/src/lib/updates.ts +10 -4
- package/vendor/@akanjs/native/packages/cli/src/lib/xml.ts +4 -3
- package/vendor/@akanjs/native/packages/cli/src/platforms/android.ts +3 -3
- package/vendor/@akanjs/native/packages/core/src/kernel.ts +3 -3
- package/vendor/@akanjs/native/packages/core/src/live.ts +1 -0
- package/vendor/@akanjs/native/packages/core/src/plugin.ts +5 -3
- package/vendor/@akanjs/native/packages/desktop/src/host.ts +13 -5
- package/vendor/@akanjs/native/packages/desktop/src/lifecycle.ts +1 -0
- package/vendor/@akanjs/native/packages/desktop/src/page-veto.ts +1 -1
- package/vendor/@akanjs/native/packages/desktop/src/plugin.ts +4 -1
- package/vendor/@akanjs/native/plugins/camera/src/web.ts +3 -3
- package/vendor/@akanjs/native/plugins/file-picker/src/index.ts +4 -1
- package/vendor/@akanjs/native/plugins/local-notifications/src/web.ts +1 -1
- package/vendor/@akanjs/native/plugins/secure-storage/src/desktop.ts +1 -1
- package/vendor/@akanjs/native/plugins/share/src/options.ts +1 -1
- package/vendor/@akanjs/native/plugins/single-instance/src/desktop.ts +3 -2
- package/vendor/@akanjs/native/plugins/updates/src/desktop.ts +4 -1
- package/vendor/@akanjs/native/plugins/window/src/index.ts +2 -2
- package/vendor/use-agentic/Compaction.ts +38 -7
- package/vendor/use-agentic/Reference.ts +16 -9
- package/vendor/use-agentic/ToolOutput.ts +25 -3
- package/vendor/use-agentic/ToolReturn.ts +32 -0
- package/vendor/use-agentic/ToolRunner.ts +15 -13
- package/vendor/use-agentic/Transcript.ts +38 -2
- package/vendor/use-agentic/WIRE.md +20 -0
- package/vendor/use-agentic/index.ts +1 -0
- package/vendor/use-agentic/types.ts +6 -0
- package/webkit/McpAppAgent.ts +12 -2
- package/webkit/RenderLayer.tsx +23 -4
- package/webkit/useDebounce.ts +23 -14
- package/client/viewTransition.ts +0 -10
- package/types/client/viewTransition.d.ts +0 -3
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type AkanSqlClient,
|
|
3
|
+
type QueryLeafOps,
|
|
4
|
+
REF_NAME_RE,
|
|
5
|
+
RESERVED_RE,
|
|
6
|
+
type SqlDialect,
|
|
7
|
+
type SqlFrag,
|
|
8
|
+
type SqliteDocumentRow,
|
|
9
|
+
type TransferRow,
|
|
10
|
+
} from "./types";
|
|
11
|
+
import { assertStorableJson, BASE_COLUMN_LEAF, decodeDateValue, isPlainObject, jsonStr, quoteIdent } from "./values";
|
|
12
|
+
|
|
13
|
+
export interface RawFilterOperators {
|
|
14
|
+
$ne?: unknown;
|
|
15
|
+
$in?: unknown[];
|
|
16
|
+
$nin?: unknown[];
|
|
17
|
+
$exists?: boolean;
|
|
18
|
+
$gt?: unknown;
|
|
19
|
+
$gte?: unknown;
|
|
20
|
+
$lt?: unknown;
|
|
21
|
+
$lte?: unknown;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Dot paths into `_doc`, or a base column, each to a value or to `RawFilterOperators` over it. */
|
|
25
|
+
export type RawFilter = Record<string, unknown>;
|
|
26
|
+
|
|
27
|
+
export interface RawFindOptions {
|
|
28
|
+
/** Only rows whose id sorts after this one: rows come in id order, so a page resumes where the last one ended. */
|
|
29
|
+
after?: string;
|
|
30
|
+
limit?: number;
|
|
31
|
+
/** Removed rows are read too unless this is `false`. */
|
|
32
|
+
withRemoved?: boolean;
|
|
33
|
+
/** Holds the rows read until the transaction ends; SQLite's one writer already does. */
|
|
34
|
+
lock?: boolean;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* A table read and written as stored rows, beneath its model: a field the model no longer declares, or a table no model
|
|
39
|
+
* owns any more, stays reachable. Writes run no hook and derive nothing.
|
|
40
|
+
*/
|
|
41
|
+
export class RawDocumentTable {
|
|
42
|
+
static readonly #baseColumns = new Set(["id", "createdAt", "updatedAt", "removedAt"]);
|
|
43
|
+
static readonly #columns = `"id", "createdAt", "updatedAt", "removedAt", "_doc"`;
|
|
44
|
+
readonly name: string;
|
|
45
|
+
readonly #connection: () => AkanSqlClient;
|
|
46
|
+
readonly #dialect: SqlDialect;
|
|
47
|
+
#exists: boolean | undefined;
|
|
48
|
+
|
|
49
|
+
constructor(name: string, connection: () => AkanSqlClient, dialect: SqlDialect) {
|
|
50
|
+
if (!REF_NAME_RE.test(name) || RESERVED_RE.test(name)) throw new Error(`"${name}" names no model table`);
|
|
51
|
+
this.name = name;
|
|
52
|
+
this.#connection = connection;
|
|
53
|
+
this.#dialect = dialect;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async exists() {
|
|
57
|
+
if (this.#exists) return true;
|
|
58
|
+
const isPostgres = this.#dialect.name === "postgres";
|
|
59
|
+
const row = await this.#connection()
|
|
60
|
+
.prepare(
|
|
61
|
+
isPostgres
|
|
62
|
+
? `SELECT to_regclass(?) IS NOT NULL AS "found"`
|
|
63
|
+
: `SELECT COUNT(*) AS "found" FROM sqlite_master WHERE "type" = 'table' AND "name" = ?`,
|
|
64
|
+
)
|
|
65
|
+
.get<{ found: unknown }>(isPostgres ? quoteIdent(this.name) : this.name);
|
|
66
|
+
this.#exists = !!Number(row?.found ?? 0);
|
|
67
|
+
return this.#exists;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async find(filter?: RawFilter, options: RawFindOptions = {}) {
|
|
71
|
+
if (!(await this.exists())) return [];
|
|
72
|
+
const where = this.#where(filter, options);
|
|
73
|
+
const limit = options.limit === undefined ? "" : ` LIMIT ${Math.max(0, Math.trunc(options.limit))}`;
|
|
74
|
+
const rows = await this.#connection()
|
|
75
|
+
.prepare(
|
|
76
|
+
`SELECT ${RawDocumentTable.#columns} FROM ${quoteIdent(this.name)} WHERE ${where.sql} ORDER BY "id"${limit}${this.#forUpdate(options.lock)}`,
|
|
77
|
+
)
|
|
78
|
+
.all<SqliteDocumentRow>(...where.params);
|
|
79
|
+
return rows.map(RawDocumentTable.#toRow);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
async count(filter?: RawFilter, options: Omit<RawFindOptions, "limit"> = {}) {
|
|
83
|
+
if (!(await this.exists())) return 0;
|
|
84
|
+
const where = this.#where(filter, options);
|
|
85
|
+
const row = await this.#connection()
|
|
86
|
+
.prepare(`SELECT COUNT(*) AS "count" FROM ${quoteIdent(this.name)} WHERE ${where.sql}`)
|
|
87
|
+
.get<{ count: unknown }>(...where.params);
|
|
88
|
+
return Number(row?.count ?? 0);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
async get(id: string, { lock = false }: Pick<RawFindOptions, "lock"> = {}) {
|
|
92
|
+
if (!(await this.exists())) return null;
|
|
93
|
+
const row = await this.#connection()
|
|
94
|
+
.prepare(
|
|
95
|
+
`SELECT ${RawDocumentTable.#columns} FROM ${quoteIdent(this.name)} WHERE "id" = ?${this.#forUpdate(lock)}`,
|
|
96
|
+
)
|
|
97
|
+
.get<SqliteDocumentRow>(id);
|
|
98
|
+
return row ? RawDocumentTable.#toRow(row) : null;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
async insert(row: TransferRow) {
|
|
102
|
+
await this.#assertExists();
|
|
103
|
+
await this.#connection()
|
|
104
|
+
.prepare(
|
|
105
|
+
`INSERT INTO ${quoteIdent(this.name)} (${RawDocumentTable.#columns}) VALUES (?, ?, ?, ?, ${this.#dialect.docValuePlaceholder()})`,
|
|
106
|
+
)
|
|
107
|
+
.run(row.id, row.createdAt, row.updatedAt, row.removedAt, this.#json(row._doc));
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
async write(id: string, { _doc, removedAt }: Pick<TransferRow, "_doc" | "removedAt">) {
|
|
111
|
+
await this.#assertExists();
|
|
112
|
+
const result = await this.#connection()
|
|
113
|
+
.prepare(
|
|
114
|
+
`UPDATE ${quoteIdent(this.name)} SET "_doc" = ${this.#dialect.docValuePlaceholder()}, "removedAt" = ? WHERE "id" = ?`,
|
|
115
|
+
)
|
|
116
|
+
.run(this.#json(_doc), removedAt, id);
|
|
117
|
+
return this.#dialect.affectedRows(result) > 0;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
async delete(id: string) {
|
|
121
|
+
await this.#assertExists();
|
|
122
|
+
const result = await this.#connection()
|
|
123
|
+
.prepare(`DELETE FROM ${quoteIdent(this.name)} WHERE "id" = ?`)
|
|
124
|
+
.run(id);
|
|
125
|
+
return this.#dialect.affectedRows(result) > 0;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
#forUpdate(lock = false) {
|
|
129
|
+
return lock && this.#dialect.name === "postgres" ? " FOR UPDATE" : "";
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
async #assertExists() {
|
|
133
|
+
if (!(await this.exists())) throw new Error(`Table "${this.name}" does not exist`);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
#json(doc: Record<string, unknown>) {
|
|
137
|
+
return assertStorableJson(jsonStr(doc), this.name);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
#where(filter: RawFilter = {}, { after, withRemoved = true }: RawFindOptions): SqlFrag {
|
|
141
|
+
const parts = Object.entries(filter).flatMap(([path, value]) => this.#leaves(path, value));
|
|
142
|
+
if (after !== undefined) parts.push({ sql: `"id" > ?`, params: [after] });
|
|
143
|
+
if (!withRemoved) parts.push({ sql: `"removedAt" IS NULL`, params: [] });
|
|
144
|
+
if (!parts.length) return { sql: "1 = 1", params: [] };
|
|
145
|
+
return { sql: parts.map((part) => `(${part.sql})`).join(" AND "), params: parts.flatMap((part) => part.params) };
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
#leaves(path: string, value: unknown): SqlFrag[] {
|
|
149
|
+
RawDocumentTable.#assertPath(path);
|
|
150
|
+
if (value === undefined) throw new Error(`Filter value for "${path}" is undefined`);
|
|
151
|
+
const leaf: QueryLeafOps = RawDocumentTable.#baseColumns.has(path) ? BASE_COLUMN_LEAF : this.#dialect;
|
|
152
|
+
if (!isPlainObject(value)) {
|
|
153
|
+
if (Array.isArray(value))
|
|
154
|
+
throw new Error(`Filter "${path}" compares an array; match it with $in or by its items`);
|
|
155
|
+
return [leaf.eq(path, value)];
|
|
156
|
+
}
|
|
157
|
+
const operators = Object.entries(value);
|
|
158
|
+
if (!operators.length || operators.some(([key]) => !key.startsWith("$")))
|
|
159
|
+
throw new Error(`Filter "${path}" compares an object; match its leaves by their own paths`);
|
|
160
|
+
return operators.map(([operator, operand]) => {
|
|
161
|
+
switch (operator) {
|
|
162
|
+
case "$ne":
|
|
163
|
+
return leaf.ne(path, operand);
|
|
164
|
+
case "$in":
|
|
165
|
+
case "$nin": {
|
|
166
|
+
if (!Array.isArray(operand)) throw new Error(`Filter "${path}" ${operator} takes an array`);
|
|
167
|
+
if (!operand.length) return { sql: operator === "$in" ? "0 = 1" : "1 = 1", params: [] };
|
|
168
|
+
return operator === "$in" ? leaf.inList(path, operand) : leaf.notInList(path, operand);
|
|
169
|
+
}
|
|
170
|
+
case "$exists":
|
|
171
|
+
return operand ? leaf.exists(path) : leaf.missing(path);
|
|
172
|
+
case "$gt":
|
|
173
|
+
case "$gte":
|
|
174
|
+
case "$lt":
|
|
175
|
+
case "$lte":
|
|
176
|
+
return leaf.compare(path, operator.slice(1) as "gt" | "gte" | "lt" | "lte", operand);
|
|
177
|
+
default:
|
|
178
|
+
throw new Error(`Unknown filter operator "${operator}" on "${path}"`);
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
static #assertPath(path: string) {
|
|
184
|
+
const segments = path.split(".");
|
|
185
|
+
|
|
186
|
+
if (segments.some((segment) => !/^[\w$-]+$/.test(segment) || /^-?\d+$/.test(segment)))
|
|
187
|
+
throw new Error(`Filter path "${path}" must be dot-separated names, without array indexes`);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
static #toRow(row: SqliteDocumentRow): TransferRow {
|
|
191
|
+
const epoch = (value: unknown) => decodeDateValue(value)?.valueOf() ?? null;
|
|
192
|
+
return {
|
|
193
|
+
id: row.id,
|
|
194
|
+
createdAt: epoch(row.createdAt) ?? 0,
|
|
195
|
+
updatedAt: epoch(row.updatedAt) ?? 0,
|
|
196
|
+
removedAt: epoch(row.removedAt),
|
|
197
|
+
_doc: (typeof row._doc === "string" ? JSON.parse(row._doc) : row._doc) as Record<string, unknown>,
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
}
|
|
@@ -586,11 +586,11 @@ export class SqlDocumentStore {
|
|
|
586
586
|
const values = Array.isArray(doc[key]) ? doc[key] : [doc[key]];
|
|
587
587
|
const fieldEnum = props.enum as { has: (value: unknown) => boolean } | undefined;
|
|
588
588
|
const invalidValue = fieldEnum ? values.find((value: unknown) => !fieldEnum.has(value)) : undefined;
|
|
589
|
-
if (invalidValue !== undefined) throw
|
|
589
|
+
if (invalidValue !== undefined) throw SqlDocumentStore.#invalidField(key, this.table);
|
|
590
590
|
}
|
|
591
591
|
const validate = props.validate as ((value: unknown, doc: MutableDocumentRecord) => boolean) | undefined;
|
|
592
592
|
if (validate && doc[key] !== undefined && doc[key] !== null && !validate(doc[key], doc)) {
|
|
593
|
-
throw
|
|
593
|
+
throw SqlDocumentStore.#invalidField(key, this.table);
|
|
594
594
|
}
|
|
595
595
|
}
|
|
596
596
|
return { ...data, ...doc };
|
|
@@ -868,6 +868,10 @@ export class SqlDocumentStore {
|
|
|
868
868
|
);
|
|
869
869
|
}
|
|
870
870
|
|
|
871
|
+
static #invalidField(key: string, table: string) {
|
|
872
|
+
return new Err.BadRequest("base.error.invalidField", { field: key }, { details: { model: table } });
|
|
873
|
+
}
|
|
874
|
+
|
|
871
875
|
#assertImmutableUnchanged(prepared: DocumentRecord, originalData: DocumentRecord) {
|
|
872
876
|
this.#immutableKeys ??= Object.entries(this.database.doc[FIELD_META] as unknown as FieldMap)
|
|
873
877
|
.filter(([, fieldMeta]) => fieldMeta.getProps().immutable)
|
|
@@ -3,6 +3,7 @@ import { randomBytes } from "node:crypto";
|
|
|
3
3
|
import { mkdtemp, rm, writeFile } from "node:fs/promises";
|
|
4
4
|
import { tmpdir } from "node:os";
|
|
5
5
|
import path from "node:path";
|
|
6
|
+
import { MigrationTables } from "./MigrationTables";
|
|
6
7
|
import type { InsightSession } from "./types";
|
|
7
8
|
import { quoteIdent } from "./values";
|
|
8
9
|
|
|
@@ -14,6 +15,8 @@ import { quoteIdent } from "./values";
|
|
|
14
15
|
export class SqliteInsightSession implements InsightSession {
|
|
15
16
|
|
|
16
17
|
static readonly #unnameable = ["sqlite_dbpage", "sqlite_stat3", "sqlite_stat4"];
|
|
18
|
+
|
|
19
|
+
static readonly #ledger = [MigrationTables.run, MigrationTables.change];
|
|
17
20
|
static readonly #documentColumn = "_doc";
|
|
18
21
|
|
|
19
22
|
readonly #db: Database;
|
|
@@ -22,7 +25,7 @@ export class SqliteInsightSession implements InsightSession {
|
|
|
22
25
|
|
|
23
26
|
private constructor(db: Database, source: string, cleanup: () => Promise<void>) {
|
|
24
27
|
this.#db = db;
|
|
25
|
-
this.#hidden = [source, ...SqliteInsightSession.#unnameable];
|
|
28
|
+
this.#hidden = [source, ...SqliteInsightSession.#unnameable, ...SqliteInsightSession.#ledger];
|
|
26
29
|
this.#cleanup = cleanup;
|
|
27
30
|
}
|
|
28
31
|
|
|
@@ -177,7 +177,6 @@ export class PostgresDialect implements SqlDialect {
|
|
|
177
177
|
const textAt = `(${this.docColumn()}) #>> ${p}`;
|
|
178
178
|
const arr = `COALESCE(NULLIF(${jsonbAt}, 'null'::jsonb), '[]'::jsonb)`;
|
|
179
179
|
const set = (next: string) => `${PostgresDialect.#setFunction}(${acc}, ${p}, ${next})`;
|
|
180
|
-
|
|
181
180
|
switch (op) {
|
|
182
181
|
case "set":
|
|
183
182
|
return { sql: set("?::text::jsonb"), params: [jsonStr(value)] };
|
|
@@ -98,7 +98,6 @@ export class SqliteDialect implements SqlDialect {
|
|
|
98
98
|
|
|
99
99
|
const cur = `json_extract(${this.docColumn()}, ${p})`;
|
|
100
100
|
const arr = `COALESCE(${cur}, json('[]'))`;
|
|
101
|
-
|
|
102
101
|
switch (op) {
|
|
103
102
|
case "set":
|
|
104
103
|
return { sql: `json_set(${acc}, ${p}, json(?))`, params: [jsonStr(value)] };
|
|
@@ -115,6 +115,7 @@ export interface InsightSession {
|
|
|
115
115
|
}
|
|
116
116
|
|
|
117
117
|
export interface DatabaseAdaptor {
|
|
118
|
+
readonly dialect?: SqlDialect;
|
|
118
119
|
getConnection(): AkanSqlClient;
|
|
119
120
|
getStore(constant: ConstantModel, database: DatabaseModel, schema: DocumentSchema): DocumentStore;
|
|
120
121
|
/** Every model table this process has opened a store for. */
|
|
@@ -144,7 +145,8 @@ export interface TransactionContext {
|
|
|
144
145
|
}
|
|
145
146
|
|
|
146
147
|
export const BASE_COLUMNS = baseDocumentColumns;
|
|
147
|
-
export const RESERVED_RE =
|
|
148
|
+
export const RESERVED_RE =
|
|
149
|
+
/^sqlite_|^_akan_meta$|^_akan_migration_run$|^_akan_migration_change$|^search_doc$|^search_fts$/i;
|
|
148
150
|
export const REF_NAME_RE = /^[A-Za-z][A-Za-z0-9_]*$/;
|
|
149
151
|
export const toSafeRefName = (value: string) => value.replace(/[^A-Za-z0-9_]+/g, "_").replace(/_+/g, "_");
|
|
150
152
|
export type DocumentRecord = Record<string, unknown>;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { renameSync } from "node:fs";
|
|
2
|
+
import { isAbsolute, parse, relative, resolve, sep } from "node:path";
|
|
2
3
|
import { getApiPrefix, getEnv } from "akanjs/base";
|
|
3
4
|
import { adapt } from "../adapt";
|
|
4
5
|
|
|
@@ -50,6 +51,13 @@ export interface StorageAdaptor {
|
|
|
50
51
|
copyData(request: CopyRequest): Promise<string>;
|
|
51
52
|
deleteData(url: string): Promise<boolean>;
|
|
52
53
|
deleteDataByPath(path: string): Promise<boolean>;
|
|
54
|
+
/**
|
|
55
|
+
* Absolute path of a stored key on this machine's disk. `null` means the key is refused (rooted, NUL, or outside
|
|
56
|
+
* its root): reject the request, never fall back to `readData` for it. The method is absent on an adaptor that
|
|
57
|
+
* keeps no local files. Without `access`, a `private/` key resolves under the private root, so a caller serving
|
|
58
|
+
* an untrusted key passes `access: "public"`.
|
|
59
|
+
*/
|
|
60
|
+
resolveLocalPath?(path: string, access?: "public" | "private"): string | null;
|
|
53
61
|
}
|
|
54
62
|
|
|
55
63
|
export interface BlobStorageOptions {
|
|
@@ -88,6 +96,17 @@ export class BlobStorage
|
|
|
88
96
|
#resolveFilePath(path: string) {
|
|
89
97
|
return path.startsWith("private/") ? `${this.privateRoot}/${path}` : `${this.root}/${path}`;
|
|
90
98
|
}
|
|
99
|
+
resolveLocalPath(path: string, access?: "public" | "private") {
|
|
100
|
+
|
|
101
|
+
if (path.includes("\0") || parse(path).root) return null;
|
|
102
|
+
const isPrivate = access ? access === "private" : path.startsWith("private/");
|
|
103
|
+
const base = resolve(isPrivate ? this.privateRoot : this.root);
|
|
104
|
+
const target = resolve(base, path);
|
|
105
|
+
|
|
106
|
+
const within = relative(base, target);
|
|
107
|
+
if (!within || within === ".." || within.startsWith(`..${sep}`) || isAbsolute(within)) return null;
|
|
108
|
+
return target;
|
|
109
|
+
}
|
|
91
110
|
async readData(path: string): Promise<ReadableStream> {
|
|
92
111
|
const filePath = this.#resolveFilePath(path);
|
|
93
112
|
return Bun.file(filePath).stream();
|
package/signal/endpointCache.ts
CHANGED
|
@@ -1,19 +1,25 @@
|
|
|
1
|
-
import { type CacheAdaptor, CacheAdaptorRole } from "akanjs/service";
|
|
1
|
+
import { type CacheAdaptor, CacheAdaptorRole, EndpointCacheKeys } from "akanjs/service";
|
|
2
2
|
import dayjs from "dayjs";
|
|
3
|
+
import type { EndpointInfo } from "./endpointInfo";
|
|
3
4
|
import type { SignalContext } from "./signalContext";
|
|
4
5
|
import { TaggedJson } from "./TaggedJson";
|
|
5
6
|
import { traceCache } from "./trace";
|
|
6
7
|
|
|
7
8
|
export class EndpointCache {
|
|
8
|
-
static #topic = "cache";
|
|
9
9
|
static #refused = new Set<string>();
|
|
10
10
|
|
|
11
|
+
static declares(endpointInfo: EndpointInfo): boolean {
|
|
12
|
+
const ttl = endpointInfo.signalOption.cache;
|
|
13
|
+
return !!ttl && Number.isFinite(ttl) && ttl > 0;
|
|
14
|
+
}
|
|
15
|
+
|
|
11
16
|
static async through(context: SignalContext, run: () => Promise<unknown>) {
|
|
12
17
|
const ttl = context.endpointInfo.signalOption.cache;
|
|
13
|
-
if (!ttl || !
|
|
18
|
+
if (!ttl || !EndpointCache.declares(context.endpointInfo)) return await run();
|
|
14
19
|
if (!EndpointCache.#cacheable(context)) return await run();
|
|
15
20
|
const cache = context.getAdaptor(CacheAdaptorRole) as unknown as CacheAdaptor;
|
|
16
|
-
const key =
|
|
21
|
+
const key = await EndpointCache.#key(context, cache);
|
|
22
|
+
if (key === undefined) return await run();
|
|
17
23
|
const cached = await EndpointCache.#read(context, cache, key);
|
|
18
24
|
if (cached !== undefined) {
|
|
19
25
|
traceCache(true);
|
|
@@ -36,15 +42,23 @@ export class EndpointCache {
|
|
|
36
42
|
return false;
|
|
37
43
|
}
|
|
38
44
|
|
|
45
|
+
static async #key(context: SignalContext, cache: CacheAdaptor) {
|
|
46
|
+
try {
|
|
47
|
+
return await EndpointCacheKeys.entryKey(cache, context.key, context.args);
|
|
48
|
+
} catch (error) {
|
|
49
|
+
context.adaptor.logger.warn(`Cache read failed for ${context.key}: ${String(error)}`);
|
|
50
|
+
return undefined;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
39
53
|
static async #read(context: SignalContext, cache: CacheAdaptor, key: string) {
|
|
40
54
|
try {
|
|
41
|
-
const cached = await cache.get<string>(
|
|
55
|
+
const cached = await cache.get<string>(EndpointCacheKeys.topic, key);
|
|
42
56
|
if (cached == null) return undefined;
|
|
43
57
|
const { returnRef, arrDepth } = context.endpointInfo.returns;
|
|
44
58
|
return TaggedJson.parse(cached, { ref: returnRef, arrDepth });
|
|
45
59
|
} catch (error) {
|
|
46
60
|
context.adaptor.logger.warn(`Cache read failed for ${context.key}: ${String(error)}`);
|
|
47
|
-
await cache.delete(
|
|
61
|
+
await cache.delete(EndpointCacheKeys.topic, key).catch(() => undefined);
|
|
48
62
|
return undefined;
|
|
49
63
|
}
|
|
50
64
|
}
|
|
@@ -53,7 +67,9 @@ export class EndpointCache {
|
|
|
53
67
|
const serialized = TaggedJson.stringify(result);
|
|
54
68
|
|
|
55
69
|
if (serialized === undefined) return;
|
|
56
|
-
|
|
70
|
+
|
|
71
|
+
const lifeMs = Math.min(ttl, EndpointCacheKeys.generationTtlMs);
|
|
72
|
+
await cache.set(EndpointCacheKeys.topic, key, serialized, { expireAt: dayjs().add(lifeMs, "millisecond") });
|
|
57
73
|
} catch (error) {
|
|
58
74
|
context.adaptor.logger.warn(`Cache write failed for ${context.key}: ${String(error)}`);
|
|
59
75
|
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { Adaptor } from "akanjs/service";
|
|
2
|
+
|
|
3
|
+
import type { EndpointInfo } from "./endpointInfo";
|
|
4
|
+
import type { EndpointErrors } from "./types";
|
|
5
|
+
|
|
6
|
+
export class EndpointErrorContract {
|
|
7
|
+
static readonly #warned = new Set<string>();
|
|
8
|
+
|
|
9
|
+
/** Refuses a declaration no response could carry: a status outside 400–599, or a key that is not an error key. */
|
|
10
|
+
static assertDeclared(errors: EndpointErrors | undefined) {
|
|
11
|
+
if (!errors) return;
|
|
12
|
+
for (const [status, keys] of Object.entries(errors) as [string, EndpointErrors[number]][]) {
|
|
13
|
+
const code = Number(status);
|
|
14
|
+
if (!Number.isInteger(code) || code < 400 || code > 599)
|
|
15
|
+
throw new Error(`errors declares status ${status}: an error answers with a 400–599 status`);
|
|
16
|
+
for (const key of keys)
|
|
17
|
+
if (!/^[^.]+\.error\.[^.]+$/.test(key))
|
|
18
|
+
throw new Error(`errors declares "${key}" under ${status}: an error key reads <module>.error.<key>`);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
static keysOf(errors: EndpointErrors | undefined): string[] {
|
|
23
|
+
return errors ? Object.values(errors).flat() : [];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
static checkThrown(endpoint: Adaptor, endpointInfo: EndpointInfo, key: string, thrownKey: string) {
|
|
27
|
+
const { errors } = endpointInfo.signalOption;
|
|
28
|
+
if (!errors || process.env.NODE_ENV === "production" || thrownKey.startsWith("base.error.")) return;
|
|
29
|
+
if (EndpointErrorContract.keysOf(errors).includes(thrownKey)) return;
|
|
30
|
+
const warnedKey = `${key}\0${thrownKey}`;
|
|
31
|
+
if (EndpointErrorContract.#warned.has(warnedKey)) return;
|
|
32
|
+
EndpointErrorContract.#warned.add(warnedKey);
|
|
33
|
+
endpoint.logger.warn(
|
|
34
|
+
`"${key}" threw ${thrownKey}, which its \`errors\` declaration leaves out: OpenAPI and the MCP catalogue do not publish it.`,
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -25,15 +25,17 @@ export class EndpointIdempotency {
|
|
|
25
25
|
static readonly #topic = "idempotency";
|
|
26
26
|
static readonly #tag = "\u0000akan";
|
|
27
27
|
|
|
28
|
+
static applies(context: SignalContext): boolean {
|
|
29
|
+
return context.endpointInfo.type === "mutation" && context.transport === "http" && context.origin === "http";
|
|
30
|
+
}
|
|
31
|
+
|
|
28
32
|
/** A Response when the call is kept or replayed; otherwise the handler's own result, which the caller answers. */
|
|
29
33
|
static async through(
|
|
30
34
|
context: SignalContext,
|
|
31
35
|
run: () => Promise<unknown>,
|
|
32
36
|
answer: (result: unknown) => Promise<unknown>,
|
|
33
37
|
) {
|
|
34
|
-
|
|
35
|
-
if (context.endpointInfo.type !== "mutation" || context.transport !== "http" || context.origin !== "http")
|
|
36
|
-
return await run();
|
|
38
|
+
if (!EndpointIdempotency.applies(context)) return await run();
|
|
37
39
|
const { req } = context.getHttpContext();
|
|
38
40
|
const idempotencyKey = req.headers.get(EndpointIdempotency.header)?.trim();
|
|
39
41
|
if (!idempotencyKey || idempotencyKey.length > 256) return await run();
|
package/signal/endpointInfo.ts
CHANGED
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
isEnum,
|
|
8
8
|
type PromiseOrObject,
|
|
9
9
|
} from "akanjs/base";
|
|
10
|
+
import { trimSlashes } from "akanjs/common";
|
|
10
11
|
import type {
|
|
11
12
|
ConstantFieldType,
|
|
12
13
|
ConstantFieldTypeInput,
|
|
@@ -18,6 +19,7 @@ import type {
|
|
|
18
19
|
UploadableClientArg,
|
|
19
20
|
} from "akanjs/constant";
|
|
20
21
|
import type { ServiceModel } from "akanjs/service";
|
|
22
|
+
import { EndpointErrorContract } from "./endpointErrorContract";
|
|
21
23
|
import type { InternalArgCls } from "./internalArg";
|
|
22
24
|
import type { ArgType, SignalOption, SrvMap } from "./types";
|
|
23
25
|
|
|
@@ -103,6 +105,7 @@ export class EndpointInfo<
|
|
|
103
105
|
}
|
|
104
106
|
|
|
105
107
|
constructor(type: ReqType, returnRef: Returns, signalOption: SignalOption<Returns, Nullable> = {}) {
|
|
108
|
+
EndpointErrorContract.assertDeclared(signalOption.errors);
|
|
106
109
|
this.type = type;
|
|
107
110
|
this.returns = EndpointInfo.getReturnInfo(returnRef, signalOption);
|
|
108
111
|
this.signalOption = signalOption;
|
|
@@ -335,7 +338,7 @@ export class EndpointInfo<
|
|
|
335
338
|
if (prefix === false || prefix === "") return "";
|
|
336
339
|
const resolved = prefix ?? defaultPrefix;
|
|
337
340
|
if (!resolved) return "";
|
|
338
|
-
const trimmed = resolved.trim()
|
|
341
|
+
const trimmed = trimSlashes(resolved.trim());
|
|
339
342
|
return trimmed ? `/${trimmed}` : "";
|
|
340
343
|
}
|
|
341
344
|
}
|
|
@@ -92,9 +92,13 @@ export class EndpointRateLimit {
|
|
|
92
92
|
}
|
|
93
93
|
|
|
94
94
|
/** `ip` runs before the request body is read; `account` after the middlewares resolved the caller. */
|
|
95
|
-
static
|
|
95
|
+
static admit(context: SignalContext, phase: "ip" | "account"): Promise<void> | undefined {
|
|
96
96
|
const budget = EndpointRateLimit.budgetOf(context.key, context.endpointInfo);
|
|
97
|
-
if (!budget || budget.by !== phase) return;
|
|
97
|
+
if (!budget || budget.by !== phase) return undefined;
|
|
98
|
+
return EndpointRateLimit.#consume(context, phase, budget);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
static async #consume(context: SignalContext, phase: "ip" | "account", budget: ResolvedBudget) {
|
|
98
102
|
const caller =
|
|
99
103
|
(phase === "account" ? EndpointRateLimit.#accountOf(context) : null) ?? EndpointRateLimit.#ipOf(context);
|
|
100
104
|
const verdict = await EndpointRateLimit.#limiterOf(context).consume(`${context.key}:${caller}`, budget);
|
package/signal/exception.ts
CHANGED
|
@@ -60,7 +60,10 @@ export class Exception extends Error {
|
|
|
60
60
|
};
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
-
/**
|
|
63
|
+
/**
|
|
64
|
+
* Forwarded to the caller instead of generalized: an `Exception`, a dictionary `Err`, a `NoDocumentError`, or a restored
|
|
65
|
+
* remote one.
|
|
66
|
+
*/
|
|
64
67
|
export interface ExceptionLike {
|
|
65
68
|
statusCode: number;
|
|
66
69
|
toJSON: () => object;
|
package/signal/index.ts
CHANGED
|
@@ -6,6 +6,7 @@ export * from "./agentTurnStream";
|
|
|
6
6
|
export * from "./base.signal";
|
|
7
7
|
export { CrossSiteGuard, type CrossSiteOption } from "./CrossSiteGuard";
|
|
8
8
|
export * from "./endpoint";
|
|
9
|
+
export * from "./endpointErrorContract";
|
|
9
10
|
export * from "./endpointInfo";
|
|
10
11
|
export * from "./endpointRateLimit";
|
|
11
12
|
export * from "./exception";
|
|
@@ -188,7 +188,7 @@ export class McpDocument {
|
|
|
188
188
|
const outputSchema = this.#outputSchema(endpoint);
|
|
189
189
|
return {
|
|
190
190
|
name: key,
|
|
191
|
-
...this.#catalogue.entryTexts(refName, key),
|
|
191
|
+
...this.#withErrors(this.#catalogue.entryTexts(refName, key), endpoint),
|
|
192
192
|
inputSchema: {
|
|
193
193
|
type: "object",
|
|
194
194
|
properties,
|
|
@@ -201,6 +201,21 @@ export class McpDocument {
|
|
|
201
201
|
};
|
|
202
202
|
}
|
|
203
203
|
|
|
204
|
+
#withErrors(texts: { title?: string; description?: string }, endpoint: SerializedEndpoint) {
|
|
205
|
+
const errors = Object.entries(endpoint.errors ?? {}) as [string, readonly string[]][];
|
|
206
|
+
if (!errors.length) return texts;
|
|
207
|
+
const listed = errors
|
|
208
|
+
.flatMap(([status, keys]) =>
|
|
209
|
+
keys.map((key) => {
|
|
210
|
+
const message = this.#options.resolveDescription?.(key);
|
|
211
|
+
return `${status} ${key}${message ? ` (${message})` : ""}`;
|
|
212
|
+
}),
|
|
213
|
+
)
|
|
214
|
+
.join(", ");
|
|
215
|
+
const line = `Errors it answers with on purpose: ${listed}.`;
|
|
216
|
+
return { ...texts, description: texts.description ? `${texts.description}\n${line}` : line };
|
|
217
|
+
}
|
|
218
|
+
|
|
204
219
|
#outputSchema(endpoint: SerializedEndpoint) {
|
|
205
220
|
if (this.#options.outputSchema === "none") return undefined;
|
|
206
221
|
|
|
@@ -21,7 +21,7 @@ export class McpUriTemplate {
|
|
|
21
21
|
|
|
22
22
|
static expand(template: string, args: Record<string, unknown>): string {
|
|
23
23
|
const queryAt = template.indexOf("{?");
|
|
24
|
-
const path = (queryAt === -1 ? template : template.slice(0, queryAt)).replace(/\{([^}]+)\}/g, (_, name: string) =>
|
|
24
|
+
const path = (queryAt === -1 ? template : template.slice(0, queryAt)).replace(/\{([^{}]+)\}/g, (_, name: string) =>
|
|
25
25
|
encodeURIComponent(String(args[name] ?? "")),
|
|
26
26
|
);
|
|
27
27
|
if (queryAt === -1) return path;
|