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,525 @@
|
|
|
1
|
+
import { Logger } from "akanjs/common";
|
|
2
|
+
import { createDocumentId, sanitizeJson } from "akanjs/document";
|
|
3
|
+
import { type DatabaseAdaptor, descriptorHash, stableJson } from "akanjs/service";
|
|
4
|
+
import { MigrationChangeLog } from "./migrationChangeLog";
|
|
5
|
+
import { MigrationChanges, type MigrationSkip } from "./migrationChanges";
|
|
6
|
+
import { MigrationContext, type MigrationGet } from "./migrationContext";
|
|
7
|
+
import { MigrationDefinition, type MigrationPlan, type MigrationSettings } from "./migrationDefinition";
|
|
8
|
+
import { MigrationError } from "./migrationError";
|
|
9
|
+
import { MigrationLedger, type MigrationRun, type MigrationRunKind } from "./migrationLedger";
|
|
10
|
+
import { MigrationLock } from "./migrationLock";
|
|
11
|
+
import type { MigrationParamSpec } from "./migrationParams";
|
|
12
|
+
import { MigrationPulse } from "./migrationPulse";
|
|
13
|
+
import { MigrationScope } from "./migrationScope";
|
|
14
|
+
|
|
15
|
+
export interface MigrationEntry {
|
|
16
|
+
id: string;
|
|
17
|
+
/** sha256 of the script's source: the ledger keeps which version of a script ran. */
|
|
18
|
+
checksum: string;
|
|
19
|
+
load: () => Promise<{ default?: unknown }>;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface MigrationRunnerOption {
|
|
23
|
+
database: DatabaseAdaptor;
|
|
24
|
+
get?: MigrationGet;
|
|
25
|
+
buildId?: string | null;
|
|
26
|
+
logger?: Logger;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export type MigrationStatus = "pending" | "applied" | "partial";
|
|
30
|
+
|
|
31
|
+
export interface MigrationListItem {
|
|
32
|
+
id: string;
|
|
33
|
+
title: string | null;
|
|
34
|
+
checksum: string;
|
|
35
|
+
/** `partial`: an apply outside one transaction failed after committing some of its batches. */
|
|
36
|
+
status: MigrationStatus;
|
|
37
|
+
appliedRunId: string | null;
|
|
38
|
+
appliedAt: number | null;
|
|
39
|
+
/** The applied run ran another version of the script than this build carries. */
|
|
40
|
+
checksumMismatch: boolean;
|
|
41
|
+
params: MigrationParamSpec[];
|
|
42
|
+
error?: string;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface MigrationPlanResult {
|
|
46
|
+
id: string;
|
|
47
|
+
runId: string;
|
|
48
|
+
planHash: string;
|
|
49
|
+
params: Record<string, unknown>;
|
|
50
|
+
summary: unknown;
|
|
51
|
+
items: unknown[];
|
|
52
|
+
itemCount: number;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface MigrationApplyResult {
|
|
56
|
+
id: string;
|
|
57
|
+
runId: string;
|
|
58
|
+
planHash: string;
|
|
59
|
+
summary: unknown;
|
|
60
|
+
changes: number;
|
|
61
|
+
problems: string[];
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export interface MigrationVerifyResult {
|
|
65
|
+
id: string;
|
|
66
|
+
runId: string;
|
|
67
|
+
problems: string[];
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export interface MigrationRollbackRun {
|
|
71
|
+
runId: string;
|
|
72
|
+
target: string;
|
|
73
|
+
reverted: number;
|
|
74
|
+
skipped: MigrationSkip[];
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export interface MigrationRollbackResult {
|
|
78
|
+
id: string;
|
|
79
|
+
runs: MigrationRollbackRun[];
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export class MigrationRunner {
|
|
83
|
+
static readonly defaultItemNum = 100;
|
|
84
|
+
readonly #entries: MigrationEntry[];
|
|
85
|
+
readonly #database: DatabaseAdaptor;
|
|
86
|
+
readonly #get: MigrationGet;
|
|
87
|
+
readonly #buildId: string | null;
|
|
88
|
+
readonly #logger: Logger;
|
|
89
|
+
readonly #ledger: MigrationLedger;
|
|
90
|
+
|
|
91
|
+
constructor(entries: MigrationEntry[], { database, get, buildId = null, logger }: MigrationRunnerOption) {
|
|
92
|
+
this.#entries = entries;
|
|
93
|
+
this.#database = database;
|
|
94
|
+
this.#get =
|
|
95
|
+
get ??
|
|
96
|
+
(() => {
|
|
97
|
+
throw new Error("ctx.get needs a booted server; run migrations through akan migrate or ops migrate");
|
|
98
|
+
});
|
|
99
|
+
this.#buildId = buildId;
|
|
100
|
+
this.#logger = logger ?? new Logger("Migration");
|
|
101
|
+
this.#ledger = new MigrationLedger(() => database.getConnection());
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
async list(): Promise<MigrationListItem[]> {
|
|
105
|
+
const runs = await this.#ledger.runsOf();
|
|
106
|
+
return await Promise.all(
|
|
107
|
+
this.#entries.map(async (entry) => {
|
|
108
|
+
const state = await this.#ledger.state(runs.filter((run) => run.migrationId === entry.id));
|
|
109
|
+
const status: MigrationStatus = state.applied ? "applied" : state.partial.length ? "partial" : "pending";
|
|
110
|
+
const item: MigrationListItem = {
|
|
111
|
+
id: entry.id,
|
|
112
|
+
title: null,
|
|
113
|
+
checksum: entry.checksum,
|
|
114
|
+
status,
|
|
115
|
+
appliedRunId: state.applied?.id ?? null,
|
|
116
|
+
appliedAt: state.applied?.finishedAt ?? null,
|
|
117
|
+
checksumMismatch: !!state.applied && state.applied.checksum !== entry.checksum,
|
|
118
|
+
params: [],
|
|
119
|
+
};
|
|
120
|
+
try {
|
|
121
|
+
const definition = await MigrationRunner.#definition(entry);
|
|
122
|
+
item.title = definition.title;
|
|
123
|
+
item.params = definition.params.specs();
|
|
124
|
+
} catch (error) {
|
|
125
|
+
item.error = MigrationError.messageOf(error);
|
|
126
|
+
}
|
|
127
|
+
return item;
|
|
128
|
+
}),
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
async plan(
|
|
133
|
+
id: string,
|
|
134
|
+
raw: Record<string, string> = {},
|
|
135
|
+
{ actor = null, items = MigrationRunner.defaultItemNum }: { actor?: string | null; items?: number } = {},
|
|
136
|
+
): Promise<MigrationPlanResult> {
|
|
137
|
+
const { entry, definition } = await this.#load(id);
|
|
138
|
+
const params = definition.params.parse(raw);
|
|
139
|
+
return await this.#locked(async (lock) => {
|
|
140
|
+
const run = await this.#begin("plan", entry, { params, actor });
|
|
141
|
+
try {
|
|
142
|
+
const plan = await this.#plan(definition, id, params, new MigrationPulse(lock));
|
|
143
|
+
const planHash = await MigrationRunner.#planHash(params, plan);
|
|
144
|
+
await this.#ledger.finish(run.id, { status: "done", planHash, summary: { plan: plan.summary } });
|
|
145
|
+
const report = (sanitizeJson(plan.items ?? []) ?? []) as unknown[];
|
|
146
|
+
return {
|
|
147
|
+
id,
|
|
148
|
+
runId: run.id,
|
|
149
|
+
planHash,
|
|
150
|
+
params: MigrationRunner.#json(params),
|
|
151
|
+
summary: sanitizeJson(plan.summary),
|
|
152
|
+
items: report.slice(0, Math.max(0, items)),
|
|
153
|
+
itemCount: report.length,
|
|
154
|
+
};
|
|
155
|
+
} catch (error) {
|
|
156
|
+
throw await this.#failed(run.id, error);
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Recomputes the plan inside the run's own transaction and refuses when it no longer hashes to `expect`, the plan a
|
|
163
|
+
* person approved. Verify runs once the writes commit; its problems leave the run applied but the result not ok.
|
|
164
|
+
*/
|
|
165
|
+
async apply(
|
|
166
|
+
id: string,
|
|
167
|
+
raw: Record<string, string> = {},
|
|
168
|
+
{ expect = null, actor = null }: { expect?: string | null; actor?: string | null } = {},
|
|
169
|
+
): Promise<MigrationApplyResult> {
|
|
170
|
+
const { entry, definition } = await this.#load(id);
|
|
171
|
+
const applyStage = definition.applyStage;
|
|
172
|
+
if (!applyStage) throw new MigrationError("usage", `${id} declares no .apply()`);
|
|
173
|
+
const params = definition.params.parse(raw);
|
|
174
|
+
const settings = definition.settings;
|
|
175
|
+
return await this.#locked(async (lock) => {
|
|
176
|
+
const runs = await this.#ledger.runsOf(id);
|
|
177
|
+
const state = await this.#ledger.state(runs);
|
|
178
|
+
if (state.applied)
|
|
179
|
+
throw new MigrationError("alreadyApplied", `${id} was applied by run ${state.applied.id}; roll it back first`);
|
|
180
|
+
const resumed = settings.transaction ? null : (state.partial[0] ?? null);
|
|
181
|
+
if (resumed && MigrationRunner.#hashable(resumed.params) !== MigrationRunner.#hashable(params))
|
|
182
|
+
throw new MigrationError(
|
|
183
|
+
"invalidParam",
|
|
184
|
+
`run ${resumed.id} stopped partway with other params; apply with the same params to resume it, or roll it back`,
|
|
185
|
+
);
|
|
186
|
+
|
|
187
|
+
const checkpoint =
|
|
188
|
+
resumed && !runs.some((run) => run.kind === "rollback" && run.target === resumed.id)
|
|
189
|
+
? resumed.checkpoint
|
|
190
|
+
: null;
|
|
191
|
+
|
|
192
|
+
const run = await this.#begin("apply", entry, { params, actor, checkpoint, summary: { settings } });
|
|
193
|
+
const pulse = new MigrationPulse(lock, this.#holdWarning(id, settings.transaction));
|
|
194
|
+
const scope = new MigrationScope({
|
|
195
|
+
database: this.#database,
|
|
196
|
+
readOnly: false,
|
|
197
|
+
batchSize: settings.batchSize,
|
|
198
|
+
log: new MigrationChangeLog(run.id, () => this.#database.getConnection()),
|
|
199
|
+
checkpoints: settings.transaction ? null : { runId: run.id, ledger: this.#ledger, resumed: checkpoint ?? {} },
|
|
200
|
+
beat: async () => await pulse.beat({ strict: true }),
|
|
201
|
+
});
|
|
202
|
+
const planned: { hash: string; summary: unknown; changes: number } = { hash: "", summary: null, changes: 0 };
|
|
203
|
+
try {
|
|
204
|
+
await this.#run(pulse, settings.transaction, async () => {
|
|
205
|
+
|
|
206
|
+
if (settings.transaction && scope.dialect.name === "postgres")
|
|
207
|
+
await scope.connection().execute("SET TRANSACTION ISOLATION LEVEL REPEATABLE READ");
|
|
208
|
+
const plan = await this.#plan(definition, id, params, pulse);
|
|
209
|
+
planned.hash = await MigrationRunner.#planHash(params, plan);
|
|
210
|
+
planned.summary = sanitizeJson(plan.summary);
|
|
211
|
+
if (expect && expect !== planned.hash)
|
|
212
|
+
throw new MigrationError("planChanged", `${id}'s plan changed since it was approved; plan it again`);
|
|
213
|
+
await applyStage(this.#context(id, params, scope), plan);
|
|
214
|
+
planned.changes = await MigrationChangeLog.count(this.#database.getConnection(), run.id);
|
|
215
|
+
await pulse.beat({ strict: true, force: true });
|
|
216
|
+
|
|
217
|
+
await this.#ledger.complete(run.id, {
|
|
218
|
+
planHash: planned.hash,
|
|
219
|
+
summary: { plan: planned.summary, changes: planned.changes, settings },
|
|
220
|
+
});
|
|
221
|
+
});
|
|
222
|
+
} catch (error) {
|
|
223
|
+
if ((await this.#ledger.get(run.id))?.status !== "done") {
|
|
224
|
+
const changes = await MigrationChangeLog.count(this.#database.getConnection(), run.id);
|
|
225
|
+
throw await this.#failed(run.id, error, {
|
|
226
|
+
status: MigrationError.codeOf(error) === "planChanged" ? "aborted" : "failed",
|
|
227
|
+
planHash: planned.hash || null,
|
|
228
|
+
summary: { plan: planned.summary, changes, settings },
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
this.#committedThenFailed(id, error);
|
|
232
|
+
}
|
|
233
|
+
const problems = await this.#verify(definition, id, params, pulse);
|
|
234
|
+
await this.#ledger.finish(run.id, {
|
|
235
|
+
status: "done",
|
|
236
|
+
planHash: planned.hash,
|
|
237
|
+
summary: { plan: planned.summary, changes: planned.changes, settings, problems },
|
|
238
|
+
});
|
|
239
|
+
return {
|
|
240
|
+
id,
|
|
241
|
+
runId: run.id,
|
|
242
|
+
planHash: planned.hash,
|
|
243
|
+
summary: planned.summary,
|
|
244
|
+
changes: planned.changes,
|
|
245
|
+
problems,
|
|
246
|
+
};
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
async verify(id: string, raw: Record<string, string> = {}, { actor = null }: { actor?: string | null } = {}) {
|
|
251
|
+
const { entry, definition } = await this.#load(id);
|
|
252
|
+
if (!definition.verifyStage) throw new MigrationError("usage", `${id} declares no .verify()`);
|
|
253
|
+
const params = definition.params.parse(raw);
|
|
254
|
+
return await this.#locked(async (lock): Promise<MigrationVerifyResult> => {
|
|
255
|
+
const run = await this.#begin("verify", entry, { params, actor });
|
|
256
|
+
const problems = await this.#verify(definition, id, params, new MigrationPulse(lock));
|
|
257
|
+
await this.#ledger.finish(
|
|
258
|
+
run.id,
|
|
259
|
+
problems.length
|
|
260
|
+
? { status: "failed", summary: { problems }, error: MigrationError.remaining(problems.length) }
|
|
261
|
+
: { status: "done", summary: { problems } },
|
|
262
|
+
);
|
|
263
|
+
return { id, runId: run.id, problems };
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* `target` is the newest apply run, or a migration id for every run of it not yet rolled back, newest first. Works
|
|
269
|
+
* for a script this build no longer carries: the change log and the settings the apply ran with are enough.
|
|
270
|
+
*/
|
|
271
|
+
async rollback(target: string, { actor = null }: { actor?: string | null } = {}): Promise<MigrationRollbackResult> {
|
|
272
|
+
return await this.#locked(async (lock) => {
|
|
273
|
+
const applies = await this.#rollbackTargets(target);
|
|
274
|
+
const runs: MigrationRollbackRun[] = [];
|
|
275
|
+
for (const apply of applies) runs.push(await this.#rollbackRun(apply, actor, lock));
|
|
276
|
+
return { id: applies[0]?.migrationId ?? target, runs };
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
async #rollbackTargets(target: string) {
|
|
281
|
+
const run = await this.#ledger.get(target);
|
|
282
|
+
if (run) {
|
|
283
|
+
if (run.kind !== "apply") throw new MigrationError("usage", `run ${target} is a ${run.kind}, not an apply`);
|
|
284
|
+
const live = await this.#liveApplies(run.migrationId);
|
|
285
|
+
if (!live.some(({ id }) => id === run.id))
|
|
286
|
+
throw new MigrationError("notApplied", `run ${target} has nothing left to roll back`);
|
|
287
|
+
const newest = live[0];
|
|
288
|
+
if (newest && newest.id !== run.id)
|
|
289
|
+
throw new MigrationError(
|
|
290
|
+
"usage",
|
|
291
|
+
`run ${newest.id} applied ${run.migrationId} after run ${target} and may build on it; roll back ${run.migrationId} to undo both, newest first`,
|
|
292
|
+
);
|
|
293
|
+
return [run];
|
|
294
|
+
}
|
|
295
|
+
const applies = await this.#liveApplies(target);
|
|
296
|
+
if (!applies.length) {
|
|
297
|
+
if (!this.#entries.some(({ id }) => id === target))
|
|
298
|
+
throw new MigrationError("notFound", `${target} is neither a migration of this build nor a run in the ledger`);
|
|
299
|
+
throw new MigrationError("notApplied", `${target} has no applied run to roll back`);
|
|
300
|
+
}
|
|
301
|
+
return applies;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
async #liveApplies(migrationId: string) {
|
|
305
|
+
const state = await this.#ledger.state(await this.#ledger.runsOf(migrationId));
|
|
306
|
+
return [state.applied, ...state.partial]
|
|
307
|
+
.filter((apply): apply is MigrationRun => !!apply)
|
|
308
|
+
.sort((a, b) => b.startedAt - a.startedAt);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
async #rollbackRun(apply: MigrationRun, actor: string | null, lock: MigrationLock): Promise<MigrationRollbackRun> {
|
|
312
|
+
const entry = this.#entries.find(({ id }) => id === apply.migrationId) ?? null;
|
|
313
|
+
const definition = entry ? await MigrationRunner.#definition(entry) : null;
|
|
314
|
+
if (entry && entry.checksum !== apply.checksum)
|
|
315
|
+
this.#logger.warn(
|
|
316
|
+
`${apply.migrationId} changed since run ${apply.id} applied it; rolling back with this build's`,
|
|
317
|
+
);
|
|
318
|
+
const settings = definition?.settings ?? MigrationRunner.#settingsOf(apply);
|
|
319
|
+
const rollbackStage = definition?.rollbackStage ?? null;
|
|
320
|
+
|
|
321
|
+
const from = rollbackStage || settings.transaction ? null : await this.#reachedBefore(apply);
|
|
322
|
+
const run = await this.#begin("rollback", entry ?? { id: apply.migrationId, checksum: apply.checksum }, {
|
|
323
|
+
params: apply.params ?? {},
|
|
324
|
+
actor,
|
|
325
|
+
target: apply.id,
|
|
326
|
+
});
|
|
327
|
+
const pulse = new MigrationPulse(lock, this.#holdWarning(apply.migrationId, settings.transaction));
|
|
328
|
+
const scope = new MigrationScope({
|
|
329
|
+
database: this.#database,
|
|
330
|
+
readOnly: false,
|
|
331
|
+
batchSize: settings.batchSize,
|
|
332
|
+
log: new MigrationChangeLog(run.id, () => this.#database.getConnection()),
|
|
333
|
+
beat: async () => await pulse.beat({ strict: true }),
|
|
334
|
+
});
|
|
335
|
+
const params = definition ? definition.params.revive(apply.params ?? {}) : (apply.params ?? {});
|
|
336
|
+
const ctx = this.#context(apply.migrationId, params, scope);
|
|
337
|
+
const changes = new MigrationChanges(
|
|
338
|
+
async (below, limit) => await MigrationChangeLog.page(this.#database.getConnection(), apply.id, below, limit),
|
|
339
|
+
(name) => ctx.table(name),
|
|
340
|
+
from,
|
|
341
|
+
);
|
|
342
|
+
const summary = () => ({ reverted: changes.reverted, skipped: changes.skipped });
|
|
343
|
+
try {
|
|
344
|
+
await this.#run(pulse, settings.transaction, async () => {
|
|
345
|
+
if (rollbackStage) await rollbackStage(ctx, changes);
|
|
346
|
+
else await changes.revertAll();
|
|
347
|
+
await pulse.beat({ strict: true, force: true });
|
|
348
|
+
await this.#ledger.complete(run.id, { summary: summary() });
|
|
349
|
+
});
|
|
350
|
+
} catch (error) {
|
|
351
|
+
if ((await this.#ledger.get(run.id))?.status !== "done") {
|
|
352
|
+
if (!rollbackStage && !settings.transaction && changes.reached !== null)
|
|
353
|
+
await this.#ledger.saveCheckpoint(run.id, { seq: String(changes.reached) });
|
|
354
|
+
throw await this.#failed(run.id, error, { summary: summary() });
|
|
355
|
+
}
|
|
356
|
+
this.#committedThenFailed(apply.migrationId, error);
|
|
357
|
+
}
|
|
358
|
+
return { runId: run.id, target: apply.id, ...summary() };
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/** Where the newest default rollback of `apply` that was cut short got down to: every newer change is handled. */
|
|
362
|
+
async #reachedBefore(apply: MigrationRun) {
|
|
363
|
+
const cut = (await this.#ledger.runsOf(apply.migrationId))
|
|
364
|
+
.filter((run) => run.kind === "rollback" && run.target === apply.id && run.status === "failed")
|
|
365
|
+
.map((run) => Number(run.checkpoint?.seq))
|
|
366
|
+
.filter((seq) => Number.isInteger(seq));
|
|
367
|
+
return cut.at(-1) ?? null;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
async #plan(
|
|
371
|
+
definition: MigrationDefinition<Record<string, unknown>>,
|
|
372
|
+
id: string,
|
|
373
|
+
params: Record<string, unknown>,
|
|
374
|
+
pulse: MigrationPulse,
|
|
375
|
+
) {
|
|
376
|
+
const scope = this.#readScope(definition.settings.batchSize, pulse);
|
|
377
|
+
const plan = definition.planStage ? await definition.planStage(this.#context(id, params, scope)) : { summary: {} };
|
|
378
|
+
return plan as MigrationPlan;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
async #verify(
|
|
382
|
+
definition: MigrationDefinition<Record<string, unknown>>,
|
|
383
|
+
id: string,
|
|
384
|
+
params: Record<string, unknown>,
|
|
385
|
+
pulse: MigrationPulse,
|
|
386
|
+
) {
|
|
387
|
+
if (!definition.verifyStage) return [];
|
|
388
|
+
const scope = this.#readScope(definition.settings.batchSize, pulse);
|
|
389
|
+
try {
|
|
390
|
+
return (await definition.verifyStage(this.#context(id, params, scope))).map(String);
|
|
391
|
+
} catch (error) {
|
|
392
|
+
this.#logger.error(
|
|
393
|
+
`${id} verify threw: ${error instanceof Error ? (error.stack ?? error.message) : String(error)}`,
|
|
394
|
+
);
|
|
395
|
+
return [`verify threw: ${MigrationError.messageOf(error)}`];
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
#context(id: string, params: Record<string, unknown>, scope: MigrationScope) {
|
|
400
|
+
return new MigrationContext({ id, params, logger: new Logger(`Migration:${id}`), get: this.#get, scope });
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
#readScope(batchSize: number, pulse: MigrationPulse) {
|
|
404
|
+
return new MigrationScope({
|
|
405
|
+
database: this.#database,
|
|
406
|
+
readOnly: true,
|
|
407
|
+
batchSize,
|
|
408
|
+
beat: async () => await pulse.beat({ strict: false }),
|
|
409
|
+
});
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
async #run(pulse: MigrationPulse, transaction: boolean, fn: () => Promise<void>) {
|
|
413
|
+
if (transaction) await this.#database.transaction(async () => await pulse.hold(fn));
|
|
414
|
+
else await fn();
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
#holdWarning(id: string, transaction: boolean) {
|
|
418
|
+
if (!transaction || this.#database.dialect?.name !== "sqlite") return null;
|
|
419
|
+
return (heldMs: number) =>
|
|
420
|
+
this.#logger.warn(
|
|
421
|
+
`${id} has held the SQLite write lock for ${Math.round(heldMs / 1000)}s; the live server's writes fail until it commits. A long run belongs in .config({ transaction: false })`,
|
|
422
|
+
);
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
#committedThenFailed(id: string, error: unknown) {
|
|
426
|
+
this.#logger.warn(`${id} committed, but what ran after the commit failed: ${MigrationError.messageOf(error)}`);
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
async #locked<T>(fn: (lock: MigrationLock) => Promise<T>) {
|
|
430
|
+
const lock = new MigrationLock(() => this.#database.getConnection(), {
|
|
431
|
+
inBand: this.#database.dialect?.name === "sqlite",
|
|
432
|
+
});
|
|
433
|
+
if (!(await lock.acquire()))
|
|
434
|
+
throw new MigrationError("locked", "Another migration run holds the lock; try again once it finishes");
|
|
435
|
+
try {
|
|
436
|
+
await this.#ledger.interruptRunning();
|
|
437
|
+
return await fn(lock);
|
|
438
|
+
} finally {
|
|
439
|
+
await lock.release();
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
async #begin(
|
|
444
|
+
kind: MigrationRunKind,
|
|
445
|
+
entry: Pick<MigrationEntry, "id" | "checksum">,
|
|
446
|
+
{
|
|
447
|
+
params,
|
|
448
|
+
actor,
|
|
449
|
+
target = null,
|
|
450
|
+
checkpoint = null,
|
|
451
|
+
summary = null,
|
|
452
|
+
}: {
|
|
453
|
+
params: Record<string, unknown>;
|
|
454
|
+
actor: string | null;
|
|
455
|
+
target?: string | null;
|
|
456
|
+
checkpoint?: Record<string, string> | null;
|
|
457
|
+
summary?: Record<string, unknown> | null;
|
|
458
|
+
},
|
|
459
|
+
) {
|
|
460
|
+
return await this.#ledger.insert({
|
|
461
|
+
id: createDocumentId(),
|
|
462
|
+
migrationId: entry.id,
|
|
463
|
+
kind,
|
|
464
|
+
status: "running",
|
|
465
|
+
target,
|
|
466
|
+
checksum: entry.checksum,
|
|
467
|
+
buildId: this.#buildId,
|
|
468
|
+
params: MigrationRunner.#json(params),
|
|
469
|
+
planHash: null,
|
|
470
|
+
summary,
|
|
471
|
+
actor,
|
|
472
|
+
checkpoint,
|
|
473
|
+
startedAt: Date.now(),
|
|
474
|
+
finishedAt: null,
|
|
475
|
+
error: null,
|
|
476
|
+
});
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
async #failed(
|
|
480
|
+
runId: string,
|
|
481
|
+
error: unknown,
|
|
482
|
+
{
|
|
483
|
+
status = "failed",
|
|
484
|
+
planHash = null,
|
|
485
|
+
summary = null,
|
|
486
|
+
}: { status?: "failed" | "aborted"; planHash?: string | null; summary?: Record<string, unknown> | null } = {},
|
|
487
|
+
) {
|
|
488
|
+
if (status === "failed")
|
|
489
|
+
this.#logger.error(
|
|
490
|
+
`run ${runId} failed: ${error instanceof Error ? (error.stack ?? error.message) : String(error)}`,
|
|
491
|
+
);
|
|
492
|
+
await this.#ledger.finish(runId, { status, planHash, summary, error: MigrationError.messageOf(error) });
|
|
493
|
+
return MigrationError.recorded(error, runId);
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
async #load(id: string) {
|
|
497
|
+
const entry = this.#entries.find((entry) => entry.id === id);
|
|
498
|
+
if (!entry) throw new MigrationError("notFound", `This build carries no migration "${id}"`);
|
|
499
|
+
return { entry, definition: await MigrationRunner.#definition(entry) };
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
static async #definition(entry: MigrationEntry) {
|
|
503
|
+
const definition = (await entry.load()).default;
|
|
504
|
+
if (!MigrationDefinition.is(definition))
|
|
505
|
+
throw new MigrationError("failed", `script/migration/${entry.id}.ts must export default migration(...)`);
|
|
506
|
+
return definition;
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
static #settingsOf(apply: MigrationRun): MigrationSettings {
|
|
510
|
+
const recorded = apply.summary?.settings as Partial<MigrationSettings> | undefined;
|
|
511
|
+
return { ...MigrationDefinition.defaultSettings, ...recorded };
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
static async #planHash(params: Record<string, unknown>, plan: MigrationPlan) {
|
|
515
|
+
return await descriptorHash({ params: sanitizeJson(params), plan: sanitizeJson(plan) });
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
static #hashable(params: Record<string, unknown> | null) {
|
|
519
|
+
return stableJson(sanitizeJson(params ?? {}));
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
static #json(params: Record<string, unknown>) {
|
|
523
|
+
return (sanitizeJson(params) ?? {}) as Record<string, unknown>;
|
|
524
|
+
}
|
|
525
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { createDocumentId } from "akanjs/document";
|
|
2
|
+
import type { AkanSqlClient, DatabaseAdaptor, SqlDialect } from "akanjs/service";
|
|
3
|
+
import type { MigrationChangeLog } from "./migrationChangeLog";
|
|
4
|
+
import type { MigrationLedger } from "./migrationLedger";
|
|
5
|
+
|
|
6
|
+
export interface MigrationScopeOption {
|
|
7
|
+
database: DatabaseAdaptor;
|
|
8
|
+
readOnly: boolean;
|
|
9
|
+
batchSize: number;
|
|
10
|
+
log?: MigrationChangeLog | null;
|
|
11
|
+
/** Set for a run outside one transaction: each `rewrite` batch commits with where it got to. */
|
|
12
|
+
checkpoints?: { runId: string; ledger: MigrationLedger; resumed: Record<string, string> } | null;
|
|
13
|
+
/** Called at every table call, so a run keeps its lease however long it holds the event loop. */
|
|
14
|
+
beat?: () => Promise<void>;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export class MigrationScope {
|
|
18
|
+
readonly database: DatabaseAdaptor;
|
|
19
|
+
readonly dialect: SqlDialect;
|
|
20
|
+
readonly readOnly: boolean;
|
|
21
|
+
readonly batchSize: number;
|
|
22
|
+
readonly log: MigrationChangeLog | null;
|
|
23
|
+
readonly #checkpoints: MigrationScopeOption["checkpoints"];
|
|
24
|
+
readonly #reached: Record<string, string>;
|
|
25
|
+
readonly #rewrites = new Map<string, number>();
|
|
26
|
+
readonly #beat: () => Promise<void>;
|
|
27
|
+
|
|
28
|
+
constructor({
|
|
29
|
+
database,
|
|
30
|
+
readOnly,
|
|
31
|
+
batchSize,
|
|
32
|
+
log = null,
|
|
33
|
+
checkpoints = null,
|
|
34
|
+
beat = async () => undefined,
|
|
35
|
+
}: MigrationScopeOption) {
|
|
36
|
+
if (!database.dialect)
|
|
37
|
+
throw new Error(
|
|
38
|
+
"This database adaptor names no SQL dialect; migrations run on SqliteDatabase or PostgresDatabase",
|
|
39
|
+
);
|
|
40
|
+
this.database = database;
|
|
41
|
+
this.dialect = database.dialect;
|
|
42
|
+
this.readOnly = readOnly;
|
|
43
|
+
this.batchSize = batchSize;
|
|
44
|
+
this.log = log;
|
|
45
|
+
this.#checkpoints = checkpoints;
|
|
46
|
+
this.#reached = { ...checkpoints?.resumed };
|
|
47
|
+
this.#beat = beat;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
static assertBatchSize(batchSize: number) {
|
|
51
|
+
if (!Number.isInteger(batchSize) || batchSize < 1)
|
|
52
|
+
throw new Error(`batchSize takes a whole number of rows above 0, not ${batchSize}`);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
connection(): AkanSqlClient {
|
|
56
|
+
return this.database.getConnection();
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
now() {
|
|
60
|
+
return Date.now();
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
createId(): string {
|
|
64
|
+
return createDocumentId();
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async beat() {
|
|
68
|
+
await this.#beat();
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** Joins the transaction already open in this context, or opens one around `fn`. */
|
|
72
|
+
async transaction<T>(fn: () => Promise<T>) {
|
|
73
|
+
return await this.database.transaction(fn);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* A rewrite is told apart by its table and `signature`, so a resumed run finds each one's place again even when it
|
|
78
|
+
* skips a rewrite the first attempt made; only identical rewrites fall back on the order they are called in.
|
|
79
|
+
*/
|
|
80
|
+
nextRewrite(table: string, signature: string) {
|
|
81
|
+
const base = `${table}:${signature}`;
|
|
82
|
+
const occurrence = this.#rewrites.get(base) ?? 0;
|
|
83
|
+
this.#rewrites.set(base, occurrence + 1);
|
|
84
|
+
const key = `${base}:${occurrence}`;
|
|
85
|
+
return { key, after: this.#checkpoints ? this.#reached[key] : undefined };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async reach(key: string, id: string) {
|
|
89
|
+
if (!this.#checkpoints) return;
|
|
90
|
+
this.#reached[key] = id;
|
|
91
|
+
await this.#checkpoints.ledger.saveCheckpoint(this.#checkpoints.runId, this.#reached);
|
|
92
|
+
}
|
|
93
|
+
}
|