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.
Files changed (333) 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/dictionary/agent.dictionary.ts +2 -2
  21. package/dictionary/base.dictionary.ts +4 -0
  22. package/dictionary/dictInfo.ts +9 -9
  23. package/document/database.ts +1 -0
  24. package/document/documentQuery.ts +42 -6
  25. package/document/filterMeta.ts +56 -8
  26. package/document/queryEvaluator.ts +13 -8
  27. package/fetch/client/fetchClient.ts +28 -62
  28. package/fetch/client/httpClient.ts +1 -0
  29. package/fetch/client/sliceInitHandle.ts +18 -18
  30. package/fetch/index.ts +1 -0
  31. package/fetch/sessionCookie.ts +37 -0
  32. package/index.ts +8 -0
  33. package/package.json +13 -27
  34. package/server/akanApp.ts +108 -29
  35. package/server/akanServer.ts +18 -7
  36. package/server/assetHeaders.ts +20 -1
  37. package/server/cachePolicy.ts +18 -5
  38. package/server/devtools/depsSerializer.ts +2 -1
  39. package/server/devtools/devtoolsRouter.ts +3 -2
  40. package/server/devtools/signalSerializer.ts +5 -5
  41. package/server/hmr/clientReport.ts +10 -5
  42. package/server/imageOptimizer.ts +117 -22
  43. package/server/lifecycle/buildEnv.ts +11 -0
  44. package/server/lifecycle/loadBuildEnv.ts +3 -0
  45. package/server/logging/logControlSocket.ts +20 -14
  46. package/server/logging/logStreamRoute.ts +12 -8
  47. package/server/logging/rotatingLogWriter.ts +15 -1
  48. package/server/machine/machineToken.ts +4 -0
  49. package/server/mcp/McpAuth.ts +12 -4
  50. package/server/mcp/McpExecutionContext.ts +4 -0
  51. package/server/mcp/McpRouter.ts +25 -13
  52. package/server/mcp/PagePromptComposer.ts +4 -7
  53. package/server/oauth/OAuthClientIdMetadata.ts +29 -6
  54. package/server/oauth/OAuthRegistration.ts +9 -0
  55. package/server/ops/failureLimiter.ts +42 -0
  56. package/server/ops/opsCommand.ts +19 -7
  57. package/server/ops/opsRoute.ts +60 -9
  58. package/server/ops/opsToken.ts +5 -0
  59. package/server/ops/snapshotJobs.ts +3 -1
  60. package/server/ops/snapshotRestore.ts +23 -1
  61. package/server/ops/sqliteSnapshot.ts +98 -18
  62. package/server/ops/statusAccess.ts +37 -19
  63. package/server/proxy/hostBasePathWebProxy.ts +4 -7
  64. package/server/proxy/localeWebProxy.ts +36 -16
  65. package/server/resolver/database.resolver.ts +15 -11
  66. package/server/resolver/service.resolver.ts +17 -2
  67. package/server/resolver/signal.resolver.ts +50 -40
  68. package/server/routeElementComposer.tsx +42 -9
  69. package/server/routeShellGate.ts +42 -0
  70. package/server/routing/apiRouter.ts +64 -1
  71. package/server/routing/serverLimits.ts +151 -0
  72. package/server/routing/soloAppRoutes.ts +23 -12
  73. package/server/rscClient.tsx +5 -3
  74. package/server/rscScrollRestoration.ts +60 -9
  75. package/server/rscWorker.tsx +76 -24
  76. package/server/sitemap.ts +5 -7
  77. package/server/ssrFromRscRenderer.tsx +36 -4
  78. package/server/ssrTypes.ts +5 -2
  79. package/server/staticPath.ts +36 -1
  80. package/server/systemPageDocument.tsx +5 -3
  81. package/server/systemPages.tsx +14 -8
  82. package/server/tunnel/TunnelAgent.ts +19 -2
  83. package/server/tunnel/TunnelDataSocket.ts +4 -2
  84. package/server/tunnel/TunnelHttpStream.ts +7 -2
  85. package/server/tunnel/TunnelTcpStream.ts +6 -3
  86. package/server/tunnel/TunnelWebsocketStream.ts +9 -2
  87. package/server/tunnel/tunnelStream.ts +6 -0
  88. package/server/types.tsx +20 -2
  89. package/server/webRouter.ts +38 -18
  90. package/service/agent.service.ts +9 -7
  91. package/service/injectInfo.ts +2 -1
  92. package/service/liveSyncHub.ts +6 -0
  93. package/service/predefinedAdaptor/anthropicLlm.ts +13 -12
  94. package/service/predefinedAdaptor/database.adaptor.ts +4 -0
  95. package/service/predefinedAdaptor/llm.adaptor.ts +3 -0
  96. package/service/predefinedAdaptor/llmOverflow.ts +5 -1
  97. package/service/predefinedAdaptor/openaiDialect.ts +9 -8
  98. package/service/predefinedAdaptor/searchIndex.ts +21 -1
  99. package/service/predefinedAdaptor/sql/QueryCompiler.ts +28 -4
  100. package/service/predefinedAdaptor/sql/SqlDocumentStore.ts +45 -11
  101. package/service/predefinedAdaptor/sql/dialect/postgres.ts +6 -1
  102. package/service/predefinedAdaptor/sql/types.ts +3 -0
  103. package/service/predefinedAdaptor/storage.adaptor.ts +11 -10
  104. package/service/serve.ts +3 -3
  105. package/service/serviceModule.ts +28 -24
  106. package/service/types.ts +3 -0
  107. package/signal/SignalFailure.ts +5 -2
  108. package/signal/agent/AgentCatalogue.ts +3 -2
  109. package/signal/agentMeter.ts +81 -9
  110. package/signal/agentRelayPolicy.ts +53 -2
  111. package/signal/agentTurnStream.ts +12 -1
  112. package/signal/guard.ts +2 -2
  113. package/signal/mcp/McpDocument.ts +3 -3
  114. package/signal/mcp/McpUriTemplate.ts +5 -5
  115. package/signal/middleware.ts +15 -1
  116. package/signal/roomKey.ts +53 -0
  117. package/signal/signalContext.ts +100 -40
  118. package/signal/slice.ts +27 -22
  119. package/signal/trace.ts +17 -6
  120. package/store/action.ts +20 -49
  121. package/store/actionTag.ts +2 -0
  122. package/store/agent/ScreenReader.ts +36 -4
  123. package/store/agent/ScreenTarget.ts +1 -1
  124. package/store/agent/StoreCatalogue.ts +7 -1
  125. package/store/agent/StoreSurfaceSource.ts +5 -0
  126. package/store/agentic/AgentConfirm.ts +30 -0
  127. package/store/agentic/StStateBuilder.ts +2 -2
  128. package/store/agentic/StToolBuilder.ts +7 -2
  129. package/store/agentic/formFields.ts +1 -2
  130. package/store/agentic/index.ts +1 -0
  131. package/store/agentic/useFieldTool.ts +4 -3
  132. package/store/deprecatedNames.ts +6 -0
  133. package/store/draftStore.ts +2 -2
  134. package/store/formUploadOf.ts +6 -5
  135. package/store/index.ts +1 -2
  136. package/store/sliceKeys.ts +9 -32
  137. package/store/state.ts +1 -1
  138. package/store/stateBuilder.ts +1 -1
  139. package/store/store.ts +1 -1
  140. package/store/storeInstance.ts +12 -8
  141. package/store/storeRegistry.ts +14 -2
  142. package/types/client/cookie.d.ts +7 -2
  143. package/types/client/csrTypes.d.ts +1 -1
  144. package/types/client/router.d.ts +4 -1
  145. package/types/common/akanNames.d.ts +149 -0
  146. package/types/common/codeAgentProfile.d.ts +4 -1
  147. package/types/common/index.d.ts +3 -1
  148. package/types/common/localeConfig.d.ts +8 -0
  149. package/types/common/localePath.d.ts +13 -0
  150. package/types/common/websocketContract.d.ts +7 -0
  151. package/types/constant/index.d.ts +1 -0
  152. package/types/constant/requestValue.d.ts +5 -0
  153. package/types/dictionary/base.dictionary.d.ts +1 -1
  154. package/types/dictionary/dictionary.d.ts +3 -3
  155. package/types/document/database.d.ts +1 -0
  156. package/types/document/documentQuery.d.ts +3 -0
  157. package/types/document/filterMeta.d.ts +4 -0
  158. package/types/fetch/client/sliceInitHandle.d.ts +1 -2
  159. package/types/fetch/index.d.ts +1 -0
  160. package/types/fetch/sessionCookie.d.ts +12 -0
  161. package/types/index.d.ts +8 -0
  162. package/types/server/akanApp.d.ts +1 -0
  163. package/types/server/assetHeaders.d.ts +5 -0
  164. package/types/server/cachePolicy.d.ts +2 -0
  165. package/types/server/devtools/devtoolsRouter.d.ts +1 -1
  166. package/types/server/imageOptimizer.d.ts +4 -1
  167. package/types/server/lifecycle/buildEnv.d.ts +2 -0
  168. package/types/server/lifecycle/loadBuildEnv.d.ts +1 -0
  169. package/types/server/logging/logStreamRoute.d.ts +1 -1
  170. package/types/server/logging/rotatingLogWriter.d.ts +3 -0
  171. package/types/server/machine/machineToken.d.ts +2 -0
  172. package/types/server/mcp/McpAuth.d.ts +2 -1
  173. package/types/server/mcp/McpExecutionContext.d.ts +1 -0
  174. package/types/server/oauth/OAuthRegistration.d.ts +2 -0
  175. package/types/server/ops/failureLimiter.d.ts +11 -0
  176. package/types/server/ops/opsRoute.d.ts +4 -2
  177. package/types/server/ops/opsToken.d.ts +2 -0
  178. package/types/server/ops/snapshotRestore.d.ts +3 -1
  179. package/types/server/ops/sqliteSnapshot.d.ts +7 -1
  180. package/types/server/ops/statusAccess.d.ts +8 -4
  181. package/types/server/proxy/hostBasePathWebProxy.d.ts +1 -0
  182. package/types/server/resolver/signal.resolver.d.ts +0 -1
  183. package/types/server/routeElementComposer.d.ts +10 -4
  184. package/types/server/routeShellGate.d.ts +12 -0
  185. package/types/server/routing/apiRouter.d.ts +6 -1
  186. package/types/server/routing/serverLimits.d.ts +23 -0
  187. package/types/server/routing/soloAppRoutes.d.ts +1 -1
  188. package/types/server/rscScrollRestoration.d.ts +1 -0
  189. package/types/server/ssrTypes.d.ts +5 -2
  190. package/types/server/staticPath.d.ts +1 -0
  191. package/types/server/systemPageDocument.d.ts +1 -0
  192. package/types/server/tunnel/TunnelAgent.d.ts +6 -0
  193. package/types/server/tunnel/TunnelDataSocket.d.ts +2 -0
  194. package/types/server/tunnel/TunnelTcpStream.d.ts +1 -1
  195. package/types/server/tunnel/tunnelStream.d.ts +1 -0
  196. package/types/server/types.d.ts +11 -2
  197. package/types/service/agent.service.d.ts +1 -1
  198. package/types/service/liveSyncHub.d.ts +1 -0
  199. package/types/service/predefinedAdaptor/anthropicLlm.d.ts +1 -1
  200. package/types/service/predefinedAdaptor/llm.adaptor.d.ts +1 -0
  201. package/types/service/predefinedAdaptor/openaiDialect.d.ts +1 -1
  202. package/types/service/predefinedAdaptor/searchIndex.d.ts +5 -1
  203. package/types/service/predefinedAdaptor/sql/SqlDocumentStore.d.ts +1 -0
  204. package/types/service/predefinedAdaptor/sql/types.d.ts +3 -0
  205. package/types/service/types.d.ts +3 -0
  206. package/types/signal/SignalFailure.d.ts +1 -1
  207. package/types/signal/agentMeter.d.ts +4 -1
  208. package/types/signal/agentRelayPolicy.d.ts +12 -0
  209. package/types/signal/guard.d.ts +1 -1
  210. package/types/signal/roomKey.d.ts +8 -0
  211. package/types/signal/signalContext.d.ts +20 -6
  212. package/types/signal/slice.d.ts +1 -0
  213. package/types/signal/trace.d.ts +4 -0
  214. package/types/store/actionTag.d.ts +2 -0
  215. package/types/store/agent/ScreenReader.d.ts +2 -1
  216. package/types/store/agent/StoreCatalogue.d.ts +5 -0
  217. package/types/store/agentic/AgentConfirm.d.ts +15 -0
  218. package/types/store/agentic/StToolBuilder.d.ts +3 -1
  219. package/types/store/agentic/index.d.ts +1 -0
  220. package/types/store/deprecatedNames.d.ts +5 -0
  221. package/types/store/index.d.ts +1 -2
  222. package/types/store/sliceKeys.d.ts +12 -2
  223. package/types/ui/Agent/agentSessionOf.d.ts +2 -0
  224. package/types/ui/Agent/useAccountBoundSession.d.ts +2 -0
  225. package/types/ui/Agent/useScreenActions.d.ts +1 -1
  226. package/types/ui/Icon.d.ts +15 -0
  227. package/types/ui/animated.d.ts +3 -3
  228. package/types/ui/sliceNamesOf.d.ts +4 -4
  229. package/types/vendor/@akanjs/native/packages/cli/src/config.d.ts +10 -0
  230. package/types/vendor/@akanjs/native/packages/cli/src/lib/updates.d.ts +6 -0
  231. package/types/vendor/@akanjs/native/packages/core/src/kernel.d.ts +6 -1
  232. package/types/vendor/@akanjs/native/packages/desktop/src/paths.d.ts +7 -0
  233. package/types/vendor/@akanjs/native/plugins/single-instance/src/socket.d.ts +2 -0
  234. package/types/vendor/@akanjs/native/plugins/updates/src/desktop.d.ts +2 -0
  235. package/types/vendor/use-agentic/AgentSession.d.ts +6 -1
  236. package/types/vendor/use-agentic/ToolRunner.d.ts +7 -2
  237. package/types/vendor/use-agentic/types.d.ts +2 -0
  238. package/types/vendor/use-agentic/useAgentTool.d.ts +2 -1
  239. package/types/webkit/useCsrValues.d.ts +3 -3
  240. package/ui/Agent/Chat.tsx +2 -0
  241. package/ui/Agent/Zone.tsx +2 -0
  242. package/ui/Agent/agentSessionOf.ts +54 -5
  243. package/ui/Agent/useAccountBoundSession.ts +26 -0
  244. package/ui/Agent/useScreenActions.ts +1 -1
  245. package/ui/BottomSheet.tsx +1 -1
  246. package/ui/Button.tsx +5 -5
  247. package/ui/Data/Item.tsx +2 -8
  248. package/ui/Data/ListContainer.tsx +4 -4
  249. package/ui/Data/QueryMaker.tsx +3 -2
  250. package/ui/Data/RefPicker.tsx +3 -2
  251. package/ui/Data/UnlistedEditModal.tsx +2 -1
  252. package/ui/Data/ViewActions.tsx +4 -4
  253. package/ui/Data/dataExport.ts +3 -0
  254. package/ui/Data/useViewModal.ts +10 -15
  255. package/ui/Dialog/LegacyModal.tsx +1 -1
  256. package/ui/Dialog/Provider.tsx +2 -2
  257. package/ui/DragAction.tsx +1 -1
  258. package/ui/Dropdown.tsx +2 -2
  259. package/ui/Field/lightRefCache.ts +2 -2
  260. package/ui/Field/useRelationOptions.ts +3 -10
  261. package/ui/Icon.tsx +87 -0
  262. package/ui/KeyboardAvoiding.tsx +1 -1
  263. package/ui/Layout/Sider.tsx +3 -3
  264. package/ui/Link/Anchor.tsx +4 -1
  265. package/ui/Load/Edit.tsx +2 -2
  266. package/ui/Load/Edit_Client.tsx +3 -3
  267. package/ui/Load/Pagination.tsx +3 -3
  268. package/ui/Load/View.tsx +15 -14
  269. package/ui/Loading/ProgressBar.tsx +1 -1
  270. package/ui/Model/DraftBar.tsx +7 -14
  271. package/ui/Model/EditModal.tsx +9 -40
  272. package/ui/Model/EditWrapper.tsx +3 -5
  273. package/ui/Model/NewWrapper_Client.tsx +3 -7
  274. package/ui/Model/Remove.tsx +2 -2
  275. package/ui/Model/RemoveWrapper.tsx +2 -4
  276. package/ui/Model/SureToRemove.tsx +2 -2
  277. package/ui/Model/ViewEditModal.tsx +9 -20
  278. package/ui/Model/ViewModal.tsx +4 -11
  279. package/ui/Model/ViewWrapper.tsx +4 -6
  280. package/ui/ScreenNavigator.tsx +2 -2
  281. package/ui/Signal/WebSocket.tsx +2 -2
  282. package/ui/System/CSR.tsx +3 -3
  283. package/ui/System/Reconnect.tsx +2 -2
  284. package/ui/System/SSR.tsx +2 -2
  285. package/ui/System/SelectLanguage.tsx +1 -1
  286. package/ui/System/ThemeToggle.tsx +1 -1
  287. package/ui/Tab/Provider.tsx +1 -0
  288. package/ui/Toast.tsx +7 -15
  289. package/ui/index.ts +0 -1
  290. package/ui/sliceNamesOf.ts +25 -15
  291. package/vendor/@akanjs/native/native/android/src/com/akanjs/runtime/AkanNativeActivity.kt +2 -1
  292. package/vendor/@akanjs/native/native/android/src/com/akanjs/runtime/AkanNativeKernel.kt +10 -2
  293. package/vendor/@akanjs/native/native/android/src/com/akanjs/runtime/AkanNativeVectors.kt +4 -0
  294. package/vendor/@akanjs/native/native/desktop/src/acl.rs +166 -0
  295. package/vendor/@akanjs/native/native/desktop/src/lib.rs +25 -6
  296. package/vendor/@akanjs/native/native/desktop/src/navigation.rs +22 -10
  297. package/vendor/@akanjs/native/native/desktop/src/routes.rs +2 -2
  298. package/vendor/@akanjs/native/native/desktop/src/vectors.rs +28 -0
  299. package/vendor/@akanjs/native/native/ios/Sources/AkanNativeBridge.swift +1 -1
  300. package/vendor/@akanjs/native/native/ios/Sources/AkanNativeKernel.swift +10 -2
  301. package/vendor/@akanjs/native/native/ios/Sources/AkanNativeVectors.swift +4 -0
  302. package/vendor/@akanjs/native/native/ios/Sources/AkanNativeViewController.swift +1 -0
  303. package/vendor/@akanjs/native/packages/cli/src/config.ts +10 -0
  304. package/vendor/@akanjs/native/packages/cli/src/lib/configkeys.ts +2 -0
  305. package/vendor/@akanjs/native/packages/cli/src/lib/csp.ts +5 -3
  306. package/vendor/@akanjs/native/packages/cli/src/lib/prepare.ts +4 -0
  307. package/vendor/@akanjs/native/packages/cli/src/lib/publish.ts +2 -0
  308. package/vendor/@akanjs/native/packages/cli/src/lib/updates.ts +30 -0
  309. package/vendor/@akanjs/native/packages/cli/src/lib/vectors.ts +1 -1
  310. package/vendor/@akanjs/native/packages/core/src/kernel.ts +18 -2
  311. package/vendor/@akanjs/native/packages/core/vectors/navigation.json +22 -1
  312. package/vendor/@akanjs/native/packages/core/vectors/routes.json +4 -0
  313. package/vendor/@akanjs/native/packages/desktop/src/main.ts +5 -1
  314. package/vendor/@akanjs/native/packages/desktop/src/paths.ts +24 -1
  315. package/vendor/@akanjs/native/plugins/external-view/src/desktop.ts +4 -5
  316. package/vendor/@akanjs/native/plugins/file-picker/src/desktop.ts +6 -8
  317. package/vendor/@akanjs/native/plugins/filesystem/src/desktop.ts +4 -2
  318. package/vendor/@akanjs/native/plugins/single-instance/src/socket.ts +22 -3
  319. package/vendor/@akanjs/native/plugins/updates/android/UpdatesPlugin.kt +6 -0
  320. package/vendor/@akanjs/native/plugins/updates/ios/UpdatesPlugin.swift +6 -0
  321. package/vendor/@akanjs/native/plugins/updates/src/desktop.ts +106 -18
  322. package/vendor/use-agentic/AgentSession.ts +7 -2
  323. package/vendor/use-agentic/ToolRunner.ts +44 -5
  324. package/vendor/use-agentic/types.ts +2 -0
  325. package/vendor/use-agentic/useAgentTool.ts +4 -2
  326. package/webkit/McpAppAgent.ts +2 -1
  327. package/webkit/bootCsr.tsx +1 -1
  328. package/webkit/useCsrValues.ts +5 -1
  329. package/webkit/usePageTool.tsx +1 -0
  330. package/store/databaseStateNames.ts +0 -24
  331. package/store/formSetterNames.ts +0 -18
  332. package/types/store/databaseStateNames.d.ts +0 -18
  333. package/types/store/formSetterNames.d.ts +0 -11
