@vyckr/tachyon 0.3.0 → 1.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 (50) hide show
  1. package/.env.example +3 -17
  2. package/README.md +87 -57
  3. package/bun.lock +127 -0
  4. package/components/counter.html +13 -0
  5. package/deno.lock +19 -0
  6. package/go.mod +3 -0
  7. package/lib/gson-2.3.jar +0 -0
  8. package/main.js +0 -0
  9. package/package.json +19 -20
  10. package/routes/DELETE +18 -0
  11. package/routes/GET +17 -0
  12. package/routes/HTML +28 -0
  13. package/routes/POST +32 -0
  14. package/routes/SOCKET +26 -0
  15. package/routes/api/:version/DELETE +10 -0
  16. package/routes/api/:version/GET +29 -0
  17. package/routes/api/:version/PATCH +24 -0
  18. package/routes/api/GET +29 -0
  19. package/routes/api/POST +16 -0
  20. package/routes/api/PUT +21 -0
  21. package/src/client/404.html +7 -0
  22. package/src/client/dev.html +14 -0
  23. package/src/client/dist.ts +20 -0
  24. package/src/client/hmr.js +12 -0
  25. package/src/client/prod.html +13 -0
  26. package/src/client/render.js +278 -0
  27. package/src/client/routes.json +1 -0
  28. package/src/client/yon.ts +353 -0
  29. package/src/router.ts +186 -0
  30. package/src/serve.ts +144 -0
  31. package/src/server/logger.ts +31 -0
  32. package/src/server/tach.ts +234 -0
  33. package/tests/index.test.ts +110 -0
  34. package/tests/stream.ts +24 -0
  35. package/tests/worker.ts +7 -0
  36. package/tsconfig.json +1 -1
  37. package/Dockerfile +0 -47
  38. package/bun.lockb +0 -0
  39. package/routes/byos/[primary]/doc/index.ts +0 -28
  40. package/routes/byos/[primary]/docs/index.ts +0 -28
  41. package/routes/byos/[primary]/join/[secondary]/docs/index.ts +0 -10
  42. package/routes/byos/[primary]/schema/index.ts +0 -17
  43. package/routes/byos/[primary]/stream/doc/index.ts +0 -28
  44. package/routes/byos/[primary]/stream/docs/index.ts +0 -28
  45. package/routes/proxy.ts +0 -8
  46. package/routes/utils/validation.ts +0 -36
  47. package/src/Tach.ts +0 -543
  48. package/src/Yon.ts +0 -25
  49. package/src/runtime.ts +0 -822
  50. package/types/index.d.ts +0 -13
