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
package/README.ko.md CHANGED
@@ -46,6 +46,28 @@ akan start <app-name> --open
46
46
  워크스페이스 이름과 앱 이름을 물은 뒤 `akan` CLI와 워크스페이스 의존성을 설치하고 샘플 앱을 만들어 줍니다. 앱은
47
47
  `http://localhost:8282`에서 열립니다.
48
48
 
49
+ ### 기존 저장소 안에서 시작하기
50
+
51
+ 앱이 놓일 폴더와 실행 방식이 정해진 작업이라면, 예를 들어 다른 저장소의 하위 폴더, Node와 npm만 있는 호스트, 스펙이 정한
52
+ URL이라면, 코딩 에이전트는 묻지 않고 아래대로 진행하면 됩니다.
53
+
54
+ - **그 폴더에 바로 만들기:** `bunx create-akan-workspace@latest . --app <app> --dir <folder> --sample false`.
55
+ `<folder>` 자체에 쓰고, 이미 있는 파일은 덮어쓰지 않고 멈추며, 샘플 모듈과 페이지는 만들지 않습니다. 함께 생기는
56
+ `AGENTS.md`, `CLAUDE.md`, `.claude/`, `.agents/`, `.cursor/`, `.mcp.json`은 지우지 마세요. 에이전트가 따를 규칙과
57
+ 도구가 들어 있습니다.
58
+ - **`<folder>`에서 작업하기.** `akan`은 워크스페이스 루트에서만 실행되고, 그곳에서 연 에이전트 세션이 워크스페이스의
59
+ 규칙과 MCP 서버를 불러옵니다.
60
+ - **변경마다 `akan verify <app>`로 확인하기.** sync, lint, 타입체크, 빌드, SSR 검사를 한 번에 하고 실패한 것만
61
+ 출력합니다. `akan preview <app> --changed`는 변경이 닿는 페이지를 열어 봅니다.
62
+ - **정해진 URL:** 페이지는 기본적으로 `/<locale>/…` 아래에 있습니다. 기본 로케일을 프리픽스 없는 경로
63
+ (`/pokemon/deoxys`)로 내보내려면 `akan.config.ts`에 `i18n: { defaultLocale: "en", locales: ["en"], localePrefix:
64
+ "as-needed" }`를 두세요. `<Link>`도 따라갑니다.
65
+ - **Node와 npm만 있는 호스트:** `npm install -g bun`으로 생성기를 실행할 런타임을 받으세요. 호스트에도 설치되도록
66
+ 워크스페이스의 `dependencies`에 `bun`을 두고, package script에서는 `devDependencies`의 `@akanjs/cli`로 `akan`을
67
+ 부르세요. 전역 설치 뒤 `akan`을 찾지 못하면 `bun pm bin -g`가 출력하는 폴더를 `PATH`에 더하세요.
68
+ - **Docker 없이 프로덕션 빌드 실행:** `akan build <app>` 뒤 `cd dist/apps/<app> && PORT=3000 bun main.js`. 빌드에
69
+ 필요한 env가 함께 들어 있습니다.
70
+
49
71
  ## 이 모든 게 한 줄입니다
50
72
 
51
73
  ```ts
package/README.md CHANGED
@@ -45,6 +45,28 @@ akan start <app-name> --open
45
45
  The creator asks for a workspace name and an app name, installs the `akan` CLI and the workspace's dependencies, and
46
46
  generates a sample app. The app opens on `http://localhost:8282`.
47
47
 
48
+ ### Inside an existing repository
49
+
50
+ When a task fixes where the app lives and how it runs, for example a folder in another repository, a host with only
51
+ Node and npm, or URLs given by a spec, a coding agent needs these, and nothing has to ask:
52
+
53
+ - **Create the workspace in that folder:** `bunx create-akan-workspace@latest . --app <app> --dir <folder> --sample false`.
54
+ It writes into `<folder>` itself, refuses to overwrite a file already there, and leaves out the sample module and
55
+ pages. Keep the `AGENTS.md`, `CLAUDE.md`, `.claude/`, `.agents/`, `.cursor/` and `.mcp.json` it writes: they carry
56
+ the rules and tools an agent works through.
57
+ - **Work from `<folder>`.** `akan` runs only from the workspace root, and an agent session opened there loads the
58
+ workspace's rules and MCP server.
59
+ - **Check each change with `akan verify <app>`.** It syncs, lints, typechecks, builds and checks SSR, and prints only
60
+ what failed. `akan preview <app> --changed` opens the pages a change reaches.
61
+ - **Exact URLs:** pages live under `/<locale>/…`. To serve the default locale at the bare path (`/pokemon/deoxys`),
62
+ set `i18n: { defaultLocale: "en", locales: ["en"], localePrefix: "as-needed" }` in `akan.config.ts`; `<Link>`
63
+ follows.
64
+ - **A host with only Node and npm:** `npm install -g bun` gives the creator its runtime. Put `bun` in the workspace's
65
+ `dependencies` so the host installs it too, and let the package scripts call `akan` from the `@akanjs/cli` in
66
+ `devDependencies`. If `akan` is not found after a global install, add the folder `bun pm bin -g` prints to `PATH`.
67
+ - **Run the production build without Docker:** `akan build <app>`, then `cd dist/apps/<app> && PORT=3000 bun main.js`.
68
+ The build carries the env it needs.
69
+
48
70
  ## All Of It Is One Line
