@vyckr/tachyon 1.1.10 → 1.2.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 (49) hide show
  1. package/.env.example +7 -4
  2. package/LICENSE +21 -0
  3. package/README.md +210 -90
  4. package/package.json +50 -33
  5. package/src/cli/bundle.ts +37 -0
  6. package/src/cli/serve.ts +100 -0
  7. package/src/{client/template.js → compiler/render-template.js} +10 -17
  8. package/src/compiler/template-compiler.ts +419 -0
  9. package/src/runtime/hot-reload-client.ts +15 -0
  10. package/src/{client/dev.html → runtime/shells/development.html} +2 -2
  11. package/src/runtime/shells/not-found.html +73 -0
  12. package/src/{client/prod.html → runtime/shells/production.html} +1 -1
  13. package/src/runtime/spa-renderer.ts +439 -0
  14. package/src/server/console-logger.ts +39 -0
  15. package/src/server/process-executor.ts +287 -0
  16. package/src/server/process-pool.ts +80 -0
  17. package/src/server/route-handler.ts +229 -0
  18. package/src/server/schema-validator.ts +161 -0
  19. package/bun.lock +0 -127
  20. package/components/clicker.html +0 -30
  21. package/deno.lock +0 -19
  22. package/go.mod +0 -3
  23. package/lib/gson-2.3.jar +0 -0
  24. package/main.js +0 -13
  25. package/routes/DELETE +0 -18
  26. package/routes/GET +0 -17
  27. package/routes/HTML +0 -131
  28. package/routes/POST +0 -32
  29. package/routes/SOCKET +0 -26
  30. package/routes/api/:version/DELETE +0 -10
  31. package/routes/api/:version/GET +0 -29
  32. package/routes/api/:version/PATCH +0 -24
  33. package/routes/api/GET +0 -29
  34. package/routes/api/POST +0 -16
  35. package/routes/api/PUT +0 -21
  36. package/src/client/404.html +0 -7
  37. package/src/client/dist.ts +0 -20
  38. package/src/client/hmr.ts +0 -12
  39. package/src/client/render.ts +0 -417
  40. package/src/client/routes.json +0 -1
  41. package/src/client/yon.ts +0 -360
  42. package/src/router.ts +0 -186
  43. package/src/serve.ts +0 -147
  44. package/src/server/logger.ts +0 -31
  45. package/src/server/tach.ts +0 -238
  46. package/tests/index.test.ts +0 -110
  47. package/tests/stream.ts +0 -24
  48. package/tests/worker.ts +0 -7
  49. package/tsconfig.json +0 -17
package/.env.example CHANGED
@@ -1,10 +1,13 @@
1
1
  # Tachyon environment variables
2
2
  PORT=8000
3
- NODE_ENV=development|production
3
+ TIMEOUT=70
4
+ DEV=
5
+ VALIDATE=
4
6
  HOSTNAME=127.0.0.1
5
7
  ALLOW_HEADERS=*
6
- ALLOW_ORGINS=*
7
- ALLOW_CREDENTIALS=true|false
8
+ ALLOW_ORIGINS=*
9
+ ALLOW_CREDENTIALS=
8
10
  ALLOW_EXPOSE_HEADERS=*
9
11
  ALLOW_MAX_AGE=3600
10
- ALLOW_METHODS=GET,POST,PUT,DELETE,PATCH,OPTIONS
12
+ ALLOW_METHODS=GET,POST,PUT,DELETE,PATCH,OPTIONS
13
+ BASIC_AUTH=username:password
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Chidelma
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,154 +1,274 @@
1
1
  # Tachyon
2
2
 
