arcway 0.1.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 (274) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +711 -0
  3. package/client/env.js +55 -0
  4. package/client/fetcher.js +50 -0
  5. package/client/graphql.js +35 -0
  6. package/client/head.js +140 -0
  7. package/client/hooks/use-api.js +80 -0
  8. package/client/hooks/use-debounce.js +12 -0
  9. package/client/hooks/use-form.js +86 -0
  10. package/client/hooks/use-graphql.js +30 -0
  11. package/client/hooks/use-interval.js +12 -0
  12. package/client/hooks/use-mutation.js +27 -0
  13. package/client/hooks/use-query.js +45 -0
  14. package/client/hooks/web/use-click-outside.js +22 -0
  15. package/client/hooks/web/use-local-storage.js +42 -0
  16. package/client/index.js +62 -0
  17. package/client/page-loader.js +155 -0
  18. package/client/provider.js +53 -0
  19. package/client/query.js +13 -0
  20. package/client/router.jsx +303 -0
  21. package/client/ui/accordion.jsx +65 -0
  22. package/client/ui/accordion.stories.jsx +48 -0
  23. package/client/ui/alert-dialog.jsx +122 -0
  24. package/client/ui/alert-dialog.stories.jsx +44 -0
  25. package/client/ui/alert.jsx +52 -0
  26. package/client/ui/alert.stories.jsx +31 -0
  27. package/client/ui/app-shell.jsx +39 -0
  28. package/client/ui/app-shell.stories.jsx +51 -0
  29. package/client/ui/aspect-ratio.jsx +6 -0
  30. package/client/ui/aspect-ratio.stories.jsx +69 -0
  31. package/client/ui/avatar.jsx +78 -0
  32. package/client/ui/avatar.stories.jsx +62 -0
  33. package/client/ui/badge.jsx +34 -0
  34. package/client/ui/badge.stories.js +32 -0
  35. package/client/ui/breadcrumb.jsx +86 -0
  36. package/client/ui/breadcrumb.stories.jsx +43 -0
  37. package/client/ui/button-group.jsx +58 -0
  38. package/client/ui/button-group.stories.jsx +67 -0
  39. package/client/ui/button.jsx +46 -0
  40. package/client/ui/button.stories.js +72 -0
  41. package/client/ui/calendar.jsx +172 -0
  42. package/client/ui/card.jsx +57 -0
  43. package/client/ui/card.stories.jsx +33 -0
  44. package/client/ui/carousel.jsx +167 -0
  45. package/client/ui/chart.jsx +244 -0
  46. package/client/ui/checkbox.jsx +24 -0
  47. package/client/ui/checkbox.stories.js +33 -0
  48. package/client/ui/collapsible.jsx +12 -0
  49. package/client/ui/collapsible.stories.jsx +42 -0
  50. package/client/ui/combobox.jsx +223 -0
  51. package/client/ui/command.jsx +128 -0
  52. package/client/ui/context-menu.jsx +170 -0
  53. package/client/ui/context-menu.stories.jsx +35 -0
  54. package/client/ui/dialog.jsx +109 -0
  55. package/client/ui/dialog.stories.jsx +37 -0
  56. package/client/ui/direction.jsx +9 -0
  57. package/client/ui/drawer.jsx +87 -0
  58. package/client/ui/dropdown-menu.jsx +172 -0
  59. package/client/ui/dropdown-menu.stories.jsx +34 -0
  60. package/client/ui/empty.jsx +76 -0
  61. package/client/ui/empty.stories.jsx +64 -0
  62. package/client/ui/field.jsx +174 -0
  63. package/client/ui/field.stories.jsx +118 -0
  64. package/client/ui/form.jsx +17 -0
  65. package/client/ui/hooks/use-mobile.js +16 -0
  66. package/client/ui/hover-card.jsx +26 -0
  67. package/client/ui/hover-card.stories.jsx +28 -0
  68. package/client/ui/index.js +649 -0
  69. package/client/ui/input-group.jsx +116 -0
  70. package/client/ui/input-group.stories.jsx +65 -0
  71. package/client/ui/input-otp.jsx +62 -0
  72. package/client/ui/input.jsx +16 -0
  73. package/client/ui/input.stories.js +27 -0
  74. package/client/ui/item.jsx +155 -0
  75. package/client/ui/item.stories.jsx +118 -0
  76. package/client/ui/kbd.jsx +24 -0
  77. package/client/ui/kbd.stories.jsx +32 -0
  78. package/client/ui/label.jsx +16 -0
  79. package/client/ui/label.stories.js +25 -0
  80. package/client/ui/lib/utils.js +6 -0
  81. package/client/ui/main-content.jsx +30 -0
  82. package/client/ui/menubar.jsx +189 -0
  83. package/client/ui/menubar.stories.jsx +43 -0
  84. package/client/ui/native-select.jsx +34 -0
  85. package/client/ui/native-select.stories.jsx +67 -0
  86. package/client/ui/navigation-menu.jsx +120 -0
  87. package/client/ui/navigation-menu.stories.jsx +45 -0
  88. package/client/ui/pagination.jsx +92 -0
  89. package/client/ui/pagination.stories.jsx +52 -0
  90. package/client/ui/panel.jsx +66 -0
  91. package/client/ui/popover.jsx +54 -0
  92. package/client/ui/popover.stories.jsx +27 -0
  93. package/client/ui/progress.jsx +19 -0
  94. package/client/ui/progress.stories.js +34 -0
  95. package/client/ui/radio-group.jsx +33 -0
  96. package/client/ui/radio-group.stories.jsx +49 -0
  97. package/client/ui/resizable.jsx +33 -0
  98. package/client/ui/scroll-area.jsx +41 -0
  99. package/client/ui/scroll-area.stories.jsx +43 -0
  100. package/client/ui/select.jsx +145 -0
  101. package/client/ui/select.stories.jsx +80 -0
  102. package/client/ui/separator.jsx +18 -0
  103. package/client/ui/separator.stories.jsx +37 -0
  104. package/client/ui/sheet.jsx +95 -0
  105. package/client/ui/sheet.stories.jsx +56 -0
  106. package/client/ui/sidebar.jsx +544 -0
  107. package/client/ui/skeleton.jsx +8 -0
  108. package/client/ui/skeleton.stories.js +23 -0
  109. package/client/ui/slider.jsx +41 -0
  110. package/client/ui/slider.stories.js +31 -0
  111. package/client/ui/sonner.jsx +37 -0
  112. package/client/ui/spinner.jsx +14 -0
  113. package/client/ui/spinner.stories.js +16 -0
  114. package/client/ui/style-mira.css +1316 -0
  115. package/client/ui/switch.jsx +22 -0
  116. package/client/ui/switch.stories.js +44 -0
  117. package/client/ui/table.jsx +33 -0
  118. package/client/ui/table.stories.jsx +42 -0
  119. package/client/ui/tabs.jsx +63 -0
  120. package/client/ui/tabs.stories.jsx +45 -0
  121. package/client/ui/textarea.jsx +15 -0
  122. package/client/ui/textarea.stories.js +33 -0
  123. package/client/ui/theme.css +459 -0
  124. package/client/ui/toggle-group.jsx +62 -0
  125. package/client/ui/toggle-group.stories.jsx +68 -0
  126. package/client/ui/toggle.jsx +34 -0
  127. package/client/ui/toggle.stories.js +46 -0
  128. package/client/ui/tooltip.jsx +37 -0
  129. package/client/ui/tooltip.stories.jsx +32 -0
  130. package/client/ui/use-transition.js +35 -0
  131. package/client/ws.js +132 -0
  132. package/package.json +134 -0
  133. package/server/bin/cli.js +42 -0
  134. package/server/bin/commands/build.js +23 -0
  135. package/server/bin/commands/dev.js +57 -0
  136. package/server/bin/commands/docs.js +30 -0
  137. package/server/bin/commands/graphql-schema.js +32 -0
  138. package/server/bin/commands/lint.js +35 -0
  139. package/server/bin/commands/mcp.js +26 -0
  140. package/server/bin/commands/migrate.js +82 -0
  141. package/server/bin/commands/schema.js +41 -0
  142. package/server/bin/commands/seed.js +36 -0
  143. package/server/bin/commands/start.js +31 -0
  144. package/server/bin/commands/test.js +20 -0
  145. package/server/bin/solo.js +4 -0
  146. package/server/boot/index.js +150 -0
  147. package/server/boot.js +2 -0
  148. package/server/build.js +23 -0
  149. package/server/cache/drivers/memory.js +23 -0
  150. package/server/cache/drivers/redis.js +28 -0
  151. package/server/cache/index.js +69 -0
  152. package/server/config/loader.js +89 -0
  153. package/server/config/modules/api.js +17 -0
  154. package/server/config/modules/build.js +9 -0
  155. package/server/config/modules/cache.js +10 -0
  156. package/server/config/modules/database.js +29 -0
  157. package/server/config/modules/events.js +15 -0
  158. package/server/config/modules/files.js +15 -0
  159. package/server/config/modules/jobs.js +20 -0
  160. package/server/config/modules/logger.js +9 -0
  161. package/server/config/modules/mail.js +11 -0
  162. package/server/config/modules/mcp.js +9 -0
  163. package/server/config/modules/pages.js +20 -0
  164. package/server/config/modules/queue.js +10 -0
  165. package/server/config/modules/redis.js +9 -0
  166. package/server/config/modules/server.js +30 -0
  167. package/server/config/modules/session.js +9 -0
  168. package/server/config/modules/websocket.js +11 -0
  169. package/server/constants.js +67 -0
  170. package/server/context.js +15 -0
  171. package/server/db/index.js +87 -0
  172. package/server/db/schema/drivers/mysql.js +28 -0
  173. package/server/db/schema/drivers/pg.js +34 -0
  174. package/server/db/schema/drivers/sqlite.js +22 -0
  175. package/server/db/schema/index.js +78 -0
  176. package/server/db/seeds.js +22 -0
  177. package/server/discovery.js +67 -0
  178. package/server/docs/openapi.js +153 -0
  179. package/server/env.js +17 -0
  180. package/server/events/drivers/memory.js +45 -0
  181. package/server/events/drivers/redis.js +64 -0
  182. package/server/events/handler.js +67 -0
  183. package/server/events/index.js +35 -0
  184. package/server/events/pattern.js +5 -0
  185. package/server/files/drivers/local.js +83 -0
  186. package/server/files/drivers/s3.js +113 -0
  187. package/server/files/index.js +57 -0
  188. package/server/filewatcher/index.js +156 -0
  189. package/server/glob.js +6 -0
  190. package/server/graphql/discovery.js +70 -0
  191. package/server/graphql/handler.js +41 -0
  192. package/server/graphql/index.js +13 -0
  193. package/server/graphql/loaders.js +19 -0
  194. package/server/graphql/merge.js +48 -0
  195. package/server/graphql/subscriptions.js +43 -0
  196. package/server/health.js +34 -0
  197. package/server/helpers.js +9 -0
  198. package/server/index.js +55 -0
  199. package/server/internals.js +139 -0
  200. package/server/jobs/cron.js +10 -0
  201. package/server/jobs/drivers/knex-queue.js +207 -0
  202. package/server/jobs/drivers/lease.js +148 -0
  203. package/server/jobs/drivers/memory-queue.js +134 -0
  204. package/server/jobs/queue.js +27 -0
  205. package/server/jobs/runner.js +197 -0
  206. package/server/jobs/throughput.js +63 -0
  207. package/server/lib/vault/encrypt.js +40 -0
  208. package/server/lib/vault/ids.js +9 -0
  209. package/server/lib/vault/index.js +14 -0
  210. package/server/lib/vault/jwt.js +55 -0
  211. package/server/lib/vault/password.js +10 -0
  212. package/server/lint/boundaries.js +77 -0
  213. package/server/logger/index.js +130 -0
  214. package/server/mail/drivers/console.js +31 -0
  215. package/server/mail/drivers/smtp.js +34 -0
  216. package/server/mail/imap.js +105 -0
  217. package/server/mail/inbound-store.js +58 -0
  218. package/server/mail/inbound.js +79 -0
  219. package/server/mail/index.js +112 -0
  220. package/server/mcp/debug-api.js +137 -0
  221. package/server/mcp/helpers.js +30 -0
  222. package/server/mcp/index.js +77 -0
  223. package/server/mcp/runtime.js +7 -0
  224. package/server/mcp/server.js +19 -0
  225. package/server/mcp/tools/debugging.js +133 -0
  226. package/server/mcp/tools/introspection.js +87 -0
  227. package/server/middlewares/cors.js +30 -0
  228. package/server/middlewares/index.js +3 -0
  229. package/server/middlewares/require-session.js +15 -0
  230. package/server/module-loader.js +9 -0
  231. package/server/pages/build-client.js +187 -0
  232. package/server/pages/build-css.js +47 -0
  233. package/server/pages/build-manifest.js +55 -0
  234. package/server/pages/build-plugins.js +75 -0
  235. package/server/pages/build-server.js +115 -0
  236. package/server/pages/build.js +116 -0
  237. package/server/pages/discovery.js +120 -0
  238. package/server/pages/fonts.js +128 -0
  239. package/server/pages/handler.js +276 -0
  240. package/server/pages/hmr.js +176 -0
  241. package/server/pages/pages-router.js +78 -0
  242. package/server/pages/ssr.js +276 -0
  243. package/server/pages/static.js +92 -0
  244. package/server/pages/watcher.js +90 -0
  245. package/server/queue/drivers/knex.js +67 -0
  246. package/server/queue/drivers/redis.js +91 -0
  247. package/server/queue/index.js +61 -0
  248. package/server/rate-limit/consume.js +21 -0
  249. package/server/rate-limit/drivers/memory.js +24 -0
  250. package/server/rate-limit/drivers/redis.js +32 -0
  251. package/server/rate-limit/index.js +33 -0
  252. package/server/redis/index.js +67 -0
  253. package/server/ring-buffer.js +44 -0
  254. package/server/route.js +4 -0
  255. package/server/router/api-router.js +317 -0
  256. package/server/router/cors.js +31 -0
  257. package/server/router/middleware.js +91 -0
  258. package/server/router/routes.js +132 -0
  259. package/server/server.js +35 -0
  260. package/server/session/helpers.js +21 -0
  261. package/server/session/index.js +89 -0
  262. package/server/static/index.js +36 -0
  263. package/server/system-jobs/index.js +50 -0
  264. package/server/system-routes/index.js +84 -0
  265. package/server/testing/index.js +263 -0
  266. package/server/validation.js +41 -0
  267. package/server/watcher.js +34 -0
  268. package/server/web-server.js +231 -0
  269. package/server/ws/discovery.js +54 -0
  270. package/server/ws/index.js +14 -0
  271. package/server/ws/realtime.js +318 -0
  272. package/server/ws/registry.js +17 -0
  273. package/server/ws/server.js +152 -0
  274. package/server/ws/ws-router.js +335 -0
