akanjs 3.1.1 → 3.1.2-rc.1

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.
Files changed (335) hide show
  1. package/README.ko.md +22 -0
  2. package/README.md +22 -0
  3. package/client/cookie.ts +23 -7
  4. package/client/csrTypes.ts +1 -1
  5. package/client/makePageProto.tsx +10 -1
  6. package/client/router.ts +57 -16
  7. package/common/IpLiteral.ts +4 -2
  8. package/common/TrustedProxy.ts +45 -2
  9. package/common/akanNames.ts +169 -0
  10. package/common/codeAgentProfile.ts +51 -4
  11. package/common/index.ts +13 -0
  12. package/common/localeConfig.ts +30 -5
  13. package/common/localePath.ts +26 -0
  14. package/common/markdownSpans.ts +3 -1
  15. package/common/mcpExposure.ts +7 -7
  16. package/common/websocketContract.ts +50 -0
  17. package/constant/index.ts +1 -0
  18. package/constant/inputParser.ts +10 -2
  19. package/constant/requestValue.ts +47 -0
  20. package/constant/via.ts +3 -13
  21. package/dictionary/agent.dictionary.ts +2 -2
  22. package/dictionary/base.dictionary.ts +4 -0
  23. package/dictionary/dictInfo.ts +9 -9
  24. package/document/database.ts +1 -0
  25. package/document/documentQuery.ts +42 -6
  26. package/document/filterMeta.ts +56 -8
  27. package/document/queryEvaluator.ts +13 -8
  28. package/fetch/client/fetchClient.ts +28 -62
  29. package/fetch/client/httpClient.ts +1 -0
  30. package/fetch/client/sliceInitHandle.ts +18 -18
  31. package/fetch/index.ts +1 -0
  32. package/fetch/sessionCookie.ts +37 -0
  33. package/index.ts +8 -0
  34. package/package.json +13 -27
  35. package/server/akanApp.ts +108 -29
  36. package/server/akanServer.ts +18 -7
  37. package/server/assetHeaders.ts +20 -1
  38. package/server/cachePolicy.ts +18 -5
  39. package/server/devtools/depsSerializer.ts +2 -1
  40. package/server/devtools/devtoolsRouter.ts +3 -2
  41. package/server/devtools/signalSerializer.ts +5 -5
  42. package/server/hmr/clientReport.ts +10 -5
  43. package/server/imageOptimizer.ts +117 -22
  44. package/server/lifecycle/buildEnv.ts +11 -0
  45. package/server/lifecycle/loadBuildEnv.ts +3 -0
  46. package/server/logging/logControlSocket.ts +20 -14
  47. package/server/logging/logStreamRoute.ts +12 -8
  48. package/server/logging/rotatingLogWriter.ts +15 -1
  49. package/server/machine/machineToken.ts +4 -0
  50. package/server/mcp/McpAuth.ts +12 -4
  51. package/server/mcp/McpExecutionContext.ts +4 -0
  52. package/server/mcp/McpRouter.ts +25 -13
  53. package/server/mcp/PagePromptComposer.ts +4 -7
  54. package/server/oauth/OAuthClientIdMetadata.ts +29 -6
  55. package/server/oauth/OAuthRegistration.ts +9 -0
  56. package/server/ops/failureLimiter.ts +42 -0
  57. package/server/ops/opsCommand.ts +19 -7
  58. package/server/ops/opsRoute.ts +60 -9
  59. package/server/ops/opsToken.ts +5 -0
  60. package/server/ops/snapshotJobs.ts +3 -1
  61. package/server/ops/snapshotRestore.ts +23 -1
  62. package/server/ops/sqliteSnapshot.ts +98 -18
  63. package/server/ops/statusAccess.ts +37 -19
  64. package/server/proxy/hostBasePathWebProxy.ts +4 -7
  65. package/server/proxy/localeWebProxy.ts +36 -16
  66. package/server/resolver/database.resolver.ts +15 -11
  67. package/server/resolver/service.resolver.ts +17 -2
  68. package/server/resolver/signal.resolver.ts +50 -40
  69. package/server/routeElementComposer.tsx +42 -9
  70. package/server/routeShellGate.ts +42 -0
  71. package/server/routing/apiRouter.ts +64 -1
  72. package/server/routing/serverLimits.ts +151 -0
  73. package/server/routing/soloAppRoutes.ts +23 -12
  74. package/server/rscClient.tsx +5 -3
  75. package/server/rscScrollRestoration.ts +60 -9
  76. package/server/rscWorker.tsx +76 -24
  77. package/server/sitemap.ts +5 -7
  78. package/server/ssrFromRscRenderer.tsx +36 -4
  79. package/server/ssrTypes.ts +5 -2
  80. package/server/staticPath.ts +36 -1
  81. package/server/systemPageDocument.tsx +5 -3
  82. package/server/systemPages.tsx +14 -8
  83. package/server/tunnel/TunnelAgent.ts +19 -2
  84. package/server/tunnel/TunnelDataSocket.ts +4 -2
  85. package/server/tunnel/TunnelHttpStream.ts +7 -2
  86. package/server/tunnel/TunnelTcpStream.ts +6 -3
  87. package/server/tunnel/TunnelWebsocketStream.ts +9 -2
  88. package/server/tunnel/tunnelStream.ts +6 -0
  89. package/server/types.tsx +20 -2
  90. package/server/webRouter.ts +38 -18
  91. package/service/agent.service.ts +9 -7
  92. package/service/injectInfo.ts +2 -1
  93. package/service/liveSyncHub.ts +6 -0
  94. package/service/predefinedAdaptor/anthropicLlm.ts +13 -12
  95. package/service/predefinedAdaptor/database.adaptor.ts +4 -0
  96. package/service/predefinedAdaptor/llm.adaptor.ts +3 -0
  97. package/service/predefinedAdaptor/llmOverflow.ts +5 -1
  98. package/service/predefinedAdaptor/openaiDialect.ts +9 -8
  99. package/service/predefinedAdaptor/searchIndex.ts +21 -1
  100. package/service/predefinedAdaptor/sql/QueryCompiler.ts +28 -4
  101. package/service/predefinedAdaptor/sql/SqlDocumentStore.ts +45 -11
  102. package/service/predefinedAdaptor/sql/dialect/postgres.ts +6 -1
  103. package/service/predefinedAdaptor/sql/types.ts +3 -0
  104. package/service/predefinedAdaptor/storage.adaptor.ts +11 -10
  105. package/service/serve.ts +3 -3
  106. package/service/serviceModule.ts +28 -24
  107. package/service/types.ts +3 -0
  108. package/signal/SignalFailure.ts +5 -2
  109. package/signal/agent/AgentCatalogue.ts +3 -2
  110. package/signal/agentMeter.ts +81 -9
  111. package/signal/agentRelayPolicy.ts +53 -2
  112. package/signal/agentTurnStream.ts +12 -1
  113. package/signal/guard.ts +2 -2
  114. package/signal/mcp/McpDocument.ts +3 -3
  115. package/signal/mcp/McpUriTemplate.ts +5 -5
  116. package/signal/middleware.ts +15 -1
  117. package/signal/roomKey.ts +53 -0
  118. package/signal/signalContext.ts +100 -40
  119. package/signal/slice.ts +27 -22
  120. package/signal/trace.ts +17 -6
  121. package/store/action.ts +20 -49
  122. package/store/actionTag.ts +2 -0
  123. package/store/agent/ScreenReader.ts +36 -4
  124. package/store/agent/ScreenTarget.ts +1 -1
  125. package/store/agent/StoreCatalogue.ts +7 -1
  126. package/store/agent/StoreSurfaceSource.ts +5 -0
  127. package/store/agentic/AgentConfirm.ts +30 -0
  128. package/store/agentic/StStateBuilder.ts +2 -2
  129. package/store/agentic/StToolBuilder.ts +7 -2
  130. package/store/agentic/formFields.ts +1 -2
  131. package/store/agentic/index.ts +1 -0
  132. package/store/agentic/useFieldTool.ts +4 -3
  133. package/store/deprecatedNames.ts +6 -0
  134. package/store/draftStore.ts +2 -2
  135. package/store/formUploadOf.ts +6 -5
  136. package/store/index.ts +1 -2
  137. package/store/sliceKeys.ts +9 -32
  138. package/store/state.ts +1 -1
  139. package/store/stateBuilder.ts +1 -1
  140. package/store/store.ts +1 -1
  141. package/store/storeInstance.ts +12 -8
  142. package/store/storeRegistry.ts +14 -2
  143. package/types/client/cookie.d.ts +7 -2
  144. package/types/client/csrTypes.d.ts +1 -1
  145. package/types/client/router.d.ts +4 -1
  146. package/types/common/akanNames.d.ts +149 -0
  147. package/types/common/codeAgentProfile.d.ts +4 -1
  148. package/types/common/index.d.ts +3 -1
  149. package/types/common/localeConfig.d.ts +8 -0
  150. package/types/common/localePath.d.ts +13 -0
  151. package/types/common/websocketContract.d.ts +7 -0
  152. package/types/constant/index.d.ts +1 -0
  153. package/types/constant/requestValue.d.ts +5 -0
  154. package/types/constant/via.d.ts +3 -8
  155. package/types/dictionary/base.dictionary.d.ts +1 -1
  156. package/types/dictionary/dictionary.d.ts +3 -3
  157. package/types/document/database.d.ts +1 -0
  158. package/types/document/documentQuery.d.ts +3 -0
  159. package/types/document/filterMeta.d.ts +4 -0
  160. package/types/fetch/client/sliceInitHandle.d.ts +1 -2
  161. package/types/fetch/index.d.ts +1 -0
  162. package/types/fetch/sessionCookie.d.ts +12 -0
  163. package/types/index.d.ts +8 -0
  164. package/types/server/akanApp.d.ts +1 -0
  165. package/types/server/assetHeaders.d.ts +5 -0
  166. package/types/server/cachePolicy.d.ts +2 -0
  167. package/types/server/devtools/devtoolsRouter.d.ts +1 -1
  168. package/types/server/imageOptimizer.d.ts +4 -1
  169. package/types/server/lifecycle/buildEnv.d.ts +2 -0
  170. package/types/server/lifecycle/loadBuildEnv.d.ts +1 -0
  171. package/types/server/logging/logStreamRoute.d.ts +1 -1
  172. package/types/server/logging/rotatingLogWriter.d.ts +3 -0
  173. package/types/server/machine/machineToken.d.ts +2 -0
  174. package/types/server/mcp/McpAuth.d.ts +2 -1
  175. package/types/server/mcp/McpExecutionContext.d.ts +1 -0
  176. package/types/server/oauth/OAuthRegistration.d.ts +2 -0
  177. package/types/server/ops/failureLimiter.d.ts +11 -0
  178. package/types/server/ops/opsRoute.d.ts +4 -2
  179. package/types/server/ops/opsToken.d.ts +2 -0
  180. package/types/server/ops/snapshotRestore.d.ts +3 -1
  181. package/types/server/ops/sqliteSnapshot.d.ts +7 -1
  182. package/types/server/ops/statusAccess.d.ts +8 -4
  183. package/types/server/proxy/hostBasePathWebProxy.d.ts +1 -0
  184. package/types/server/resolver/signal.resolver.d.ts +0 -1
  185. package/types/server/routeElementComposer.d.ts +10 -4
  186. package/types/server/routeShellGate.d.ts +12 -0
  187. package/types/server/routing/apiRouter.d.ts +6 -1
  188. package/types/server/routing/serverLimits.d.ts +23 -0
  189. package/types/server/routing/soloAppRoutes.d.ts +1 -1
  190. package/types/server/rscScrollRestoration.d.ts +1 -0
  191. package/types/server/ssrTypes.d.ts +5 -2
  192. package/types/server/staticPath.d.ts +1 -0
  193. package/types/server/systemPageDocument.d.ts +1 -0
  194. package/types/server/tunnel/TunnelAgent.d.ts +6 -0
  195. package/types/server/tunnel/TunnelDataSocket.d.ts +2 -0
  196. package/types/server/tunnel/TunnelTcpStream.d.ts +1 -1
  197. package/types/server/tunnel/tunnelStream.d.ts +1 -0
  198. package/types/server/types.d.ts +11 -2
  199. package/types/service/agent.service.d.ts +1 -1
  200. package/types/service/liveSyncHub.d.ts +1 -0
  201. package/types/service/predefinedAdaptor/anthropicLlm.d.ts +1 -1
  202. package/types/service/predefinedAdaptor/llm.adaptor.d.ts +1 -0
  203. package/types/service/predefinedAdaptor/openaiDialect.d.ts +1 -1
  204. package/types/service/predefinedAdaptor/searchIndex.d.ts +5 -1
  205. package/types/service/predefinedAdaptor/sql/SqlDocumentStore.d.ts +1 -0
  206. package/types/service/predefinedAdaptor/sql/types.d.ts +3 -0
  207. package/types/service/types.d.ts +3 -0
  208. package/types/signal/SignalFailure.d.ts +1 -1
  209. package/types/signal/agentMeter.d.ts +4 -1
  210. package/types/signal/agentRelayPolicy.d.ts +12 -0
  211. package/types/signal/guard.d.ts +1 -1
  212. package/types/signal/roomKey.d.ts +8 -0
  213. package/types/signal/signalContext.d.ts +20 -6
  214. package/types/signal/slice.d.ts +1 -0
  215. package/types/signal/trace.d.ts +4 -0
  216. package/types/store/actionTag.d.ts +2 -0
  217. package/types/store/agent/ScreenReader.d.ts +2 -1
  218. package/types/store/agent/StoreCatalogue.d.ts +5 -0
  219. package/types/store/agentic/AgentConfirm.d.ts +15 -0
  220. package/types/store/agentic/StToolBuilder.d.ts +3 -1
  221. package/types/store/agentic/index.d.ts +1 -0
  222. package/types/store/deprecatedNames.d.ts +5 -0
  223. package/types/store/index.d.ts +1 -2
  224. package/types/store/sliceKeys.d.ts +12 -2
  225. package/types/ui/Agent/agentSessionOf.d.ts +2 -0
  226. package/types/ui/Agent/useAccountBoundSession.d.ts +2 -0
  227. package/types/ui/Agent/useScreenActions.d.ts +1 -1
  228. package/types/ui/Icon.d.ts +15 -0
  229. package/types/ui/animated.d.ts +3 -3
  230. package/types/ui/sliceNamesOf.d.ts +4 -4
  231. package/types/vendor/@akanjs/native/packages/cli/src/config.d.ts +10 -0
  232. package/types/vendor/@akanjs/native/packages/cli/src/lib/updates.d.ts +6 -0
  233. package/types/vendor/@akanjs/native/packages/core/src/kernel.d.ts +6 -1
  234. package/types/vendor/@akanjs/native/packages/desktop/src/paths.d.ts +7 -0
  235. package/types/vendor/@akanjs/native/plugins/single-instance/src/socket.d.ts +2 -0
  236. package/types/vendor/@akanjs/native/plugins/updates/src/desktop.d.ts +2 -0
  237. package/types/vendor/use-agentic/AgentSession.d.ts +6 -1
  238. package/types/vendor/use-agentic/ToolRunner.d.ts +7 -2
  239. package/types/vendor/use-agentic/types.d.ts +2 -0
  240. package/types/vendor/use-agentic/useAgentTool.d.ts +2 -1
  241. package/types/webkit/useCsrValues.d.ts +3 -3
  242. package/ui/Agent/Chat.tsx +2 -0
  243. package/ui/Agent/Zone.tsx +2 -0
  244. package/ui/Agent/agentSessionOf.ts +54 -5
  245. package/ui/Agent/useAccountBoundSession.ts +26 -0
  246. package/ui/Agent/useScreenActions.ts +1 -1
  247. package/ui/BottomSheet.tsx +1 -1
  248. package/ui/Button.tsx +5 -5
  249. package/ui/Data/Item.tsx +2 -8
  250. package/ui/Data/ListContainer.tsx +4 -4
  251. package/ui/Data/QueryMaker.tsx +3 -2
  252. package/ui/Data/RefPicker.tsx +3 -2
  253. package/ui/Data/UnlistedEditModal.tsx +2 -1
  254. package/ui/Data/ViewActions.tsx +4 -4
  255. package/ui/Data/dataExport.ts +3 -0
  256. package/ui/Data/useViewModal.ts +10 -15
  257. package/ui/Dialog/LegacyModal.tsx +1 -1
  258. package/ui/Dialog/Provider.tsx +2 -2
  259. package/ui/DragAction.tsx +1 -1
  260. package/ui/Dropdown.tsx +2 -2
  261. package/ui/Field/lightRefCache.ts +2 -2
  262. package/ui/Field/useRelationOptions.ts +3 -10
  263. package/ui/Icon.tsx +87 -0
  264. package/ui/KeyboardAvoiding.tsx +1 -1
  265. package/ui/Layout/Sider.tsx +3 -3
  266. package/ui/Link/Anchor.tsx +4 -1
  267. package/ui/Load/Edit.tsx +2 -2
  268. package/ui/Load/Edit_Client.tsx +3 -3
  269. package/ui/Load/Pagination.tsx +3 -3
  270. package/ui/Load/View.tsx +15 -14
  271. package/ui/Loading/ProgressBar.tsx +1 -1
  272. package/ui/Model/DraftBar.tsx +7 -14
  273. package/ui/Model/EditModal.tsx +9 -40
  274. package/ui/Model/EditWrapper.tsx +3 -5
  275. package/ui/Model/NewWrapper_Client.tsx +3 -7
  276. package/ui/Model/Remove.tsx +2 -2
  277. package/ui/Model/RemoveWrapper.tsx +2 -4
  278. package/ui/Model/SureToRemove.tsx +2 -2
  279. package/ui/Model/ViewEditModal.tsx +9 -20
  280. package/ui/Model/ViewModal.tsx +4 -11
  281. package/ui/Model/ViewWrapper.tsx +4 -6
  282. package/ui/ScreenNavigator.tsx +2 -2
  283. package/ui/Signal/WebSocket.tsx +2 -2
  284. package/ui/System/CSR.tsx +3 -3
  285. package/ui/System/Reconnect.tsx +2 -2
  286. package/ui/System/SSR.tsx +2 -2
  287. package/ui/System/SelectLanguage.tsx +1 -1
  288. package/ui/System/ThemeToggle.tsx +1 -1
  289. package/ui/Tab/Provider.tsx +1 -0
  290. package/ui/Toast.tsx +7 -15
  291. package/ui/index.ts +0 -1
  292. package/ui/sliceNamesOf.ts +25 -15
  293. package/vendor/@akanjs/native/native/android/src/com/akanjs/runtime/AkanNativeActivity.kt +2 -1
  294. package/vendor/@akanjs/native/native/android/src/com/akanjs/runtime/AkanNativeKernel.kt +10 -2
  295. package/vendor/@akanjs/native/native/android/src/com/akanjs/runtime/AkanNativeVectors.kt +4 -0
  296. package/vendor/@akanjs/native/native/desktop/src/acl.rs +166 -0
  297. package/vendor/@akanjs/native/native/desktop/src/lib.rs +25 -6
  298. package/vendor/@akanjs/native/native/desktop/src/navigation.rs +22 -10
  299. package/vendor/@akanjs/native/native/desktop/src/routes.rs +2 -2
  300. package/vendor/@akanjs/native/native/desktop/src/vectors.rs +28 -0
  301. package/vendor/@akanjs/native/native/ios/Sources/AkanNativeBridge.swift +1 -1
  302. package/vendor/@akanjs/native/native/ios/Sources/AkanNativeKernel.swift +10 -2
  303. package/vendor/@akanjs/native/native/ios/Sources/AkanNativeVectors.swift +4 -0
  304. package/vendor/@akanjs/native/native/ios/Sources/AkanNativeViewController.swift +1 -0
  305. package/vendor/@akanjs/native/packages/cli/src/config.ts +10 -0
  306. package/vendor/@akanjs/native/packages/cli/src/lib/configkeys.ts +2 -0
  307. package/vendor/@akanjs/native/packages/cli/src/lib/csp.ts +5 -3
  308. package/vendor/@akanjs/native/packages/cli/src/lib/prepare.ts +4 -0
  309. package/vendor/@akanjs/native/packages/cli/src/lib/publish.ts +2 -0
  310. package/vendor/@akanjs/native/packages/cli/src/lib/updates.ts +30 -0
  311. package/vendor/@akanjs/native/packages/cli/src/lib/vectors.ts +1 -1
  312. package/vendor/@akanjs/native/packages/core/src/kernel.ts +18 -2
  313. package/vendor/@akanjs/native/packages/core/vectors/navigation.json +22 -1
  314. package/vendor/@akanjs/native/packages/core/vectors/routes.json +4 -0
  315. package/vendor/@akanjs/native/packages/desktop/src/main.ts +5 -1
  316. package/vendor/@akanjs/native/packages/desktop/src/paths.ts +24 -1
  317. package/vendor/@akanjs/native/plugins/external-view/src/desktop.ts +4 -5
  318. package/vendor/@akanjs/native/plugins/file-picker/src/desktop.ts +6 -8
  319. package/vendor/@akanjs/native/plugins/filesystem/src/desktop.ts +4 -2
  320. package/vendor/@akanjs/native/plugins/single-instance/src/socket.ts +22 -3
  321. package/vendor/@akanjs/native/plugins/updates/android/UpdatesPlugin.kt +6 -0
  322. package/vendor/@akanjs/native/plugins/updates/ios/UpdatesPlugin.swift +6 -0
  323. package/vendor/@akanjs/native/plugins/updates/src/desktop.ts +106 -18
  324. package/vendor/use-agentic/AgentSession.ts +7 -2
  325. package/vendor/use-agentic/ToolRunner.ts +44 -5
  326. package/vendor/use-agentic/types.ts +2 -0
  327. package/vendor/use-agentic/useAgentTool.ts +4 -2
  328. package/webkit/McpAppAgent.ts +2 -1
  329. package/webkit/bootCsr.tsx +1 -1
  330. package/webkit/useCsrValues.ts +5 -1
  331. package/webkit/usePageTool.tsx +1 -0
  332. package/store/databaseStateNames.ts +0 -24
  333. package/store/formSetterNames.ts +0 -18
  334. package/types/store/databaseStateNames.d.ts +0 -18
  335. 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: { allowHosts?: string[]; proxyBaseUrl?: string };
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 = ["**/.env", "**/.env.*", "**/secrets/**", "**/*.pem", "**/*.key"];
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,
@@ -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 = 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
- if (lastParsed && lastParsed.defaultLocale === defaultLocale && lastParsed.locales === locales)
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({ defaultLocale, locales: locales?.split(",") });
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
+ }
@@ -36,7 +36,9 @@ export class MarkdownSpans {
36
36
  }
