@soederpop/luca 0.0.2

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 (358) hide show
  1. package/CLAUDE.md +71 -0
  2. package/README.md +78 -0
  3. package/bun.lock +2928 -0
  4. package/bunfig.toml +3 -0
  5. package/commands/audit-docs.ts +740 -0
  6. package/commands/build-scaffolds.ts +154 -0
  7. package/commands/generate-api-docs.ts +114 -0
  8. package/commands/update-introspection.ts +67 -0
  9. package/docs/CLI.md +335 -0
  10. package/docs/README.md +88 -0
  11. package/docs/TABLE-OF-CONTENTS.md +157 -0
  12. package/docs/apis/clients/elevenlabs.md +84 -0
  13. package/docs/apis/clients/graph.md +56 -0
  14. package/docs/apis/clients/openai.md +69 -0
  15. package/docs/apis/clients/rest.md +41 -0
  16. package/docs/apis/clients/websocket.md +107 -0
  17. package/docs/apis/features/agi/assistant.md +471 -0
  18. package/docs/apis/features/agi/assistants-manager.md +154 -0
  19. package/docs/apis/features/agi/claude-code.md +602 -0
  20. package/docs/apis/features/agi/conversation-history.md +352 -0
  21. package/docs/apis/features/agi/conversation.md +333 -0
  22. package/docs/apis/features/agi/docs-reader.md +121 -0
  23. package/docs/apis/features/agi/openai-codex.md +318 -0
  24. package/docs/apis/features/agi/openapi.md +138 -0
  25. package/docs/apis/features/agi/semantic-search.md +387 -0
  26. package/docs/apis/features/agi/skills-library.md +216 -0
  27. package/docs/apis/features/node/container-link.md +133 -0
  28. package/docs/apis/features/node/content-db.md +313 -0
  29. package/docs/apis/features/node/disk-cache.md +379 -0
  30. package/docs/apis/features/node/dns.md +651 -0
  31. package/docs/apis/features/node/docker.md +705 -0
  32. package/docs/apis/features/node/downloader.md +81 -0
  33. package/docs/apis/features/node/esbuild.md +59 -0
  34. package/docs/apis/features/node/file-manager.md +182 -0
  35. package/docs/apis/features/node/fs.md +581 -0
  36. package/docs/apis/features/node/git.md +330 -0
  37. package/docs/apis/features/node/google-auth.md +174 -0
  38. package/docs/apis/features/node/google-calendar.md +187 -0
  39. package/docs/apis/features/node/google-docs.md +151 -0
  40. package/docs/apis/features/node/google-drive.md +225 -0
  41. package/docs/apis/features/node/google-sheets.md +179 -0
  42. package/docs/apis/features/node/grep.md +290 -0
  43. package/docs/apis/features/node/helpers.md +135 -0
  44. package/docs/apis/features/node/ink.md +334 -0
  45. package/docs/apis/features/node/ipc-socket.md +260 -0
  46. package/docs/apis/features/node/json-tree.md +86 -0
  47. package/docs/apis/features/node/launcher-app-command-listener.md +145 -0
  48. package/docs/apis/features/node/networking.md +281 -0
  49. package/docs/apis/features/node/nlp.md +133 -0
  50. package/docs/apis/features/node/opener.md +97 -0
  51. package/docs/apis/features/node/os.md +118 -0
  52. package/docs/apis/features/node/package-finder.md +402 -0
  53. package/docs/apis/features/node/postgres.md +212 -0
  54. package/docs/apis/features/node/proc.md +430 -0
  55. package/docs/apis/features/node/process-manager.md +210 -0
  56. package/docs/apis/features/node/python.md +278 -0
  57. package/docs/apis/features/node/repl.md +88 -0
  58. package/docs/apis/features/node/runpod.md +673 -0
  59. package/docs/apis/features/node/secure-shell.md +169 -0
  60. package/docs/apis/features/node/semantic-search.md +401 -0
  61. package/docs/apis/features/node/sqlite.md +211 -0
  62. package/docs/apis/features/node/telegram.md +254 -0
  63. package/docs/apis/features/node/tts.md +118 -0
  64. package/docs/apis/features/node/ui.md +703 -0
  65. package/docs/apis/features/node/vault.md +64 -0
  66. package/docs/apis/features/node/vm.md +84 -0
  67. package/docs/apis/features/node/window-manager.md +337 -0
  68. package/docs/apis/features/node/yaml-tree.md +85 -0
  69. package/docs/apis/features/node/yaml.md +176 -0
  70. package/docs/apis/features/web/asset-loader.md +47 -0
  71. package/docs/apis/features/web/container-link.md +133 -0
  72. package/docs/apis/features/web/esbuild.md +59 -0
  73. package/docs/apis/features/web/helpers.md +135 -0
  74. package/docs/apis/features/web/network.md +30 -0
  75. package/docs/apis/features/web/speech.md +55 -0
  76. package/docs/apis/features/web/vault.md +64 -0
  77. package/docs/apis/features/web/vm.md +84 -0
  78. package/docs/apis/features/web/voice.md +67 -0
  79. package/docs/apis/servers/express.md +127 -0
  80. package/docs/apis/servers/mcp.md +213 -0
  81. package/docs/apis/servers/websocket.md +99 -0
  82. package/docs/documentation-audit.md +134 -0
  83. package/docs/examples/content-db.md +77 -0
  84. package/docs/examples/disk-cache.md +83 -0
  85. package/docs/examples/docker.md +101 -0
  86. package/docs/examples/downloader.md +70 -0
  87. package/docs/examples/esbuild.md +80 -0
  88. package/docs/examples/file-manager.md +82 -0
  89. package/docs/examples/fs.md +83 -0
  90. package/docs/examples/git.md +85 -0
  91. package/docs/examples/google-auth.md +88 -0
  92. package/docs/examples/google-calendar.md +94 -0
  93. package/docs/examples/google-docs.md +82 -0
  94. package/docs/examples/google-drive.md +96 -0
  95. package/docs/examples/google-sheets.md +95 -0
  96. package/docs/examples/grep.md +85 -0
  97. package/docs/examples/ink-blocks.md +75 -0
  98. package/docs/examples/ink-renderer.md +41 -0
  99. package/docs/examples/ink.md +103 -0
  100. package/docs/examples/ipc-socket.md +103 -0
  101. package/docs/examples/json-tree.md +91 -0
  102. package/docs/examples/launcher-app-command-listener.md +120 -0
  103. package/docs/examples/networking.md +58 -0
  104. package/docs/examples/nlp.md +91 -0
  105. package/docs/examples/opener.md +78 -0
  106. package/docs/examples/os.md +72 -0
  107. package/docs/examples/package-finder.md +89 -0
  108. package/docs/examples/port-exposer.md +89 -0
  109. package/docs/examples/postgres.md +91 -0
  110. package/docs/examples/proc.md +81 -0
  111. package/docs/examples/process-manager.md +79 -0
  112. package/docs/examples/python.md +91 -0
  113. package/docs/examples/repl.md +93 -0
  114. package/docs/examples/runpod.md +119 -0
  115. package/docs/examples/secure-shell.md +92 -0
  116. package/docs/examples/sqlite.md +86 -0
  117. package/docs/examples/telegram.md +77 -0
  118. package/docs/examples/tts.md +86 -0
  119. package/docs/examples/ui.md +80 -0
  120. package/docs/examples/vault.md +70 -0
  121. package/docs/examples/vm.md +86 -0
  122. package/docs/examples/window-manager.md +125 -0
  123. package/docs/examples/yaml-tree.md +93 -0
  124. package/docs/examples/yaml.md +104 -0
  125. package/docs/ideas/class-registration-refactor-possibilities.md +197 -0
  126. package/docs/ideas/container-use-api.md +9 -0
  127. package/docs/ideas/easy-auth-for-express-servers-and-luca-serve.md +0 -0
  128. package/docs/ideas/feature-stacks.md +22 -0
  129. package/docs/ideas/luca-cli-self-sufficiency-demo.md +23 -0
  130. package/docs/ideas/mcp-design.md +9 -0
  131. package/docs/ideas/web-container-debugging-feature.md +13 -0
  132. package/docs/introspection-audit.md +49 -0
  133. package/docs/introspection.md +154 -0
  134. package/docs/mcp/readme.md +162 -0
  135. package/docs/models.ts +38 -0
  136. package/docs/philosophy.md +85 -0
  137. package/docs/principles.md +7 -0
  138. package/docs/prompts/audit-codebase-for-failures-to-use-the-container.md +34 -0
  139. package/docs/prompts/mcp-test-easy-command.md +27 -0
  140. package/docs/reports/assistant-bugs.md +38 -0
  141. package/docs/reports/attach-pattern-usage.md +18 -0
  142. package/docs/reports/code-audit-results.md +391 -0
  143. package/docs/reports/introspection-audit-tasks.md +378 -0
  144. package/docs/reports/luca-mcp-improvements.md +128 -0
  145. package/docs/scaffolds/client.md +140 -0
  146. package/docs/scaffolds/command.md +106 -0
  147. package/docs/scaffolds/endpoint.md +176 -0
  148. package/docs/scaffolds/feature.md +148 -0
  149. package/docs/scaffolds/server.md +187 -0
  150. package/docs/tasks/web-container-helper-discovery.md +71 -0
  151. package/docs/todos.md +1 -0
  152. package/docs/tutorials/01-getting-started.md +106 -0
  153. package/docs/tutorials/02-container.md +210 -0
  154. package/docs/tutorials/03-scripts.md +194 -0
  155. package/docs/tutorials/04-features-overview.md +196 -0
  156. package/docs/tutorials/05-state-and-events.md +171 -0
  157. package/docs/tutorials/06-servers.md +157 -0
  158. package/docs/tutorials/07-endpoints.md +198 -0
  159. package/docs/tutorials/08-commands.md +171 -0
  160. package/docs/tutorials/09-clients.md +162 -0
  161. package/docs/tutorials/10-creating-features.md +198 -0
  162. package/docs/tutorials/11-contentbase.md +191 -0
  163. package/docs/tutorials/12-assistants.md +215 -0
  164. package/docs/tutorials/13-introspection.md +147 -0
  165. package/docs/tutorials/14-type-system.md +174 -0
  166. package/docs/tutorials/15-project-patterns.md +222 -0
  167. package/docs/tutorials/16-google-features.md +534 -0
  168. package/docs/tutorials/17-tui-blocks.md +530 -0
  169. package/docs/tutorials/18-semantic-search.md +334 -0
  170. package/index.ts +1 -0
  171. package/luca.console.ts +9 -0
  172. package/main.py +6 -0
  173. package/package.json +154 -0
  174. package/pyproject.toml +7 -0
  175. package/scripts/animations/chrome-glitch.ts +55 -0
  176. package/scripts/animations/index.ts +16 -0
  177. package/scripts/animations/neon-pulse.ts +64 -0
  178. package/scripts/animations/types.ts +6 -0
  179. package/scripts/build-web.ts +28 -0
  180. package/scripts/examples/ask-luca-expert.ts +42 -0
  181. package/scripts/examples/assistant-questions.ts +12 -0
  182. package/scripts/examples/excalidraw-expert.ts +75 -0
  183. package/scripts/examples/expert-chat.ts +0 -0
  184. package/scripts/examples/file-manager.ts +14 -0
  185. package/scripts/examples/ideas.ts +12 -0
  186. package/scripts/examples/interactive-chat.ts +20 -0
  187. package/scripts/examples/openai-tool-calls.ts +113 -0
  188. package/scripts/examples/opening-a-web-browser.ts +5 -0
  189. package/scripts/examples/telegram-bot.ts +79 -0
  190. package/scripts/examples/telegram-ink-ui.ts +302 -0
  191. package/scripts/examples/using-assistant-with-mcp.ts +560 -0
  192. package/scripts/examples/using-claude-code.ts +10 -0
  193. package/scripts/examples/using-contentdb.ts +35 -0
  194. package/scripts/examples/using-conversations.ts +35 -0
  195. package/scripts/examples/using-disk-cache.ts +10 -0
  196. package/scripts/examples/using-docker-shell.ts +75 -0
  197. package/scripts/examples/using-elevenlabs.ts +25 -0
  198. package/scripts/examples/using-google-calendar.ts +57 -0
  199. package/scripts/examples/using-google-docs.ts +74 -0
  200. package/scripts/examples/using-google-drive.ts +74 -0
  201. package/scripts/examples/using-google-sheets.ts +89 -0
  202. package/scripts/examples/using-nlp.ts +55 -0
  203. package/scripts/examples/using-ollama.ts +10 -0
  204. package/scripts/examples/using-openai-codex.ts +23 -0
  205. package/scripts/examples/using-postgres.ts +55 -0
  206. package/scripts/examples/using-runpod.ts +32 -0
  207. package/scripts/examples/using-tts.ts +40 -0
  208. package/scripts/examples/vm-loading-esm-modules.ts +16 -0
  209. package/scripts/scaffold.ts +391 -0
  210. package/scripts/scratch.ts +15 -0
  211. package/scripts/test-command-listener.ts +123 -0
  212. package/scripts/test-window-manager-lifecycle.ts +86 -0
  213. package/scripts/test-window-manager.ts +43 -0
  214. package/scripts/update-introspection-data.ts +58 -0
  215. package/src/agi/README.md +14 -0
  216. package/src/agi/container.server.ts +114 -0
  217. package/src/agi/endpoints/ask.ts +60 -0
  218. package/src/agi/endpoints/conversations/[id].ts +45 -0
  219. package/src/agi/endpoints/conversations.ts +31 -0
  220. package/src/agi/endpoints/experts.ts +37 -0
  221. package/src/agi/features/assistant.ts +767 -0
  222. package/src/agi/features/assistants-manager.ts +260 -0
  223. package/src/agi/features/claude-code.ts +1111 -0
  224. package/src/agi/features/conversation-history.ts +497 -0
  225. package/src/agi/features/conversation.ts +799 -0
  226. package/src/agi/features/openai-codex.ts +631 -0
  227. package/src/agi/features/openapi.ts +438 -0
  228. package/src/agi/features/skills-library.ts +425 -0
  229. package/src/agi/index.ts +6 -0
  230. package/src/agi/lib/token-counter.ts +122 -0
  231. package/src/browser.ts +25 -0
  232. package/src/bus.ts +100 -0
  233. package/src/cli/cli.ts +70 -0
  234. package/src/client.ts +461 -0
  235. package/src/clients/civitai/index.ts +541 -0
  236. package/src/clients/client-template.ts +41 -0
  237. package/src/clients/comfyui/index.ts +597 -0
  238. package/src/clients/elevenlabs/index.ts +291 -0
  239. package/src/clients/openai/index.ts +451 -0
  240. package/src/clients/supabase/index.ts +366 -0
  241. package/src/command.ts +164 -0
  242. package/src/commands/chat.ts +182 -0
  243. package/src/commands/console.ts +192 -0
  244. package/src/commands/describe.ts +433 -0
  245. package/src/commands/eval.ts +116 -0
  246. package/src/commands/help.ts +214 -0
  247. package/src/commands/index.ts +14 -0
  248. package/src/commands/mcp.ts +64 -0
  249. package/src/commands/prompt.ts +807 -0
  250. package/src/commands/run.ts +257 -0
  251. package/src/commands/sandbox-mcp.ts +439 -0
  252. package/src/commands/scaffold.ts +79 -0
  253. package/src/commands/serve.ts +172 -0
  254. package/src/container.ts +781 -0
  255. package/src/endpoint.ts +340 -0
  256. package/src/feature.ts +75 -0
  257. package/src/hash-object.ts +97 -0
  258. package/src/helper.ts +543 -0
  259. package/src/introspection/generated.agi.ts +23388 -0
  260. package/src/introspection/generated.node.ts +18899 -0
  261. package/src/introspection/generated.web.ts +2021 -0
  262. package/src/introspection/index.ts +256 -0
  263. package/src/introspection/scan.ts +912 -0
  264. package/src/node/container.ts +354 -0
  265. package/src/node/feature.ts +13 -0
  266. package/src/node/features/container-link.ts +558 -0
  267. package/src/node/features/content-db.ts +475 -0
  268. package/src/node/features/disk-cache.ts +382 -0
  269. package/src/node/features/dns.ts +655 -0
  270. package/src/node/features/docker.ts +912 -0
  271. package/src/node/features/downloader.ts +92 -0
  272. package/src/node/features/esbuild.ts +68 -0
  273. package/src/node/features/file-manager.ts +357 -0
  274. package/src/node/features/fs.ts +534 -0
  275. package/src/node/features/git.ts +492 -0
  276. package/src/node/features/google-auth.ts +502 -0
  277. package/src/node/features/google-calendar.ts +300 -0
  278. package/src/node/features/google-docs.ts +404 -0
  279. package/src/node/features/google-drive.ts +339 -0
  280. package/src/node/features/google-sheets.ts +279 -0
  281. package/src/node/features/grep.ts +406 -0
  282. package/src/node/features/helpers.ts +374 -0
  283. package/src/node/features/ink.ts +490 -0
  284. package/src/node/features/ipc-socket.ts +459 -0
  285. package/src/node/features/json-tree.ts +188 -0
  286. package/src/node/features/launcher-app-command-listener.ts +388 -0
  287. package/src/node/features/networking.ts +925 -0
  288. package/src/node/features/nlp.ts +211 -0
  289. package/src/node/features/opener.ts +166 -0
  290. package/src/node/features/os.ts +157 -0
  291. package/src/node/features/package-finder.ts +539 -0
  292. package/src/node/features/port-exposer.ts +342 -0
  293. package/src/node/features/postgres.ts +273 -0
  294. package/src/node/features/proc.ts +502 -0
  295. package/src/node/features/process-manager.ts +542 -0
  296. package/src/node/features/python.ts +444 -0
  297. package/src/node/features/repl.ts +194 -0
  298. package/src/node/features/runpod.ts +802 -0
  299. package/src/node/features/secure-shell.ts +248 -0
  300. package/src/node/features/semantic-search.ts +924 -0
  301. package/src/node/features/sqlite.ts +289 -0
  302. package/src/node/features/telegram.ts +342 -0
  303. package/src/node/features/tts.ts +184 -0
  304. package/src/node/features/ui.ts +857 -0
  305. package/src/node/features/vault.ts +164 -0
  306. package/src/node/features/vm.ts +312 -0
  307. package/src/node/features/window-manager.ts +804 -0
  308. package/src/node/features/yaml-tree.ts +149 -0
  309. package/src/node/features/yaml.ts +132 -0
  310. package/src/node.ts +70 -0
  311. package/src/react/index.ts +175 -0
  312. package/src/registry.ts +199 -0
  313. package/src/scaffolds/generated.ts +1613 -0
  314. package/src/scaffolds/template.ts +37 -0
  315. package/src/schemas/base.ts +255 -0
  316. package/src/server.ts +135 -0
  317. package/src/servers/express.ts +209 -0
  318. package/src/servers/mcp.ts +805 -0
  319. package/src/servers/socket.ts +120 -0
  320. package/src/state.ts +101 -0
  321. package/src/web/clients/socket.ts +82 -0
  322. package/src/web/container.ts +74 -0
  323. package/src/web/extension.ts +30 -0
  324. package/src/web/feature.ts +12 -0
  325. package/src/web/features/asset-loader.ts +64 -0
  326. package/src/web/features/container-link.ts +385 -0
  327. package/src/web/features/esbuild.ts +79 -0
  328. package/src/web/features/helpers.ts +267 -0
  329. package/src/web/features/network.ts +61 -0
  330. package/src/web/features/speech.ts +87 -0
  331. package/src/web/features/vault.ts +189 -0
  332. package/src/web/features/vm.ts +78 -0
  333. package/src/web/features/voice-recognition.ts +129 -0
  334. package/src/web/shims/isomorphic-vm.ts +149 -0
  335. package/test/bus.test.ts +134 -0
  336. package/test/clients-servers.test.ts +216 -0
  337. package/test/container-link.test.ts +274 -0
  338. package/test/features.test.ts +160 -0
  339. package/test/integration.test.ts +787 -0
  340. package/test/node-container.test.ts +121 -0
  341. package/test/rate-limit.test.ts +272 -0
  342. package/test/semantic-search.test.ts +550 -0
  343. package/test/state.test.ts +121 -0
  344. package/test-integration/assistant.test.ts +138 -0
  345. package/test-integration/assistants-manager.test.ts +123 -0
  346. package/test-integration/claude-code.test.ts +98 -0
  347. package/test-integration/conversation-history.test.ts +205 -0
  348. package/test-integration/conversation.test.ts +137 -0
  349. package/test-integration/elevenlabs.test.ts +55 -0
  350. package/test-integration/google-services.test.ts +80 -0
  351. package/test-integration/helpers.ts +89 -0
  352. package/test-integration/openai-codex.test.ts +93 -0
  353. package/test-integration/runpod.test.ts +58 -0
  354. package/test-integration/server-endpoints.test.ts +97 -0
  355. package/test-integration/skills-library.test.ts +157 -0
  356. package/test-integration/telegram.test.ts +46 -0
  357. package/tsconfig.json +58 -0
  358. package/uv.lock +8 -0