49
71
 
50
72
  ```ts
package/client/cookie.ts CHANGED
@@ -14,7 +14,7 @@ import {
14
14
  SessionCookies,
15
15
  } from "akanjs/common";
16
16
  import type { Account } from "akanjs/fetch";
17
- import { cookieJar as serverCookieJar, headers as serverHeaders } from "akanjs/fetch";
17
+ import { SessionCookie, cookieJar as serverCookieJar, headers as serverHeaders } from "akanjs/fetch";
18
18
  import { secretStorage } from "./storage";
19
19
  import { fetch } from "./useClient";
20
20
 
@@ -114,10 +114,19 @@ const sentAuthToken = (): string | undefined => {
114
114
  }
115
115
  };
116
116
 
117
+ /** The session's claims from its readable cookie: a JWT without its signature, so never a credential. */
118
+ export const getSessionClaims = (): string | undefined =>
119
+ isCsrClient() ? undefined : cookieJar().read(SessionCookie.claimsKey()).value || undefined;
120
+
121
+ const serverHoldsSession = () => getEnv().side !== "server" && !!getSessionClaims();
122
+
117
123
  /** The auth token this app holds: on a CSR client the one `fetch` sends first, since a native shell's page keeps no
118
- * session cookie; then the app-scoped cookie, unless it is held more than once. */
124
+ * session cookie; then the app-scoped cookie, unless it is held more than once; then, while the server holds the
125
+ * session where no script reads it, the token this page was handed at sign-in, if any. */
119
126
  export const getAuthToken = (): string | undefined =>
120
- (isCsrClient() ? sentAuthToken() : undefined) ?? readSessionToken(cookieJar()).token;
127
+ (isCsrClient() ? sentAuthToken() : undefined) ??
128
+ readSessionToken(cookieJar()).token ??
129
+ (serverHoldsSession() ? sentAuthToken() : undefined);
121
130
 
122
131
  /** The auth token this app holds in client storage — the OS credential store in a native app, localStorage elsewhere. */
123
132
  export const getStoredAuthToken = async (): Promise<string | undefined> =>
@@ -134,7 +143,7 @@ export const getAccount = <AddData = unknown>(): Account<AddData> => {
134
143
  const embedded = globalThis.__AKAN_EMBED__;
135
144
  if (embedded)
136
145
  return embedded.account ? ({ ...defaultAccount, ...embedded.account } as Account<AddData>) : defaultAccount;
137
- const jwt = getAuthToken() ?? bearerTokenOf(getHeader("authorization"));
146
+ const jwt = getAuthToken() ?? bearerTokenOf(getHeader("authorization")) ?? getSessionClaims();
138
147
  if (!jwt) return defaultAccount;
139
148
  const account = decodeJwtPayload<Account<AddData>>(jwt);
140
149
  if (account.appName !== getEnv().appName || account.environment !== getEnv().environment) return defaultAccount;
@@ -149,12 +158,17 @@ interface SetAuthOption {
149
158
  const announceAuth = () => {
150
159
  if (typeof window !== "undefined") window.dispatchEvent(new Event("akan:auth"));
151
160
  };
152
- /** Sets the active auth token on fetch, the cookie jar (except on a CSR client), and client storage. */
161
+ /** Sets the active auth token on fetch, then keeps it where the next launch finds it: client storage on a CSR client,
162
+ * the session cookie elsewhere unless the server already holds it there (`SessionCookie.issue`). */
153
163
  export const setAuth = ({ jwt }: SetAuthOption) => {
154
164
  fetch.setJwt(jwt);
155
165
 
156
- if (!isCsrClient()) setCookie(authTokenKey(), jwt);
157
- void secretStorage.setItem(storedAuthTokenKey(), jwt);
166
+ if (isCsrClient()) void secretStorage.setItem(storedAuthTokenKey(), jwt);
167
+ else {
168
+
169
+ void secretStorage.removeItem(storedAuthTokenKey());
170
+ if (!serverHoldsSession()) setCookie(authTokenKey(), jwt);
171
+ }
158
172
 
159
173
  for (const key of retiredAuthTokenKeys()) removeCookie(key);
160
174
  void secretStorage.removeItem(legacyAuthTokenKey);
@@ -209,9 +223,11 @@ export const initAuth = ({ jwt }: InitAuthOption = {}) => {
209
223
  Logger.verbose(`JWT ${token ? "restored" : "not found"}`);
210
224
  };
211
225
 
226
+ /** Signs this page out; an HttpOnly session the server holds ends only with `SessionCookie.expire`. */
212
227
  export const resetAuth = () => {
213
228
  fetch.setJwt(null);
214
229
  removeCookie(authTokenKey());
230
+ removeCookie(SessionCookie.claimsKey());
215
231
  for (const key of retiredAuthTokenKeys()) removeCookie(key);
216
232
  void secretStorage.removeItem(storedAuthTokenKey());
217
233
  void secretStorage.removeItem(legacyAuthTokenKey);
@@ -1,8 +1,8 @@
1
1
  "use client";
2
+ import type { AnimatedComponent, AnimatedProps, Interpolation, SpringValue } from "@react-spring/web";
2
3
  import type { ReactDOMAttributes } from "@use-gesture/react/dist/declarations/src/types";
3
4
  import type { PromiseOrObject } from "akanjs/base";
4
5
  import { type ForwardRefExoticComponent, type ReactNode, type RefObject, useContext } from "react";
5
- import type { AnimatedComponent, AnimatedProps, Interpolation, SpringValue } from "react-spring";
6
6
  import type { TailwindBreakpoint } from "./frameConfig";
7
7
  import type { RouteDefinition } from "./route/RouteDefinition";
8
8
  import type { RouterInstance } from "./router";
@@ -21,6 +21,15 @@ interface ErrPayload extends ErrRestoreOption {
21
21
  data?: Record<string, unknown>;
22
22
  }
23
23
 
24
+ const htmlEntities: Record<string, string> = { "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" };
25
+ const escapeRichParams = (param: { [key: string]: string | number } | undefined) =>
26
+ Object.fromEntries(
27
+ Object.entries(param ?? {}).map(([key, value]) => [
28
+ key,
29
+ String(value).replace(/[&<>"']/g, (char) => htmlEntities[char] ?? char),
30
+ ]),
31
+ );
32
+
24
33
  const getPageInfo = (): { locale: string; path: string } => {
25
34
  const { defaultLocale, locales } = parseAkanI18nEnv();
26
35
  const localeSet = new Set(locales);
@@ -159,7 +168,7 @@ export const makePageProto = <
159
168
 
160
169
  dangerouslySetInnerHTML={{
161
170
  __html: translator.translate(lang, key, {
162
- ...param,
171
+ ...escapeRichParams(param),
163
172
  br: `<br />`,
164
173
  }),
165
174
  }}
package/client/router.ts CHANGED
@@ -1,6 +1,8 @@
1
1
  import { getEnv } from "akanjs/base";
2
2
  import {
3
+ type AkanLocalePrefix,
3
4
  getBasePathFromPathname,
5
+ LocalePath,
4
6
  Logger,
5
7
  originFromRequest,
6
8
  parseAkanI18nEnv,
@@ -108,11 +110,6 @@ const getConfiguredBasePaths = () => new Set(parseBasePaths(process.env.AKAN_PUB
108
110
  const shouldExposeBasePath = () => getEnv().operationMode === "local";
109
111
  const CSR_RUNTIME_SEARCH_PARAMS = ["csr", "akanMobileTarget", "akanMobileBasePath", "akanMobileIndexPath"] as const;
110
112
 
111
- const getLocaleFromPathname = (pathname: string) => {
112
- const [firstSegment] = pathname.split("/").filter(Boolean);
113
- return parseAkanI18nEnv().locales.find((locale) => locale === firstSegment);
114
- };
115
-
116
113
  const normalizeRoutePath = (path: string | undefined) => {
117
114
  const normalized = path?.trim();
118
115
  if (!normalized) return undefined;
@@ -163,7 +160,17 @@ declare global {
163
160
  var __AKAN_ROUTER__: Router | undefined;
164
161
  }
165
162
 
166
- export const getPathInfo = (requestUrl: string, lang: string, prefix: string) => {
163
+ const withoutNetworkPath = (pathname: string) => {
164
+
165
+ const path = pathname.replace(/[\t\n\r]/g, "").replace(/^[\u0000-\u0020/\\]+/, "");
166
+ return `/${path}`;
167
+ };
168
+ export const getPathInfo = (
169
+ requestUrl: string,
170
+ lang: string,
171
+ prefix: string,
172
+ localePrefix: AkanLocalePrefix | undefined = parseAkanI18nEnv().localePrefix,
173
+ ) => {
167
174
  const [urlWithoutHash, hash = ""] = requestUrl.split("#");
168
175
  const [url, search = ""] = urlWithoutHash.split("?");
169
176
  const langLength = lang.length + 1;
@@ -177,13 +184,20 @@ export const getPathInfo = (requestUrl: string, lang: string, prefix: string) =>
177
184
  ? pathWithSubRoute.slice(prefixLength)
178
185
  : pathWithSubRoute;
179
186
  const subRoute = prefix ? `/${prefix}` : "";
180
-
181
- const pathname = Href.hasWebScheme(path) ? path : path === "/" ? `/${lang}${subRoute}` : `/${lang}${subRoute}${path}`;
187
+ const langSegment = LocalePath.segmentOf(lang, { defaultLocale: parseAkanI18nEnv().defaultLocale, localePrefix });
188
+ const appPathname = `${langSegment}${subRoute}${path === "/" ? "" : path}`;
189
+
190
+ const pathname = Href.hasWebScheme(path)
191
+ ? path
192
+ : langSegment || subRoute
193
+ ? appPathname
194
+ : withoutNetworkPath(appPathname);
182
195
  const href = `${pathname}${search ? `?${search}` : ""}${hash ? `#${hash}` : ""}`;
183
196
  return { path, pathname, hash, search, href };
184
197
  };
185
198
  class Router {
186
199
  isInitialized: boolean = false;
200
+ #isCsr: boolean = false;
187
201
  #prefix = "";
188
202
  #lang = parseAkanI18nEnv().defaultLocale;
189
203
  #routePaths = new Set<string>();
@@ -201,6 +215,7 @@ class Router {
201
215
  #expectedPops: { idx: number; isAllowed: boolean; onLand?: () => void }[] = [];
202
216
 
203
217
  #beforeInit: (() => void)[] = [];
218
+ #frameParentOrigins: string[] | "*" = [];
204
219
  #instance: RouterInstance = {
205
220
  push: (href: string) => {
206
221
  const { href: fullHref } = this.#getPathInfo(href);
@@ -218,6 +233,7 @@ class Router {
218
233
  },
219
234
  };
220
235
  init(options: SsrClientRouterOption | SsrServerRouterOption | CSRClientRouterOption) {
236
+ this.#isCsr = options.type === "csr";
221
237
  this.#prefix = options.prefix ?? "";
222
238
  this.#lang = options.lang ?? parseAkanI18nEnv().defaultLocale;
223
239
  this.#routePaths = new Set(
@@ -395,8 +411,17 @@ class Router {
395
411
  csrLocation.replace(nextState);
396
412
  }
397
413
 
414
+ #localePrefix(): AkanLocalePrefix {
415
+ return this.#isCsr ? "always" : (parseAkanI18nEnv().localePrefix ?? "always");
416
+ }
417
+ #langSegment(lang = this.#lang) {
418
+ return LocalePath.segmentOf(lang, {
419
+ defaultLocale: parseAkanI18nEnv().defaultLocale,
420
+ localePrefix: this.#localePrefix(),
421
+ });
422
+ }
398
423
  #getPathInfo(href: string, prefix = this.#prefix) {
399
- return getPathInfo(href, this.#lang, prefix);
424
+ return getPathInfo(href, this.#lang, prefix, this.#localePrefix());
400
425
  }
401
426
  #getNavigationPathInfo(href: string) {
402
427
  return this.#getPathInfo(href, shouldExposeBasePath() ? this.#prefix : "");
@@ -417,12 +442,16 @@ class Router {
417
442
  return `${pathname}${query ? `?${query}` : ""}${hash ? `#${hash}` : ""}`;
418
443
  }
419
444
  #getVisiblePathInfo(href: string, lang = this.#lang) {
420
- return getPathInfo(href, lang, shouldExposeBasePath() ? this.#prefix : "");
445
+ return getPathInfo(href, lang, shouldExposeBasePath() ? this.#prefix : "", this.#localePrefix());
421
446
  }
422
447
 
423
448
  #postPathAfterBack() {
424
449
  if (this.canGoBack()) this.#backsAwaitingPopstate += 1;
425
450
  }
451
+ /** Origins besides the page's own that hear `pathChange` when they frame it in production; `"*"` is any parent. */
452
+ setFrameParentOrigins(origins: string[] | "*") {
453
+ this.#frameParentOrigins = origins;
454
+ }
426
455
  /** Tells the frame embedding this page where it went: a new path, or a page of a paginated list. */
427
456
  postPathChange(change: { path: string; pathname: string; hash: string } | { page: number }) {
428
457
  const message =
@@ -431,8 +460,13 @@ class Router {
431
460
  : { type: "pathChange", path: change.path, pathname: change.pathname, hash: change.hash };
432
461
  if (!("page" in change)) Logger.info(`pathChange-start:${change.path}${change.hash ? `#${change.hash}` : ""}`);
433
462
 
434
- if (isMcpView()) return;
435
- window.parent.postMessage(message, "*");
463
+ if (isMcpView() || window.parent === window) return;
464
+
465
+ const targets =
466
+ getEnv().operationMode === "local" || this.#frameParentOrigins === "*"
467
+ ? ["*"]
468
+ : [window.location.origin, ...this.#frameParentOrigins];
469
+ for (const target of targets) window.parent.postMessage(message, target);
436
470
  }
437
471
  #postDevSyncNavigation(kind: "push" | "replace", href: string) {
438
472
  if (globalThis.__AKAN_DEV_SYNC_NAVIGATION_APPLYING__) return;
@@ -639,7 +673,12 @@ class Router {
639
673
  const langFromPath = reqPathname.split("/").find(Boolean);
640
674
  const lang = (h.get("x-locale") ?? langFromPath ?? this.#lang) as string;
641
675
  const basePath = getServerBasePath(reqPathname, lang, h.get("x-base-path") ?? undefined, this.#prefix);
642
- const { pathname, href: fullHref } = getPathInfo(href, lang, shouldExposeBasePath() ? basePath : "");
676
+ const { pathname, href: fullHref } = getPathInfo(
677
+ href,
678
+ lang,
679
+ shouldExposeBasePath() ? basePath : "",
680
+ this.#localePrefix(),
681
+ );
643
682
  Logger.info(`redirect to:${pathname}`);
644
683
  throw new AkanRedirectError(fullHref, method, status);
645
684
  } else {
@@ -663,7 +702,8 @@ class Router {
663
702
  setLang(lang: string) {
664
703
  if (getEnv().side === "server") throw new Error("setLang is only available in client side");
665
704
  this.#checkInitialized();
666
- const currentLang = getLocaleFromPathname(csrLocation.pathname) ?? this.#lang;
705
+ const i18n = { ...parseAkanI18nEnv(), localePrefix: this.#localePrefix() };
706
+ const currentLang = LocalePath.split(csrLocation.pathname, i18n).locale ?? this.#lang;
667
707
  const { path, search, hash } = this.#getVisiblePathInfo(csrLocation.href, currentLang);
668
708
  this.#lang = lang;
669
709
  this.#instance.replace(`${path}${search ? `?${search}` : ""}${hash ? `#${hash}` : ""}`);
@@ -679,13 +719,14 @@ class Router {
679
719
  }
680
720
  getFullPath(withLang = true) {
681
721
  if (getEnv().side === "server") throw new Error("getPath is only available in client side");
682
- return `${withLang ? `/${this.#lang}/` : ""}${this.#prefix}${this.getPath()}`;
722
+ const path = `${this.#prefix}${this.getPath()}`;
723
+ return withLang ? `${this.#langSegment()}/${path}`.replace(/^\/+/, "/") : path;
683
724
  }