37
37
 
38
38
  static isSafeHref(href: string) {
39
- return !/^[a-z][a-z0-9+.-]*:/i.test(href) || /^(?:https?|mailto|tel):/i.test(href);
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 {
@@ -1,4 +1,4 @@
1
- import { capitalize } from "./capitalize";
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 cap = capitalize(refName);
28
- if (key === refName || key === `light${cap}`) return "get" as const;
29
- if (key === `create${cap}`) return "create" as const;
30
- if (key === `update${cap}`) return "update" as const;
31
- if (key === `remove${cap}`) return "remove" as const;
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 === `light${capitalize(refName)}` && !view)
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";
@@ -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)) return deserialize(argRef, arrDepth, value, option);
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
- return Object.fromEntries(
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
+ }
package/constant/via.ts CHANGED
@@ -209,10 +209,6 @@ export interface ConstantMethods<Schema = any> {
209
209
  set(obj: Partial<Schema>): this;
210
210
  }
211
211
 
212
- type DatabaseSchemaOf<Schema, HiddenKey> = {
213
- [K in keyof Schema]: K extends HiddenKey ? NonNullable<Schema[K]> : Schema[K];
214
- };
215
-
216
212
  export interface ConstantStatics<
217
213
  Schema = any,
218
214
  OwnSchema = Schema,