3
- Tachyon is a simple to use API framework built with TypeScript (Bun). Tachyon aim to provide a simple and intuitive API framework for building serverless applications and abstracts away the complexity of configuations, letting you focus on building your application.
3
+ Tachyon is a **polyglot, file-system-routed full-stack framework for [Bun](https://bun.sh)**. It lets you define API routes as plain executable files written in any language, and build reactive front-end pages with a lightweight HTML template syntax all without configuration.
4
4
 
5
5
  ## Features
6
6
 
7
- - Customizable methods for routes
8
- - Use of file-system based routing
9
- - Hot reloading of routes in development mode
10
- - Supports dynamic routes
7
+ - **File-system routing** — routes are directories; HTTP methods are files
8
+ - **Polyglot handlers** write routes in Bun, Python, Ruby, Go, Rust, Java, or any language with a shebang
9
+ - **Reactive front-end (Yon)** HTML templates with bindings, loops, conditionals, and custom components
10
+ - **Lazy component loading** — defer component rendering until visible with `IntersectionObserver`
11
+ - **NPM dependency bundling** — use npm packages in front-end code via `/modules/` imports
12
+ - **Hot Module Replacement** — watches `routes/` and `components/` and reloads on change
13
+ - **Custom 404 page** — drop a `404.html` in your project root to override the default
14
+ - **Schema validation** — per-route request/response validation via `OPTIONS` files
15
+ - **Auth** — built-in Basic Auth and JWT decoding
16
+ - **Streaming** — SSE responses via `Accept: text/event-stream`
11
17
 
12
18
  ## Installation
13
19
 
14
20
  ```bash
15
- bun add @vyckr/tachyon
21
+ npm install @vyckr/tachyon
16
22
  ```
17
23
 
18
- ## Configuration
24
+ ## Quick Start
25
+
26
+ ```bash
27
+ # Start the development server (expects routes/ in the current directory)
28
+ tach.serve
19
29
 
20
- The .env file should be in the root directory of your project. The following environment variables:
30
+ # Build front-end assets into dist/
31
+ tach.bundle
21
32
  ```
22
- # Tachyon environment variables
23
- PORT=8000 (optional)
24
- NODE_ENV=development|production (optional)
25
- HOSTNAME=127.0.0.1 (optional)
26
- ALLOW_HEADERS=* (optional)
27
- ALLOW_ORGINS=* (optional)
28
- ALLOW_CREDENTIALS=true|false (optional)
29
- ALLOW_EXPOSE_HEADERS=* (optional)
30
- ALLOW_MAX_AGE=3600 (optional)
31
- ALLOW_METHODS=GET,POST,PUT,DELETE,PATCH (optional)
33
+
34
+ Or via npm scripts if you declare them in your own `package.json`:
35
+
36
+ ```json
37
+ {
38
+ "scripts": {
39
+ "start": "tach.serve",
40
+ "bundle": "tach.bundle"
41
+ }
42
+ }
32
43
  ```
33
44
 
34
- ### Requirements
35
- - Make sure to have a 'routes' directory in the root of your project
36
- - Dynamic routes should start with a colon `:`
37
- - The first parameter should NOT be a dynamic route (e.g. /:version/doc/GET)
38
- - All dynamic routes should be within odd indexes (e.g. /v1/:path/login/:id/POST)
39
- - The last parameter in the route should always be a capitalized method as a file name without file extension (e.g. /v1/:path/login/:id/name/DELETE)
40
- - Front-end Pages end with capitalized `HTML` filename (e.g. /v1/HTML)
41
- - Node modules should be imported dynamically with `modules` prefix (e.g. const { default: dayjs } = await import(`/modules/dayjs.js`))
42
- - Components should be in the `components` folder and end with `.html` extension (e.g. /components/counter.html)
43
- - First line of the file should be a shebang for the executable file (e.g. #!/usr/bin/env python3)
44
- - Request context can be retrieved by extracting the last element in args and parsing it.
45
- - Response of executable script must be in a String format and must written to the `/tmp` folder with the the process ID as the file name (e.g. `/tmp/1234`).
46
- - Use the exit method of the executable script with a status code to end the process of the executable script
47
-
48
- ### Examples
45
+ ## Configuration
49
46
 
47
+ Create a `.env` file in your project root. All variables are optional.
50
48
 
51
- ```html
52
- <!-- /routes/HTML -->
53
- <script>
54
- // top-level await
55
- const { default: dayjs } = await import("/modules/dayjs.js")
49
+ ```env
50
+ PORT=8000
51
+ HOSTNAME=127.0.0.1
52
+ TIMEOUT=70
53
+ DEV=true
56
54
 
57
- console.log(dayjs().format())
55
+ # CORS
56
+ ALLOW_HEADERS=*
57
+ ALLOW_ORIGINS=*
58
+ ALLOW_CREDENTIALS=false
59
+ ALLOW_EXPOSE_HEADERS=*
60
+ ALLOW_MAX_AGE=3600
61
+ ALLOW_METHODS=GET,POST,PUT,DELETE,PATCH,OPTIONS
58
62
 
59
- const greeting = "Hello World!"
60
- </script>
63
+ # Auth
64
+ BASIC_AUTH=username:password
65
+
66
+ # Validation (set to any value to enable)
67
+ VALIDATE=true
61
68
 
62
- <p>${greeting}</p>
69
+ # Custom route/asset paths (defaults to <cwd>/routes, <cwd>/components, <cwd>/assets)
70
+ ROUTES_PATH=
71
+ COMPONENTS_PATH=
72
+ ASSETS_PATH=
63
73
  ```
64
74
 
65
- ```typescript
66
- // routes/v1/:collection/GET
75
+ ## Route Structure
67
76
 
68
- #!/usr/bin/env bun
77
+ ```
78
+ routes/
79
+ GET → GET /
80
+ POST → POST /
81
+ api/
82
+ GET → GET /api
83
+ :version/
84
+ GET → GET /api/:version
85
+ DELETE → DELETE /api/:version
86
+ dashboard/
87
+ HTML → front-end page at /dashboard
88
+ OPTIONS → schema file (optional, enables validation)
89
+ ```
69
90
 
70
- for await(const chunk of Bun.stdin.stream()) {
91
+ ### Requirements
71
92
 
72
- console.log("Executing Bun....");
93
+ - Every route handler is an **executable file** — include a shebang on the first line
94
+ - The last path segment must be an **uppercase HTTP method** (e.g. `GET`, `POST`, `DELETE`) or `HTML` for a front-end page
95
+ - Dynamic segments start with `:` (e.g. `:version`, `:id`)
96
+ - The first path segment must **not** be dynamic
97
+ - Adjacent dynamic segments are not allowed (e.g. `/:a/:b/GET` is invalid)
98
+ - Node modules must be imported dynamically with the `/modules/` prefix: `await import('/modules/dayjs.js')`
99
+ - Components live in `components/` and must have a `.html` extension
100
+
101
+ ### Request Context
102
+
103
+ Every handler receives the full request context on `stdin` as a JSON object:
104
+
105
+ ```json
106
+ {
107
+ "headers": { "content-type": "application/json" },
108
+ "body": { "name": "Alice" },
109
+ "query": { "page": 1 },
110
+ "paths": { "version": "v2" },
111
+ "context": {
112
+ "ipAddress": "127.0.0.1",
113
+ "bearer": { "header": {}, "payload": {}, "signature": "..." }
114
+ }
115
+ }
116
+ ```
73
117
 
74
- const data = new TextDecoder().decode(chunk)
118
+ ### Route Handler Examples
75
119
 
76
- const ctx = JSON.parse(data)
120
+ **Bun (TypeScript)**
121
+ ```typescript
122
+ // routes/v1/:collection/GET
123
+ #!/usr/bin/env bun
77
124
 
78
- ctx.message = "Hello from Bun!"
125
+ const { body, paths, context } = await Bun.stdin.json()
79
126
 
80
- const response = JSON.stringify(ctx)
127
+ const response = { collection: paths.collection, from: context.ipAddress }
81
128
 
82
- await Bun.write(`/tmp/${process.pid}`, response)
83
- }
129
+ Bun.stdout.write(JSON.stringify(response))
84
130
  ```
85
131
 
132
+ **Python**
86
133
  ```python
87
134
  # routes/v1/:collection/POST
88
-
89
135
  #!/usr/bin/env python3
90
- import json
91
- import sys
92
- import os
93
-
94
- print("Executing Python....")
95
-
96
- ctx = json.loads(sys.stdin.read())
136
+ import json, sys
97
137
 
98
- ctx["message"] = "Hello from Python!"
99
-
100
- file = open(f"/tmp/{os.getpid()}", "w")
101
-
102
- file.write(json.dumps(ctx))
103
-
104
- file.close()
138
+ stdin = json.loads(sys.stdin.read())
139
+ sys.stdout.write(json.dumps({ "message": "Hello from Python!" }))
105
140
  ```
106
141
 
142
+ **Ruby**
107
143
  ```ruby
108
144
  # routes/v1/:collection/DELETE
109
-
110
145
  #!/usr/bin/env ruby
111
146
  require 'json'
112
147
 
113
- puts "Executing Ruby...."
148
+ stdin = JSON.parse(ARGF.read)
149
+ print JSON.generate({ message: "Hello from Ruby!" })
150
+ ```
151
+
152
+ ### Schema Validation
153
+
154
+ Place an `OPTIONS` file in any route directory to enable validation:
155
+
156
+ ```json
157
+ {
158
+ "POST": {
159
+ "req": {
160
+ "name": "string",
161
+ "age?": 0
162
+ },
163
+ "res": {
164
+ "message": "string"
165
+ },
166
+ "err": {
167
+ "detail": "string"
168
+ }
169
+ }
170
+ }
171
+ ```
172
+
173
+ Nullable fields are suffixed with `?`. Set `VALIDATE=true` in your `.env` to enable.
114
174
 
115
- ctx = JSON.parse(ARGF.read)
175
+ ## Front-end Pages (Yon)
116
176
 
117
- ctx["message"] = "Hello from Ruby!"
177
+ Create an `HTML` file inside any route directory to define a front-end page:
118
178
 
119
- File.write("/tmp/#{Process.pid}", JSON.unparse(ctx))
179
+ ```html
180
+ <!-- routes/HTML -->
181
+ <script>
182
+ document.title = "Home"
183
+ let count = 0
184
+ </script>
185
+
186
+ <h1>Count: {count}</h1>
187
+ <button @click="count++">Increment</button>
120
188
  ```
121
189
 
122
- To run the application, you can use the following command:
190
+ ### Template Syntax
191
+
192
+ | Syntax | Description |
193
+ |--------|-------------|
194
+ | `{expr}` | Interpolate expression |
195
+ | `@event="handler()"` | Event binding |
196
+ | `:prop="value"` | Bind attribute to expression |
197
+ | `:value="variable"` | Two-way input binding |
198
+ | `<loop :for="...">` | Loop block |
199
+ | `<logic :if="...">` | Conditional block |
200
+ | `<myComp_ prop=val />` | Custom component (trailing `_`) |
201
+ | `<myComp_ lazy />` | Lazy-loaded component (renders when visible) |
202
+
203
+ ### Custom Components
204
+
205
+ ```html
206
+ <!-- components/counter.html -->
207
+ <script>
208
+ let count = 0
209
+ </script>
123
210
 
124
- ```bash
125
- bun tach
211
+ <button @click="count++">Clicked {count} times</button>
126
212
  ```
127
213
 
128
- To invoke the API endpoints, you can use the following commands:
214
+ Use in a page:
129
215
 
130
- ```bash
131
- curl -X GET http://localhost:8000/v1/users
216
+ ```html
217
+ <counter_ />
132
218
  ```
133
219
 
134
- ```bash
135
- curl -X POST http://localhost:8000/v1/users -d '{"name": "John Doe", "age": 30}'
220
+ ### Lazy Loading
221
+
222
+ Add the `lazy` attribute to defer a component's loading until it scrolls into view. The component renders a lightweight placeholder and uses `IntersectionObserver` to load the module on demand.
223
+
224
+ ```html
225
+ <!-- Eager (default) — loaded immediately -->
226
+ <counter_ />
227
+
228
+ <!-- Lazy — loaded when visible in the viewport -->
229
+ <counter_ lazy />
136
230
  ```
137
231
 
138
- ```bash
139
- curl -X PATCH http://localhost:8000/v1/users -d '{"name": "Jane Doe", "age": 31}'
232
+ Lazy components are fully interactive once loaded — event delegation and state management work identically to eager components.
233
+
234
+ ### NPM Modules in Front-end Code
235
+
236
+ Any package listed in your project's `dependencies` is automatically bundled and served at `/modules/<name>.js`. Import them dynamically in your `<script>` blocks:
237
+
238
+ ```html
239
+ <script>
240
+ const { default: dayjs } = await import('/modules/dayjs.js')
241
+ let timestamp = dayjs().format('MMM D, YYYY h:mm A')
242
+ </script>
243
+
244
+ <p>Last updated: {timestamp}</p>
140
245
  ```
141
246
 
142
- ```bash
143
- curl -X DELETE http://localhost:8000/v1/users/5e8b0a9c-c0d1-4d3b-a0b1-e2d8e0e9a1c0
247
+ ### Custom 404 Page
248
+
249
+ Place a `404.html` file in your project root to override the default 404 page. It uses the same Yon template syntax:
250
+
251
+ ```html
252
+ <!-- 404.html -->
253
+ <script>
254
+ document.title = "Not Found"
255
+ </script>
256
+
257
+ <h1>Oops!</h1>
258
+ <p>This page doesn't exist.</p>
259
+ <a href="/">Go home</a>
144
260
  ```
145
261
 
146
- To to build front-end assets into a `dist` folder, use the following command:
262
+ If no custom `404.html` is found, Tachyon serves a built-in styled 404 page.
263
+
264
+ ## Building for Production
147
265
 
148
- ```bash
149
- bun yon
266
+ ```bash
267
+ tach.bundle
150
268
  ```
151
269
 
152
- # License
270
+ Outputs compiled assets to `dist/`.
271
+
272
+ ## License
153
273
 
154
- Tachyon is licensed under the MIT License.
274
+ MIT
package/package.json CHANGED
@@ -1,35 +1,52 @@
1
1
  {
2
- "name": "@vyckr/tachyon",
3
- "version": "1.1.10",
4
- "author": "Chidelma",
5
- "repository": {
6
- "type": "git",
7
- "url": "git+https://github.com/Chidelma/Tachyon.git"
8
- },
9
- "devDependencies": {
10
- "@types/bun": "~1.2.8",
11
- "@types/deno": "^2.0.0",
12
- "@types/jsdom": "^21.1.7",
13
- "@types/node": "^20.4.2"
14
- },
15
- "bin": {
16
- "tach": "./src/serve.ts",
17
- "yon": "./src/client/dist.ts"
18
- },
19
- "bugs": {
20
- "url": "https://github.com/Chidelma/Tachyon/issues"
21
- },
22
- "keywords": [
23
- "tachyon",
24
- "api",
25
- "framework",
26
- "typescript",
27
- "bun"
28
- ],
29
- "license": "MIT",
30
- "type": "module",
31
- "dependencies": {
32
- "dayjs": "^1.11.13",
33
- "jsdom": "^26.0.0"
34
- }
2
+ "name": "@vyckr/tachyon",
3
+ "version": "1.2.0",
4
+ "description": "A polyglot, file-system-routed full-stack framework for Bun",
5
+ "author": "Chidelma",
6
+ "license": "MIT",
7
+ "type": "module",
8
+ "main": "./src/server/route-handler.ts",
9
+ "exports": {
10
+ ".": "./src/server/route-handler.ts",
11
+ "./server": "./src/server/process-executor.ts",
12
+ "./compiler": "./src/compiler/template-compiler.ts"
13
+ },
14
+ "bin": {
15
+ "tach.serve": "./src/cli/serve.ts",
16
+ "tach.bundle": "./src/cli/bundle.ts"
17
+ },
18
+ "scripts": {
19
+ "start": "bun src/cli/serve.ts",
20
+ "bundle": "bun src/cli/bundle.ts",
21
+ "typecheck": "tsc --noEmit",
22
+ "test": "bun test"
23
+ },
24
+ "files": [
25
+ "src/",
26
+ "README.md",
27
+ ".env.example"
28
+ ],
29
+ "keywords": [
30
+ "tachyon",
31
+ "bun",
32
+ "framework",
33
+ "api",
34
+ "polyglot",
35
+ "file-system-routing",
36
+ "typescript"
37
+ ],
38
+ "repository": {
39
+ "type": "git",
40
+ "url": "git+https://github.com/Chidelma/Tachyon.git"
41
+ },
42
+ "bugs": {
43
+ "url": "https://github.com/Chidelma/Tachyon/issues"
44
+ },
45
+ "devDependencies": {
46
+ "@types/bun": "^1.3.4",
47
+ "@types/deno": "^2.0.0",
48
+ "@types/node": "^20.4.2",
49
+ "playwright": "^1.59.1",
50
+ "typescript": "^6.0.2"
51
+ }
35
52
  }
@@ -0,0 +1,37 @@
1
+ #!/usr/bin/env bun
2
+ import Router from "../server/route-handler.js"
3
+ import Yon from "../compiler/template-compiler.js"
4
+ import "../server/console-logger.js"
5
+ import { mkdir } from "node:fs/promises"
6
+
7
+ const start = Date.now()
8
+
9
+ const distPath = `${process.cwd()}/dist`
10
+
11
+ await mkdir(distPath, { recursive: true })
12
+
13
+ await Yon.createStaticRoutes()
14
+
15
+ for (const route in Router.reqRoutes) {
16
+
17
+ // Skip the HMR script — it is a dev-only asset
18
+ if (route.includes('hot-reload-client')) continue
19
+
20
+ const handler = Router.reqRoutes[route]['GET']
21
+
22
+ if (!handler) continue
23
+
24
+ try {
25
+ const res = await handler()
26
+ await Bun.write(Bun.file(`${distPath}${route}`), await res.blob())
27
+ } catch (err) {
28
+ console.error(`Failed to build route ${route}: ${(err as Error).message}`, process.pid)
29
+ }
30
+ }
31
+
32
+ await Bun.write(
33
+ Bun.file(`${distPath}/index.html`),
34
+ await Bun.file(`${import.meta.dir}/../runtime/shells/production.html`).text()
35
+ )
36
+
37
+ console.info(`Built in ${Date.now() - start}ms`, process.pid)
@@ -0,0 +1,100 @@
1
+ #!/usr/bin/env bun
2
+ import Tach from "../server/process-executor.js"
3
+ import Pool from "../server/process-pool.js"
4
+ import Router from "../server/route-handler.js"
5
+ import Yon from "../compiler/template-compiler.js"
6
+ import "../server/console-logger.js"
7
+ import { watch } from "fs"
8
+ import { access } from "fs/promises"
9
+ import type { Middleware } from "../server/route-handler.js"
10
+
11
+ /** Debounce delay (ms) applied to file-watcher events before triggering an HMR reload */
12
+ const HMR_DEBOUNCE_MS = 1000
13
+
14
+ const start = Date.now()
15
+
16
+ async function pathExists(path: string): Promise<boolean> {
17
+ try { await access(path); return true } catch { return false }
18
+ }
19
+
20
+ async function loadMiddleware() {
21
+ const extensions = ['.ts', '.js']
22
+ for (const ext of extensions) {
23
+ const filePath = `${Router.middlewarePath}${ext}`
24
+ if (await pathExists(filePath)) {
25
+ const mod = await import(filePath)
26
+ Router.middleware = (mod.default ?? mod) as Middleware
27
+ return
28
+ }
29
+ }
30
+ Router.middleware = null
31
+ }
32
+
33
+ async function configureRoutes(isReload = false) {
34
+ if (isReload) Pool.clearWarmedProcesses()
35
+ await loadMiddleware()
36
+ await Router.validateRoutes()
37
+ Tach.createServerRoutes()
38
+ Pool.prewarmAllHandlers()
39
+ await Yon.createStaticRoutes()
40
+ }
41
+
42
+ await configureRoutes()
43
+
44
+ let debounceTimer: Timer
45
+
46
+ const server = Bun.serve({
47
+ idleTimeout: process.env.TIMEOUT ? Number(process.env.TIMEOUT) : 0,
48
+
49
+ fetch(req, server) {
50
+
51
+ if (new URL(req.url).pathname !== "/hmr") {
52
+ return new Response("Not Found", { status: 404 })
53
+ }
54
+
55
+ server.timeout(req, 0)
56
+
57
+ return new Response(new ReadableStream({
58
+ async start(controller) {
59
+
60
+ const onFileChange = () => {
61
+ clearTimeout(debounceTimer)
62
+ debounceTimer = setTimeout(async () => {
63
+ try {
64
+ console.info("HMR Update", process.pid)
65
+ await configureRoutes(true)
66
+ server.reload({ routes: Router.reqRoutes })
67
+ controller.enqueue("\n\n")
68
+ } catch (err) {
69
+ console.error(`HMR reload failed: ${(err as Error).message}`, process.pid)
70
+ }
71
+ }, HMR_DEBOUNCE_MS)
72
+ }
73
+
74
+ if (await pathExists(Router.routesPath)) watch(Router.routesPath, { recursive: true }, onFileChange)
75
+ if (await pathExists(Router.componentsPath)) watch(Router.componentsPath, { recursive: true }, onFileChange)
76
+ }
77
+ }), { headers: { "Content-Type": "text/event-stream" } })
78
+ },
79
+
80
+ routes: Router.reqRoutes,
81
+ port: process.env.PORT || 8080,
82
+ hostname: process.env.HOSTNAME || '0.0.0.0',
83
+ development: !!process.env.DEV,
84
+ })
85
+
86
+ console.info(`Server running on http://${server.hostname}:${server.port} — started in ${Date.now() - start}ms`, process.pid)
87
+
88
+ process.on('SIGINT', () => {
89
+ clearTimeout(debounceTimer)
90
+ Pool.clearWarmedProcesses()
91
+ server.stop()
92
+ process.exit(0)
93
+ })
94
+
95
+ process.on('SIGTERM', () => {
96
+ clearTimeout(debounceTimer)
97
+ Pool.clearWarmedProcesses()
98
+ server.stop()
99
+ process.exit(0)
100
+ })