@@ -0,0 +1,353 @@
1
+ import { JSDOM } from 'jsdom'
2
+ import Router from "../router.js";
3
+ import { EventEmitter } from 'node:stream';
4
+ import { BunRequest } from 'bun';
5
+
6
+ export default class Yon {
7
+
8
+ private static htmlMethod = 'HTML'
9
+
10
+ private static emitter = new EventEmitter()
11
+
12
+ private static compMapping = new Map<string, string>()
13
+
14
+ static getParams(request: BunRequest, route: string) {
15
+
16
+ const url = new URL(request.url)
17
+
18
+ const params = url.pathname.split('/').slice(route.split('/').length)
19
+
20
+ return { params: Router.parseParams(params) }
21
+ }
22
+
23
+ static async createStaticRoutes() {
24
+
25
+ Router.reqRoutes["/render.js"] = {
26
+ GET: async () => new Response(await Bun.file(`${import.meta.dir}/render.js`).bytes(), { headers: { 'Content-Type': 'application/javascript' } })
27
+ }
28
+
29
+ Router.reqRoutes["/hmr.js"] = {
30
+ GET: async () => new Response(await Bun.file(`${import.meta.dir}/hmr.js`).bytes(), { headers: { 'Content-Type': 'application/javascript' } })
31
+ }
32
+
33
+ Router.reqRoutes["/routes.json"] = {
34
+ GET: async () => new Response(await Bun.file(`${import.meta.dir}/routes.json`).bytes(), { headers: { 'Content-Type': 'application/json' } })
35
+ }
36
+
37
+ const main = Bun.file(`${process.cwd()}/main.js`)
38
+
39
+ if(await main.exists()) {
40
+ Router.reqRoutes["/main.js"] = {
41
+ GET: async () => new Response(await main.bytes(), { headers: { 'Content-Type': 'application/javascript' } })
42
+ }
43
+ }
44
+
45
+ let styles = ''
46
+
47
+ Yon.emitter.addListener('style', (msg) => {
48
+ styles += `${msg}\n`
49
+ })
50
+
51
+ await Promise.all([Yon.bundleDependencies(), Yon.bundleComponents(), Yon.bundlePages(), Yon.bundleAssets()])
52
+
53
+ await Bun.write(Bun.file(`${import.meta.dir}/routes.json`), JSON.stringify(Router.routeSlugs))
54
+
55
+ Yon.emitter.removeAllListeners('style')
56
+
57
+ if(styles) {
58
+ Router.reqRoutes["/styles.css"] = {
59
+ GET: () => new Response(styles, { headers: { 'Content-Type': 'text/css' }})
60
+ }
61
+ }
62
+ }
63
+
64
+ private static extractComponents(data: string) {
65
+
66
+ const html = new JSDOM('').window.document.createElement('div')
67
+
68
+ html.innerHTML = data
69
+
70
+ const scripts = html.querySelectorAll('script')
71
+
72
+ const script = scripts[0]
73
+
74
+ scripts.forEach(s => s.remove())
75
+
76
+ const styles = html.querySelectorAll('style')
77
+
78
+ const style = styles[0]
79
+
80
+ styles.forEach(s => s.remove())
81
+
82
+ return { html, script, style }
83
+ }
84
+
85
+ private static parseHTML(elements: HTMLCollection, imports: Map<string, Set<string>> = new Map<string, Set<string>>()) {
86
+
87
+ const parsed: { static?: string, render?: string, element?: string }[] = []
88
+
89
+ for (const element of elements) {
90
+
91
+ if(element.tagName.startsWith('TY-')) {
92
+
93
+ const component = element.tagName.split('-')[1].toLowerCase()
94
+
95
+ if(component === 'loop') {
96
+ const attribute = element.attributes[0];
97
+ if (attribute.name === ':for') parsed.push({ render: `for(${attribute.value}) {`})
98
+ } else if(component === "logic") {
99
+ const attribute = element.attributes[0]
100
+ if (attribute.name === ':if') parsed.push({ render: `if(${attribute.value}) {`});
101
+ if (attribute.name === ':else-if') parsed.push({ render: `else if(${attribute.value}) {`});
102
+ if (attribute.name === ':else') parsed.push({ render: `else {`});
103
+ } else {
104
+
105
+ const exports: string[] = []
106
+
107
+ const filepath = Yon.compMapping.get(component)
108
+
109
+ if(filepath) {
110
+
111
+ for(let i = 0; i < element.attributes.length; i++) {
112
+
113
+ if(element.attributes[i].name.startsWith(':')) {
114
+ const propName = element.attributes[i].name.slice(1)
115
+ exports.push(`${propName} = ${"${" + element.attributes[i].value + "}"}`)
116
+ } else {
117
+ const propName = element.attributes[i].name
118
+ exports.push(`${propName} = "${element.attributes[i].value}"`)
119
+ }
120
+ }
121
+
122
+ if(imports.has(filepath)) {
123
+
124
+ if(!imports.get(filepath)?.has(component)) {
125
+ parsed.push({ static: `const { default: ${component} } = import('/components/${filepath}')`})
126
+ imports.get(filepath)?.add(component)
127
+ }
128
+
129
+ } else {
130
+
131
+ parsed.push({ static: `const { default: ${component} } = await import('/components/${filepath}')`})
132
+ imports.set(filepath, new Set<string>([component]))
133
+ }
134
+
135
+ const hash = Bun.randomUUIDv7().split('-')[1]
136
+
137
+ parsed.push({ static: `const comp_${hash} = await ${component}(\`${exports.join(';')}\`)`})
138
+
139
+ parsed.push({ render: `elements += comp_${hash}(execute && execute.compId === "ty-${hash}" ? execute : null).replaceAll('class="', 'class="ty-${hash} ')`})
140
+ }
141
+ }
142
+
143
+ const temp = new JSDOM('').window.document.createElement('div');
144
+ temp.innerHTML = element.innerHTML
145
+
146
+ parsed.push(...this.parseHTML(temp.children, imports))
147
+
148
+ if(component === "loop" || component === "logic") parsed.push({ render: '}'})
149
+
150
+ } else {
151
+
152
+ for(let i = 0; i < element.attributes.length; i++) {
153
+
154
+ const attr = element.attributes[i]
155
+
156
+ if(attr.name.startsWith(':')) {
157
+
158
+ const attrName = attr.name.slice(1)
159
+
160
+ element.removeAttribute(attr.name)
161
+ element.setAttribute(attrName, "${" + attr.value + "}")
162
+ }
163
+ }
164
+
165
+ parsed.push({ element: `\`${element.outerHTML}\`` })
166
+ }
167
+ }
168
+
169
+ return parsed
170
+ }
171
+
172
+ private static createJSData(html: { static?: string, render?: string, element?: string }[], scriptTag?: HTMLScriptElement, style?: HTMLStyleElement) {
173
+
174
+ const hash = Bun.randomUUIDv7().split('-')[3]
175
+
176
+ if(style && style.innerHTML) Yon.emitter.emit('style', `@scope (.ty-${hash}) { ${style.innerHTML} }`)
177
+
178
+ const outers: string[] = []
179
+ const inners: string[] = []
180
+
181
+ html.forEach(h => {
182
+ if(h.static) outers.push(h.static)
183
+ if(h.element) {
184
+ const temp = new JSDOM('').window.document.createElement('div');
185
+ temp.innerHTML = h.element
186
+ temp.children[0].classList.add(`ty-${hash}`)
187
+ inners.push(`elements += ${temp.innerHTML}`)
188
+ }
189
+ if(h.render) inners.push(h.render)
190
+ })
191
+
192
+ return `
193
+
194
+ export default async function(props) {
195
+
196
+ ${scriptTag ? scriptTag.innerHTML : ''}
197
+
198
+ ${outers.join('\n')}
199
+
200
+ props?.split(';').map(exp => eval(exp))
201
+
202
+ return function(execute) {
203
+
204
+ if(execute) {
205
+ const { classId, compId, func } = execute
206
+ if(classId === "ty-${hash}" || compId === "ty-${hash}") {
207
+ eval(func)
208
+ }
209
+ }
210
+
211
+ let elements = '';
212
+
213
+ ${inners.join('\n')}
214
+
215
+ return elements
216
+ }
217
+ }
218
+ `
219
+ }
220
+
221
+ private static async addToStatix(html: HTMLDivElement, script: HTMLScriptElement, style: HTMLStyleElement, route: string, dir: 'pages' | 'components') {
222
+
223
+ const module = Yon.parseHTML(html.children)
224
+
225
+ const jsData = Yon.createJSData(module, script, style)
226
+
227
+ route = route.replace('.html', `.${script?.lang || 'js'}`)
228
+
229
+ await Bun.write(Bun.file(`/tmp/${route}`), jsData)
230
+
231
+ const result = await Bun.build({
232
+ entrypoints: [`/tmp/${route}`],
233
+ external: ["*"],
234
+ minify: {
235
+ whitespace: true,
236
+ syntax: true
237
+ }
238
+ })
239
+
240
+ route = route.replace('.ts', '.js')
241
+
242
+ Router.reqRoutes[`/${dir}/${route}`] = {
243
+ GET: () => new Response(result.outputs[0], { headers: { 'Content-Type': 'application/javascript' } })
244
+ }
245
+ }
246
+
247
+ private static async bundleAssets() {
248
+
249
+ const routes = Array.from(new Bun.Glob(`**/*`).scanSync({ cwd: Router.assetsPath }))
250
+
251
+ for(const route of routes) {
252
+
253
+ Router.reqRoutes[`/assets/${route}`] = {
254
+ GET: async () => new Response(await Bun.file(`${Router.assetsPath}/${route}`).text())
255
+ }
256
+ }
257
+ }
258
+
259
+ private static async bundlePages() {
260
+
261
+ const routes = Array.from(new Bun.Glob(`**/${Yon.htmlMethod}`).scanSync({ cwd: Router.routesPath }))
262
+
263
+ for(const route of routes) {
264
+
265
+ await Router.validateRoute(route)
266
+
267
+ const data = await Bun.file(`${Router.routesPath}/${route}`).text()
268
+
269
+ const { html, script, style } = Yon.extractComponents(data)
270
+
271
+ await Yon.addToStatix(html, script, style, `${route}.${script.lang || 'js'}`, 'pages')
272
+ }
273
+
274
+ const nfFile = Bun.file(`${process.cwd()}/404.html`)
275
+
276
+ const data = await nfFile.exists() ? await nfFile.text() : await Bun.file(`${import.meta.dir}/404.html`).text()
277
+
278
+ const { html, script, style } = Yon.extractComponents(data)
279
+
280
+ await Yon.addToStatix(html, script, style, '404.html', 'pages')
281
+ }
282
+
283
+ private static async bundleComponents() {
284
+
285
+ const components = Array.from(new Bun.Glob(`**/*.html`).scanSync({ cwd: Router.componentsPath }))
286
+
287
+ for(let comp of components) {
288
+
289
+ const folders = comp.split('/')
290
+
291
+ const filename = folders[folders.length - 1].replace('.html', '')
292
+
293
+ Yon.compMapping.set(filename, comp.replace('.html', '.js'))
294
+
295
+ const data = await Bun.file(`${Router.componentsPath}/${comp}`).text()
296
+
297
+ const { html, script, style } = Yon.extractComponents(data)
298
+
299
+ await Yon.addToStatix(html, script, style, comp, 'components')
300
+ }
301
+ }
302
+
303
+ private static async bundleDependencies() {
304
+
305
+ const packageFile = Bun.file(`${process.cwd()}/package.json`)
306
+
307
+ const otherEntries = ['index.js', 'index', 'index.node']
308
+
309
+ if(await packageFile.exists()) {
310
+
311
+ const packages = await packageFile.json()
312
+
313
+ const modules = Object.keys(packages.dependencies ?? {})
314
+
315
+ for(const module of modules) {
316
+
317
+ let modPack = await Bun.file(`${process.cwd()}/node_modules/${module}/package.json`).json()
318
+
319
+ let idx = 0
320
+ let entryExists = false
321
+
322
+ while(!modPack.main && !entryExists && idx < otherEntries.length) {
323
+
324
+ entryExists = await Bun.file(`${process.cwd()}/node_modules/${module}/${otherEntries[idx]}`).exists()
325
+
326
+ if(entryExists) {
327
+ modPack.main = otherEntries[idx]
328
+ break
329
+ }
330
+
331
+ idx++
332
+ }
333
+
334
+ if(!modPack.main) continue
335
+
336
+ try {
337
+
338
+ const result = await Bun.build({
339
+ entrypoints: [`${process.cwd()}/node_modules/${module}/${(modPack.main as string).replace('./', '')}`],
340
+ minify: true
341
+ })
342
+
343
+ for(const output of result.outputs) {
344
+ Router.reqRoutes[`/modules/${module}.js`] = {
345
+ GET: () => new Response(output, { headers: { 'Content-Type': 'application/javascript' } })
346
+ }
347
+ }
348
+
349
+ } catch(e) {}
350
+ }
351
+ }
352
+ }
353
+ }
package/src/router.ts ADDED
@@ -0,0 +1,186 @@
1
+ import { $, BunRequest, Server } from 'bun'
2
+
3
+ export interface _ctx {
4
+ request: Record<string, any>,
5
+ slugs?: Record<string, any>,
6
+ ipAddress: string,
7
+ params?: Array<any>,
8
+ query?: Record<string, any>,
9
+ body: string
10
+ }
11
+
12
+ export default class Router {
13
+
14
+ static readonly reqRoutes: Record<string, Record<string, (req?: BunRequest, server?: Server) => Promise<Response> | Response>> = {}
15
+
16
+ static readonly allRoutes = new Map<string, Set<string>>()
17
+
18
+ static readonly routeSlugs: Record<string, Record<string, number>> = {}
19
+
20
+ static readonly routesPath = `${process.cwd()}/routes`
21
+ static readonly componentsPath = `${process.cwd()}/components`
22
+ static readonly assetsPath = `${process.cwd()}/assets`
23
+
24
+ private static readonly allMethods = process.env.ALLOW_METHODS ? process.env.ALLOW_METHODS.split(',') : ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD', 'OPTIONS']
25
+
26
+ static headers = {
27
+ "Access-Control-Allow-Headers": process.env.ALLOW_HEADERS || "",
28
+ "Access-Control-Allow-Origin": process.env.ALLLOW_ORGINS || "",
29
+ "Access-Control-Allow-Credential": process.env.ALLOW_CREDENTIALS || "false",
30
+ "Access-Control-Expose-Headers": process.env.ALLOW_EXPOSE_HEADERS || "",
31
+ "Access-Control-Max-Age": process.env.ALLOW_MAX_AGE || "",
32
+ "Access-Control-Allow-Methods": process.env.ALLOW_METHODS || ""
33
+ }
34
+
35
+ static async validateRoute(route: string, staticPaths: string[] = []) {
36
+
37
+ const paths = route.split('/')
38
+
39
+ const pattern = /^:.*/
40
+
41
+ const slugs: Record<string, number> = {}
42
+
43
+ if(pattern.test(paths[0])) throw new Error(`Invalid route ${route}`)
44
+
45
+ paths.forEach((path, idx) => {
46
+
47
+ if(pattern.test(path) && (pattern.test(paths[idx - 1]) || pattern.test(paths[idx + 1]))) {
48
+ throw new Error(`Invalid route ${route}`)
49
+ }
50
+
51
+ if(pattern.test(path)) slugs[path] = idx
52
+ })
53
+
54
+ const staticPath = paths.filter((path) => !pattern.test(path)).join(',')
55
+
56
+ if(staticPaths.includes(staticPath)) throw new Error(`Duplicate route ${route}`)
57
+
58
+ staticPaths.push(staticPath)
59
+
60
+ await $`chmod +x ${Router.routesPath}/${route}`
61
+
62
+ const method = paths.pop()!
63
+
64
+ route = `/${paths.join('/')}`
65
+
66
+ if(!Router.allRoutes.has(route)) Router.allRoutes.set(route, new Set<string>())
67
+
68
+ Router.allRoutes.get(route)?.add(method)
69
+
70
+ if(Object.keys(slugs).length > 0 || method === 'HTML') Router.routeSlugs[route] = slugs
71
+ }
72
+
73
+ static parseRequest(request: BunRequest) {
74
+
75
+ const req: Record<string, any> = {}
76
+
77
+ req.headers = Object.fromEntries(request.headers)
78
+ req.cache = request.cache
79
+ req.credentials = request.credentials
80
+ req.destination = request.destination
81
+ req.integrity = request.integrity
82
+ req.keepalive = request.keepalive
83
+ req.method = request.method
84
+ req.mode = request.mode
85
+ req.redirect = request.redirect
86
+ req.referrer = request.referrer
87
+ req.referrerPolicy = request.referrerPolicy
88
+ req.url = request.url
89
+ req.parans = request.params
90
+
91
+ return req
92
+ }
93
+
94
+ static processRequest(request: BunRequest, route: string, ctx: _ctx) {
95
+
96
+ const { params } = Router.getParams(request, route)
97
+
98
+ ctx.slugs = request.params
99
+
100
+ const searchParams = new URL(request.url).searchParams
101
+
102
+ let queryParams: Record<string, any> | undefined;
103
+
104
+ if(searchParams.size > 0) queryParams = Router.parseKVParams(searchParams)
105
+
106
+ ctx.params = params
107
+ ctx.query = queryParams
108
+
109
+ return { handler: `${Router.routesPath}${route}/${request.method}`, ctx }
110
+ }
111
+
112
+ private static getParams(request: BunRequest, route: string) {
113
+
114
+ const url = new URL(request.url)
115
+
116
+ const params = url.pathname.split("/").slice(route.split("/").length)
117
+
118
+ return { params: Router.parseParams(params) }
119
+ }
120
+
121
+ static parseParams(input: string[]) {
122
+
123
+ const params: (string | boolean | number | null | undefined)[] = []
124
+
125
+ for(const param of input) {
126
+
127
+ const num = Number(param)
128
+
129
+ if(!Number.isNaN(num)) params.push(num)
130
+
131
+ else if(param === 'true') params.push(true)
132
+
133
+ else if(param === 'false') params.push(false)
134
+
135
+ else if(param === 'null') params.push(null)
136
+
137
+ else if(param === 'undefined') params.push(undefined)
138
+
139
+ else params.push(param)
140
+ }
141
+
142
+ return params
143
+ }
144
+
145
+ private static parseKVParams(input: URLSearchParams | FormData) {
146
+
147
+ const params: Record<string, any> = {}
148
+
149
+ for(const [key, val] of input) {
150
+
151
+ if(typeof val === "string") {
152
+
153
+ try {
154
+
155
+ params[key] = JSON.parse(val)
156
+
157
+ } catch {
158
+
159
+ const num = Number(val)
160
+
161
+ if(!Number.isNaN(num)) params[key] = num
162
+
163
+ else if(val === 'true') params[key] = true
164
+
165
+ else if(val === 'false') params[key] = false
166
+
167
+ else if(typeof val === "string" && val.includes(',')) params[key] = Router.parseParams(val.split(','))
168
+
169
+ else if(val === 'null') params[key] = null
170
+
171
+ if(params[key] === undefined) params[key] = val
172
+ }
173
+
174
+ } else params[key] = val
175
+ }
176
+
177
+ return params
178
+ }
179
+
180
+ static async validateRoutes() {
181
+
182
+ const routes = Array.from(new Bun.Glob(`**/{${Router.allMethods.join(',')},SOCKET}`).scanSync({ cwd: Router.routesPath }))
183
+
184
+ for(const route of routes) await Router.validateRoute(route)
185
+ }
186
+ }
package/src/serve.ts ADDED
@@ -0,0 +1,144 @@
1
+ #!/usr/bin/env bun
2
+ import Tach from "./server/tach.js"
3
+ import Router, { _ctx } from "./router.js"
4
+ import Yon from "./client/yon.js"
5
+ import { Logger } from "./server/logger.js"
6
+ import { ServerWebSocket } from "bun"
7
+ import { watch } from "fs/promises"
8
+ import { watch as watcher } from "node:fs";
9
+
10
+ type WebSocketData = {
11
+ handler: string,
12
+ ctx: _ctx,
13
+ path: string
14
+ }
15
+
16
+ const start = Date.now()
17
+
18
+ Logger()
19
+
20
+ async function configureRoutes() {
21
+ await Router.validateRoutes()
22
+ Tach.createServerRoutes()
23
+ await Yon.createStaticRoutes()
24
+ }
25
+
26
+ await configureRoutes()
27
+
28
+ const server = Bun.serve({
29
+ routes: Router.reqRoutes,
30
+ // websocket: {
31
+ // async open(ws: ServerWebSocket<WebSocketData>) {
32
+
33
+ // const { handler, path } = ws.data
34
+
35
+ // const proc = Bun.spawn({
36
+ // cmd: [handler],
37
+ // stdout: 'inherit',
38
+ // stderr: "pipe",
39
+ // stdin: "pipe"
40
+ // })
41
+
42
+ // Tach.webSockets.set(ws, proc)
43
+
44
+ // console.info(`WebSocket Connected - ${path} - ${proc.pid}`)
45
+
46
+ // for await(const ev of watch(`/tmp`)) {
47
+
48
+ // if(ev.filename === proc.pid.toString()) {
49
+
50
+ // const status = ws.send(Bun.mmap(`/tmp/${proc.pid}`))
51
+
52
+ // console.info(`WebSocket Message Sent - ${path} - ${proc.pid} - ${status} byte(s)`)
53
+ // }
54
+ // }
55
+ // },
56
+ // async message(ws: ServerWebSocket<WebSocketData>, message: string) {
57
+
58
+ // const proc = Tach.webSockets.get(ws)!
59
+
60
+ // const { ctx, path } = ws.data
61
+
62
+ // ctx.body = message
63
+
64
+ // proc.stdin.write(JSON.stringify(ctx))
65
+
66
+ // proc.stdin.flush()
67
+
68
+ // console.info(`WebSocket Message Received - ${path} - ${proc.pid} - ${message.length} byte(s)`)
69
+ // },
70
+ // close(ws, code, reason) {
71
+
72
+ // const { path } = ws.data
73
+
74
+ // const proc = Tach.webSockets.get(ws)!
75
+
76
+ // proc.stdin.end()
77
+
78
+ // Tach.webSockets.delete(ws)
79
+
80
+ // console.info(`WebSocket Disconnected - ${path} - ${proc.pid} - Code (${code}): ${reason}`)
81
+ // },
82
+ // },
83
+ port: process.env.PORT || 8080,
84
+ hostname: process.env.HOSTNAME || '0.0.0.0',
85
+ development: process.env.NODE_ENV === 'development'
86
+ })
87
+
88
+ if(server.development) {
89
+
90
+ const socket = Bun.serve({
91
+ fetch(req) {
92
+ socket.upgrade(req)
93
+ return undefined
94
+ },
95
+ websocket: {
96
+ open(ws) {
97
+ console.info("HMR Enabled")
98
+
99
+ watcher(Router.routesPath, { recursive: true }, () => {
100
+ queueMicrotask(async () => {
101
+ console.info("HMR Update")
102
+ await configureRoutes()
103
+ server.reload({ routes: Router.reqRoutes })
104
+ ws.send('')
105
+ })
106
+ })
107
+
108
+ watcher(Router.componentsPath, { recursive: true }, () => {
109
+ queueMicrotask(async () => {
110
+ console.info("HMR Update")
111
+ await configureRoutes()
112
+ server.reload({ routes: Router.reqRoutes })
113
+ ws.send('')
114
+ })
115
+ })
116
+ },
117
+ message(ws, message) {
118
+
119
+ },
120
+ close(ws, code, reason) {
121
+ console.info(`HMR Closed ${code} ${reason}`)
122
+ },
123
+ },
124
+ port: 9876
125
+ })
126
+
127
+ watcher(Router.routesPath, { recursive: true }, () => {
128
+ queueMicrotask(async () => {
129
+ await configureRoutes()
130
+ server.reload({ routes: Router.reqRoutes })
131
+ })
132
+ })
133
+
134
+ watcher(Router.componentsPath, { recursive: true }, () => {
135
+ queueMicrotask(async () => {
136
+ await configureRoutes()
137
+ server.reload({ routes: Router.reqRoutes })
138
+ })
139
+ })
140
+ }
141
+
142
+ const elapsed = Date.now() - start
143
+
144
+ console.info(`Live Server is running on http://${server.hostname}:${server.port} (Press CTRL+C to quit) - ${elapsed.toFixed(2)}ms`)