@@ -1,12 +1,12 @@
1
1
  import type { Cls, EnumInstance, MergeAllDoubleKeyOfObjects } from "akanjs/base";
2
- import { FILTER_DICT_SHAPE, FILTER_META, getNonArrayModel, isEnum } from "akanjs/base";
2
+ import { Any, FILTER_DICT_SHAPE, FILTER_META, getNonArrayModel, isEnum, PrimitiveRegistry } from "akanjs/base";
3
3
  import {
4
4
  type BaseObject,
5
5
  type ConstantFieldType,
6
6
  type ConstantFieldTypeInput,
7
7
  type DocumentModel,
8
- deserialize,
9
8
  type FieldToValue,
9
+ InputParser,
10
10
  type PlainTypeToFieldType,
11
11
  type QueryOf,
12
12
  type Serialized,
@@ -66,7 +66,8 @@ export const getFilterInfoByKey = <ArgNames extends string[] = [], Args extends
66
66
  modelRef: FilterCls,
67
67
  key: string,
68
68
  ): FilterInfo<ArgNames, Args, Model> => {
69
- const queryMeta = getFilterMeta(modelRef).query[key];
69
+ const { query } = getFilterMeta(modelRef);
70
+ const queryMeta = Object.hasOwn(query, key) ? query[key] : undefined;
70
71
  if (!queryMeta) throw new Error(`queryMeta is not defined for key: ${key}`);
71
72
  return queryMeta;
72
73
  };
