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,120 @@
1
+ import { glob } from '../glob.js';
2
+ import path from 'node:path';
3
+ import fs from 'node:fs/promises';
4
+ import {
5
+ filePathToPattern,
6
+ compilePattern,
7
+ sortBySpecificity,
8
+ matchPattern,
9
+ } from '../router/routes.js';
10
+ function dirDepth(dirPath) {
11
+ return dirPath === '' ? 0 : dirPath.split(path.sep).length;
12
+ }
13
+ function dirDepthForward(dirPath) {
14
+ return dirPath === '' ? 0 : dirPath.split('/').length;
15
+ }
16
+ async function discoverSpecialFiles(pagesDir, fileName, extensions) {
17
+ const pattern = path.join(pagesDir, '**', `${fileName}.{${extensions}}`);
18
+ const files = await glob(pattern);
19
+ const result = [];
20
+ for (const filePath of files) {
21
+ const dirPath = path.relative(pagesDir, path.dirname(filePath)).replace(/\\/g, '/');
22
+ result.push({ dirPath, filePath });
23
+ }
24
+ result.sort((a, b) => dirDepth(b.dirPath) - dirDepth(a.dirPath));
25
+ return result;
26
+ }
27
+ function resolveFileChain(pagePattern, items) {
28
+ const pagePath = pagePattern === '/' ? '' : pagePattern.slice(1);
29
+ const chain = [];
30
+ for (const item of items) {
31
+ if (item.dirPath === '') {
32
+ chain.push(item);
33
+ continue;
34
+ }
35
+ const prefix = item.dirPath.replace(/\\/g, '/').replace(/\[([^\]]+)\]/g, ':$1');
36
+ const normalized = pagePath.replace(/\\/g, '/');
37
+ if (normalized === prefix || normalized.startsWith(prefix + '/')) {
38
+ chain.push(item);
39
+ }
40
+ }
41
+ chain.sort((a, b) => dirDepthForward(a.dirPath) - dirDepthForward(b.dirPath));
42
+ return chain;
43
+ }
44
+ async function discoverPages(pagesDir) {
45
+ const pattern = path.join(pagesDir, '**', '*.{tsx,jsx,ts,js}');
46
+ const pageFiles = await glob(pattern);
47
+ const pages = [];
48
+ for (const filePath of pageFiles) {
49
+ const fileName = path.basename(filePath);
50
+ if (fileName.startsWith('_')) continue;
51
+ const relPath = path.relative(pagesDir, filePath);
52
+ const urlPattern = filePathToPattern(relPath);
53
+ const { regex, paramNames, catchAllParam } = compilePattern(urlPattern);
54
+ pages.push({
55
+ pattern: urlPattern,
56
+ regex,
57
+ paramNames,
58
+ ...(catchAllParam ? { catchAllParam } : {}),
59
+ filePath,
60
+ });
61
+ }
62
+ sortBySpecificity(pages);
63
+ return pages;
64
+ }
65
+ async function discoverLayouts(pagesDir) {
66
+ return discoverSpecialFiles(pagesDir, '_layout', 'tsx,jsx,ts,js');
67
+ }
68
+ function resolveLayoutChain(pagePattern, layouts) {
69
+ return resolveFileChain(pagePattern, layouts);
70
+ }
71
+ async function discoverLoadings(pagesDir) {
72
+ return discoverSpecialFiles(pagesDir, '_loading', 'tsx,jsx,ts,js');
73
+ }
74
+ function resolveLoadingChain(pagePattern, loadings) {
75
+ return resolveFileChain(pagePattern, loadings);
76
+ }
77
+ async function discoverPageMiddleware(pagesDir) {
78
+ return discoverSpecialFiles(pagesDir, '_middleware', 'ts,js');
79
+ }
80
+ function resolvePageMiddlewareChain(pagePattern, middlewares) {
81
+ return resolveFileChain(pagePattern, middlewares);
82
+ }
83
+ async function discoverErrorPages(pagesDir) {
84
+ const result = {};
85
+ const errorFiles = await glob(path.join(pagesDir, '_error.{tsx,jsx,ts,js}'));
86
+ if (errorFiles.length > 0) {
87
+ result.errorPage = errorFiles[0];
88
+ }
89
+ const notFoundFiles = await glob(path.join(pagesDir, '_404.{tsx,jsx,ts,js}'));
90
+ if (notFoundFiles.length > 0) {
91
+ result.notFoundPage = notFoundFiles[0];
92
+ }
93
+ return result;
94
+ }
95
+ async function discoverStyles(pagesDir) {
96
+ const stylesPath = path.join(pagesDir, '_styles.css');
97
+ try {
98
+ await fs.access(stylesPath);
99
+ return stylesPath;
100
+ } catch {
101
+ return void 0;
102
+ }
103
+ }
104
+ function matchPage(pages, pathname) {
105
+ const result = matchPattern(pages, pathname);
106
+ if (!result) return null;
107
+ return { page: result.match, params: result.params };
108
+ }
109
+ export {
110
+ discoverErrorPages,
111
+ discoverLayouts,
112
+ discoverLoadings,
113
+ discoverPageMiddleware,
114
+ discoverPages,
115
+ discoverStyles,
116
+ matchPage,
117
+ resolveLayoutChain,
118
+ resolveLoadingChain,
119
+ resolvePageMiddlewareChain,
120
+ };
@@ -0,0 +1,128 @@
1
+ import path from 'node:path';
2
+ import fs from 'node:fs/promises';
3
+ const MIME_MAP = {
4
+ '.woff2': 'font/woff2',
5
+ '.woff': 'font/woff',
6
+ '.ttf': 'font/ttf',
7
+ '.otf': 'font/otf',
8
+ };
9
+ const FORMAT_MAP = {
10
+ '.woff2': 'woff2',
11
+ '.woff': 'woff',
12
+ '.ttf': 'truetype',
13
+ '.otf': 'opentype',
14
+ };
15
+ const FONT_EXTENSIONS = new Set(Object.keys(MIME_MAP));
16
+ const FORMAT_PRIORITY = {
17
+ '.woff2': 0,
18
+ '.woff': 1,
19
+ '.ttf': 2,
20
+ '.otf': 3,
21
+ };
22
+ const NAMED_WEIGHTS = {
23
+ thin: 100,
24
+ hairline: 100,
25
+ extralight: 200,
26
+ ultralight: 200,
27
+ light: 300,
28
+ regular: 400,
29
+ normal: 400,
30
+ medium: 500,
31
+ semibold: 600,
32
+ demibold: 600,
33
+ bold: 700,
34
+ extrabold: 800,
35
+ ultrabold: 800,
36
+ black: 900,
37
+ heavy: 900,
38
+ };
39
+ async function scanFontDirectory(srcDir, publicDir) {
40
+ const dirPath = path.join(publicDir, srcDir);
41
+ let entries;
42
+ try {
43
+ entries = await fs.readdir(dirPath);
44
+ } catch {
45
+ return [];
46
+ }
47
+ const files = [];
48
+ for (const entry of entries) {
49
+ const ext = path.extname(entry).toLowerCase();
50
+ if (!FONT_EXTENSIONS.has(ext)) continue;
51
+ const baseName = path.basename(entry, ext).toLowerCase();
52
+ let weight;
53
+ const numericWeight = parseInt(baseName, 10);
54
+ if (!isNaN(numericWeight) && numericWeight >= 1 && numericWeight <= 999) {
55
+ weight = numericWeight;
56
+ } else {
57
+ weight = NAMED_WEIGHTS[baseName];
58
+ }
59
+ if (weight === void 0) continue;
60
+ files.push({
61
+ weight,
62
+ ext,
63
+ relativePath: `${srcDir}/${entry}`,
64
+ });
65
+ }
66
+ files.sort((a, b) => {
67
+ if (a.weight !== b.weight) return a.weight - b.weight;
68
+ return (FORMAT_PRIORITY[a.ext] ?? 99) - (FORMAT_PRIORITY[b.ext] ?? 99);
69
+ });
70
+ return files;
71
+ }
72
+ function generateFontFaceCss(family, files) {
73
+ const byWeight = new Map();
74
+ for (const file of files) {
75
+ const group = byWeight.get(file.weight) ?? [];
76
+ group.push(file);
77
+ byWeight.set(file.weight, group);
78
+ }
79
+ const rules = [];
80
+ for (const [weight, weightFiles] of byWeight) {
81
+ const sorted = [...weightFiles].sort(
82
+ (a, b) => (FORMAT_PRIORITY[a.ext] ?? 99) - (FORMAT_PRIORITY[b.ext] ?? 99),
83
+ );
84
+ const srcEntries = sorted.map((f) => {
85
+ const url = f.relativePath.startsWith('/') ? f.relativePath : `/${f.relativePath}`;
86
+ const format = FORMAT_MAP[f.ext] ?? 'woff2';
87
+ return `url('${url}') format('${format}')`;
88
+ });
89
+ rules.push(
90
+ `@font-face {
91
+ font-family: '${family}';
92
+ src: ${srcEntries.join(',\n ')};
93
+ font-weight: ${weight};
94
+ font-style: normal;
95
+ font-display: swap;
96
+ }`,
97
+ );
98
+ }
99
+ return rules.join('\n\n');
100
+ }
101
+ async function resolveFonts(configs, publicDir) {
102
+ const cssBlocks = [];
103
+ const preloadTags = [];
104
+ for (const config of configs) {
105
+ const files = await scanFontDirectory(config.src, publicDir);
106
+ if (files.length === 0) continue;
107
+ const css = generateFontFaceCss(config.family, files);
108
+ if (css) cssBlocks.push(css);
109
+ if (config.preload && config.preload.length > 0) {
110
+ const preloadWeights = new Set(config.preload);
111
+ for (const weight of preloadWeights) {
112
+ const weightFiles = files.filter((f) => f.weight === weight);
113
+ if (weightFiles.length === 0) continue;
114
+ const best = weightFiles[0];
115
+ const url = best.relativePath.startsWith('/') ? best.relativePath : `/${best.relativePath}`;
116
+ const type = MIME_MAP[best.ext] ?? 'font/woff2';
117
+ preloadTags.push(
118
+ `<link rel="preload" as="font" type="${type}" href="${url}" crossorigin="anonymous" />`,
119
+ );
120
+ }
121
+ }
122
+ }
123
+ return {
124
+ fontFaceCss: cssBlocks.join('\n\n'),
125
+ preloadHtml: preloadTags.join('\n'),
126
+ };
127
+ }
128
+ export { generateFontFaceCss, resolveFonts, scanFontDirectory };
@@ -0,0 +1,276 @@
1
+ import path from 'node:path';
2
+ import fs from 'node:fs';
3
+ import { createRequire } from 'node:module';
4
+ import { EventEmitter } from 'node:events';
5
+ import { compilePattern, sortBySpecificity, matchPattern } from '../router/routes.js';
6
+ import { parseCookies, resolveSession, flattenHeaders } from '../session/helpers.js';
7
+ import { loadComponent, renderPage, renderErrorPage, syncReactInternals } from './ssr.js';
8
+ import { MIME_TYPES } from './static.js';
9
+ import {
10
+ renderPage as renderPage2,
11
+ buildScriptTags as buildScriptTags2,
12
+ buildCssLinkTag as buildCssLinkTag2,
13
+ buildFontPreloadTags,
14
+ } from './ssr.js';
15
+ function createPagesHandler(options) {
16
+ const rootDir = options.rootDir;
17
+ const outDir = path.resolve(rootDir, options.outDir ?? '.build/pages');
18
+ const manifestPath = path.join(outDir, 'pages-manifest.json');
19
+ const sessionConfig = options.session;
20
+ const appContext = options.appContext ?? null;
21
+ const devMode = options.devMode ?? false;
22
+ if (!fs.existsSync(manifestPath)) {
23
+ return null;
24
+ }
25
+ const projectRequire = createRequire(path.join(options.rootDir, 'package.json'));
26
+ const projectReactModule = projectRequire('react');
27
+ const projectReact = {
28
+ createElement: projectReactModule.createElement,
29
+ renderToPipeableStream: projectRequire('react-dom/server').renderToPipeableStream,
30
+ };
31
+ syncReactInternals(projectReactModule);
32
+ let manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf-8'));
33
+ let routes = compileRoutes(manifest);
34
+ let clientManifestJson = buildClientManifestJson(manifest);
35
+ const componentCache = new Map();
36
+ let cacheVersion = 0;
37
+ const reloadEmitter = devMode ? new EventEmitter() : null;
38
+ if (manifest.entries.length === 0 && !manifest.notFoundBundle && !manifest.errorBundle) {
39
+ return null;
40
+ }
41
+ function reload() {
42
+ if (!fs.existsSync(manifestPath)) return;
43
+ manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf-8'));
44
+ routes = compileRoutes(manifest);
45
+ clientManifestJson = buildClientManifestJson(manifest);
46
+ componentCache.clear();
47
+ cacheVersion++;
48
+ }
49
+ function emitEvent(event) {
50
+ if (!reloadEmitter) return;
51
+ reloadEmitter.emit('update', event);
52
+ }
53
+ async function handle(req, res) {
54
+ const method = req.method ?? 'GET';
55
+ const url = req.url ?? '/';
56
+ const pathname = url.split('?')[0];
57
+ if (method !== 'GET') return false;
58
+ if (reloadEmitter && pathname === '/__livereload') {
59
+ res.writeHead(200, {
60
+ 'Content-Type': 'text/event-stream',
61
+ 'Cache-Control': 'no-cache',
62
+ Connection: 'keep-alive',
63
+ });
64
+ res.write('data: connected\n\n');
65
+ const onUpdate = (event) => {
66
+ res.write(`data: ${JSON.stringify(event)}
67
+
68
+ `);
69
+ };
70
+ reloadEmitter.on('update', onUpdate);
71
+ req.on('close', () => reloadEmitter.off('update', onUpdate));
72
+ return true;
73
+ }
74
+ const matched = matchPageRoute(routes, pathname);
75
+ if (!matched) {
76
+ if (manifest.notFoundBundle) {
77
+ await renderErrorPage(
78
+ manifest.notFoundBundle,
79
+ 404,
80
+ { pathname },
81
+ outDir,
82
+ res,
83
+ componentCache,
84
+ manifest,
85
+ cacheVersion,
86
+ projectReact,
87
+ );
88
+ return true;
89
+ }
90
+ return false;
91
+ }
92
+ if (matched.route.middlewareServerBundles.length > 0) {
93
+ const middlewareResult = await runPageMiddleware(
94
+ matched.route,
95
+ matched.params,
96
+ pathname,
97
+ req,
98
+ outDir,
99
+ componentCache,
100
+ cacheVersion,
101
+ sessionConfig,
102
+ appContext,
103
+ );
104
+ if (middlewareResult) {
105
+ if (middlewareResult.redirect) {
106
+ const status2 = middlewareResult.status ?? 302;
107
+ const headers2 = {
108
+ Location: middlewareResult.redirect,
109
+ ...middlewareResult.headers,
110
+ };
111
+ res.writeHead(status2, headers2);
112
+ res.end();
113
+ return true;
114
+ }
115
+ const status = middlewareResult.status ?? 403;
116
+ const headers = {
117
+ 'Content-Type': 'text/html; charset=utf-8',
118
+ ...middlewareResult.headers,
119
+ };
120
+ res.writeHead(status, headers);
121
+ res.end(middlewareResult.body ?? `<h1>${status} - Forbidden</h1>`);
122
+ return true;
123
+ }
124
+ }
125
+ try {
126
+ await renderPage(
127
+ matched.route,
128
+ matched.params,
129
+ pathname,
130
+ manifest,
131
+ clientManifestJson,
132
+ outDir,
133
+ res,
134
+ componentCache,
135
+ cacheVersion,
136
+ projectReact,
137
+ devMode,
138
+ );
139
+ } catch (err) {
140
+ if (manifest.errorBundle && !res.headersSent) {
141
+ const errorMessage =
142
+ process.env.NODE_ENV === 'production'
143
+ ? 'An unexpected error occurred'
144
+ : err instanceof Error
145
+ ? err.message
146
+ : String(err);
147
+ await renderErrorPage(
148
+ manifest.errorBundle,
149
+ 500,
150
+ { error: errorMessage },
151
+ outDir,
152
+ res,
153
+ componentCache,
154
+ manifest,
155
+ cacheVersion,
156
+ projectReact,
157
+ );
158
+ } else if (!res.headersSent) {
159
+ res.writeHead(500, { 'Content-Type': 'text/html' });
160
+ res.end('<h1>500 - Internal Server Error</h1>');
161
+ }
162
+ }
163
+ return true;
164
+ }
165
+ return {
166
+ handle,
167
+ reload,
168
+ ...(devMode
169
+ ? {
170
+ emitEvent,
171
+ getClientManifestJson: () => clientManifestJson,
172
+ }
173
+ : {}),
174
+ };
175
+ }
176
+ function compileRoutes(manifest) {
177
+ const routes = manifest.entries.map((entry) => {
178
+ const { regex, paramNames, catchAllParam } = compilePattern(entry.pattern);
179
+ return {
180
+ pattern: entry.pattern,
181
+ regex,
182
+ paramNames,
183
+ ...(catchAllParam ? { catchAllParam } : {}),
184
+ serverBundle: entry.serverBundle,
185
+ clientBundle: entry.clientBundle,
186
+ layoutServerBundles: entry.layoutServerBundles ?? [],
187
+ middlewareServerBundles: entry.middlewareServerBundles ?? [],
188
+ };
189
+ });
190
+ sortBySpecificity(routes);
191
+ return routes;
192
+ }
193
+ function matchPageRoute(routes, pathname) {
194
+ const result = matchPattern(routes, pathname);
195
+ if (!result) return null;
196
+ return { route: result.match, params: result.params };
197
+ }
198
+ async function runPageMiddleware(
199
+ route,
200
+ params,
201
+ pathname,
202
+ req,
203
+ outDir,
204
+ componentCache,
205
+ cacheVersion,
206
+ sessionConfig,
207
+ appContext,
208
+ ) {
209
+ const headers = flattenHeaders(req.headers);
210
+ const cookies = parseCookies(req);
211
+ const session = await resolveSession(cookies, sessionConfig);
212
+ const page = {
213
+ pathname,
214
+ query: params,
215
+ headers,
216
+ cookies,
217
+ session,
218
+ };
219
+ const ctx = appContext
220
+ ? {
221
+ page,
222
+ db: appContext.db,
223
+ log: appContext.log,
224
+ events: appContext.events,
225
+ cache: appContext.cache,
226
+ queue: appContext.queue,
227
+ files: appContext.files,
228
+ mail: appContext.mail,
229
+ }
230
+ : { page };
231
+ for (const bundlePath of route.middlewareServerBundles) {
232
+ const fullPath = path.join(outDir, bundlePath);
233
+ const middlewareFn = await loadComponent(
234
+ fullPath,
235
+ `middleware:${bundlePath}`,
236
+ componentCache,
237
+ cacheVersion,
238
+ );
239
+ if (!middlewareFn || typeof middlewareFn !== 'function') {
240
+ continue;
241
+ }
242
+ const result = await middlewareFn(ctx);
243
+ if (result) {
244
+ return result;
245
+ }
246
+ }
247
+ return null;
248
+ }
249
+ function buildClientManifestJson(manifest) {
250
+ const clientManifest = {
251
+ routes: manifest.entries.map((entry) => {
252
+ const route = {
253
+ pattern: entry.pattern,
254
+ paramNames: entry.paramNames,
255
+ clientBundle: `/static/${entry.navBundle}`,
256
+ layoutBundles: (entry.layoutClientBundles ?? []).map((b) => `/static/${b}`),
257
+ loadingBundles: (entry.loadingClientBundles ?? []).map((b) => `/static/${b}`),
258
+ };
259
+ if (entry.catchAllParam) {
260
+ route.catchAllParam = entry.catchAllParam;
261
+ }
262
+ return route;
263
+ }),
264
+ };
265
+ return JSON.stringify(clientManifest).replace(/</g, '\\u003c');
266
+ }
267
+ export {
268
+ MIME_TYPES,
269
+ buildClientManifestJson,
270
+ buildCssLinkTag2 as buildCssLinkTag,
271
+ buildFontPreloadTags,
272
+ buildScriptTags2 as buildScriptTags,
273
+ createPagesHandler,
274
+ matchPageRoute,
275
+ renderPage2 as renderPage,
276
+ };
@@ -0,0 +1,176 @@
1
+ import path from 'node:path';
2
+ import fs from 'node:fs/promises';
3
+ import { createRequire } from 'node:module';
4
+ import esbuild from 'esbuild';
5
+ function reactRefreshPlugin(rootDir) {
6
+ const require2 = createRequire(import.meta.url);
7
+ const refreshBabelPath = require2.resolve('react-refresh/babel');
8
+ return {
9
+ name: 'react-refresh',
10
+ setup(build) {
11
+ build.onLoad({ filter: /\.(jsx?|tsx)$/ }, async (args) => {
12
+ if (args.path.includes('node_modules')) return;
13
+ const source = await fs.readFile(args.path, 'utf-8');
14
+ let babel;
15
+ try {
16
+ babel = await import('@babel/core');
17
+ } catch {
18
+ return;
19
+ }
20
+ let result;
21
+ try {
22
+ result = await babel.transformAsync(source, {
23
+ filename: args.path,
24
+ plugins: [refreshBabelPath],
25
+ parserOpts: {
26
+ sourceType: 'module',
27
+ plugins: ['jsx', 'typescript'],
28
+ },
29
+ // Don't transform anything else — just add refresh calls
30
+ presets: [],
31
+ ast: false,
32
+ sourceMaps: 'inline',
33
+ configFile: false,
34
+ babelrc: false,
35
+ });
36
+ } catch {
37
+ return;
38
+ }
39
+ if (!result?.code) return;
40
+ const moduleId = path.relative(rootDir, args.path).replace(/\\/g, '/');
41
+ const preamble = [
42
+ `var $RefreshReg$ = function() {};`,
43
+ `var $RefreshSig$ = function() { return function(type) { return type; }; };`,
44
+ `if (typeof window !== 'undefined' && window.__REACT_REFRESH__) {`,
45
+ ` $RefreshReg$ = function(type, id) {`,
46
+ ` window.__REACT_REFRESH__.register(type, ${JSON.stringify(moduleId)} + " " + id);`,
47
+ ` };`,
48
+ ` $RefreshSig$ = window.__REACT_REFRESH__.createSignatureFunctionForTransform;`,
49
+ `}`,
50
+ ].join('\n');
51
+ return {
52
+ contents: preamble + '\n' + result.code,
53
+ loader: args.path.endsWith('.tsx') || args.path.endsWith('.ts') ? 'tsx' : 'jsx',
54
+ };
55
+ });
56
+ },
57
+ };
58
+ }
59
+ async function buildHmrRuntimeBundle(outDir) {
60
+ const require2 = createRequire(import.meta.url);
61
+ const runtimeEntry = require2.resolve('react-refresh/runtime');
62
+ const hmrDir = path.join(outDir, 'client', 'hmr');
63
+ await fs.mkdir(hmrDir, { recursive: true });
64
+ await esbuild.build({
65
+ entryPoints: [runtimeEntry],
66
+ outfile: path.join(hmrDir, 'runtime.js'),
67
+ format: 'esm',
68
+ platform: 'browser',
69
+ bundle: true,
70
+ minify: false,
71
+ sourcemap: false,
72
+ });
73
+ }
74
+ function buildHmrScript() {
75
+ return `<script type="module">
76
+ import RefreshRuntime from '/static/client/hmr/runtime.js';
77
+ RefreshRuntime.injectIntoGlobalHook(window);
78
+ window.$RefreshReg$ = function() {};
79
+ window.$RefreshSig$ = function() { return function(type) { return type; }; };
80
+ window.__REACT_REFRESH__ = RefreshRuntime;
81
+
82
+ var es = new EventSource('/__livereload');
83
+ es.onmessage = function(e) {
84
+ if (e.data === 'connected') return;
85
+ var msg;
86
+ try { msg = JSON.parse(e.data); } catch { window.location.reload(); return; }
87
+ if (msg.type === 'reload') {
88
+ window.location.reload();
89
+ } else if (msg.type === 'hmr-update') {
90
+ var imports = msg.modules.map(function(m) {
91
+ return import('/static/' + m + '?t=' + msg.timestamp);
92
+ });
93
+ Promise.all(imports).then(function() {
94
+ RefreshRuntime.performReactRefresh();
95
+ if (msg.manifest) {
96
+ var el = document.getElementById('__app_manifest');
97
+ if (el) el.textContent = msg.manifest;
98
+ window.dispatchEvent(new CustomEvent('manifest-update'));
99
+ }
100
+ }).catch(function(err) {
101
+ console.warn('[HMR] Fast Refresh failed, doing full reload', err);
102
+ window.location.reload();
103
+ });
104
+ } else if (msg.type === 'css-update') {
105
+ var links = document.querySelectorAll('link[rel="stylesheet"][href*="/static/"]');
106
+ links.forEach(function(link) {
107
+ link.href = '/static/' + msg.cssBundle + '?t=' + msg.timestamp;
108
+ });
109
+ }
110
+ };
111
+ es.onerror = function() {
112
+ setTimeout(function() { es.close(); }, 5000);
113
+ };
114
+ </script>`;
115
+ }
116
+ function diffClientMetafiles(oldMeta, newMeta, outDir) {
117
+ const oldOutputs = new Set(Object.keys(oldMeta.outputs));
118
+ const newOutputs = new Set(Object.keys(newMeta.outputs));
119
+ const newFiles = [];
120
+ for (const f of newOutputs) {
121
+ if (!oldOutputs.has(f) && !f.endsWith('.map')) {
122
+ newFiles.push(f);
123
+ }
124
+ }
125
+ if (newFiles.length === 0) {
126
+ return { changedNavBundles: [], changedCssBundle: null, needsFullReload: false };
127
+ }
128
+ const getEntryNames = (meta) => {
129
+ const names = new Set();
130
+ for (const output of Object.values(meta.outputs)) {
131
+ if (output.entryPoint) {
132
+ const base = path.basename(output.entryPoint, path.extname(output.entryPoint));
133
+ names.add(base);
134
+ }
135
+ }
136
+ return names;
137
+ };
138
+ const oldEntries = getEntryNames(oldMeta);
139
+ const newEntries = getEntryNames(newMeta);
140
+ let needsFullReload = false;
141
+ if (oldEntries.size !== newEntries.size) {
142
+ needsFullReload = true;
143
+ } else {
144
+ for (const name of oldEntries) {
145
+ if (!newEntries.has(name)) {
146
+ needsFullReload = true;
147
+ break;
148
+ }
149
+ }
150
+ }
151
+ const changedNavBundles = [];
152
+ let changedCssBundle = null;
153
+ for (const f of newFiles) {
154
+ const rel = path.relative(outDir, f).replace(/\\/g, '/');
155
+ if (/^client\/nav_/.test(rel)) {
156
+ changedNavBundles.push(rel);
157
+ } else if (rel.endsWith('.css')) {
158
+ changedCssBundle = rel;
159
+ }
160
+ }
161
+ const hasChangedHydrationBundles = newFiles.some((f) => {
162
+ const rel = path.relative(outDir, f).replace(/\\/g, '/');
163
+ return (
164
+ rel.startsWith('client/') &&
165
+ !rel.startsWith('client/nav_') &&
166
+ !rel.startsWith('client/chunks/') &&
167
+ !rel.endsWith('.css') &&
168
+ !rel.startsWith('client/hmr/')
169
+ );
170
+ });
171
+ if (hasChangedHydrationBundles && changedNavBundles.length === 0) {
172
+ needsFullReload = true;
173
+ }
174
+ return { changedNavBundles, changedCssBundle, needsFullReload };
175
+ }
176
+ export { buildHmrRuntimeBundle, buildHmrScript, diffClientMetafiles, reactRefreshPlugin };