@@ -246,7 +242,7 @@ export interface ConstantStatics<
246
242
  _SecretKey: SecretKey;
247
243
  _OwnSchema: OwnSchema;
248
244
  _OwnFieldObj: OwnFieldObj;
249
- _DatabaseSchema: DatabaseSchemaOf<Schema, HiddenKey>;
245
+ _DatabaseSchema: Schema;
250
246
  _ModelType: ModelType;
251
247
  [SERVER_VALUE]: Schema;
252
248
  [CLIENT_VALUE]: Schema;
@@ -264,13 +260,7 @@ export interface DatabaseConstantStatics<Schema = any, FieldObj extends FieldObj
264
260
  enums: Set<EnumInstance>;
265
261
  text: TextFieldPaths;
266
262
  cascade: CascadePaths;
267
- _DatabaseSchema: {
268
- [K in keyof Schema]: K extends keyof FieldObj
269
- ? FieldObj[K]["fieldType"] extends "hidden"
270
- ? NonNullable<Schema[K]>
271
- : Schema[K]
272
- : Schema[K];
273
- };
263
+ _DatabaseSchema: Schema;
274
264
  }
275
265
 
276
266
  export type ConstantModelRef<
@@ -294,7 +284,7 @@ export type DocumentConstantModelRef<
294
284
  Schema = any,