@@ -78,7 +79,10 @@ export const setFilterInfoByKey = <ArgNames extends string[] = [], Args extends
78
79
  const filterMeta = getFilterMeta(modelRef);
79
80
  Object.assign(filterMeta.query, { [key]: filterInfo });
80
81
  };
81
- export const getFilterSortByKey = (modelRef: FilterCls, key: string) => getFilterMeta(modelRef).sort[key];
82
+ export const getFilterSortByKey = (modelRef: FilterCls, key: string) => {
83
+ const { sort } = getFilterMeta(modelRef);
84
+ return Object.hasOwn(sort, key) ? sort[key] : undefined;
85
+ };
82
86
 
83
87
  export const fillMissingFilterArgs = (filterInfo: FilterInfo, args: unknown[]) => {
84
88
  if (args.length >= filterInfo.args.length) return args;
@@ -110,7 +114,10 @@ export interface FilterArgInfo {
110
114
  enum?: EnumInstance;
111
115
  nullable: boolean;
112
116
  ref?: string;
117
+ maxItems: number;
113
118
  }
119
+
120
+ export const DEFAULT_FILTER_ARG_MAX_ITEMS = 1000;
114
121
  /** Unwraps a filter arg's declaration the way `EndpointInfo.getArgInfo` unwraps an endpoint's. */
115
122
  export const getFilterArgInfo = (arg: FilterInfo["args"][number]): FilterArgInfo => {
116
123
  const [singleArg, arrDepth] = getNonArrayModel(arg.argRef as Cls);
@@ -122,6 +129,7 @@ export const getFilterArgInfo = (arg: FilterInfo["args"][number]): FilterArgInfo
122
129
  enum: argIsEnum ? (singleArg as EnumInstance) : undefined,
123
130
  nullable: !!arg.option?.nullable,
124
131
  ref: arg.option?.ref,
132
+ maxItems: arg.option?.maxItems ?? DEFAULT_FILTER_ARG_MAX_ITEMS,
125
133
  };
126
134
  };