684
725
  getPrefix() {
685
726
  return this.#prefix;
686
727
  }
687
728
  getPrefixedPath(path: string) {
688
- return this.#prefix ? `${this.#lang ? `/${this.#lang}` : ""}/${this.#prefix}${path}` : path;
729
+ return this.#prefix ? `${this.#lang ? this.#langSegment() : ""}/${this.#prefix}${path}` : path;
689
730
  }
690
731
  }
691
732
  const browserRouter = () => {
@@ -4,9 +4,11 @@ export class IpLiteral {
4
4
 
5
5
  static parse(entry: string): string | null {
6
6
  const trimmed = entry.trim();
7
- const bracketed = /^\[([^\]]*:[^\]]*)\](?::\d{1,5})?$/.exec(trimmed);
7
+
8
+ const bracketed = /^\[([^\]]*)\](?::\d{1,5})?$/.exec(trimmed)?.[1];
9
+ const v6InBrackets = bracketed?.includes(":") ? bracketed : undefined;
8
10
  const v4WithPort = /^(\d{1,3}(?:\.\d{1,3}){3}):\d{1,5}$/.exec(trimmed);
9
- const [address = "", zone, ...rest] = (bracketed?.[1] ?? v4WithPort?.[1] ?? trimmed).split("%");
11
+ const [address = "", zone, ...rest] = (v6InBrackets ?? v4WithPort?.[1] ?? trimmed).split("%");
10
12
  if (rest.length) return null;