295
285
  FieldObj extends FieldObject = FieldObject,
296
286
  ModelType extends ConstantType = ConstantType,
297
- DatabaseSchema = DatabaseSchemaOf<Schema, never>,
287
+ DatabaseSchema = Schema,
298
288
  > = Cls<
299
289
  Schema,
300
290
  {
@@ -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}. Reason: {reason}",
30
- "{provider}가 이번 턴을 거절했습니다 (status {status}). 사유: {reason}",
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})",
@@ -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
- [`${refName}Id`]: t(["Id", "아이디"]).desc([`Id of ${capRefName}`, `${capRefName} 아이디`]),
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
- [refName]: crud(`Get ${capRefName}`, `${capRefName} 조회`).arg(idArg),
190
- [`light${capRefName}`]: crud(`Get light version of ${capRefName}`, `${capRefName} 경량화 버전 조회`).arg(idArg),
191
- [`create${capRefName}`]: crud(`Create ${capRefName}`, `${capRefName} 생성`).arg(dataArg),
192
- [`update${capRefName}`]: crud(`Update ${capRefName}`, `${capRefName} 수정`).arg((t) => ({
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
- [`remove${capRefName}`]: crud(`Remove ${capRefName}`, `${capRefName} 삭제`).arg(idArg),
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 = `${refName}List${capitalize(sliceKey)}`;
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 })) {
@@ -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
- kind: "search",
209
- text,
210
- ...options,
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