127
135
  export const getFilterArgInfos = (filterInfo: FilterInfo): FilterArgInfo[] => filterInfo.args.map(getFilterArgInfo);
@@ -129,9 +137,37 @@ export const getFilterArgInfos = (filterInfo: FilterInfo): FilterArgInfo[] => fi
129
137
  /** A caller named a filter that does not exist, or gave it arguments it cannot take. Its callers answer 400. */
130
138
  export class FilterQueryError extends Error {}
131
139
 
132
- const tryDeserializeFilterArg = (arg: FilterArgInfo, value: unknown, key: string) => {
140
+ const countItems = (value: unknown, arrDepth: number): number =>
141
+ arrDepth && Array.isArray(value) ? value.reduce((sum: number, item) => sum + countItems(item, arrDepth - 1), 0) : 1;
142
+
143
+ const isPlainContainer = (value: unknown) => {
144
+ if (!value || typeof value !== "object") return false;
145
+ const proto = Object.getPrototypeOf(value);
146
+ return Array.isArray(value) || proto === Object.prototype || proto === null;
147
+ };
148
+
149
+ const primitiveShapeProblem = (value: unknown, arrDepth: number): string | null => {
150
+ if (!arrDepth) return isPlainContainer(value) ? "a single value" : null;
151
+ if (!Array.isArray(value)) return "a list";
152
+ for (const item of value) {
153
+ const problem = primitiveShapeProblem(item, arrDepth - 1);
154
+ if (problem) return problem;
155
+ }
156
+ return null;
157
+ };
158
+
159
+ const parseFilterArg = (arg: FilterArgInfo, value: unknown, key: string) => {
160
+ const shapeProblem = PrimitiveRegistry.has(arg.argRef as Cls) ? primitiveShapeProblem(value, arg.arrDepth) : null;
161
+ if (shapeProblem && value !== null && value !== undefined)
162
+ throw new FilterQueryError(`Invalid filter argument "${arg.name}" for key: ${key}: expected ${shapeProblem}`);
163
+ if (arg.arrDepth && countItems(value, arg.arrDepth) > arg.maxItems)
164
+ throw new FilterQueryError(`Filter argument "${arg.name}" for key: ${key} takes at most ${arg.maxItems} items`);
133
165
  try {
134
- return deserialize(arg.argRef, arg.arrDepth, value, { key: arg.name, nullable: arg.nullable });
166
+ return InputParser.parse(arg.argRef as Cls, arg.arrDepth, value, {
167
+ key: arg.name,
168
+ nullable: arg.nullable,
169
+ enum: arg.enum,
170
+ });
135
171
  } catch (error) {
136
172
  throw new FilterQueryError(
137
173
  `Invalid filter argument "${arg.name}" for key: ${key}: ${error instanceof Error ? error.message : String(error)}`,
@@ -149,7 +185,8 @@ export const resolveFilterQuery = (
149
185
  args?: unknown[] | null,
150
186
  ): QueryOf<any> => {
151
187
  const key = queryKey || "any";
152
- const filterInfo = getFilterMeta(filterRef).query[key];
188
+ const { query } = getFilterMeta(filterRef);
189
+ const filterInfo = Object.hasOwn(query, key) ? query[key] : undefined;
153
190
  const queryFn = filterInfo?.queryFn;
154
191
  if (!queryFn) throw new FilterQueryError(`No filter query for key: ${key}`);
155
192
  const given = Array.isArray(args) ? args : [];
@@ -157,7 +194,7 @@ export const resolveFilterQuery = (
157
194
  const value = given[idx];
158
195
  if (!arg.nullable && (value === null || value === undefined))
159
196
  throw new FilterQueryError(`Missing filter argument "${arg.name}" for key: ${key}`);
160
- const parsed = tryDeserializeFilterArg(arg, value, key);
197
+ const parsed = parseFilterArg(arg, value, key);
161
198
 
162
199
  return parsed === null ? undefined : parsed;
163
200
  });
@@ -276,6 +313,7 @@ interface ArgProps<Value = unknown> {
276
313
  nullable?: boolean;
277
314
  ref?: string;
278
315
  default?: Value;
316
+ maxItems?: number;
279
317
  }
280
318
  export class FilterInfo<ArgNames extends string[] = any, Args extends any[] = any, Model = any> {
281
319
  readonly argNames: ArgNames = [] as unknown as ArgNames;
@@ -293,6 +331,7 @@ export class FilterInfo<ArgNames extends string[] = any, Args extends any[] = an
293
331
  >(name: ArgName, argRef: Arg, option?: Omit<ArgProps<_FieldToValue>, "nullable">) {
294
332
  if (this.queryFn) throw new Error("Query function is already set");
295
333
  else if (this.args.at(-1)?.option?.nullable) throw new Error("Last argument is nullable");
334
+ FilterInfo.#assertTyped(name, argRef);
296
335
  this.argNames.push(name);
297
336
  this.args.push({ name, argRef, option });
298
337
  return this as unknown as FilterInfo<[...ArgNames, ArgName], [...Args, arg: _FieldToValue], Model>;
@@ -304,6 +343,7 @@ export class FilterInfo<ArgNames extends string[] = any, Args extends any[] = an
304
343
  _FieldToValue = DocumentModel<FieldToValue<Arg>>,
305
344
  >(name: ArgName, argRef: Arg, option?: Omit<ArgProps<_FieldToValue>, "nullable">) {
306
345
  if (this.queryFn) throw new Error("Query function is already set");
346
+ FilterInfo.#assertTyped(name, argRef);
307
347
  this.argNames.push(name);
308
348
  this.args.push({ name, argRef, option: { ...option, nullable: true } });
309
349
  return this as unknown as FilterInfo<[...ArgNames, ArgName], [...Args, opt?: _FieldToValue | null], Model>;
@@ -313,6 +353,14 @@ export class FilterInfo<ArgNames extends string[] = any, Args extends any[] = an
313
353
  this.queryFn = query;
314
354
  return this;
315
355
  }
356
+
357
+ static #assertTyped(name: string, argRef: ConstantFieldTypeInput) {
358
+ const [single] = getNonArrayModel(argRef as Cls);
359
+ if (single === Any)
360
+ throw new Error(
361
+ `Filter argument "${name}" is declared as Any; declare its type (a primitive, an enum or a scalar) so a caller's value is checked against it`,
362
+ );
363
+ }
316
364
  }
317
365
 
318
366
  export const filter = () => new FilterInfo<[], [], any>();
@@ -4,7 +4,9 @@ import {
4
4
  type DocumentQueryNode,
5
5
  type DocumentQueryOperator,
6
6
  encodeDocumentValue,
7
+ fieldValueProblem,
7
8
  isDocumentQueryNode,
9
+ queryNodeProblem,
8
10
  queryOperatorKeys,
9
11
  sanitizeJson,
10
12
  } from "./documentQuery";
@@ -51,6 +53,8 @@ export class DocumentQueryEvaluator {
51
53
  static unevaluableReason(query: DocumentQuery | undefined): string | null {
52
54
  if (!query || typeof query !== "object") return null;
53
55
  if (isDocumentQueryNode(query)) {
56
+ const problem = queryNodeProblem(query);
57
+ if (problem) return problem;
54
58
  if (query.kind === "raw") return "q.raw() carries SQL that only the database can run";
55
59
  if (query.kind === "search") return "q.search() compiles to a full-text JOIN with no in-memory equivalent";
56
60
  if (query.kind === "all" || query.kind === "any")
@@ -69,12 +73,12 @@ export class DocumentQueryEvaluator {
69
73
 
70
74
  static #unevaluableField(path: string, value: unknown): string | null {
71
75
  if (!value || typeof value !== "object") return null;
76
+ const problem = fieldValueProblem(path, value);
77
+ if (problem) return problem;
72
78
  if (baseDocumentColumns.has(path)) return null;
73
79
  const keyAbsence = `${path}: exists/missing asks whether the key is in the stored JSON, which a document in hand cannot answer — use q.empty()`;
74
- if (isDocumentQueryNode(value)) {
75
- if (value.kind === "op") return value.op === "exists" || value.op === "missing" ? keyAbsence : null;
76
- return DocumentQueryEvaluator.unevaluableReason(value as DocumentQuery);
77
- }
80
+ if (isDocumentQueryNode(value))
81
+ return value.kind === "op" && (value.op === "exists" || value.op === "missing") ? keyAbsence : null;
78
82
  const operators = value as Record<string, unknown>;
79
83
 
80
84
  if ("exists" in operators || "missing" in operators || ("empty" in operators && !operators.empty))
@@ -102,6 +106,8 @@ export class DocumentQueryEvaluator {
102
106
  }
103
107
 
104
108
  #node(node: DocumentQueryNode, row: DocumentRowView): boolean {
109
+ const problem = queryNodeProblem(node);
110
+ if (problem) throw new Error(problem);
105
111
  switch (node.kind) {
106
112
 
107
113
  case "all":
@@ -119,10 +125,9 @@ export class DocumentQueryEvaluator {
119
125
  }
120
126
 
121
127
  #field(path: string, value: unknown, row: DocumentRowView): boolean {
122
- if (isDocumentQueryNode(value)) {
123
- if (value.kind !== "op") return this.evaluate({ [path]: value } as DocumentQuery, row);
124
- return this.#operator(path, value.op, value.value, row);
125
- }
128
+ const problem = fieldValueProblem(path, value);
129
+ if (problem) throw new Error(problem);
130
+ if (isDocumentQueryNode(value) && value.kind === "op") return this.#operator(path, value.op, value.value, row);
126
131
  if (value && typeof value === "object" && !Array.isArray(value)) {
127
132
  const operators = value as Record<string, unknown>;
128
133
  const keys = Object.keys(operators);
@@ -1,12 +1,14 @@
1
1
  import { getApiPrefix, getEnv, getWsPrefix, PrimitiveRegistry, type PromiseOrObject } from "akanjs/base";
2
2
  import {
3
- capitalize,
3
+ endpointNames,
4
4
  type FetchPolicy,
5
5
  fileUploadContract,
6
6
  Logger,
7
+ modelNames,
7
8
  type RetryPolicy,
8
9
  readSessionToken,
9
10
  resolveFileUploadCapability,
11
+ sliceNames,
10
12
  trimTrailingSlashes,
11
13
  } from "akanjs/common";
12
14
  import { type BaseInsight, type BaseObject, ConstantRegistry, deserialize, serialize } from "akanjs/constant";
@@ -377,7 +379,7 @@ export class FetchClient {
377
379
  Object.entries(this.handler).forEach(([key, handler]) => {
378
380
  if (!(key in instance.handler)) instance.handler[key] = handler;
379
381
  });
380
- instance.setJwt(jwt ?? this.jwt);
382
+ instance.setJwt(jwt ?? (FetchClient.#sameOrigin(instance.origin, this.origin) ? this.jwt : null));
381
383
  if (connect) instance.connect();
382
384
  return FetchClient.#makeProxy(instance);
383
385
  }
@@ -390,7 +392,6 @@ export class FetchClient {
390
392
  setJwt(jwt: string | null) {
391
393
  this.jwt = jwt;
392
394
  this.ws.setJwt(jwt);
393
- for (const ws of this.#originWs.values()) ws.setJwt(jwt);
394
395
  }
395
396
 
396
397
  #resolveWs(origin?: string) {
@@ -400,7 +401,6 @@ export class FetchClient {
400
401
  if (cached) return cached;
401
402
  const ws = new WsClient(FetchClient.#makeWsUri(target), this.ErrorCls).configure(this.#wsOptions);
402
403
  this.#originWs.set(target, ws);
403
- ws.setJwt(this.jwt);
404
404
  ws.connect();
405
405
  return ws;
406
406
  }
@@ -425,9 +425,12 @@ export class FetchClient {
425
425
  static #sameOrigin(a: string, b: string) {
426
426
  return FetchClient.#normalizeOrigin(a) === FetchClient.#normalizeOrigin(b);
427
427
  }
428
+ #ownOrigin(option?: FetchPolicy) {
429
+ return !option?.origin || FetchClient.#sameOrigin(option.origin, this.origin);
430
+ }
428
431
 
429
432
  #forwardsVisitor(option?: FetchPolicy) {
430
- return !this.#foreign && (!option?.origin || FetchClient.#sameOrigin(option.origin, this.origin));
433
+ return !this.#foreign && this.#ownOrigin(option);
431
434
  }
432
435
  #makeAuthHeaders(option?: FetchPolicy): Record<string, string> {
433
436
  if (option?.token) return { Authorization: `Bearer ${option.token}` };
@@ -441,7 +444,7 @@ export class FetchClient {
441
444
  }
442
445
  } catch {
443
446
  }
444
- return this.jwt ? { Authorization: `Bearer ${this.jwt}` } : {};
447
+ return this.jwt && this.#ownOrigin(option) ? { Authorization: `Bearer ${this.jwt}` } : {};
445
448
  }
446
449
  #baseUrlOf(endpoint: SerializedEndpoint, origin?: string) {
447
450
  if (endpoint.globalPrefix !== false) return origin;
@@ -519,7 +522,7 @@ export class FetchClient {
519
522
  this.#setHandlerFactory(key, () => this.#makeHttpFn(key, endpoint, prefix));
520
523
  return;
521
524
  case "pubsub": {
522
- this.#setHandlerFactory(`subscribe${capitalize(key)}`, () => {
525
+ this.#setHandlerFactory(endpointNames(key).subscribe, () => {
523
526
  const roomArgs = endpoint.args.filter((arg) => arg.type === "room");
524
527
  const roomArgLength = roomArgs.length;
525
528
  const serializerMap = this.#makeArgSerializer(endpoint.args);
@@ -567,7 +570,7 @@ export class FetchClient {
567
570
  this.#resolveWs(fetchPolicy?.origin).emit(key, data);
568
571
  };
569
572
  });
570
- this.#setHandlerFactory(`listen${capitalize(key)}`, () => {
573
+ this.#setHandlerFactory(endpointNames(key).listen, () => {
571
574
  const parseReturn = this.#makeReturnParser(endpoint.returns);
572
575
  const wrappedListeners = new WeakMap<(data: unknown) => void, (data: unknown) => void>();
573
576
  return ((handleEvent: (data: unknown) => void, fetchPolicy: FetchPolicy = {}) => {
@@ -654,15 +657,7 @@ export class FetchClient {
654
657
  }
655
658
 
656
659
  static getBaseEndpoint(refName: string, signal: SerializedSignal) {
657
- const capRefName = capitalize(refName);
658
- const names = {
659
- createModel: `create${capRefName}`,
660
- updateModel: `update${capRefName}`,
661
- removeModel: `remove${capRefName}`,
662
- model: refName,
663
- modelId: `${refName}Id`,
664
- lightModel: `light${capRefName}`,
665
- };
660
+ const names = modelNames(refName);
666
661
  const createGuards = FetchClient.#mountedGuards(signal, "create");
667
662
  const updateGuards = FetchClient.#mountedGuards(signal, "update");
668
663
  const removeGuards = FetchClient.#mountedGuards(signal, "remove");
@@ -725,22 +720,6 @@ export class FetchClient {
725
720
  const guards = verb === "get" ? signal.getGuards : (signal[`${verb}Guards`] ?? signal.cruGuards);
726
721
  return guards?.some((guard) => guard !== "None") ? guards : undefined;
727
722
  }
728
- static #modelNames(refName: string) {
729
- const capRefName = capitalize(refName);
730
- return {
731
- createModel: `create${capRefName}`,
732
- updateModel: `update${capRefName}`,
733
- removeModel: `remove${capRefName}`,
734
- model: refName,
735
- lightModel: `light${capRefName}`,
736
- viewModel: `view${capRefName}`,
737
- getModelView: `get${capRefName}View`,
738
- editModel: `edit${capRefName}`,
739
- getModelEdit: `get${capRefName}Edit`,
740
- addModelFiles: `add${capRefName}Files`,
741
- mergeModel: `merge${capRefName}`,
742
- };
743
- }
744
723
 
745
724
  static #helpersOf(signal: SerializedSignal) {
746
725
  const get = !!FetchClient.#mountedGuards(signal, "get");
@@ -749,24 +728,13 @@ export class FetchClient {
749
728
  update || !!FetchClient.#mountedGuards(signal, "create") || !!FetchClient.#mountedGuards(signal, "remove");
750
729
  return { view: get, edit: get && write, merge: update };
751
730
  }
752
- static #sliceNames(refName: string, suffix: string) {
753
- const capSuffix = capitalize(suffix);
754
- const capRefName = capitalize(refName);
755
- return {
756
- list: `${refName}List${capSuffix}`,
757
- insight: `${refName}Insight${capSuffix}`,
758
- live: `${refName}Live${capSuffix}`,
759
- init: `init${capRefName}${capSuffix}`,
760
- getInit: `get${capRefName}Init${capSuffix}`,
761
- };
762
- }
763
731
  /** The `fetch.*` names a module's signal registers, and the bare socket route of each pubsub and message. */
764
732
  static claimsOf(refName: string, signal: SerializedSignal) {
765
733
  const handlers: FetchClaim[] = [];
766
734
  const sockets: FetchClaim[] = [];
767
735
  const claim = (key: string, endpoint: SerializedEndpoint, by: string) => {
768
- handlers.push({ key: endpoint.type === "pubsub" ? `subscribe${capitalize(key)}` : key, by });
769
- if (endpoint.type === "message") handlers.push({ key: `listen${capitalize(key)}`, by });
736
+ handlers.push({ key: endpoint.type === "pubsub" ? endpointNames(key).subscribe : key, by });
737
+ if (endpoint.type === "message") handlers.push({ key: endpointNames(key).listen, by });
770
738
  if (endpoint.type === "pubsub" || endpoint.type === "message") sockets.push({ key, by });
771
739
  };
772
740
  for (const [key, endpoint] of Object.entries(signal.endpoint))
@@ -775,7 +743,7 @@ export class FetchClient {
775
743
  const generated = "the generated CRUD";
776
744
  for (const [key, endpoint] of Object.entries(FetchClient.getBaseEndpoint(refName, signal)))
777
745
  claim(key, endpoint, generated);
778
- const names = FetchClient.#modelNames(refName);
746
+ const names = modelNames(refName);
779
747
  const helpers = FetchClient.#helpersOf(signal);
780
748
  const helperKeys = [
781
749
  ...(helpers.view ? [names.viewModel, names.getModelView] : []),
@@ -788,13 +756,13 @@ export class FetchClient {
788
756
  const by = suffix ? `the slice "${suffix}"` : "the root slice";
789
757
  for (const [key, endpoint] of Object.entries(FetchClient.getEndpointFromSlice(refName, suffix, slice)))
790
758
  claim(key, endpoint, by);
791
- const { init, getInit } = FetchClient.#sliceNames(refName, suffix);
759
+ const { init, getInit } = sliceNames(refName, suffix).endpoint;
792
760
  handlers.push({ key: init, by }, { key: getInit, by });
793
761
  }
794
762
  return { handlers, sockets };
795
763
  }
796
764
  #registerModelBaseEndpoint(refName: string, signal: SerializedSignal) {
797
- const names = FetchClient.#modelNames(refName);
765
+ const names = modelNames(refName);
798
766
 
799
767
  for (const key of Object.values(names).filter((key) => this.#generatedBy.get(key) === refName)) {
800
768
  this.#handlerFactory.delete(key);
@@ -810,7 +778,7 @@ export class FetchClient {
810
778
  if (helpers.view) {
811
779
  this.#setHandlerFactory(
812
780
  names.viewModel,
813
- () => this.#makeModelHandleFn(refName, names.model, names.viewModel, `${refName}View`),
781
+ () => this.#makeModelHandleFn(refName, names.model, names.viewModel, names.modelView),
814
782
  refName,
815
783
  );
816
784
  this.#setHandlerFactory(names.getModelView, () => this.#makeModelObjFn(refName, names.getModelView), refName);
@@ -818,7 +786,7 @@ export class FetchClient {
818
786
  if (helpers.edit) {
819
787
  this.#setHandlerFactory(
820
788
  names.editModel,
821
- () => this.#makeModelHandleFn(refName, names.model, names.editModel, `${refName}Edit`),
789
+ () => this.#makeModelHandleFn(refName, names.model, names.editModel, names.modelEdit),
822
790
  refName,
823
791
  );
824
792
  this.#setHandlerFactory(names.getModelEdit, () => this.#makeModelObjFn(refName, names.getModelEdit), refName);
@@ -867,13 +835,15 @@ export class FetchClient {
867
835
  }
868
836
 
869
837
  #makeModelObjFn(refName: string, callerKey: string) {
838
+ const names = modelNames(refName);
870
839
  return (async (id: string, option?: FetchPolicy) => {
871
840
  const modelObj = await this.#requireHandler(refName, callerKey)(id, { ...option, crystalize: false });
872
- return { refName, [`${refName}Obj`]: modelObj, [`${refName}ViewAt`]: new Date() };
841
+ return { refName, [names.modelObj]: modelObj, [names.modelViewAt]: new Date() };
873
842
  }) as FetchHandler;
874
843
  }
875
844
 
876
845
  #makeModelHandleFn(refName: string, modelKey: string, callerKey: string, payloadKey: string) {
846
+ const names = modelNames(refName);
877
847
  return ((id: string, option?: FetchPolicy) => {
878
848
  const cnst = ConstantRegistry.getDatabase(refName);
879
849
  const modelFn = this.#requireHandler(modelKey, callerKey);
@@ -882,7 +852,7 @@ export class FetchClient {
882
852
  let payload: object | undefined;
883
853
  const modelOf = (modelObj: object) => (model ??= new cnst.full(modelObj));
884
854
  const payloadOf = (modelObj: object) =>
885
- (payload ??= { refName, [`${refName}Obj`]: modelObj, [`${refName}ViewAt`]: new Date() });
855
+ (payload ??= { refName, [names.modelObj]: modelObj, [names.modelViewAt]: new Date() });
886
856
  return FetchHandle.of<Record<string, unknown>, Record<string, Promise<unknown>>>(
887
857
  [modelRequest],
888
858
  async () => {
@@ -902,7 +872,7 @@ export class FetchClient {
902
872
  suffix: string,
903
873
  slice: SerializedSlice,
904
874
  ): { [key: string]: SerializedEndpoint } {
905
- const names = FetchClient.#sliceNames(refName, suffix);
875
+ const names = sliceNames(refName, suffix).endpoint;
906
876
 
907
877
  const mcp = {
908
878
  ...(slice.mcp === false ? { mcp: false as const } : {}),
@@ -936,10 +906,7 @@ export class FetchClient {
936
906
  return endpoint;
937
907
  }
938
908
  #registerSlice(refName: string, suffix: string, slice: SerializedSlice, prefix?: string) {
939
- const capSuffix = capitalize(suffix);
940
- const sliceName = `${refName}${capSuffix}`;
941
- const capRefName = capitalize(refName);
942
- const names = FetchClient.#sliceNames(refName, suffix);
909
+ const { slice: sliceName, endpoint: names } = sliceNames(refName, suffix);
943
910
 
944
911
  const endpoint = FetchClient.getEndpointFromSlice(refName, suffix, slice);
945
912
 
@@ -971,8 +938,7 @@ export class FetchClient {
971
938
  ) as Promise<BaseInsight | null>;
972
939
  return new SliceInitHandle({
973
940
  refName,
974
- capRefName,
975
- capSuffix,
941
+ suffix,
976
942
  sliceName,
977
943
  argLength,
978
944
  queryArgs,
@@ -990,7 +956,7 @@ export class FetchClient {
990
956
  names.init,
991
957
  names.getInit,
992
958
  );
993
- return await initFn(...args)[`${refName}Init${capSuffix}`];
959
+ return await initFn(...args)[names.initResult];
994
960
  });
995
961
  }
996
962
  #makeArgSerializer(args: SerializedArg[]) {
@@ -1117,7 +1083,7 @@ export class FetchClient {
1117
1083
  const cnst = ConstantRegistry.getDatabase(refName, { allowEmpty: true });
1118
1084
  if (!cnst) return;
1119
1085
  const slices = Object.entries(serializedSignal.slice).map(([suffix, serializedSlice]) => {
1120
- const sliceName = `${refName}${capitalize(suffix)}`;
1086
+ const { slice: sliceName } = sliceNames(refName, suffix);
1121
1087
  proxy.slice[sliceName] = { refName, sliceName, argLength: serializedSlice.args.length };
1122
1088
  return { refName, sliceName, serializedSlice };
1123
1089
  });
@@ -25,6 +25,7 @@ const jsonContentType = /^application\/(?:[\w.+-]+\+)?json\b/i;
25
25
 
26
26
  const transportErrorKeyMap = {
27
27
  408: "base.error.gatewayTimeout",
28
+ 413: "base.error.payloadTooLarge",
28
29
  429: "base.error.tooManyRequests",
29
30
  502: "base.error.serverUnavailable",
30
31
  503: "base.error.serverUnavailable",
@@ -1,12 +1,12 @@
1
1
  import { DataList } from "akanjs/base";
2
+ import { modelNames, sliceNames } from "akanjs/common";
2
3
  import { type BaseInsight, type BaseObject, withSharedInstances } from "akanjs/constant";
3
4
 
4
5
  import { FetchHandle } from "./fetchHandle";
5
6
 
6
7
  export interface SliceInitInput {
7
8
  refName: string;
8
- capRefName: string;
9
- capSuffix: string;
9
+ suffix: string;
10
10
  sliceName: string;
11
11
  argLength: number;
12
12
  queryArgs: unknown[];
@@ -35,12 +35,9 @@ export class SliceInitHandle {
35
35
  }
36
36
 
37
37
  build() {
38
- const { refName, capSuffix, listRequest, insightRequest } = this.#input;
39
- const keys = {
40
- init: `${refName}Init${capSuffix}`,
41
- list: `${refName}List${capSuffix}`,
42
- insight: `${refName}Insight${capSuffix}`,
43
- };
38
+ const { refName, suffix, listRequest, insightRequest } = this.#input;
39
+ const { initResult, list, insight } = sliceNames(refName, suffix).endpoint;
40
+ const keys = { init: initResult, list, insight };
44
41
  return FetchHandle.of<Record<string, unknown>, Record<string, Promise<unknown>>>(
45
42
  [listRequest, insightRequest],
46
43
  async () => {
@@ -62,23 +59,26 @@ export class SliceInitHandle {
62
59
  }
63
60
 
64
61
  #serverInitOf(modelObjList: BaseObject[], modelObjInsight: BaseInsight | null) {
65
- const { refName, capRefName, sliceName, argLength, queryArgs, page, limit, sort } = this.#input;
62
+ const { refName, sliceName, argLength, queryArgs, page, limit, sort } = this.#input;
63
+ const { modelObjList: modelObjListKey, modelObjInsight: modelObjInsightKey } = modelNames(refName);
64
+ const { pageOfModel, lastPageOfModel, limitOfModel, hasMoreOfModel, queryArgsOfModel, sortOfModel, modelInitAt } =
65
+ sliceNames(refName).state;
66
66
  const lastPage =
67
67
  modelObjInsight?.count && limit > 0 ? Math.max(Math.floor((modelObjInsight.count - 1) / limit) + 1, 1) : 1;
68
68
  this.#serverInit ??= {
69
69
  refName,
70
70
  sliceName,
71
71
  argLength,
72
- [`${refName}ObjList`]: modelObjList,
73
- [`${refName}ObjInsight`]: modelObjInsight,
74
- [`pageOf${capRefName}`]: page,
75
- [`lastPageOf${capRefName}`]: lastPage,
76
- [`limitOf${capRefName}`]: limit,
72
+ [modelObjListKey]: modelObjList,
73
+ [modelObjInsightKey]: modelObjInsight,
74
+ [pageOfModel]: page,
75
+ [lastPageOfModel]: lastPage,
76
+ [limitOfModel]: limit,
77
77
 
78
- [`hasMoreOf${capRefName}`]: !!limit && modelObjList.length >= limit,
79
- [`queryArgsOf${capRefName}`]: queryArgs,
80
- [`sortOf${capRefName}`]: sort,
81
- [`${refName}InitAt`]: new Date(),
78
+ [hasMoreOfModel]: !!limit && modelObjList.length >= limit,
79
+ [queryArgsOfModel]: queryArgs,
80
+ [sortOfModel]: sort,
81
+ [modelInitAt]: new Date(),
82
82
  };
83
83
  return this.#serverInit;
84
84
  }
package/fetch/index.ts CHANGED
@@ -4,4 +4,5 @@ export * from "./client";
4
4
  export * from "./fetchType";
5
5
  export * from "./renderScope";
6
6
  export * from "./requestStorage";
7
+ export * from "./sessionCookie";
7
8
  export * from "./types";
@@ -0,0 +1,37 @@
1
+ import { getEnv } from "akanjs/base";
2
+ import { authTokenKey } from "akanjs/common";
3
+
4
+ interface SessionCookieIssueOption {
5
+ /** Seconds the pair is kept; left out, both are session cookies. */
6
+ maxAge?: number;
7
+ }
8
+
9
+ export class SessionCookie {
10
+ static claimsKey(): string {
11
+ return `${authTokenKey()}.claims`;
12
+ }
13
+
14
+ static claimsOf(jwt: string): string {
15
+ const [header = "", payload = ""] = jwt.split(".");
16
+ return `${header}.${payload}`;
17
+ }
18
+
19
+ static issue(jwt: string, { maxAge }: SessionCookieIssueOption = {}): string[] {
20
+ const age = maxAge === undefined ? "" : `; Max-Age=${maxAge}`;
21
+ return [
22
+ `${authTokenKey()}=${encodeURIComponent(jwt)}; ${SessionCookie.#attrs()}; HttpOnly${age}`,
23
+ `${SessionCookie.claimsKey()}=${encodeURIComponent(SessionCookie.claimsOf(jwt))}; ${SessionCookie.#attrs()}${age}`,
24
+ ];
25
+ }
26
+
27
+ static expire(): string[] {
28
+ return [
29
+ `${authTokenKey()}=; ${SessionCookie.#attrs()}; HttpOnly; Max-Age=0`,
30
+ `${SessionCookie.claimsKey()}=; ${SessionCookie.#attrs()}; Max-Age=0`,
31
+ ];
32
+ }
33
+
34
+ static #attrs() {
35
+ return `Path=/; SameSite=Lax${getEnv().operationMode === "local" ? "" : "; Secure"}`;
36
+ }
37
+ }
package/index.ts CHANGED
@@ -163,6 +163,10 @@ export interface AkanNativeUpdatesConfig {
163
163
  * phone always takes a new web bundle by itself. Default true.
164
164
  */
165
165
  automatic?: boolean;
166
+ /** Windows: SHA-1 thumbprints a release must be Authenticode-signed by, in place of the installed app's signer. */
167
+ windowsSigners?: string[];
168
+ /** Seconds a published manifest stays valid, at least 3600; left out, manifests never expire. */
169
+ expiresIn?: number;
166
170
  }
167
171
 
168
172
  export interface AkanNativeIosConfig {
@@ -328,6 +332,10 @@ export interface AkanNativeSettings {
328
332
  * internal test target only, since anyone holding the device can then read and drive the page.
329
333
  */
330
334
  devtools?: boolean;
335
+ /** Directives extend the default policy, a string replaces it (`"strict"` is the native preset), false ships none. */
336
+ csp?: false | string | Record<string, string | string[]>;
337
+ /** The URL patterns `opener` may open. Default https, `mailto:` and `tel:` links; `["*"]` lifts the limit. */
338
+ openUrls?: string[];
331
339
  ios?: AkanNativeIosConfig;
332
340
  android?: AkanNativeAndroidConfig;
333
341
  desktop?: AkanNativeDesktopConfig;