@@ -0,0 +1,32 @@
1
+ import React from 'react';
2
+ import { expect, within } from 'storybook/test';
3
+ import { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider } from './tooltip.jsx';
4
+ import { Button } from './button.jsx';
5
+ const meta = {
6
+ title: 'UI/Tooltip',
7
+ component: Tooltip,
8
+ decorators: [
9
+ (Story) => (
10
+ <TooltipProvider>
11
+ <Story />
12
+ </TooltipProvider>
13
+ ),
14
+ ],
15
+ };
16
+ var stdin_default = meta;
17
+ const Default = {
18
+ render: () => (
19
+ <Tooltip>
20
+ <TooltipTrigger asChild>
21
+ <Button>Hover me</Button>
22
+ </TooltipTrigger>
23
+ <TooltipContent>Tooltip text</TooltipContent>
24
+ </Tooltip>
25
+ ),
26
+ play: async ({ canvasElement }) => {
27
+ const canvas = within(canvasElement);
28
+ const trigger = canvas.getByRole('button', { name: 'Hover me' });
29
+ await expect(trigger).toBeInTheDocument();
30
+ },
31
+ };
32
+ export { Default, stdin_default as default };
@@ -0,0 +1,35 @@
1
+ import { useState, useEffect, useRef, useCallback } from 'react';
2
+ function useTransition(open, duration = 150) {
3
+ const [mounted, setMounted] = useState(open);
4
+ const [stage, setStage] = useState(open ? 'idle' : 'exit');
5
+ const timerRef = useRef(void 0);
6
+ const rafRef = useRef(void 0);
7
+ useEffect(() => {
8
+ if (open) {
9
+ setMounted(true);
10
+ setStage('enter');
11
+ rafRef.current = requestAnimationFrame(() => {
12
+ rafRef.current = requestAnimationFrame(() => {
13
+ setStage('idle');
14
+ });
15
+ });
16
+ } else if (mounted) {
17
+ setStage('exit');
18
+ clearTimeout(timerRef.current);
19
+ timerRef.current = setTimeout(() => {
20
+ setMounted(false);
21
+ }, duration);
22
+ }
23
+ return () => {
24
+ clearTimeout(timerRef.current);
25
+ if (rafRef.current) cancelAnimationFrame(rafRef.current);
26
+ };
27
+ }, [open]);
28
+ const onAnimationEnd = useCallback(() => {
29
+ if (!open) {
30
+ setMounted(false);
31
+ }
32
+ }, [open]);
33
+ return { mounted, stage, onAnimationEnd };
34
+ }
35
+ export { useTransition };
package/client/ws.js ADDED
@@ -0,0 +1,132 @@
1
+ import { io } from 'socket.io-client';
2
+
3
+ class WsManager {
4
+ socket = null;
5
+ socketId = null;
6
+ subscriptions = new Map();
7
+ pendingRequests = new Map();
8
+ messageId = 0;
9
+ requestTimeout;
10
+ url;
11
+ wsPath;
12
+
13
+ constructor(options) {
14
+ this.url = options.url;
15
+ this.requestTimeout = options.requestTimeout ?? 3e4;
16
+
17
+ try {
18
+ this.wsPath = new URL(this.url).pathname || '/ws';
19
+ } catch {
20
+ this.wsPath = '/ws';
21
+ }
22
+ }
23
+
24
+ isConnected() {
25
+ return !!this.socket?.connected && this.socketId !== null;
26
+ }
27
+
28
+ connect() {
29
+ if (this.socket) return;
30
+
31
+ try {
32
+ const parsed = new URL(this.url);
33
+ this.socket = io(parsed.origin, {
34
+ path: this.wsPath,
35
+ transports: ['websocket'],
36
+ withCredentials: true,
37
+ });
38
+ } catch {
39
+ return;
40
+ }
41
+
42
+ this.socket.on('msg', (data) => {
43
+ // Handle initial connection handshake
44
+ if (data.path === this.wsPath && data.data?.socketId) {
45
+ this.socketId = data.data.socketId;
46
+ for (const path of this.subscriptions.keys()) {
47
+ this.socket.emit('msg', { path, method: 'SUBSCRIBE' });
48
+ }
49
+ return;
50
+ }
51
+
52
+ // Resolve pending request-response
53
+ if (data.id) {
54
+ const pending = this.pendingRequests.get(data.id);
55
+ if (pending) {
56
+ clearTimeout(pending.timeout);
57
+ this.pendingRequests.delete(data.id);
58
+ pending.resolve(data);
59
+ }
60
+ }
61
+
62
+ // Notify subscription listeners
63
+ if (data.path) {
64
+ const callbacks = this.subscriptions.get(data.path);
65
+ if (callbacks) {
66
+ for (const cb of callbacks) cb(data);
67
+ }
68
+ }
69
+ });
70
+
71
+ this.socket.on('disconnect', () => {
72
+ this.socketId = null;
73
+ for (const [id, pending] of this.pendingRequests) {
74
+ clearTimeout(pending.timeout);
75
+ pending.reject(new Error('WebSocket connection closed'));
76
+ this.pendingRequests.delete(id);
77
+ }
78
+ });
79
+ }
80
+
81
+ disconnect() {
82
+ if (this.socket) {
83
+ this.socket.disconnect();
84
+ this.socket = null;
85
+ }
86
+ this.socketId = null;
87
+ }
88
+
89
+ subscribe(path, callback) {
90
+ let callbacks = this.subscriptions.get(path);
91
+ const isNew = !callbacks;
92
+
93
+ if (!callbacks) {
94
+ callbacks = new Set();
95
+ this.subscriptions.set(path, callbacks);
96
+ }
97
+ callbacks.add(callback);
98
+
99
+ if (isNew && this.isConnected()) {
100
+ this.socket.emit('msg', { path, method: 'SUBSCRIBE' });
101
+ }
102
+
103
+ return () => {
104
+ callbacks.delete(callback);
105
+ if (callbacks.size === 0) {
106
+ this.subscriptions.delete(path);
107
+ if (this.isConnected()) {
108
+ this.socket.emit('msg', { path, method: 'UNSUBSCRIBE' });
109
+ }
110
+ }
111
+ };
112
+ }
113
+
114
+ async request(path, method, body) {
115
+ if (!this.isConnected()) {
116
+ throw new Error('WebSocket not connected');
117
+ }
118
+
119
+ const id = String(++this.messageId);
120
+ return new Promise((resolve, reject) => {
121
+ const timeout = setTimeout(() => {
122
+ this.pendingRequests.delete(id);
123
+ reject(new Error(`WebSocket request timeout: ${method} ${path}`));
124
+ }, this.requestTimeout);
125
+
126
+ this.pendingRequests.set(id, { resolve, reject, timeout });
127
+ this.socket.emit('msg', { path, method, body, id });
128
+ });
129
+ }
130
+ }
131
+
132
+ export { WsManager };
package/package.json ADDED
@@ -0,0 +1,134 @@
1
+ {
2
+ "name": "arcway",
3
+ "version": "0.1.0",
4
+ "description": "A convention-based framework for building modular monoliths with strict domain boundaries.",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "main": "./server/index.js",
8
+ "files": [
9
+ "server/",
10
+ "client/",
11
+ "LICENSE",
12
+ "README.md"
13
+ ],
14
+ "bin": {
15
+ "arcway": "./server/bin/solo.js"
16
+ },
17
+ "imports": {
18
+ "#server/*": "./server/*",
19
+ "#client/*": "./client/*"
20
+ },
21
+ "exports": {
22
+ ".": "./server/index.js",
23
+ "./internals": "./server/internals.js",
24
+ "./lib/vault": "./server/lib/vault/index.js",
25
+ "./lib/client": "./client/index.js",
26
+ "./ui": "./client/ui/index.js",
27
+ "./middlewares": "./server/middlewares/index.js",
28
+ "./ui/theme.css": "./client/ui/theme.css",
29
+ "./ui/style-mira.css": "./client/ui/style-mira.css"
30
+ },
31
+ "scripts": {
32
+ "test": "vitest run --project=unit",
33
+ "test:storybook": "TMPDIR=~/tmp PLAYWRIGHT_BROWSERS_PATH=~/.cache/playwright vitest run --project=storybook",
34
+ "test:all": "TMPDIR=~/tmp PLAYWRIGHT_BROWSERS_PATH=~/.cache/playwright vitest run",
35
+ "format": "prettier --write 'server/**/*.js' 'client/**/*.js' 'tests/**/*.js'",
36
+ "format:check": "prettier --check 'server/**/*.js' 'client/**/*.js' 'tests/**/*.js'",
37
+ "lint": "eslint server/ client/ tests/",
38
+ "lint:fix": "eslint server/ client/ tests/ --fix",
39
+ "storybook": "storybook dev -p 6006",
40
+ "build-storybook": "storybook build"
41
+ },
42
+ "dependencies": {
43
+ "@aws-sdk/client-s3": "^3.987.0",
44
+ "@babel/core": "^7.29.0",
45
+ "@base-ui/react": "^1.2.0",
46
+ "@modelcontextprotocol/sdk": "^1.26.0",
47
+ "@tailwindcss/postcss": "^4.1.18",
48
+ "arktype": "^2.1.29",
49
+ "bcryptjs": "^3.0.3",
50
+ "better-sqlite3": "^12.6.2",
51
+ "cache-manager": "^7.2.8",
52
+ "chokidar": "^5.0.0",
53
+ "class-variance-authority": "^0.7.1",
54
+ "clsx": "^2.1.1",
55
+ "cmdk": "^1.1.1",
56
+ "commander": "^14.0.3",
57
+ "cookie": "^1.1.1",
58
+ "cron-parser": "^5.5.0",
59
+ "dataloader": "^2.2.3",
60
+ "dotenv": "^17.2.4",
61
+ "embla-carousel-react": "^8.6.0",
62
+ "esbuild": "^0.27.3",
63
+ "glob": "^11.0.0",
64
+ "graphql": "^16.12.0",
65
+ "graphql-ws": "^6.0.7",
66
+ "graphql-yoga": "^5.18.0",
67
+ "imapflow": "^1.2.9",
68
+ "input-otp": "^1.4.2",
69
+ "ioredis": "^5.9.2",
70
+ "ioredis-mock": "^8.13.1",
71
+ "iron-session": "^8.0.4",
72
+ "jose": "^6.1.3",
73
+ "knex": "^3.1.0",
74
+ "lucide-react": "^0.564.0",
75
+ "mailparser": "^3.9.3",
76
+ "nanoid": "^5.1.6",
77
+ "nodemailer": "^8.0.1",
78
+ "p-limit": "^7.3.0",
79
+ "pg": "^8.13.0",
80
+ "pino": "^10.3.1",
81
+ "pino-pretty": "^13.1.3",
82
+ "postcss": "^8.5.6",
83
+ "radix-ui": "^1.4.3",
84
+ "rate-limiter-flexible": "^9.1.1",
85
+ "react-day-picker": "^9.13.2",
86
+ "react-refresh": "^0.18.0",
87
+ "react-resizable-panels": "^4.6.4",
88
+ "recharts": "^3.7.0",
89
+ "socket.io": "^4.8.3",
90
+ "socket.io-client": "^4.8.3",
91
+ "sonner": "^2.0.7",
92
+ "swr": "^2.3.3",
93
+ "tailwind-merge": "^3.4.1",
94
+ "tailwindcss": "^4.1.18",
95
+ "tailwindcss-animate": "^1.0.7",
96
+ "vaul": "^1.1.2",
97
+ "ws": "^8.19.0",
98
+ "zod": "^3.24.0"
99
+ },
100
+ "peerDependencies": {
101
+ "react": ">=18",
102
+ "react-dom": ">=18"
103
+ },
104
+ "peerDependenciesMeta": {
105
+ "react": {
106
+ "optional": true
107
+ },
108
+ "react-dom": {
109
+ "optional": true
110
+ }
111
+ },
112
+ "devDependencies": {
113
+ "@chromatic-com/storybook": "^5.0.1",
114
+ "@storybook/addon-a11y": "^10.2.8",
115
+ "@storybook/addon-docs": "^10.2.8",
116
+ "@storybook/addon-onboarding": "^10.2.8",
117
+ "@storybook/addon-vitest": "^10.2.8",
118
+ "@storybook/react-vite": "^10.2.8",
119
+ "@tailwindcss/vite": "^4.1.18",
120
+ "@testing-library/react": "^16.3.2",
121
+ "@vitest/browser-playwright": "4.0.18",
122
+ "@vitest/coverage-v8": "^4.0.18",
123
+ "eslint": "^9.39.2",
124
+ "eslint-plugin-storybook": "^10.2.8",
125
+ "happy-dom": "^20.6.1",
126
+ "jsdom": "^28.0.0",
127
+ "playwright": "^1.58.2",
128
+ "prettier": "^3.8.1",
129
+ "react": "^19.2.4",
130
+ "react-dom": "^19.2.4",
131
+ "storybook": "^10.2.8",
132
+ "vitest": "^4.0.18"
133
+ }
134
+ }
@@ -0,0 +1,42 @@
1
+ import { Command } from 'commander';
2
+ import { createRequire } from 'node:module';
3
+ import registerDev from './commands/dev.js';
4
+ import registerStart from './commands/start.js';
5
+ import registerMcp from './commands/mcp.js';
6
+ import registerBuild from './commands/build.js';
7
+ import registerSeed from './commands/seed.js';
8
+ import registerDocs from './commands/docs.js';
9
+ import registerTest from './commands/test.js';
10
+ import registerLint from './commands/lint.js';
11
+ import registerGraphQLSchema from './commands/graphql-schema.js';
12
+ import registerSchema from './commands/schema.js';
13
+ import registerMigrate from './commands/migrate.js';
14
+
15
+ function getPackageVersion() {
16
+ try {
17
+ const require2 = createRequire(import.meta.url);
18
+ const pkg = require2('../../package.json');
19
+ return pkg.version ?? '0.0.0';
20
+ } catch {
21
+ return '0.0.0';
22
+ }
23
+ }
24
+
25
+ function createProgram() {
26
+ const program = new Command();
27
+ program.name('arcway').description('Arcway framework CLI').version(getPackageVersion());
28
+ registerDev(program);
29
+ registerMcp(program);
30
+ registerStart(program);
31
+ registerBuild(program);
32
+ registerSeed(program);
33
+ registerDocs(program);
34
+ registerTest(program);
35
+ registerLint(program);
36
+ registerGraphQLSchema(program);
37
+ registerSchema(program);
38
+ registerMigrate(program);
39
+ return program;
40
+ }
41
+
42
+ export { createProgram };
@@ -0,0 +1,23 @@
1
+ import { build } from '#server/build.js';
2
+
3
+ async function runBuild() {
4
+ const rootDir = process.cwd();
5
+ try {
6
+ console.log('Building pages for production...');
7
+ const result = await build({ rootDir });
8
+ if (result.pages) {
9
+ console.log('Pages build complete.');
10
+ } else {
11
+ console.log('No pages to build.');
12
+ }
13
+ } catch (err) {
14
+ console.error('Build failed:', err instanceof Error ? err.message : err);
15
+ process.exit(1);
16
+ }
17
+ }
18
+
19
+ function register(program) {
20
+ program.command('build').description('Build pages for production').action(runBuild);
21
+ }
22
+
23
+ export default register;
@@ -0,0 +1,57 @@
1
+ import path from 'node:path';
2
+ import { startServer } from './start.js';
3
+ import { startWatcher } from '#server/watcher.js';
4
+
5
+ async function runDev() {
6
+ const rootDir = process.cwd();
7
+ let app = await startServer('development');
8
+ let restarting = false;
9
+
10
+ const stopWatcher = startWatcher({
11
+ rootDir,
12
+ onChange: async (filePath) => {
13
+ if (restarting) return;
14
+ const relative = path.relative(rootDir, filePath).replace(/\\/g, '/');
15
+ if (relative.startsWith('api/')) return;
16
+ restarting = true;
17
+ app.logger.info(`File changed: ${relative}, restarting...`);
18
+ try {
19
+ await app.shutdown();
20
+ } catch {
21
+ // Shutdown errors during restart are non-fatal
22
+ }
23
+ try {
24
+ app = await startServer('development');
25
+ } catch (err) {
26
+ app.logger.error('Restart failed', { error: String(err) });
27
+ app.logger.info('Watching for changes to retry...');
28
+ }
29
+ restarting = false;
30
+ },
31
+ });
32
+
33
+ // Override the shutdown handlers to also stop the watcher
34
+ process.removeAllListeners('SIGINT');
35
+ process.removeAllListeners('SIGTERM');
36
+
37
+ let shuttingDown = false;
38
+ const shutdown = async () => {
39
+ if (shuttingDown) return;
40
+ shuttingDown = true;
41
+ stopWatcher();
42
+ await app.shutdown();
43
+ process.exit(0);
44
+ };
45
+
46
+ process.on('SIGINT', shutdown);
47
+ process.on('SIGTERM', shutdown);
48
+ }
49
+
50
+ function register(program) {
51
+ program
52
+ .command('dev')
53
+ .description('Start development server with file watching and auto-restart')
54
+ .action(runDev);
55
+ }
56
+
57
+ export default register;
@@ -0,0 +1,30 @@
1
+ import fs from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { discoverRoutes } from '#server/router/routes.js';
4
+ import { generateOpenAPISpec } from '#server/docs/openapi.js';
5
+ async function runDocs(outFile) {
6
+ const rootDir = process.cwd();
7
+ try {
8
+ console.log('Discovering routes...');
9
+ const routes = await discoverRoutes(path.join(rootDir, 'api'));
10
+ console.log(`Found ${routes.length} route(s)`);
11
+ const spec = generateOpenAPISpec(routes, {
12
+ title: 'Arcway API',
13
+ version: '1.0.0',
14
+ });
15
+ const json = JSON.stringify(spec, null, 2);
16
+ await fs.writeFile(outFile, json, 'utf-8');
17
+ console.log(`OpenAPI spec written to ${outFile}`);
18
+ } catch (err) {
19
+ console.error('Docs generation failed:', err instanceof Error ? err.message : err);
20
+ process.exit(1);
21
+ }
22
+ }
23
+ function register(program) {
24
+ program
25
+ .command('docs')
26
+ .description('Generate OpenAPI specification from discovered routes')
27
+ .argument('[outFile]', 'output file path', 'openapi.json')
28
+ .action(runDocs);
29
+ }
30
+ export default register;
@@ -0,0 +1,32 @@
1
+ import path from 'node:path';
2
+ import fs from 'node:fs/promises';
3
+ import { discoverGraphQL } from '#server/graphql/discovery.js';
4
+ import { mergeGraphQLSchemas } from '#server/graphql/merge.js';
5
+ async function runGraphQLSchema(outFile) {
6
+ const rootDir = process.cwd();
7
+ try {
8
+ console.log('Discovering GraphQL modules...');
9
+ const graphqlEntries = await discoverGraphQL(rootDir);
10
+ if (graphqlEntries.length === 0) {
11
+ console.log('No GraphQL modules found in graphql/ directory.');
12
+ return;
13
+ }
14
+ const mergedSDL = mergeGraphQLSchemas(graphqlEntries);
15
+ const outputPath = path.resolve(rootDir, outFile);
16
+ await fs.mkdir(path.dirname(outputPath), { recursive: true });
17
+ await fs.writeFile(outputPath, mergedSDL, 'utf-8');
18
+ console.log(`
19
+ GraphQL schema written to ${path.relative(rootDir, outputPath)}`);
20
+ } catch (err) {
21
+ console.error('GraphQL schema export failed:', err instanceof Error ? err.message : err);
22
+ process.exit(1);
23
+ }
24
+ }
25
+ function register(program) {
26
+ program
27
+ .command('graphql:schema')
28
+ .description('Export merged GraphQL SDL schema')
29
+ .argument('[outFile]', 'output file path', 'schema.graphql')
30
+ .action(runGraphQLSchema);
31
+ }
32
+ export default register;
@@ -0,0 +1,35 @@
1
+ import { checkServerClientBoundaries } from '#server/lint/boundaries.js';
2
+ async function runLint() {
3
+ const rootDir = process.cwd();
4
+ let hasViolations = false;
5
+ try {
6
+ console.log('Checking server/client boundaries...');
7
+ const scResult = await checkServerClientBoundaries(rootDir);
8
+ console.log(` Files scanned: ${scResult.filesScanned}`);
9
+ if (scResult.violations.length === 0) {
10
+ console.log(' No server/client boundary violations found.');
11
+ } else {
12
+ hasViolations = true;
13
+ console.log(`
14
+ ${scResult.violations.length} server/client boundary violation(s) found:
15
+ `);
16
+ for (const v of scResult.violations) {
17
+ console.log(` ${v.file}:${v.line}`);
18
+ console.log(` ${v.message}`);
19
+ console.log(` Import: ${v.importPath}`);
20
+ console.log('');
21
+ }
22
+ }
23
+ if (hasViolations) {
24
+ process.exit(1);
25
+ }
26
+ console.log('\nAll boundary checks passed.');
27
+ } catch (err) {
28
+ console.error('Lint failed:', err instanceof Error ? err.message : err);
29
+ process.exit(1);
30
+ }
31
+ }
32
+ function register(program) {
33
+ program.command('lint').description('Check server/client boundary violations').action(runLint);
34
+ }
35
+ export default register;
@@ -0,0 +1,26 @@
1
+ import { makeConfig } from '#server/config/loader.js';
2
+ import { loadEnvFiles } from '#server/env.js';
3
+ async function runMcp() {
4
+ const rootDir = process.cwd();
5
+ loadEnvFiles(rootDir, 'development');
6
+ try {
7
+ const config = await makeConfig(rootDir);
8
+ if (config.mcp?.enabled === false) {
9
+ process.stderr.write('MCP is disabled in arcway.config.js (mcp.enabled = false)\n');
10
+ process.exit(1);
11
+ }
12
+ const { startMcpServer } = await import('../../mcp/server.js');
13
+ await startMcpServer(rootDir);
14
+ } catch (err) {
15
+ process.stderr.write(`MCP server failed: ${err instanceof Error ? err.message : err}
16
+ `);
17
+ process.exit(1);
18
+ }
19
+ }
20
+ function register(program) {
21
+ program
22
+ .command('mcp')
23
+ .description('Start MCP (Model Context Protocol) debug server over stdio for AI coding agents')
24
+ .action(runMcp);
25
+ }
26
+ export default register;
@@ -0,0 +1,82 @@
1
+ import path from 'node:path';
2
+ import fs from 'node:fs/promises';
3
+ import { makeConfig } from '#server/config/loader.js';
4
+ import { loadEnvFiles } from '#server/env.js';
5
+ import { createDB } from '#server/db/index.js';
6
+ async function runMigrateMake(name) {
7
+ const rootDir = process.cwd();
8
+ try {
9
+ const migrationsDir = path.resolve(rootDir, 'migrations');
10
+ await fs.mkdir(migrationsDir, { recursive: true });
11
+ const now = new Date();
12
+ const timestamp = [
13
+ now.getFullYear(),
14
+ String(now.getMonth() + 1).padStart(2, '0'),
15
+ String(now.getDate()).padStart(2, '0'),
16
+ String(now.getHours()).padStart(2, '0'),
17
+ String(now.getMinutes()).padStart(2, '0'),
18
+ ].join('');
19
+ const fileName = `${timestamp}-${name}.js`;
20
+ const filePath = path.join(migrationsDir, fileName);
21
+ const template = `export async function up(knex) {
22
+ // TODO: implement migration
23
+ }
24
+
25
+ export async function down(knex) {
26
+ // TODO: implement rollback
27
+ }
28
+ `;
29
+ await fs.writeFile(filePath, template, 'utf-8');
30
+ console.log(`Created migration: ${path.relative(rootDir, filePath)}`);
31
+ } catch (err) {
32
+ console.error('Failed to create migration:', err instanceof Error ? err.message : err);
33
+ process.exit(1);
34
+ }
35
+ }
36
+ async function runMigrateRun() {
37
+ const rootDir = process.cwd();
38
+ const mode = process.env.NODE_ENV === 'production' ? 'production' : 'development';
39
+ loadEnvFiles(rootDir, mode);
40
+ try {
41
+ const config = await makeConfig(rootDir);
42
+ const db = await createDB(config.database);
43
+ await db.runMigrations();
44
+ await db.destroy();
45
+ } catch (err) {
46
+ console.error('Migration failed:', err instanceof Error ? err.message : err);
47
+ process.exit(1);
48
+ }
49
+ }
50
+ async function runMigrateRollback() {
51
+ const rootDir = process.cwd();
52
+ const mode = process.env.NODE_ENV === 'production' ? 'production' : 'development';
53
+ loadEnvFiles(rootDir, mode);
54
+ try {
55
+ const config = await makeConfig(rootDir);
56
+ const db = await createDB(config.database);
57
+ const { batch, log } = await db.runRollback();
58
+ if (log.length > 0) {
59
+ console.log(`Rolled back batch ${batch}: ${log.join(', ')}`);
60
+ } else {
61
+ console.log('Nothing to rollback.');
62
+ }
63
+ await db.destroy();
64
+ } catch (err) {
65
+ console.error('Rollback failed:', err instanceof Error ? err.message : err);
66
+ process.exit(1);
67
+ }
68
+ }
69
+ function register(program) {
70
+ const migrate = program.command('migrate').description('Database migration commands');
71
+ migrate
72
+ .command('make')
73
+ .description('Create a new migration file')
74
+ .argument('<name>', 'migration name (e.g. create_users)')
75
+ .action(runMigrateMake);
76
+ migrate.command('run').description('Run pending migrations').action(runMigrateRun);
77
+ migrate
78
+ .command('rollback')
79
+ .description('Rollback the last batch of migrations')
80
+ .action(runMigrateRollback);
81
+ }
82
+ export default register;