akanjs 3.1.1 → 3.1.2-rc.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.ko.md +22 -0
- package/README.md +22 -0
- package/client/cookie.ts +23 -7
- package/client/csrTypes.ts +1 -1
- package/client/makePageProto.tsx +10 -1
- package/client/router.ts +57 -16
- package/common/IpLiteral.ts +4 -2
- package/common/TrustedProxy.ts +45 -2
- package/common/akanNames.ts +169 -0
- package/common/codeAgentProfile.ts +51 -4
- package/common/index.ts +13 -0
- package/common/localeConfig.ts +30 -5
- package/common/localePath.ts +26 -0
- package/common/markdownSpans.ts +3 -1
- package/common/mcpExposure.ts +7 -7
- package/common/websocketContract.ts +50 -0
- package/constant/index.ts +1 -0
- package/constant/inputParser.ts +10 -2
- package/constant/requestValue.ts +47 -0
- package/dictionary/agent.dictionary.ts +2 -2
- package/dictionary/base.dictionary.ts +4 -0
- package/dictionary/dictInfo.ts +9 -9
- package/document/database.ts +1 -0
- package/document/documentQuery.ts +42 -6
- package/document/filterMeta.ts +56 -8
- package/document/queryEvaluator.ts +13 -8
- package/fetch/client/fetchClient.ts +28 -62
- package/fetch/client/httpClient.ts +1 -0
- package/fetch/client/sliceInitHandle.ts +18 -18
- package/fetch/index.ts +1 -0
- package/fetch/sessionCookie.ts +37 -0
- package/index.ts +8 -0
- package/package.json +13 -27
- package/server/akanApp.ts +108 -29
- package/server/akanServer.ts +18 -7
- package/server/assetHeaders.ts +20 -1
- package/server/cachePolicy.ts +18 -5
- package/server/devtools/depsSerializer.ts +2 -1
- package/server/devtools/devtoolsRouter.ts +3 -2
- package/server/devtools/signalSerializer.ts +5 -5
- package/server/hmr/clientReport.ts +10 -5
- package/server/imageOptimizer.ts +117 -22
- package/server/lifecycle/buildEnv.ts +11 -0
- package/server/lifecycle/loadBuildEnv.ts +3 -0
- package/server/logging/logControlSocket.ts +20 -14
- package/server/logging/logStreamRoute.ts +12 -8
- package/server/logging/rotatingLogWriter.ts +15 -1
- package/server/machine/machineToken.ts +4 -0
- package/server/mcp/McpAuth.ts +12 -4
- package/server/mcp/McpExecutionContext.ts +4 -0
- package/server/mcp/McpRouter.ts +25 -13
- package/server/mcp/PagePromptComposer.ts +4 -7
- package/server/oauth/OAuthClientIdMetadata.ts +29 -6
- package/server/oauth/OAuthRegistration.ts +9 -0
- package/server/ops/failureLimiter.ts +42 -0
- package/server/ops/opsCommand.ts +19 -7
- package/server/ops/opsRoute.ts +60 -9
- package/server/ops/opsToken.ts +5 -0
- package/server/ops/snapshotJobs.ts +3 -1
- package/server/ops/snapshotRestore.ts +23 -1
- package/server/ops/sqliteSnapshot.ts +98 -18
- package/server/ops/statusAccess.ts +37 -19
- package/server/proxy/hostBasePathWebProxy.ts +4 -7
- package/server/proxy/localeWebProxy.ts +36 -16
- package/server/resolver/database.resolver.ts +15 -11
- package/server/resolver/service.resolver.ts +17 -2
- package/server/resolver/signal.resolver.ts +50 -40
- package/server/routeElementComposer.tsx +42 -9
- package/server/routeShellGate.ts +42 -0
- package/server/routing/apiRouter.ts +64 -1
- package/server/routing/serverLimits.ts +151 -0
- package/server/routing/soloAppRoutes.ts +23 -12
- package/server/rscClient.tsx +5 -3
- package/server/rscScrollRestoration.ts +60 -9
- package/server/rscWorker.tsx +76 -24
- package/server/sitemap.ts +5 -7
- package/server/ssrFromRscRenderer.tsx +36 -4
- package/server/ssrTypes.ts +5 -2
- package/server/staticPath.ts +36 -1
- package/server/systemPageDocument.tsx +5 -3
- package/server/systemPages.tsx +14 -8
- package/server/tunnel/TunnelAgent.ts +19 -2
- package/server/tunnel/TunnelDataSocket.ts +4 -2
- package/server/tunnel/TunnelHttpStream.ts +7 -2
- package/server/tunnel/TunnelTcpStream.ts +6 -3
- package/server/tunnel/TunnelWebsocketStream.ts +9 -2
- package/server/tunnel/tunnelStream.ts +6 -0
- package/server/types.tsx +20 -2
- package/server/webRouter.ts +38 -18
- package/service/agent.service.ts +9 -7
- package/service/injectInfo.ts +2 -1
- package/service/liveSyncHub.ts +6 -0
- package/service/predefinedAdaptor/anthropicLlm.ts +13 -12
- package/service/predefinedAdaptor/database.adaptor.ts +4 -0
- package/service/predefinedAdaptor/llm.adaptor.ts +3 -0
- package/service/predefinedAdaptor/llmOverflow.ts +5 -1
- package/service/predefinedAdaptor/openaiDialect.ts +9 -8
- package/service/predefinedAdaptor/searchIndex.ts +21 -1
- package/service/predefinedAdaptor/sql/QueryCompiler.ts +28 -4
- package/service/predefinedAdaptor/sql/SqlDocumentStore.ts +45 -11
- package/service/predefinedAdaptor/sql/dialect/postgres.ts +6 -1
- package/service/predefinedAdaptor/sql/types.ts +3 -0
- package/service/predefinedAdaptor/storage.adaptor.ts +11 -10
- package/service/serve.ts +3 -3
- package/service/serviceModule.ts +28 -24
- package/service/types.ts +3 -0
- package/signal/SignalFailure.ts +5 -2
- package/signal/agent/AgentCatalogue.ts +3 -2
- package/signal/agentMeter.ts +81 -9
- package/signal/agentRelayPolicy.ts +53 -2
- package/signal/agentTurnStream.ts +12 -1
- package/signal/guard.ts +2 -2
- package/signal/mcp/McpDocument.ts +3 -3
- package/signal/mcp/McpUriTemplate.ts +5 -5
- package/signal/middleware.ts +15 -1
- package/signal/roomKey.ts +53 -0
- package/signal/signalContext.ts +100 -40
- package/signal/slice.ts +27 -22
- package/signal/trace.ts +17 -6
- package/store/action.ts +20 -49
- package/store/actionTag.ts +2 -0
- package/store/agent/ScreenReader.ts +36 -4
- package/store/agent/ScreenTarget.ts +1 -1
- package/store/agent/StoreCatalogue.ts +7 -1
- package/store/agent/StoreSurfaceSource.ts +5 -0
- package/store/agentic/AgentConfirm.ts +30 -0
- package/store/agentic/StStateBuilder.ts +2 -2
- package/store/agentic/StToolBuilder.ts +7 -2
- package/store/agentic/formFields.ts +1 -2
- package/store/agentic/index.ts +1 -0
- package/store/agentic/useFieldTool.ts +4 -3
- package/store/deprecatedNames.ts +6 -0
- package/store/draftStore.ts +2 -2
- package/store/formUploadOf.ts +6 -5
- package/store/index.ts +1 -2
- package/store/sliceKeys.ts +9 -32
- package/store/state.ts +1 -1
- package/store/stateBuilder.ts +1 -1
- package/store/store.ts +1 -1
- package/store/storeInstance.ts +12 -8
- package/store/storeRegistry.ts +14 -2
- package/types/client/cookie.d.ts +7 -2
- package/types/client/csrTypes.d.ts +1 -1
- package/types/client/router.d.ts +4 -1
- package/types/common/akanNames.d.ts +149 -0
- package/types/common/codeAgentProfile.d.ts +4 -1
- package/types/common/index.d.ts +3 -1
- package/types/common/localeConfig.d.ts +8 -0
- package/types/common/localePath.d.ts +13 -0
- package/types/common/websocketContract.d.ts +7 -0
- package/types/constant/index.d.ts +1 -0
- package/types/constant/requestValue.d.ts +5 -0
- package/types/dictionary/base.dictionary.d.ts +1 -1
- package/types/dictionary/dictionary.d.ts +3 -3
- package/types/document/database.d.ts +1 -0
- package/types/document/documentQuery.d.ts +3 -0
- package/types/document/filterMeta.d.ts +4 -0
- package/types/fetch/client/sliceInitHandle.d.ts +1 -2
- package/types/fetch/index.d.ts +1 -0
- package/types/fetch/sessionCookie.d.ts +12 -0
- package/types/index.d.ts +8 -0
- package/types/server/akanApp.d.ts +1 -0
- package/types/server/assetHeaders.d.ts +5 -0
- package/types/server/cachePolicy.d.ts +2 -0
- package/types/server/devtools/devtoolsRouter.d.ts +1 -1
- package/types/server/imageOptimizer.d.ts +4 -1
- package/types/server/lifecycle/buildEnv.d.ts +2 -0
- package/types/server/lifecycle/loadBuildEnv.d.ts +1 -0
- package/types/server/logging/logStreamRoute.d.ts +1 -1
- package/types/server/logging/rotatingLogWriter.d.ts +3 -0
- package/types/server/machine/machineToken.d.ts +2 -0
- package/types/server/mcp/McpAuth.d.ts +2 -1
- package/types/server/mcp/McpExecutionContext.d.ts +1 -0
- package/types/server/oauth/OAuthRegistration.d.ts +2 -0
- package/types/server/ops/failureLimiter.d.ts +11 -0
- package/types/server/ops/opsRoute.d.ts +4 -2
- package/types/server/ops/opsToken.d.ts +2 -0
- package/types/server/ops/snapshotRestore.d.ts +3 -1
- package/types/server/ops/sqliteSnapshot.d.ts +7 -1
- package/types/server/ops/statusAccess.d.ts +8 -4
- package/types/server/proxy/hostBasePathWebProxy.d.ts +1 -0
- package/types/server/resolver/signal.resolver.d.ts +0 -1
- package/types/server/routeElementComposer.d.ts +10 -4
- package/types/server/routeShellGate.d.ts +12 -0
- package/types/server/routing/apiRouter.d.ts +6 -1
- package/types/server/routing/serverLimits.d.ts +23 -0
- package/types/server/routing/soloAppRoutes.d.ts +1 -1
- package/types/server/rscScrollRestoration.d.ts +1 -0
- package/types/server/ssrTypes.d.ts +5 -2
- package/types/server/staticPath.d.ts +1 -0
- package/types/server/systemPageDocument.d.ts +1 -0
- package/types/server/tunnel/TunnelAgent.d.ts +6 -0
- package/types/server/tunnel/TunnelDataSocket.d.ts +2 -0
- package/types/server/tunnel/TunnelTcpStream.d.ts +1 -1
- package/types/server/tunnel/tunnelStream.d.ts +1 -0
- package/types/server/types.d.ts +11 -2
- package/types/service/agent.service.d.ts +1 -1
- package/types/service/liveSyncHub.d.ts +1 -0
- package/types/service/predefinedAdaptor/anthropicLlm.d.ts +1 -1
- package/types/service/predefinedAdaptor/llm.adaptor.d.ts +1 -0
- package/types/service/predefinedAdaptor/openaiDialect.d.ts +1 -1
- package/types/service/predefinedAdaptor/searchIndex.d.ts +5 -1
- package/types/service/predefinedAdaptor/sql/SqlDocumentStore.d.ts +1 -0
- package/types/service/predefinedAdaptor/sql/types.d.ts +3 -0
- package/types/service/types.d.ts +3 -0
- package/types/signal/SignalFailure.d.ts +1 -1
- package/types/signal/agentMeter.d.ts +4 -1
- package/types/signal/agentRelayPolicy.d.ts +12 -0
- package/types/signal/guard.d.ts +1 -1
- package/types/signal/roomKey.d.ts +8 -0
- package/types/signal/signalContext.d.ts +20 -6
- package/types/signal/slice.d.ts +1 -0
- package/types/signal/trace.d.ts +4 -0
- package/types/store/actionTag.d.ts +2 -0
- package/types/store/agent/ScreenReader.d.ts +2 -1
- package/types/store/agent/StoreCatalogue.d.ts +5 -0
- package/types/store/agentic/AgentConfirm.d.ts +15 -0
- package/types/store/agentic/StToolBuilder.d.ts +3 -1
- package/types/store/agentic/index.d.ts +1 -0
- package/types/store/deprecatedNames.d.ts +5 -0
- package/types/store/index.d.ts +1 -2
- package/types/store/sliceKeys.d.ts +12 -2
- package/types/ui/Agent/agentSessionOf.d.ts +2 -0
- package/types/ui/Agent/useAccountBoundSession.d.ts +2 -0
- package/types/ui/Agent/useScreenActions.d.ts +1 -1
- package/types/ui/Icon.d.ts +15 -0
- package/types/ui/animated.d.ts +3 -3
- package/types/ui/sliceNamesOf.d.ts +4 -4
- package/types/vendor/@akanjs/native/packages/cli/src/config.d.ts +10 -0
- package/types/vendor/@akanjs/native/packages/cli/src/lib/updates.d.ts +6 -0
- package/types/vendor/@akanjs/native/packages/core/src/kernel.d.ts +6 -1
- package/types/vendor/@akanjs/native/packages/desktop/src/paths.d.ts +7 -0
- package/types/vendor/@akanjs/native/plugins/single-instance/src/socket.d.ts +2 -0
- package/types/vendor/@akanjs/native/plugins/updates/src/desktop.d.ts +2 -0
- package/types/vendor/use-agentic/AgentSession.d.ts +6 -1
- package/types/vendor/use-agentic/ToolRunner.d.ts +7 -2
- package/types/vendor/use-agentic/types.d.ts +2 -0
- package/types/vendor/use-agentic/useAgentTool.d.ts +2 -1
- package/types/webkit/useCsrValues.d.ts +3 -3
- package/ui/Agent/Chat.tsx +2 -0
- package/ui/Agent/Zone.tsx +2 -0
- package/ui/Agent/agentSessionOf.ts +54 -5
- package/ui/Agent/useAccountBoundSession.ts +26 -0
- package/ui/Agent/useScreenActions.ts +1 -1
- package/ui/BottomSheet.tsx +1 -1
- package/ui/Button.tsx +5 -5
- package/ui/Data/Item.tsx +2 -8
- package/ui/Data/ListContainer.tsx +4 -4
- package/ui/Data/QueryMaker.tsx +3 -2
- package/ui/Data/RefPicker.tsx +3 -2
- package/ui/Data/UnlistedEditModal.tsx +2 -1
- package/ui/Data/ViewActions.tsx +4 -4
- package/ui/Data/dataExport.ts +3 -0
- package/ui/Data/useViewModal.ts +10 -15
- package/ui/Dialog/LegacyModal.tsx +1 -1
- package/ui/Dialog/Provider.tsx +2 -2
- package/ui/DragAction.tsx +1 -1
- package/ui/Dropdown.tsx +2 -2
- package/ui/Field/lightRefCache.ts +2 -2
- package/ui/Field/useRelationOptions.ts +3 -10
- package/ui/Icon.tsx +87 -0
- package/ui/KeyboardAvoiding.tsx +1 -1
- package/ui/Layout/Sider.tsx +3 -3
- package/ui/Link/Anchor.tsx +4 -1
- package/ui/Load/Edit.tsx +2 -2
- package/ui/Load/Edit_Client.tsx +3 -3
- package/ui/Load/Pagination.tsx +3 -3
- package/ui/Load/View.tsx +15 -14
- package/ui/Loading/ProgressBar.tsx +1 -1
- package/ui/Model/DraftBar.tsx +7 -14
- package/ui/Model/EditModal.tsx +9 -40
- package/ui/Model/EditWrapper.tsx +3 -5
- package/ui/Model/NewWrapper_Client.tsx +3 -7
- package/ui/Model/Remove.tsx +2 -2
- package/ui/Model/RemoveWrapper.tsx +2 -4
- package/ui/Model/SureToRemove.tsx +2 -2
- package/ui/Model/ViewEditModal.tsx +9 -20
- package/ui/Model/ViewModal.tsx +4 -11
- package/ui/Model/ViewWrapper.tsx +4 -6
- package/ui/ScreenNavigator.tsx +2 -2
- package/ui/Signal/WebSocket.tsx +2 -2
- package/ui/System/CSR.tsx +3 -3
- package/ui/System/Reconnect.tsx +2 -2
- package/ui/System/SSR.tsx +2 -2
- package/ui/System/SelectLanguage.tsx +1 -1
- package/ui/System/ThemeToggle.tsx +1 -1
- package/ui/Tab/Provider.tsx +1 -0
- package/ui/Toast.tsx +7 -15
- package/ui/index.ts +0 -1
- package/ui/sliceNamesOf.ts +25 -15
- package/vendor/@akanjs/native/native/android/src/com/akanjs/runtime/AkanNativeActivity.kt +2 -1
- package/vendor/@akanjs/native/native/android/src/com/akanjs/runtime/AkanNativeKernel.kt +10 -2
- package/vendor/@akanjs/native/native/android/src/com/akanjs/runtime/AkanNativeVectors.kt +4 -0
- package/vendor/@akanjs/native/native/desktop/src/acl.rs +166 -0
- package/vendor/@akanjs/native/native/desktop/src/lib.rs +25 -6
- package/vendor/@akanjs/native/native/desktop/src/navigation.rs +22 -10
- package/vendor/@akanjs/native/native/desktop/src/routes.rs +2 -2
- package/vendor/@akanjs/native/native/desktop/src/vectors.rs +28 -0
- package/vendor/@akanjs/native/native/ios/Sources/AkanNativeBridge.swift +1 -1
- package/vendor/@akanjs/native/native/ios/Sources/AkanNativeKernel.swift +10 -2
- package/vendor/@akanjs/native/native/ios/Sources/AkanNativeVectors.swift +4 -0
- package/vendor/@akanjs/native/native/ios/Sources/AkanNativeViewController.swift +1 -0
- package/vendor/@akanjs/native/packages/cli/src/config.ts +10 -0
- package/vendor/@akanjs/native/packages/cli/src/lib/configkeys.ts +2 -0
- package/vendor/@akanjs/native/packages/cli/src/lib/csp.ts +5 -3
- package/vendor/@akanjs/native/packages/cli/src/lib/prepare.ts +4 -0
- package/vendor/@akanjs/native/packages/cli/src/lib/publish.ts +2 -0
- package/vendor/@akanjs/native/packages/cli/src/lib/updates.ts +30 -0
- package/vendor/@akanjs/native/packages/cli/src/lib/vectors.ts +1 -1
- package/vendor/@akanjs/native/packages/core/src/kernel.ts +18 -2
- package/vendor/@akanjs/native/packages/core/vectors/navigation.json +22 -1
- package/vendor/@akanjs/native/packages/core/vectors/routes.json +4 -0
- package/vendor/@akanjs/native/packages/desktop/src/main.ts +5 -1
- package/vendor/@akanjs/native/packages/desktop/src/paths.ts +24 -1
- package/vendor/@akanjs/native/plugins/external-view/src/desktop.ts +4 -5
- package/vendor/@akanjs/native/plugins/file-picker/src/desktop.ts +6 -8
- package/vendor/@akanjs/native/plugins/filesystem/src/desktop.ts +4 -2
- package/vendor/@akanjs/native/plugins/single-instance/src/socket.ts +22 -3
- package/vendor/@akanjs/native/plugins/updates/android/UpdatesPlugin.kt +6 -0
- package/vendor/@akanjs/native/plugins/updates/ios/UpdatesPlugin.swift +6 -0
- package/vendor/@akanjs/native/plugins/updates/src/desktop.ts +106 -18
- package/vendor/use-agentic/AgentSession.ts +7 -2
- package/vendor/use-agentic/ToolRunner.ts +44 -5
- package/vendor/use-agentic/types.ts +2 -0
- package/vendor/use-agentic/useAgentTool.ts +4 -2
- package/webkit/McpAppAgent.ts +2 -1
- package/webkit/bootCsr.tsx +1 -1
- package/webkit/useCsrValues.ts +5 -1
- package/webkit/usePageTool.tsx +1 -0
- package/store/databaseStateNames.ts +0 -24
- package/store/formSetterNames.ts +0 -18
- package/types/store/databaseStateNames.d.ts +0 -18
- package/types/store/formSetterNames.d.ts +0 -11
|
@@ -75,7 +75,13 @@ export interface CodeAgentProfile {
|
|
|
75
75
|
paths: { root: string; allow?: string[]; deny?: string[] };
|
|
76
76
|
session: { store: "file" | "memory" | "remote"; crossSession: boolean };
|
|
77
77
|
interaction: { question: CodeAgentInteractionMode; approval: CodeAgentInteractionMode };
|
|
78
|
-
network: {
|
|
78
|
+
network: {
|
|
79
|
+
/** Hosts `web_fetch` may reach at all; any other is refused. Unset reaches any host. */
|
|
80
|
+
allowHosts?: string[];
|
|
81
|
+
/** Hosts (and subdomains) fetched without asking; others and networking commands need approval. Unset never asks. */
|
|
82
|
+
askOutside?: string[];
|
|
83
|
+
proxyBaseUrl?: string;
|
|
84
|
+
};
|
|
79
85
|
ui: { canPrompt: boolean };
|
|
80
86
|
limits: {
|
|
81
87
|
turnMs: number;
|
|
@@ -97,8 +103,49 @@ export const codeAgentReadOnlyBuiltins: CodeAgentBuiltinTool[] = ["read", "ls",
|
|
|
97
103
|
|
|
98
104
|
const baseLimits = { turnMs: 900_000, toolOutputBytes: 200_000, contextTokens: 0, feedback: 2 };
|
|
99
105
|
|
|
100
|
-
/** Paths no profile may read, whatever its allowlist says. */
|
|
101
|
-
export const codeAgentDeniedPaths = [
|
|
106
|
+
/** Paths no profile may read or write, whatever its allowlist says. `~/` is the home directory. */
|
|
107
|
+
export const codeAgentDeniedPaths = [
|
|
108
|
+
"**/.env",
|
|
109
|
+
"**/.env.*",
|
|
110
|
+
|
|
111
|
+
"**/env.server.{debug,develop,main,testing,local}*.ts",
|
|
112
|
+
"**/secrets/**",
|
|
113
|
+
"**/*.pem",
|
|
114
|
+
"**/*.key",
|
|
115
|
+
"**/*-secret.yaml",
|
|
116
|
+
"**/*secrets.yaml",
|
|
117
|
+
"**/kubeconfig*",
|
|
118
|
+
"**/regcred.yaml",
|
|
119
|
+
"**/credentials.sh",
|
|
120
|
+
"**/.jenkins.conf",
|
|
121
|
+
"**/bunkan-id*",
|
|
122
|
+
|
|
123
|
+
"**/.akan/code/mcp.json",
|
|
124
|
+
"**/.akan/code/extensions/**",
|
|
125
|
+
"~/.akan/**",
|
|
126
|
+
"~/.ssh/**",
|
|
127
|
+
"~/.aws/**",
|
|
128
|
+
"~/.kube/**",
|
|
129
|
+
"~/.docker/config.json",
|
|
130
|
+
"~/.netrc",
|
|
131
|
+
"~/.git-credentials",
|
|
132
|
+
];
|
|
133
|
+
|
|
134
|
+
/** Documentation and registry hosts the `local` profile reads without asking; nobody but their owner sees a request. */
|
|
135
|
+
const trustedHosts = [
|
|
136
|
+
"akanjs.com",
|
|
137
|
+
"github.com",
|
|
138
|
+
"githubusercontent.com",
|
|
139
|
+
"npmjs.com",
|
|
140
|
+
"npmjs.org",
|
|
141
|
+
"bun.sh",
|
|
142
|
+
"bun.com",
|
|
143
|
+
"developer.mozilla.org",
|
|
144
|
+
"nodejs.org",
|
|
145
|
+
"typescriptlang.org",
|
|
146
|
+
"react.dev",
|
|
147
|
+
"tailwindcss.com",
|
|
148
|
+
];
|
|
102
149
|
|
|
103
150
|
export const codeAgentPresets = {
|
|
104
151
|
local: (root: string): CodeAgentProfile => ({
|
|
@@ -114,7 +161,7 @@ export const codeAgentPresets = {
|
|
|
114
161
|
paths: { root, deny: codeAgentDeniedPaths },
|
|
115
162
|
session: { store: "file", crossSession: true },
|
|
116
163
|
interaction: { question: "await", approval: "await" },
|
|
117
|
-
network: {},
|
|
164
|
+
network: { askOutside: trustedHosts },
|
|
118
165
|
ui: { canPrompt: true },
|
|
119
166
|
limits: baseLimits,
|
|
120
167
|
context: { projectFiles: true, skills: true },
|
package/common/index.ts
CHANGED
|
@@ -1,3 +1,13 @@
|
|
|
1
|
+
export {
|
|
2
|
+
databaseStateNames,
|
|
3
|
+
endpointNames,
|
|
4
|
+
formSetterNames,
|
|
5
|
+
modelNames,
|
|
6
|
+
moduleNames,
|
|
7
|
+
queryNames,
|
|
8
|
+
sliceNames,
|
|
9
|
+
stateSetterName,
|
|
10
|
+
} from "./akanNames";
|
|
1
11
|
export { AppHosts } from "./appHosts";
|
|
2
12
|
export { applyMixins, getAllPropertyDescriptors } from "./applyMixins";
|
|
3
13
|
export { cookieHeaderHasAuthToken, isAuthTokenKey, legacyAuthTokenKey } from "./authCookie";
|
|
@@ -109,10 +119,13 @@ export {
|
|
|
109
119
|
export {
|
|
110
120
|
type AkanI18nConfig,
|
|
111
121
|
type AkanI18nConfigInput,
|
|
122
|
+
type AkanLocalePrefix,
|
|
123
|
+
akanLocalePrefixes,
|
|
112
124
|
DEFAULT_AKAN_I18N,
|
|
113
125
|
parseAkanI18nEnv,
|
|
114
126
|
resolveAkanI18nConfig,
|
|
115
127
|
} from "./localeConfig";
|
|
128
|
+
export { LocalePath } from "./localePath";
|
|
116
129
|
export {
|
|
117
130
|
type LogContextReader,
|
|
118
131
|
type LogContextSnapshot,
|
package/common/localeConfig.ts
CHANGED
|
@@ -1,16 +1,26 @@
|
|
|
1
|
+
export const akanLocalePrefixes = ["always", "as-needed"] as const;
|
|
2
|
+
export type AkanLocalePrefix = (typeof akanLocalePrefixes)[number];
|
|
3
|
+
|
|
1
4
|
export interface AkanI18nConfig {
|
|
2
5
|
defaultLocale: string;
|
|
3
6
|
locales: string[];
|
|
7
|
+
/**
|
|
8
|
+
* `"always"` (absent means this) serves every page under `/<locale>/…` and redirects an unprefixed path by
|
|
9
|
+
* Accept-Language; `"as-needed"` serves the default locale's pages unprefixed, so a single-locale app has no prefix.
|
|
10
|
+
*/
|
|
11
|
+
localePrefix?: AkanLocalePrefix;
|
|
4
12
|
}
|
|
5
13
|
|
|
6
14
|
export interface AkanI18nConfigInput {
|
|
7
15
|
defaultLocale?: string;
|
|
8
16
|
locales?: readonly string[];
|
|
17
|
+
localePrefix?: AkanLocalePrefix;
|
|
9
18
|
}
|
|
10
19
|
|
|
11
20
|
export const DEFAULT_AKAN_I18N: AkanI18nConfig = Object.freeze({
|
|
12
21
|
defaultLocale: "en",
|
|
13
22
|
locales: Object.freeze(["en", "ko"]) as unknown as string[],
|
|
23
|
+
localePrefix: "always",
|
|
14
24
|
});
|
|
15
25
|
|
|
16
26
|
const normalizeLocale = (locale: string) => locale.trim();
|
|
@@ -18,30 +28,45 @@ const normalizeLocale = (locale: string) => locale.trim();
|
|
|
18
28
|
export function resolveAkanI18nConfig(input: AkanI18nConfigInput = {}): AkanI18nConfig {
|
|
19
29
|
const locales = [...new Set((input.locales ?? DEFAULT_AKAN_I18N.locales).map(normalizeLocale).filter(Boolean))];
|
|
20
30
|
const defaultLocale = normalizeLocale(input.defaultLocale ?? DEFAULT_AKAN_I18N.defaultLocale);
|
|
31
|
+
const localePrefix = input.localePrefix ?? "always";
|
|
21
32
|
|
|
22
33
|
if (locales.length === 0) throw new Error("[i18n] locales must include at least one locale");
|
|
23
34
|
if (!defaultLocale) throw new Error("[i18n] defaultLocale must not be empty");
|
|
24
35
|
if (!locales.includes(defaultLocale)) {
|
|
25
36
|
throw new Error(`[i18n] defaultLocale "${defaultLocale}" must be included in locales: ${locales.join(",")}`);
|
|
26
37
|
}
|
|
38
|
+
if (!akanLocalePrefixes.includes(localePrefix))
|
|
39
|
+
throw new Error(`[i18n] localePrefix "${localePrefix}" must be one of: ${akanLocalePrefixes.join(", ")}`);
|
|
27
40
|
|
|
28
|
-
return { defaultLocale, locales };
|
|
41
|
+
return { defaultLocale, locales, localePrefix };
|
|
29
42
|
}
|
|
30
43
|
|
|
31
|
-
let lastParsed: { defaultLocale?: string; locales?: string; config: AkanI18nConfig } | null =
|
|
44
|
+
let lastParsed: { defaultLocale?: string; locales?: string; localePrefix?: string; config: AkanI18nConfig } | null =
|
|
45
|
+
null;
|
|
32
46
|
|
|
33
47
|
export function parseAkanI18nEnv(
|
|
34
48
|
env: Record<string, string | undefined> = {
|
|
35
49
|
AKAN_PUBLIC_DEFAULT_LOCALE: typeof process !== "undefined" ? process.env.AKAN_PUBLIC_DEFAULT_LOCALE : undefined,
|
|
36
50
|
AKAN_PUBLIC_LOCALES: typeof process !== "undefined" ? process.env.AKAN_PUBLIC_LOCALES : undefined,
|
|
51
|
+
AKAN_PUBLIC_LOCALE_PREFIX: typeof process !== "undefined" ? process.env.AKAN_PUBLIC_LOCALE_PREFIX : undefined,
|
|
37
52
|
},
|
|
38
53
|
): AkanI18nConfig {
|
|
39
54
|
const defaultLocale = env.AKAN_PUBLIC_DEFAULT_LOCALE;
|
|
40
55
|
const locales = env.AKAN_PUBLIC_LOCALES;
|
|
41
|
-
|
|
56
|
+
const localePrefix = env.AKAN_PUBLIC_LOCALE_PREFIX;
|
|
57
|
+
if (
|
|
58
|
+
lastParsed &&
|
|
59
|
+
lastParsed.defaultLocale === defaultLocale &&
|
|
60
|
+
lastParsed.locales === locales &&
|
|
61
|
+
lastParsed.localePrefix === localePrefix
|
|
62
|
+
)
|
|
42
63
|
return lastParsed.config;
|
|
43
|
-
const resolved = resolveAkanI18nConfig({
|
|
64
|
+
const resolved = resolveAkanI18nConfig({
|
|
65
|
+
defaultLocale,
|
|
66
|
+
locales: locales?.split(","),
|
|
67
|
+
localePrefix: (localePrefix || undefined) as AkanLocalePrefix | undefined,
|
|
68
|
+
});
|
|
44
69
|
const config = Object.freeze({ ...resolved, locales: Object.freeze(resolved.locales) as unknown as string[] });
|
|
45
|
-
lastParsed = { defaultLocale, locales, config };
|
|
70
|
+
lastParsed = { defaultLocale, locales, localePrefix, config };
|
|
46
71
|
return config;
|
|
47
72
|
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { AkanI18nConfig } from "./localeConfig";
|
|
2
|
+
|
|
3
|
+
type LocaleRouting = Pick<AkanI18nConfig, "defaultLocale" | "locales" | "localePrefix">;
|
|
4
|
+
|
|
5
|
+
export class LocalePath {
|
|
6
|
+
static omits(locale: string, { defaultLocale, localePrefix }: Omit<LocaleRouting, "locales">) {
|
|
7
|
+
return localePrefix === "as-needed" && locale === defaultLocale;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
static segmentOf(locale: string, i18n: Omit<LocaleRouting, "locales">) {
|
|
11
|
+
return LocalePath.omits(locale, i18n) ? "" : `/${locale}`;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
static split(pathname: string, i18n: LocaleRouting): { locale: string | null; path: string; isPrefixed: boolean } {
|
|
15
|
+
const [, first = "", ...rest] = pathname.split("/");
|
|
16
|
+
if (first && i18n.locales.includes(first)) return { locale: first, path: `/${rest.join("/")}`, isPrefixed: true };
|
|
17
|
+
const locale = i18n.localePrefix === "as-needed" ? i18n.defaultLocale : null;
|
|
18
|
+
return { locale, path: pathname || "/", isPrefixed: false };
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
static join(locale: string, path: string, i18n: Omit<LocaleRouting, "locales">) {
|
|
22
|
+
const segment = LocalePath.segmentOf(locale, i18n);
|
|
23
|
+
if (path === "/" || path === "") return segment || "/";
|
|
24
|
+
return `${segment}${path.startsWith("/") ? path : `/${path}`}`;
|
|
25
|
+
}
|
|
26
|
+
}
|
package/common/markdownSpans.ts
CHANGED
|
@@ -36,7 +36,9 @@ export class MarkdownSpans {
|
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
static isSafeHref(href: string) {
|
|
39
|
-
|
|
39
|
+
|
|
40
|
+
const parsed = href.replace(/[\t\n\r]/g, "").replace(/^[\u0000-\u0020]+/, "");
|
|
41
|
+
return !/^[a-z][a-z0-9+.-]*:/i.test(parsed) || /^(?:https?|mailto|tel):/i.test(parsed);
|
|
40
42
|
}
|
|
41
43
|
|
|
42
44
|
static plain(text: string): string {
|
package/common/mcpExposure.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { modelNames } from "./akanNames";
|
|
2
2
|
|
|
3
3
|
export interface McpExposureEndpoint {
|
|
4
4
|
type: string;
|
|
@@ -24,11 +24,11 @@ export interface McpExposureOption {
|
|
|
24
24
|
export const isMcpDescribableArg = (arg: { refName: string }) => arg.refName !== "Any";
|
|
25
25
|
|
|
26
26
|
export const mcpBaseVerbOf = (refName: string, key: string) => {
|
|
27
|
-
const
|
|
28
|
-
if (key ===
|
|
29
|
-
if (key ===
|
|
30
|
-
if (key ===
|
|
31
|
-
if (key ===
|
|
27
|
+
const names = modelNames(refName);
|
|
28
|
+
if (key === names.model || key === names.lightModel) return "get" as const;
|
|
29
|
+
if (key === names.createModel) return "create" as const;
|
|
30
|
+
if (key === names.updateModel) return "update" as const;
|
|
31
|
+
if (key === names.removeModel) return "remove" as const;
|
|
32
32
|
return null;
|
|
33
33
|
};
|
|
34
34
|
|
|
@@ -55,7 +55,7 @@ export const mcpRefusalOf = (
|
|
|
55
55
|
return `its guards (${(endpoint.guards ?? []).join(", ")}) admit no agent — an act reserved for a person, so it is off the agent shelf. HTTP still serves it.`;
|
|
56
56
|
if (!endpoint.guards?.length)
|
|
57
57
|
return "it declares no guards, and exposure follows them — write `guards: [Public]` if anonymous access is the intent.";
|
|
58
|
-
if (key ===
|
|
58
|
+
if (key === modelNames(refName).lightModel && !view)
|
|
59
59
|
return `it reads the same document as \`${refName}\` in a smaller shape — call \`${refName}\` instead.`;
|
|
60
60
|
if (endpoint.type === "pubsub" || endpoint.type === "message")
|
|
61
61
|
return `\`${endpoint.type}\` rides the websocket, and its internal arguments read a socket an MCP request does not have.`;
|
|
@@ -37,8 +37,58 @@ export const websocketHeartbeatContract = {
|
|
|
37
37
|
makeAck: (): WebsocketHeartbeatAckData => ({ type: "pong" }),
|
|
38
38
|
} as const;
|
|
39
39
|
|
|
40
|
+
const canonicalOf = (value: unknown, holding: Set<object>): unknown => {
|
|
41
|
+
if (typeof value === "bigint") return `${value}n`;
|
|
42
|
+
if (typeof value === "function" || typeof value === "symbol") return undefined;
|
|
43
|
+
if (value === null || typeof value !== "object") return value;
|
|
44
|
+
if (holding.has(value)) throw new Error("A room key cannot be built from a value that contains itself");
|
|
45
|
+
holding.add(value);
|
|
46
|
+
try {
|
|
47
|
+
const toJSON = (value as { toJSON?: unknown }).toJSON;
|
|
48
|
+
if (typeof toJSON === "function") return canonicalOf(toJSON.call(value), holding);
|
|
49
|
+
if (ArrayBuffer.isView(value))
|
|
50
|
+
return { "%bytes": [...new Uint8Array(value.buffer, value.byteOffset, value.byteLength)] };
|
|
51
|
+
if (value instanceof Map)
|
|
52
|
+
return { "%map": [...value].map(([key, item]) => [canonicalOf(key, holding), canonicalOf(item, holding)]) };
|
|
53
|
+
if (value instanceof Set) return { "%set": [...value].map((item) => canonicalOf(item, holding)) };
|
|
54
|
+
if (value instanceof RegExp) return { "%regexp": [value.source, value.flags] };
|
|
55
|
+
if (Array.isArray(value)) return value.map((item) => canonicalOf(item, holding) ?? null);
|
|
56
|
+
const record: Record<string, unknown> = {};
|
|
57
|
+
for (const key of Object.keys(value).sort()) {
|
|
58
|
+
const item = canonicalOf((value as Record<string, unknown>)[key], holding);
|
|
59
|
+
if (item !== undefined) record[key] = item;
|
|
60
|
+
}
|
|
61
|
+
return record;
|
|
62
|
+
} finally {
|
|
63
|
+
holding.delete(value);
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
export const canonicalJson = (value: unknown): string => JSON.stringify(canonicalOf(value, new Set())) ?? "~";
|
|
68
|
+
|
|
69
|
+
const escapeRoomToken = (text: string) =>
|
|
70
|
+
text.replace(/[^A-Za-z0-9_.]/g, (char) => {
|
|
71
|
+
const code = char.charCodeAt(0);
|
|
72
|
+
return code < 0x100
|
|
73
|
+
? `%${code.toString(16).toUpperCase().padStart(2, "0")}`
|
|
74
|
+
: `%u${code.toString(16).toUpperCase().padStart(4, "0")}`;
|
|
75
|
+
});
|
|
76
|
+
|
|
40
77
|
export const websocketRoomContract = {
|
|
78
|
+
/** The label a client files a subscription under; the server joins `roomOf`, and the ack names both. */
|
|
41
79
|
idOf: (key: string, args: unknown[]) => `${key}${args.length ? "-" : ""}${args.join("-")}`,
|
|
80
|
+
roomOf: (key: string, tokens: string[]) => (tokens.length ? `${key}-${tokens.join("-")}` : key),
|
|
81
|
+
/** For an argument of one declared type; `~` is never an escape's output, so it can stand for an absent value. */
|
|
82
|
+
tokenOf: (value: unknown): string => {
|
|
83
|
+
if (value === null || value === undefined) return "~";
|
|
84
|
+
if (typeof value === "string") return escapeRoomToken(value);
|
|
85
|
+
if (typeof value === "number" || typeof value === "boolean" || typeof value === "bigint")
|
|
86
|
+
return escapeRoomToken(String(value));
|
|
87
|
+
const canonical = canonicalOf(value, new Set());
|
|
88
|
+
return escapeRoomToken(typeof canonical === "string" ? canonical : (JSON.stringify(canonical) ?? "~"));
|
|
89
|
+
},
|
|
90
|
+
/** For an `Any` argument, where `"1"` and `1` are different values. */
|
|
91
|
+
jsonTokenOf: (value: unknown): string => (value === undefined ? "~" : escapeRoomToken(canonicalJson(value))),
|
|
42
92
|
} as const;
|
|
43
93
|
|
|
44
94
|
export interface WebsocketBinaryFrame {
|
package/constant/index.ts
CHANGED
|
@@ -13,6 +13,7 @@ export * from "./invalidInputError";
|
|
|
13
13
|
export * from "./labelOf";
|
|
14
14
|
export * from "./mask";
|
|
15
15
|
export * from "./purify";
|
|
16
|
+
export * from "./requestValue";
|
|
16
17
|
export * from "./serialize";
|
|
17
18
|
export * from "./textFieldPathSet";
|
|
18
19
|
export * from "./textFieldPaths";
|
package/constant/inputParser.ts
CHANGED
|
@@ -17,6 +17,7 @@ import type { ConstantField, FieldObject } from "./fieldInfo";
|
|
|
17
17
|
import { FieldPlan, type FieldPlanEntry } from "./fieldPlan";
|
|
18
18
|
import { defaultPlanOf } from "./getDefault";
|
|
19
19
|
import { InvalidInputError } from "./invalidInputError";
|
|
20
|
+
import { RequestValue } from "./requestValue";
|
|
20
21
|
import { deserialize } from "./serialize";
|
|
21
22
|
|
|
22
23
|
type ModelCls = Cls & { [FIELD_META]: FieldObject };
|
|
@@ -50,7 +51,11 @@ export class InputParser {
|
|
|
50
51
|
if (nullable) return null;
|
|
51
52
|
throw new InvalidInputError(key, null);
|
|
52
53
|
}
|
|
53
|
-
if (PrimitiveRegistry.has(argRef))
|
|
54
|
+
if (PrimitiveRegistry.has(argRef)) {
|
|
55
|
+
const parsed = deserialize(argRef, arrDepth, value, option);
|
|
56
|
+
RequestValue.admit(parsed, key);
|
|
57
|
+
return parsed;
|
|
58
|
+
}
|
|
54
59
|
return InputParser.#walk(value, arrDepth, key, (item, path) => InputParser.#value(argRef, item, path));
|
|
55
60
|
}
|
|
56
61
|
|
|
@@ -81,6 +86,7 @@ export class InputParser {
|
|
|
81
86
|
}
|
|
82
87
|
if (enumRef && !enumRef.has(parsed as never))
|
|
83
88
|
throw new InvalidInputError(path, `one of ${enumRef.values.join(", ")}`);
|
|
89
|
+
RequestValue.admit(parsed, path);
|
|
84
90
|
return parsed;
|
|
85
91
|
}
|
|
86
92
|
|
|
@@ -130,7 +136,7 @@ export class InputParser {
|
|
|
130
136
|
static #map(of: Cls | Cls[], value: unknown, path: string) {
|
|
131
137
|
if (!isRecord(value)) throw new InvalidInputError(path, "object");
|
|
132
138
|
const [valueRef, valueArrDepth] = getNonArrayModel(of);
|
|
133
|
-
|
|
139
|
+
const parsed = Object.fromEntries(
|
|
134
140
|
Object.entries(value).map(([key, item]) => [
|
|
135
141
|
key,
|
|
136
142
|
InputParser.#walk(item, valueArrDepth, `${path}.${key}`, (entry, entryPath) =>
|
|
@@ -138,6 +144,8 @@ export class InputParser {
|
|
|
138
144
|
),
|
|
139
145
|
]),
|
|
140
146
|
);
|
|
147
|
+
RequestValue.admit(parsed, path);
|
|
148
|
+
return parsed;
|
|
141
149
|
}
|
|
142
150
|
|
|
143
151
|
static #isRequired(field: ConstantField) {
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { InvalidInputError } from "./invalidInputError";
|
|
2
|
+
|
|
3
|
+
const isPlainRecord = (value: unknown): value is Record<string, unknown> => {
|
|
4
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
5
|
+
const proto = Object.getPrototypeOf(value);
|
|
6
|
+
return proto === Object.prototype || proto === null;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export class RequestValue {
|
|
10
|
+
|
|
11
|
+
static readonly #key = Symbol.for("akanjs.constant.requestValues");
|
|
12
|
+
|
|
13
|
+
static get #values(): WeakSet<object> {
|
|
14
|
+
const holder = globalThis as unknown as Record<symbol, WeakSet<object> | undefined>;
|
|
15
|
+
const values = holder[RequestValue.#key] ?? new WeakSet<object>();
|
|
16
|
+
holder[RequestValue.#key] = values;
|
|
17
|
+
return values;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
static admit(value: unknown, path: string): void {
|
|
21
|
+
if (Array.isArray(value)) {
|
|
22
|
+
value.forEach((item, idx) => {
|
|
23
|
+
RequestValue.admit(item, `${path}.${idx}`);
|
|
24
|
+
});
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
if (!isPlainRecord(value)) return;
|
|
28
|
+
const values = RequestValue.#values;
|
|
29
|
+
if (values.has(value)) return;
|
|
30
|
+
if (RequestValue.#isRawSqlShaped(value)) throw new InvalidInputError(path, "a value, not a SQL condition");
|
|
31
|
+
values.add(value);
|
|
32
|
+
for (const [key, item] of Object.entries(value)) RequestValue.admit(item, `${path}.${key}`);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
static isFrom(value: unknown): boolean {
|
|
36
|
+
return !!value && typeof value === "object" && RequestValue.#values.has(value);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
static #isRawSqlShaped(value: Record<string, unknown>) {
|
|
40
|
+
return (
|
|
41
|
+
value.kind === "raw" &&
|
|
42
|
+
typeof value.sql === "string" &&
|
|
43
|
+
Array.isArray(value.params) &&
|
|
44
|
+
Object.keys(value).every((key) => key === "kind" || key === "sql" || key === "params")
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -26,8 +26,8 @@ export const agentDictionary = serviceDictionary(["en", "ko"])
|
|
|
26
26
|
],
|
|
27
27
|
|
|
28
28
|
llmRequestFailed: [
|
|
29
|
-
"{provider} refused this turn with status {status}
|
|
30
|
-
"{provider}가 이번 턴을 거절했습니다 (status {status})
|
|
29
|
+
"{provider} refused this turn with status {status}",
|
|
30
|
+
"{provider}가 이번 턴을 거절했습니다 (status {status})",
|
|
31
31
|
],
|
|
32
32
|
|
|
33
33
|
contextOverflow: [
|
|
@@ -44,6 +44,10 @@ export const baseDictionary = serviceDictionary(["en", "ko"])
|
|
|
44
44
|
"Too many requests. Please try again in {seconds} seconds.",
|
|
45
45
|
"요청이 너무 많습니다. {seconds}초 후 다시 시도해주세요.",
|
|
46
46
|
],
|
|
47
|
+
payloadTooLarge: [
|
|
48
|
+
"The request is too large for the server to accept.",
|
|
49
|
+
"요청이 너무 커서 서버가 받을 수 없습니다.",
|
|
50
|
+
],
|
|
47
51
|
unexpectedResponse: [
|
|
48
52
|
"The server returned an unexpected response ({status}).",
|
|
49
53
|
"서버가 예상하지 못한 응답을 보냈습니다. ({status})",
|
package/dictionary/dictInfo.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { GetStateObject } from "akanjs/base";
|
|
2
|
-
import { capitalize } from "akanjs/common";
|
|
2
|
+
import { capitalize, modelNames, sliceNames } from "akanjs/common";
|
|
3
3
|
import type { BaseInsight, BaseObject } from "akanjs/constant";
|
|
4
4
|
import type { BaseFilterQueryKey, BaseFilterSortKey } from "akanjs/document";
|
|
5
5
|
import type { DictArgNames, DictEndpointShape, DictFilterQuery, DictFilterSort, DictSliceShape } from "./dictShape";
|
|
@@ -177,23 +177,24 @@ export class ModelDictInfo<
|
|
|
177
177
|
};
|
|
178
178
|
static getBaseSignalDictionary<T extends string>(refName: T): BaseModelCrudGetSignalTranslation<T, [string, string]> {
|
|
179
179
|
const capRefName = capitalize(refName);
|
|
180
|
+
const names = modelNames(refName);
|
|
180
181
|
type Translate = (trans: [string, string]) => FieldTranslation<[string, string]>;
|
|
181
182
|
const crud = (en: string, ko: string) => fn([en, ko]).desc([en, ko]);
|
|
182
183
|
const idArg = (t: Translate) => ({
|
|
183
|
-
[
|
|
184
|
+
[names.modelId]: t(["Id", "아이디"]).desc([`Id of ${capRefName}`, `${capRefName} 아이디`]),
|
|
184
185
|
});
|
|
185
186
|
const dataArg = (t: Translate) => ({
|
|
186
187
|
data: t(["Data", "데이터"]).desc([`Data of ${capRefName}`, `${capRefName} 데이터`]),
|
|
187
188
|
});
|
|
188
189
|
return {
|
|
189
|
-
[
|
|
190
|
-
[
|
|
191
|
-
[
|
|
192
|
-
[
|
|
190
|
+
[names.model]: crud(`Get ${capRefName}`, `${capRefName} 조회`).arg(idArg),
|
|
191
|
+
[names.lightModel]: crud(`Get light version of ${capRefName}`, `${capRefName} 경량화 버전 조회`).arg(idArg),
|
|
192
|
+
[names.createModel]: crud(`Create ${capRefName}`, `${capRefName} 생성`).arg(dataArg),
|
|
193
|
+
[names.updateModel]: crud(`Update ${capRefName}`, `${capRefName} 수정`).arg((t) => ({
|
|
193
194
|
...idArg(t),
|
|
194
195
|
...dataArg(t),
|
|
195
196
|
})),
|
|
196
|
-
[
|
|
197
|
+
[names.removeModel]: crud(`Remove ${capRefName}`, `${capRefName} 삭제`).arg(idArg),
|
|
197
198
|
} as unknown as BaseModelCrudGetSignalTranslation<T, [string, string]>;
|
|
198
199
|
}
|
|
199
200
|
static baseSliceDictionary: {
|
|
@@ -438,8 +439,7 @@ export class ModelDictInfo<
|
|
|
438
439
|
for (const [sliceKey, sliceTrans] of Object.entries(
|
|
439
440
|
this.sliceDictionary as { [key: string]: FunctionTranslation<Languages> },
|
|
440
441
|
)) {
|
|
441
|
-
const listKey =
|
|
442
|
-
const insightKey = `${refName}Insight${capitalize(sliceKey)}`;
|
|
442
|
+
const { list: listKey, insight: insightKey } = sliceNames(refName, sliceKey).endpoint;
|
|
443
443
|
root.head(["signal"], listKey, sliceTrans, (t) => `Slice List - ${t}`);
|
|
444
444
|
root.head(["signal"], insightKey, sliceTrans, (t) => `Slice Insight - ${t}`);
|
|
445
445
|
for (const [argKey, argTrans] of Object.entries(sliceTrans.argTrans as { [key: string]: Translated })) {
|
package/document/database.ts
CHANGED
|
@@ -152,6 +152,7 @@ export type DatabaseInstanceWithQuerySort<
|
|
|
152
152
|
__load: (id?: string) => Promise<Doc | null>;
|
|
153
153
|
__loadMany: (ids: string[]) => Promise<Doc[]>;
|
|
154
154
|
__create: (data: _DataInput) => Promise<Doc>;
|
|
155
|
+
__createMany: (data: _DataInput[]) => Promise<Doc[]>;
|
|
155
156
|
__update: (id: string, data: Partial<Doc>) => Promise<Doc>;
|
|
156
157
|
__remove: (id: string) => Promise<Doc>;
|
|
157
158
|
__removeMany: (query: _QueryOfDoc) => Promise<UpdateResult>;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type Dayjs, dayjs } from "akanjs/base";
|
|
2
2
|
import { isValidDate } from "akanjs/common";
|
|
3
|
+
import { RequestValue } from "akanjs/constant";
|
|
3
4
|
|
|
4
5
|
export type DocumentId = string & { readonly __brand: "DocumentId" };
|
|
5
6
|
|
|
@@ -170,6 +171,40 @@ export const isDocumentQueryNode = (value: unknown): value is DocumentQueryNode
|
|
|
170
171
|
}
|
|
171
172
|
};
|
|
172
173
|
|
|
174
|
+
const builtNodeKey = Symbol.for("akanjs.document.builtQueryNodes");
|
|
175
|
+
const builtNodeHolder = globalThis as unknown as Record<symbol, WeakSet<object> | undefined>;
|
|
176
|
+
const builtNodes = builtNodeHolder[builtNodeKey] ?? new WeakSet<object>();
|
|
177
|
+
builtNodeHolder[builtNodeKey] = builtNodes;
|
|
178
|
+
const built = <Node extends DocumentQueryNode>(node: Node): Node => {
|
|
179
|
+
builtNodes.add(node);
|
|
180
|
+
return node;
|
|
181
|
+
};
|
|
182
|
+
export const isBuiltQueryNode = (node: object) => builtNodes.has(node);
|
|
183
|
+
|
|
184
|
+
export const queryNodeProblem = (node: DocumentQueryNode): string | null => {
|
|
185
|
+
if (RequestValue.isFrom(node))
|
|
186
|
+
return `A q.${node.kind}() condition arrived as a request value; build query conditions with q in code`;
|
|
187
|
+
if ((node.kind === "raw" || node.kind === "search") && !isBuiltQueryNode(node))
|
|
188
|
+
return `A q.${node.kind}() condition must be built by q.${node.kind}(); an object of the same shape is refused`;
|
|
189
|
+
return null;
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
export const fieldValueProblem = (path: string, value: unknown): string | null => {
|
|
193
|
+
|
|
194
|
+
if (
|
|
195
|
+
RequestValue.isFrom(value) &&
|
|
196
|
+
(isDocumentQueryNode(value) || Object.keys(value as object).some((key) => queryOperatorKeys.has(key)))
|
|
197
|
+
)
|
|
198
|
+
return `The value of "${path}" arrived in a request and holds query operators; build the condition with q from checked values`;
|
|
199
|
+
if (isDocumentQueryNode(value)) {
|
|
200
|
+
if (value.kind === "op") return null;
|
|
201
|
+
if (value.kind === "raw" || value.kind === "search")
|
|
202
|
+
return `q.${value.kind}() cannot be the value of "${path}"; put it beside the field's condition`;
|
|
203
|
+
return `q.${value.kind}() cannot be the value of "${path}"; it combines whole conditions, as in q.${value.kind}({ ${path}: ... })`;
|
|
204
|
+
}
|
|
205
|
+
return null;
|
|
206
|
+
};
|
|
207
|
+
|
|
173
208
|
export interface SqlFragment {
|
|
174
209
|
sql: string;
|
|
175
210
|
params: unknown[];
|
|
@@ -202,13 +237,14 @@ export const createDocumentQueryHelper = () => ({
|
|
|
202
237
|
empty: (path: string) => ({ [path]: op("empty") }),
|
|
203
238
|
has: (value: unknown) => op("has", value),
|
|
204
239
|
contains: (value: unknown) => op("contains", value),
|
|
205
|
-
raw: (sql: string, params: unknown[] = []): DocumentQueryNode => ({ kind: "raw", sql, params }),
|
|
240
|
+
raw: (sql: string, params: unknown[] = []): DocumentQueryNode => built({ kind: "raw", sql, params }),
|
|
206
241
|
|
|
207
|
-
search: (text: string, options: DocumentSearchOptions = {}): DocumentQueryNode =>
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
242
|
+
search: (text: string, options: DocumentSearchOptions = {}): DocumentQueryNode =>
|
|
243
|
+
built({
|
|
244
|
+
kind: "search",
|
|
245
|
+
text,
|
|
246
|
+
...options,
|
|
247
|
+
}),
|
|
212
248
|
when: (condition: unknown, query: DocumentQuery): DocumentQuery => (condition ? query : {}),
|
|
213
249
|
});
|
|
214
250
|
|