@@ -0,0 +1,340 @@
1
+ import { Helper } from './helper.js'
2
+ import type { Container, ContainerContext } from './container.js'
3
+ import { Registry } from './registry.js'
4
+ import { z } from 'zod'
5
+ import { EndpointStateSchema, EndpointOptionsSchema, EndpointEventsSchema } from './schemas/base.js'
6
+
7
+ export interface AvailableEndpoints {}
8
+
9
+ export type EndpointState = z.infer<typeof EndpointStateSchema>
10
+ export type EndpointOptions = z.infer<typeof EndpointOptionsSchema>
11
+
12
+ export type EndpointHandler = (
13
+ parameters: Record<string, any>,
14
+ context: EndpointContext
15
+ ) => Promise<any> | any
16
+
17
+ export type EndpointContext = {
18
+ container: Container<any>
19
+ request: any
20
+ response: any
21
+ query: Record<string, any>
22
+ body: Record<string, any>
23
+ params: Record<string, any>
24
+ }
25
+
26
+ export interface EndpointRateLimit {
27
+ /** Maximum requests allowed per window */
28
+ maxRequests: number
29
+ /** Window size in seconds (default: 1) */
30
+ windowSeconds?: number
31
+ }
32
+
33
+ export interface EndpointModule {
34
+ path: string
35
+ get?: EndpointHandler
36
+ post?: EndpointHandler
37
+ put?: EndpointHandler
38
+ patch?: EndpointHandler
39
+ delete?: EndpointHandler
40
+ getSchema?: z.ZodType
41
+ postSchema?: z.ZodType
42
+ putSchema?: z.ZodType
43
+ patchSchema?: z.ZodType
44
+ deleteSchema?: z.ZodType
45
+ /** Rate limit applied to all methods on this endpoint */
46
+ rateLimit?: EndpointRateLimit
47
+ /** Per-method rate limits (overrides the endpoint-level rateLimit) */
48
+ getRateLimit?: EndpointRateLimit
49
+ postRateLimit?: EndpointRateLimit
50
+ putRateLimit?: EndpointRateLimit
51
+ patchRateLimit?: EndpointRateLimit
52
+ deleteRateLimit?: EndpointRateLimit
53
+ description?: string
54
+ tags?: string[]
55
+ }
56
+
57
+ const HTTP_METHODS = ['get', 'post', 'put', 'patch', 'delete'] as const
58
+
59
+ /**
60
+ * Sliding-window rate limiter keyed by IP address.
61
+ * Tracks timestamps of requests and prunes entries older than the window.
62
+ */
63
+ class RateLimiter {
64
+ private _windows = new Map<string, number[]>()
65
+
66
+ /** Returns true if the request is allowed, false if rate-limited. */
67
+ allow(key: string, maxRequests: number, windowMs: number): boolean {
68
+ const now = Date.now()
69
+ let timestamps = this._windows.get(key)
70
+
71
+ if (!timestamps) {
72
+ timestamps = []
73
+ this._windows.set(key, timestamps)
74
+ }
75
+
76
+ // Prune timestamps outside the window
77
+ while (timestamps.length > 0 && timestamps[0] <= now - windowMs) {
78
+ timestamps.shift()
79
+ }
80
+
81
+ if (timestamps.length >= maxRequests) {
82
+ return false
83
+ }
84
+
85
+ timestamps.push(now)
86
+ return true
87
+ }
88
+
89
+ /** Clear all tracking state */
90
+ reset(): void {
91
+ this._windows.clear()
92
+ }
93
+ }
94
+
95
+ export type EndpointFactory = <T extends keyof AvailableEndpoints>(
96
+ key: T,
97
+ options?: ConstructorParameters<AvailableEndpoints[T]>[0]
98
+ ) => NonNullable<InstanceType<AvailableEndpoints[T]>>
99
+
100
+ export interface EndpointsInterface {
101
+ endpoints: EndpointsRegistry
102
+ endpoint: EndpointFactory
103
+ }
104
+
105
+ export class Endpoint<
106
+ T extends EndpointState = EndpointState,
107
+ K extends EndpointOptions = EndpointOptions
108
+ > extends Helper<T, K> {
109
+ static override shortcut = 'endpoints.base'
110
+ static override description = 'File-based HTTP endpoint with Remix-like DX'
111
+ static override stateSchema = EndpointStateSchema
112
+ static override optionsSchema = EndpointOptionsSchema
113
+ static override eventsSchema = EndpointEventsSchema
114
+
115
+ private _module: EndpointModule | null = null
116
+ private _rateLimiter = new RateLimiter()
117
+
118
+ static attach(container: Container & EndpointsInterface): any {
119
+ Object.assign(container, {
120
+ get endpoints() {
121
+ return endpoints
122
+ },
123
+
124
+ endpoint<T extends keyof AvailableEndpoints>(
125
+ id: T,
126
+ options?: ConstructorParameters<AvailableEndpoints[T]>[0]
127
+ ): InstanceType<AvailableEndpoints[T]> {
128
+ const BaseClass = endpoints.lookup(id as string) as any
129
+
130
+ return container.createHelperInstance({
131
+ cache: helperCache,
132
+ type: 'endpoint',
133
+ id: String(id),
134
+ BaseClass,
135
+ options,
136
+ fallbackName: String(id),
137
+ }) as InstanceType<AvailableEndpoints[T]>
138
+ },
139
+ })
140
+
141
+ container.registerHelperType('endpoints', 'endpoint')
142
+
143
+ return container
144
+ }
145
+
146
+ override get initialState(): T {
147
+ return ({
148
+ mounted: false,
149
+ path: this.options.path || '',
150
+ methods: [],
151
+ requestCount: 0,
152
+ } as unknown) as T
153
+ }
154
+
155
+ get path() {
156
+ return this.options.path
157
+ }
158
+
159
+ get module() {
160
+ return this._module
161
+ }
162
+
163
+ get methods(): string[] {
164
+ if (!this._module) return []
165
+ return HTTP_METHODS.filter((m) => typeof (this._module as any)[m] === 'function')
166
+ }
167
+
168
+ get isMounted() {
169
+ return !!this.state.get('mounted')
170
+ }
171
+
172
+ async load(mod?: EndpointModule): Promise<this> {
173
+ if (mod) {
174
+ this._module = mod
175
+ } else if (this.options.filePath) {
176
+ const imported = await import(`${this.options.filePath}?t=${Date.now()}`)
177
+ this._module = imported.default || imported
178
+ }
179
+
180
+ this.state.set('methods', this.methods)
181
+ this.state.set('path', this.path)
182
+ this.emit('loaded', this._module)
183
+ return this
184
+ }
185
+
186
+ async reload(): Promise<this> {
187
+ this._module = null
188
+ return this.load()
189
+ }
190
+
191
+ handler(method: string): EndpointHandler | undefined {
192
+ return this._module?.[method as keyof EndpointModule] as EndpointHandler | undefined
193
+ }
194
+
195
+ schema(method: string): z.ZodType | undefined {
196
+ return this._module?.[`${method}Schema` as keyof EndpointModule] as z.ZodType | undefined
197
+ }
198
+
199
+ /** Returns the rate limit config for a given method, or undefined if none. */
200
+ rateLimitFor(method: string): EndpointRateLimit | undefined {
201
+ const perMethod = this._module?.[`${method}RateLimit` as keyof EndpointModule] as EndpointRateLimit | undefined
202
+ return perMethod || this._module?.rateLimit
203
+ }
204
+
205
+ /** Access the rate limiter instance (useful for testing or manual resets) */
206
+ get rateLimiter(): RateLimiter {
207
+ return this._rateLimiter
208
+ }
209
+
210
+ mount(app: any): this {
211
+ for (const method of this.methods) {
212
+ const endpoint = this
213
+
214
+ app[method](this.path, async (req: any, res: any) => {
215
+ try {
216
+ // Rate limit check
217
+ const limit = endpoint.rateLimitFor(method)
218
+ if (limit) {
219
+ const ip = req.ip || req.socket?.remoteAddress || 'unknown'
220
+ const key = `${method}:${ip}`
221
+ const windowMs = (limit.windowSeconds ?? 1) * 1000
222
+ if (!endpoint._rateLimiter.allow(key, limit.maxRequests, windowMs)) {
223
+ endpoint.emit('error', new Error(`Rate limit exceeded for ${method.toUpperCase()} ${endpoint.path}`))
224
+ res.status(429).json({ error: 'Too Many Requests' })
225
+ return
226
+ }
227
+ }
228
+
229
+ const currentHandler = endpoint.handler(method)
230
+ if (!currentHandler) {
231
+ res.status(404).json({ error: 'Not found' })
232
+ return
233
+ }
234
+
235
+ const parameters = { ...req.query, ...req.body, ...req.params }
236
+ const currentSchema = endpoint.schema(method)
237
+ const validated = currentSchema ? currentSchema.parse(parameters) : parameters
238
+
239
+ const ctx: EndpointContext = {
240
+ container: endpoint.container,
241
+ request: req,
242
+ response: res,
243
+ query: req.query || {},
244
+ body: req.body || {},
245
+ params: req.params || {},
246
+ }
247
+
248
+ const result = await currentHandler(validated, ctx)
249
+ endpoint.state.set('requestCount', (endpoint.state.get('requestCount') || 0) + 1)
250
+ endpoint.emit('request', method, endpoint.path, parameters)
251
+
252
+ if (!res.headersSent) {
253
+ res.json(result)
254
+ }
255
+ } catch (err: any) {
256
+ endpoint.emit('error', err)
257
+ if (!res.headersSent) {
258
+ if (err.name === 'ZodError') {
259
+ const issues = err.issues || err.errors || []
260
+ const details = issues.map((e: any) => `${(e.path || []).join('.')}: ${e.message}`).join(', ')
261
+ console.error(`[${method.toUpperCase()} ${endpoint.path}] Validation failed: ${details}`)
262
+ res.status(400).json({ error: `Validation failed: ${details}`, details: issues })
263
+ } else {
264
+ console.error(`[${method.toUpperCase()} ${endpoint.path}] ${err.message}`)
265
+ res.status(500).json({ error: err.message })
266
+ }
267
+ }
268
+ }
269
+ })
270
+ }
271
+
272
+ this.state.set('mounted', true)
273
+ this.emit('mounted', this.path)
274
+ return this
275
+ }
276
+
277
+ toOpenAPIPathItem(): Record<string, any> {
278
+ const pathItem: Record<string, any> = {}
279
+
280
+ for (const method of this.methods) {
281
+ const methodSchema = this.schema(method)
282
+ const operationId = `${method}_${this.path.replace(/\//g, '_').replace(/^_/, '')}`
283
+
284
+ const operation: Record<string, any> = {
285
+ operationId,
286
+ summary: this._module?.description || `${method.toUpperCase()} ${this.path}`,
287
+ tags: this._module?.tags || [],
288
+ responses: {
289
+ '200': {
290
+ description: 'Successful response',
291
+ content: { 'application/json': { schema: { type: 'object' } } },
292
+ },
293
+ ...(this.rateLimitFor(method) ? { '429': { description: 'Rate limit exceeded' } } : {}),
294
+ '400': { description: 'Validation error' },
295
+ '500': { description: 'Server error' },
296
+ },
297
+ }
298
+
299
+ if (methodSchema) {
300
+ try {
301
+ const jsonSchema = (methodSchema as any).toJSONSchema()
302
+
303
+ if (method === 'get' || method === 'delete') {
304
+ operation.parameters = Object.entries((jsonSchema as any).properties || {}).map(
305
+ ([name, prop]: [string, any]) => ({
306
+ name,
307
+ in: 'query',
308
+ required: (jsonSchema as any).required?.includes(name) || false,
309
+ schema: prop,
310
+ description: prop.description || '',
311
+ })
312
+ )
313
+ } else {
314
+ operation.requestBody = {
315
+ required: true,
316
+ content: { 'application/json': { schema: jsonSchema } },
317
+ }
318
+ }
319
+ } catch {
320
+ // Schema conversion failed, serve without parameter docs
321
+ }
322
+ }
323
+
324
+ pathItem[method] = operation
325
+ }
326
+
327
+ return pathItem
328
+ }
329
+ }
330
+
331
+ export class EndpointsRegistry extends Registry<Endpoint<any>> {
332
+ override scope = 'endpoints'
333
+ override baseClass = Endpoint
334
+ }
335
+
336
+ export const endpoints = new EndpointsRegistry()
337
+
338
+ export const helperCache = new Map()
339
+
340
+ export default Endpoint
package/src/feature.ts ADDED
@@ -0,0 +1,75 @@
1
+ import { Helper } from './helper.js';
2
+ import { Registry } from './registry.js'
3
+ import type { ContainerContext } from './container.js'
4
+ import { kebabCase, camelCase } from 'lodash-es'
5
+ import type { YAML } from './node/features/yaml.js';
6
+ import { z } from 'zod'
7
+ import { FeatureStateSchema, FeatureOptionsSchema, FeatureEventsSchema } from './schemas/base.js'
8
+
9
+ /**
10
+ * Use module augmentation to register features, the same way you would register
11
+ * them at runtime. This will help developers get autocomplete etc.
12
+ */
13
+ export interface AvailableFeatures {
14
+ yaml: typeof YAML
15
+ }
16
+
17
+ export type FeatureOptions = z.infer<typeof FeatureOptionsSchema>
18
+ export type FeatureState = z.infer<typeof FeatureStateSchema>
19
+
20
+ export abstract class Feature<T extends FeatureState = FeatureState, K extends FeatureOptions = FeatureOptions> extends Helper<T, K> {
21
+ static override stateSchema = FeatureStateSchema
22
+ static override optionsSchema = FeatureOptionsSchema
23
+ static override eventsSchema = FeatureEventsSchema
24
+
25
+ get shortcut() {
26
+ return (this.constructor as any).shortcut as string
27
+ }
28
+
29
+ get isEnabled() {
30
+ return this.state.get('enabled')
31
+ }
32
+
33
+ constructor(options: K, context: ContainerContext) {
34
+ super(options, context)
35
+
36
+ if(typeof context.container !== 'object') {
37
+ console.error(this, options, context)
38
+ throw new Error('You should not instantiate a feature directly. Use container.feature() instead.')
39
+ }
40
+
41
+
42
+ if(options?.enable) {
43
+ this.enable()
44
+ }
45
+ }
46
+
47
+ /**
48
+ * For features where there only needs to be a single instance, you
49
+ * can use this method to attach the feature to the container.
50
+ */
51
+ protected attachToContainer() {
52
+ Object.defineProperty(this.container, this.shortcut.split('.').pop()!, {
53
+ get: () => this,
54
+ configurable: true,
55
+ enumerable: true,
56
+ })
57
+ }
58
+
59
+ async enable(options: any = {}) : Promise<this> {
60
+ this.attachToContainer()
61
+ this.emit('enabled')
62
+ this.state.set('enabled', true)
63
+
64
+ this.container.emit('featureEnabled', this.shortcut, this)
65
+
66
+ return this
67
+ }
68
+ }
69
+
70
+ export class FeaturesRegistry extends Registry<Feature<any, any>> {
71
+ override scope = "features"
72
+ override baseClass = Feature as any
73
+ }
74
+
75
+ export const features = new FeaturesRegistry()
@@ -0,0 +1,97 @@
1
+ /**
2
+ * Browser-compatible object hashing. Produces a deterministic string signature
3
+ * for any JavaScript value, suitable for use as a cache key or identity check.
4
+ *
5
+ * Replaces the `object-hash` npm package which has Node.js-specific dependencies.
6
+ */
7
+
8
+ function sortedEntries(obj: Record<string, any>): [string, any][] {
9
+ return Object.keys(obj).sort().map((k) => [k, obj[k]])
10
+ }
11
+
12
+ function serialize(value: any, seen: Set<any>): string {
13
+ if (value === null) return 'null'
14
+ if (value === undefined) return 'undefined'
15
+
16
+ const type = typeof value
17
+
18
+ if (type === 'boolean' || type === 'number' || type === 'bigint') {
19
+ return `${type}:${value}`
20
+ }
21
+
22
+ if (type === 'string') {
23
+ return `string:${value.length}:${value}`
24
+ }
25
+
26
+ if (type === 'symbol') {
27
+ return `symbol:${value.toString()}`
28
+ }
29
+
30
+ if (type === 'function') {
31
+ return `function:${value.name || 'anonymous'}`
32
+ }
33
+
34
+ // Circular reference guard
35
+ if (seen.has(value)) return 'circular'
36
+ seen.add(value)
37
+
38
+ if (value instanceof Date) {
39
+ return `date:${value.toISOString()}`
40
+ }
41
+
42
+ if (value instanceof RegExp) {
43
+ return `regexp:${value.toString()}`
44
+ }
45
+
46
+ if (value instanceof Error) {
47
+ return `error:${value.name}:${value.message}`
48
+ }
49
+
50
+ if (value instanceof Set) {
51
+ const items = Array.from(value).map((v) => serialize(v, seen)).sort()
52
+ return `set:[${items.join(',')}]`
53
+ }
54
+
55
+ if (value instanceof Map) {
56
+ const entries = Array.from(value.entries())
57
+ .map(([k, v]) => `${serialize(k, seen)}=>${serialize(v, seen)}`)
58
+ .sort()
59
+ return `map:{${entries.join(',')}}`
60
+ }
61
+
62
+ if (ArrayBuffer.isView(value) || value instanceof ArrayBuffer) {
63
+ const buf = value instanceof ArrayBuffer ? new Uint8Array(value) : new Uint8Array((value as any).buffer, (value as any).byteOffset, (value as any).byteLength)
64
+ return `buffer:${Array.from(buf).join(',')}`
65
+ }
66
+
67
+ if (Array.isArray(value)) {
68
+ const items = value.map((v) => serialize(v, seen))
69
+ return `array:[${items.join(',')}]`
70
+ }
71
+
72
+ // Plain objects — sort keys for determinism
73
+ if (type === 'object') {
74
+ const entries = sortedEntries(value)
75
+ .map(([k, v]) => `${k}:${serialize(v, seen)}`)
76
+ return `object:{${entries.join(',')}}`
77
+ }
78
+
79
+ return `unknown:${String(value)}`
80
+ }
81
+
82
+ /**
83
+ * Simple non-crypto hash (djb2 variant) that works in all JS environments.
84
+ */
85
+ function djb2(str: string): string {
86
+ let h1 = 0x811c9dc5 // FNV offset basis
87
+ for (let i = 0; i < str.length; i++) {
88
+ h1 ^= str.charCodeAt(i)
89
+ h1 = (h1 * 0x01000193) >>> 0 // FNV prime, keep as uint32
90
+ }
91
+ return h1.toString(36)
92
+ }
93
+
94
+ export default function hashObject(value: any): string {
95
+ const serialized = serialize(value, new Set())
96
+ return djb2(serialized)
97
+ }