11
13
  if (zone !== undefined && !(address.includes(":") && /^[A-Za-z0-9_.-]{1,64}$/.test(zone))) return null;
12
14
  const bytes = IpBytes.of(IpBytes.normalize(address));
@@ -27,6 +27,7 @@ export class TrustedProxy {
27
27
  static readonly #verdicts = new Map<string, boolean>();
28
28
  static readonly #verdictLimit = 1024;
29
29
  static #warnedIgnoredPeer = false;
30
+ static #warnedUnlistedChain = false;
30
31
 
31
32
  static configure(list: string | null | undefined) {
32
33
  TrustedProxy.reset();
@@ -37,6 +38,7 @@ export class TrustedProxy {
37
38
  TrustedProxy.#policy = null;
38
39
  TrustedProxy.#verdicts.clear();
39
40
  TrustedProxy.#warnedIgnoredPeer = false;
41
+ TrustedProxy.#warnedUnlistedChain = false;
40
42
  }
41
43
 
42
44
  static #load(): TrustPolicy {
@@ -104,6 +106,7 @@ export class TrustedProxy {
104
106
 
105
107
  const judge = TrustedProxy.#load().strict ? (address: string) => TrustedProxy.isTrusted(address) : undefined;
106
108
  const recorded = clientAddressFromHeaders(headers, judge);
109
+ if (peerAddress) TrustedProxy.#noteUnlistedChain(headers, peerAddress);
107
110
  if (recorded) return { address: recorded, forwarded: true };
108
111
  } else if (peerAddress) TrustedProxy.#noteIgnoredPeer(headers, peerAddress);
109
112
  return {
@@ -123,16 +126,56 @@ export class TrustedProxy {
123
126
  );
124
127
  }
125
128
 
129
+ static #noteUnlistedChain(headers: Headers, peerAddress: string) {
130
+ if (TrustedProxy.#warnedUnlistedChain) return;
131
+ const policy = TrustedProxy.#load();
132
+ if (policy.strict || policy.trustAll) return;
133
+ const hops = (headers.get("x-forwarded-for") ?? "")
134
+ .split(",")
135
+ .map((hop) => hop.trim())
136
+ .filter((hop) => hop);
137
+ const realIp = headers.get("x-real-ip")?.trim();
138
+ const last = hops.at(-1);
139
+ const disagrees = realIp ? !!last && IpLiteral.parse(last) !== IpLiteral.parse(realIp) : hops.length > 1;
140
+ if (!disagrees) return;
141
+ const bytes = IpBytes.of(normalizeIpAddress(peerAddress));
142
+ if (!bytes || TrustedProxy.#isLoopback(bytes)) return;
143
+ TrustedProxy.#warnedUnlistedChain = true;
144
+ new Logger("TrustedProxy").warn(
145
+ `AKAN_TRUSTED_PROXIES is unset, so the client address comes from the x-real-ip or leftmost x-forwarded-for that ${normalizeIpAddress(peerAddress)} sent. If that proxy appends to those headers instead of overwriting them, list it in AKAN_TRUSTED_PROXIES so the chain is read from the nearest hop.`,
146
+ );
147
+ }
148
+
126
149
  /** For refusing an outbound fetch to a caller-chosen name: judges the address alone, never the proxy list. */
127
150
  static isPrivateAddress(address: string): boolean {
128
151
  const bytes = IpBytes.of(normalizeIpAddress(address));
129
- if (!bytes) return true;
152
+ return !bytes || TrustedProxy.#isNonPublic(bytes);
153
+ }
154
+
155
+ static #isNonPublic(bytes: Uint8Array): boolean {
130
156
  if (TrustedProxy.#isLoopback(bytes) || TrustedProxy.#isPrivateRange(bytes)) return true;
131
157
  if (bytes.length === 4) {
132
158
  const [a = 0, b = 0] = bytes;
133
159
  return a === 0 || (a === 100 && b >= 64 && b <= 127) || a >= 224;
134
160
  }
135
- return bytes.every((byte) => byte === 0) || bytes[0] === 0xff;
161
+ const embedded = TrustedProxy.#embeddedIpv4(bytes);
162
+ if (embedded) return TrustedProxy.#isNonPublic(embedded);
163
+ const [first = 0, second = 0, third = 0, fourth = 0, fifth = 0, sixth = 0] = bytes;
164
+ const localNat64 =
165
+ first === 0 && second === 0x64 && third === 0xff && fourth === 0x9b && fifth === 0 && sixth === 1;
166
+ return localNat64 || first === 0xff;
167
+ }
168
+
169
+ static #embeddedIpv4(bytes: Uint8Array): Uint8Array | null {
170
+ const zero = (from: number, to: number) => bytes.subarray(from, to).every((byte) => byte === 0);
171
+ const groupAt = (idx: number) => ((bytes[idx] ?? 0) << 8) | (bytes[idx + 1] ?? 0);
172
+ const tail = bytes.slice(12);
173
+ if (zero(0, 10) && (groupAt(10) === 0xffff || groupAt(10) === 0)) return tail;
174
+ if (zero(0, 8) && groupAt(8) === 0xffff && groupAt(10) === 0) return tail;
175
+ if (groupAt(0) === 0x64 && groupAt(2) === 0xff9b && zero(4, 12)) return tail;
176
+ if (groupAt(0) === 0x2002) return bytes.slice(2, 6);
177
+ if (groupAt(0) === 0x2001 && groupAt(2) === 0) return tail.map((byte) => byte ^ 0xff);
178
+ return null;
136
179
  }
137
180
 
138
181
  static #isLoopback(bytes: Uint8Array): boolean {
@@ -0,0 +1,169 @@
1
+ import { capitalize, lowerlize } from "./capitalize";
2
+
3
+ export const sliceNames = (refName: string, suffix = "") => {
4
+ const [Model, Suffix] = [capitalize(refName), capitalize(suffix)];
5
+ const live = `${refName}Live${Suffix}`;
6
+ return {
7
+ slice: `${refName}${Suffix}`,
8
+ state: {
9
+ defaultModel: `default${Model}${Suffix}`,
10
+ modelInitList: `${refName}InitList${Suffix}`,
11
+ modelInsight: `${refName}Insight${Suffix}`,
12
+ modelList: `${refName}List${Suffix}`,
13
+ modelListLoading: `${refName}ListLoading${Suffix}`,
14
+ modelInitAt: `${refName}InitAt${Suffix}`,
15
+ modelStaleAt: `${refName}StaleAt${Suffix}`,
16
+ lastPageOfModel: `lastPageOf${Model}${Suffix}`,
17
+ pageOfModel: `pageOf${Model}${Suffix}`,
18
+ limitOfModel: `limitOf${Model}${Suffix}`,
19
+ hasMoreOfModel: `hasMoreOf${Model}${Suffix}`,
20
+ isCumulativeOfModel: `isCumulativeOf${Model}${Suffix}`,
21
+ hasInsightOfModel: `hasInsightOf${Model}${Suffix}`,
22
+ queryArgsOfModel: `queryArgsOf${Model}${Suffix}`,
23
+ sortOfModel: `sortOf${Model}${Suffix}`,
24
+ modelSelection: `${refName}Selection${Suffix}`,
25
+ },
26
+ action: {
27
+ initModel: `init${Model}${Suffix}`,
28
+ refreshModel: `refresh${Model}${Suffix}`,
29
+ selectModel: `select${Model}${Suffix}`,
30
+ setPageOfModel: `setPageOf${Model}${Suffix}`,
31
+ loadMoreOfModel: `loadMoreOf${Model}${Suffix}`,
32
+ setLimitOfModel: `setLimitOf${Model}${Suffix}`,
33
+ setQueryArgsOfModel: `setQueryArgsOf${Model}${Suffix}`,
34
+ setSortOfModel: `setSortOf${Model}${Suffix}`,
35
+ applyLiveModel: `applyLive${Model}${Suffix}`,
36
+ watchLiveModel: `watchLive${Model}${Suffix}`,
37
+ },
38
+ endpoint: {
39
+ list: `${refName}List${Suffix}`,
40
+ insight: `${refName}Insight${Suffix}`,
41
+ live,
42
+ subscribeLive: endpointNames(live).subscribe,
43
+ init: `init${Model}${Suffix}`,
44
+ getInit: `get${Model}Init${Suffix}`,
45
+ initResult: `${refName}Init${Suffix}`,
46
+ },
47
+ };
48
+ };
49
+
50
+ export const queryNames = (queryKey: string) => {
51
+ const Key = capitalize(queryKey);
52
+ return {
53
+ list: `list${Key}`,
54
+ listIds: `listIds${Key}`,
55
+ find: `find${Key}`,
56
+ findId: `findId${Key}`,
57
+ pick: `pick${Key}`,
58
+ pickId: `pickId${Key}`,
59
+ exists: `exists${Key}`,
60
+ count: `count${Key}`,
61
+ insight: `insight${Key}`,
62
+ query: `query${Key}`,
63
+ remove: `remove${Key}`,
64
+ removeOne: `removeOne${Key}`,
65
+ purge: `purge${Key}`,
66
+ update: `update${Key}`,
67
+ updateOne: `updateOne${Key}`,
68
+ };
69
+ };
70
+
71
+ export const databaseStateNames = (refName: string) => {
72
+ const className = capitalize(refName);
73
+ return {
74
+ model: refName,
75
+ modelLoading: `${refName}Loading`,
76
+ modelForm: `${refName}Form`,
77
+ modelFormLoading: `${refName}FormLoading`,
78
+ modelFormUploading: `${refName}FormUploading`,
79
+ modelSubmit: `${refName}Submit`,
80
+ modelViewAt: `${refName}ViewAt`,
81
+ modelModal: `${refName}Modal`,
82
+ modelDraft: `${refName}FormDraft`,
83
+ modelOperation: `${refName}Operation`,
84
+ defaultModel: `default${className}`,
85
+ };
86
+ };
87
+
88
+ export const modelNames = (refName: string) => {
89
+ const Model = capitalize(refName);
90
+ return {
91
+ ...databaseStateNames(refName),
92
+ getModel: `get${Model}`,
93
+ loadModel: `load${Model}`,
94
+ loadModelMany: `load${Model}Many`,
95
+ createModel: `create${Model}`,
96
+ createModelMany: `create${Model}Many`,
97
+ updateModel: `update${Model}`,
98
+ removeModel: `remove${Model}`,
99
+ modelModel: `${refName}Model`,
100
+ modelLoader: `${refName}Loader`,
101
+ modelCache: `${refName}Cache`,
102
+ modelId: `${refName}Id`,
103
+ lightModel: `light${Model}`,
104
+ viewModel: `view${Model}`,
105
+ getModelView: `get${Model}View`,
106
+ modelView: `${refName}View`,
107
+ editModel: `edit${Model}`,
108
+ getModelEdit: `get${Model}Edit`,
109
+ modelEdit: `${refName}Edit`,
110
+ modelObj: `${refName}Obj`,
111
+ modelObjList: `${refName}ObjList`,
112
+ modelObjInsight: `${refName}ObjInsight`,
113
+ addModelFiles: `add${Model}Files`,
114
+ mergeModel: `merge${Model}`,
115
+ createModelInForm: `create${Model}InForm`,
116
+ updateModelInForm: `update${Model}InForm`,
117
+ checkModelSubmitable: `check${Model}Submitable`,
118
+ submitModel: `submit${Model}`,
119
+ newModel: `new${Model}`,
120
+ setModel: `set${Model}`,
121
+ resetModel: `reset${Model}`,
122
+ invalidateModel: `invalidate${Model}`,
123
+ loadModelFormDraft: `load${Model}FormDraft`,
124
+ restoreModelFormDraft: `restore${Model}FormDraft`,
125
+ discardModelFormDraft: `discard${Model}FormDraft`,
126
+ writeOnModel: `writeOn${Model}`,
127
+ setViewOfModel: `setViewOf${Model}`,
128
+ exportCsvOfModel: `exportCsvOf${Model}`,
129
+ exportJsonOfModel: `exportJsonOf${Model}`,
130
+ };
131
+ };
132
+
133
+ export const moduleNames = (refName: string) => {
134
+ const Model = capitalize(refName);
135
+ return {
136
+ service: `${refName}Service`,
137
+ ServiceClass: `${Model}Service`,
138
+ SliceClass: `${Model}Slice`,
139
+ ModelClass: `${Model}Model`,
140
+ FilterClass: `${Model}Filter`,
141
+ SignalClass: `${Model}Signal`,
142
+ InternalClass: `${Model}Internal`,
143
+ EndpointClass: `${Model}Endpoint`,
144
+ };
145
+ };
146
+
147
+ export const formSetterNames = (className: string, key: string) => {
148
+ const classKeyName = capitalize(key);
149
+ return {
150
+ field: lowerlize(key),
151
+ Field: classKeyName,
152
+ setFieldOnModel: `set${classKeyName}On${className}`,
153
+ addFieldOnModel: `add${classKeyName}On${className}`,
154
+ subFieldOnModel: `sub${classKeyName}On${className}`,
155
+ addOrSubFieldOnModel: `addOrSub${classKeyName}On${className}`,
156
+
157
+ moveFieldOnModel: `move${classKeyName}On${className}`,
158
+ uploadFieldOnModel: `upload${classKeyName}On${className}`,
159
+
160
+ postSetField: `_postSet${classKeyName}`,
161
+ };
162
+ };
163
+
164
+ export const stateSetterName = (key: string) => `set${capitalize(key)}`;
165
+
166
+ export const endpointNames = (key: string) => {
167
+ const Key = capitalize(key);
168
+ return { subscribe: `subscribe${Key}`, listen: `listen${Key}` };
169
+ };