@vyckr/tachyon 0.3.0 → 1.0.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.
- package/.env.example +3 -17
- package/README.md +87 -57
- package/bun.lock +127 -0
- package/components/counter.html +13 -0
- package/deno.lock +19 -0
- package/go.mod +3 -0
- package/lib/gson-2.3.jar +0 -0
- package/main.js +0 -0
- package/package.json +19 -20
- package/routes/DELETE +18 -0
- package/routes/GET +17 -0
- package/routes/HTML +28 -0
- package/routes/POST +32 -0
- package/routes/SOCKET +26 -0
- package/routes/api/:version/DELETE +10 -0
- package/routes/api/:version/GET +29 -0
- package/routes/api/:version/PATCH +24 -0
- package/routes/api/GET +29 -0
- package/routes/api/POST +16 -0
- package/routes/api/PUT +21 -0
- package/src/client/404.html +7 -0
- package/src/client/dev.html +14 -0
- package/src/client/dist.ts +20 -0
- package/src/client/hmr.js +12 -0
- package/src/client/prod.html +13 -0
- package/src/client/render.js +278 -0
- package/src/client/routes.json +1 -0
- package/src/client/yon.ts +341 -0
- package/src/router.ts +185 -0
- package/src/serve.ts +144 -0
- package/src/server/logger.ts +31 -0
- package/src/server/tach.ts +234 -0
- package/tests/index.test.ts +110 -0
- package/tests/stream.ts +24 -0
- package/tests/worker.ts +7 -0
- package/tsconfig.json +1 -1
- package/Dockerfile +0 -47
- package/bun.lockb +0 -0
- package/routes/byos/[primary]/doc/index.ts +0 -28
- package/routes/byos/[primary]/docs/index.ts +0 -28
- package/routes/byos/[primary]/join/[secondary]/docs/index.ts +0 -10
- package/routes/byos/[primary]/schema/index.ts +0 -17
- package/routes/byos/[primary]/stream/doc/index.ts +0 -28
- package/routes/byos/[primary]/stream/docs/index.ts +0 -28
- package/routes/proxy.ts +0 -8
- package/routes/utils/validation.ts +0 -36
- package/src/Tach.ts +0 -543
- package/src/Yon.ts +0 -25
- package/src/runtime.ts +0 -822
- package/types/index.d.ts +0 -13
package/src/Tach.ts
DELETED
|
@@ -1,543 +0,0 @@
|
|
|
1
|
-
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
|
-
import { watch } from "node:fs";
|
|
3
|
-
import Silo from "@vyckr/byos";
|
|
4
|
-
import { Glob, Server } from "bun";
|
|
5
|
-
|
|
6
|
-
const Tach = {
|
|
7
|
-
|
|
8
|
-
indexedRoutes: new Map<string, Map<string, Function>>(),
|
|
9
|
-
|
|
10
|
-
routeSlugs: new Map<string, Map<string, number>>(),
|
|
11
|
-
|
|
12
|
-
allMethods: process.env.ALLOW_METHODS ? process.env.ALLOW_METHODS.split(',') : ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'],
|
|
13
|
-
|
|
14
|
-
inDevelopment: process.env.PRODUCTION ? false : true,
|
|
15
|
-
|
|
16
|
-
headers: {
|
|
17
|
-
"Access-Control-Allow-Headers": process.env.ALLOW_HEADERS || "",
|
|
18
|
-
"Access-Control-Allow-Origin": process.env.ALLLOW_ORGINS || "",
|
|
19
|
-
"Access-Control-Allow-Credential": process.env.ALLOW_CREDENTIALS || "false",
|
|
20
|
-
"Access-Control-Expose-Headers": process.env.ALLOW_EXPOSE_HEADERS || "",
|
|
21
|
-
"Access-Control-Max-Age": process.env.ALLOW_MAX_AGE || "",
|
|
22
|
-
"Access-Control-Allow-Methods": process.env.ALLOW_METHODS || ""
|
|
23
|
-
},
|
|
24
|
-
|
|
25
|
-
dbPath: process.env.DB_DIR,
|
|
26
|
-
|
|
27
|
-
saveStats: process.env.SAVE_STATS === 'true',
|
|
28
|
-
saveRequests: process.env.SAVE_REQUESTS === 'true',
|
|
29
|
-
saveErrors: process.env.SAVE_ERRORS === 'true',
|
|
30
|
-
saveLogs: process.env.SAVE_LOGS === 'true',
|
|
31
|
-
|
|
32
|
-
logsTableName: "_logs",
|
|
33
|
-
errorsTableName: "_errors",
|
|
34
|
-
requestTableName: "_requests",
|
|
35
|
-
statsTableName: "_stats",
|
|
36
|
-
|
|
37
|
-
context: new AsyncLocalStorage<_log[]>(),
|
|
38
|
-
|
|
39
|
-
routesPath: process.env.LAMBDA_TASK_ROOT ? `${process.env.LAMBDA_TASK_ROOT}/routes` : `${process.cwd()}/routes`,
|
|
40
|
-
|
|
41
|
-
proxyMod: null,
|
|
42
|
-
|
|
43
|
-
async proxy(req: Request, server?: Server) {
|
|
44
|
-
|
|
45
|
-
const request = req.clone()
|
|
46
|
-
|
|
47
|
-
const logs: _log[] = []
|
|
48
|
-
|
|
49
|
-
const url = new URL(req.url)
|
|
50
|
-
|
|
51
|
-
const startTime = Date.now()
|
|
52
|
-
|
|
53
|
-
const ipAddress = server && server.requestIP ? server.requestIP(req)!.address : '0.0.0.0'
|
|
54
|
-
|
|
55
|
-
return await Tach.context.run(logs, async () => {
|
|
56
|
-
|
|
57
|
-
let res: Response
|
|
58
|
-
|
|
59
|
-
try {
|
|
60
|
-
|
|
61
|
-
const data = await Tach.processRequest(req, { ipAddress, request: req, requestTime: startTime, logs, slugs: new Map<string, any>() })
|
|
62
|
-
|
|
63
|
-
res = Tach.processResponse(200, data)
|
|
64
|
-
|
|
65
|
-
if(logs.length > 0 && Tach.saveLogs && Tach.dbPath) await Promise.all(logs.map(log => {
|
|
66
|
-
return Silo.putData(Tach.logsTableName, { ipAddress, path: url.pathname, method: req.method, ...log })
|
|
67
|
-
}))
|
|
68
|
-
|
|
69
|
-
if(!Tach.isAsyncIterator(data)) {
|
|
70
|
-
|
|
71
|
-
const status = res.status
|
|
72
|
-
const response_size = typeof data !== "undefined" ? String(data).length : 0
|
|
73
|
-
const url = new URL(req.url)
|
|
74
|
-
const method = req.method
|
|
75
|
-
const date = Date.now()
|
|
76
|
-
const duration = date - startTime
|
|
77
|
-
|
|
78
|
-
console.info(`"${method} ${url.pathname}" ${status} - ${duration}ms - ${response_size} byte(s)`)
|
|
79
|
-
|
|
80
|
-
if(Tach.dbPath && Tach.saveStats) await Silo.putData(Tach.statsTableName, { ipAddress, cpu: process.cpuUsage(), memory: process.memoryUsage(), date: Date.now() })
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
} catch(e) {
|
|
84
|
-
|
|
85
|
-
const method = request.method
|
|
86
|
-
|
|
87
|
-
await Tach.logError(e as Error, ipAddress, url, method, logs, startTime)
|
|
88
|
-
|
|
89
|
-
if(Tach.dbPath && Tach.saveStats) await Silo.putData(Tach.statsTableName, { ipAddress, cpu: process.cpuUsage(), memory: process.memoryUsage(), date: Date.now() })
|
|
90
|
-
|
|
91
|
-
res = Response.json({ detail: (e as Error).message }, { status: (e as Error).cause as number ?? 500, headers: Tach.headers })
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
return res
|
|
95
|
-
})
|
|
96
|
-
},
|
|
97
|
-
|
|
98
|
-
pathsMatch(routeSegs: string[], pathSegs: string[]) {
|
|
99
|
-
|
|
100
|
-
if (routeSegs.length !== pathSegs.length) {
|
|
101
|
-
return false;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
const slugs = Tach.routeSlugs.get(`${routeSegs.join('/')}/index.ts`) || Tach.routeSlugs.get(`${routeSegs.join('/')}/index.js`) || new Map<string, number>()
|
|
105
|
-
|
|
106
|
-
for (let i = 0; i < routeSegs.length; i++) {
|
|
107
|
-
if (!slugs.has(routeSegs[i]) && routeSegs[i] !== pathSegs[i]) {
|
|
108
|
-
return false;
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
return true;
|
|
113
|
-
},
|
|
114
|
-
|
|
115
|
-
getHandler(request: Request) {
|
|
116
|
-
|
|
117
|
-
const url = new URL(request.url);
|
|
118
|
-
|
|
119
|
-
let handler;
|
|
120
|
-
let params: string[] = [];
|
|
121
|
-
const paths = url.pathname.split('/').slice(1);
|
|
122
|
-
const allowedMethods: string[] = [];
|
|
123
|
-
|
|
124
|
-
let slugs = new Map<string, string>()
|
|
125
|
-
|
|
126
|
-
let bestMatchKey = '';
|
|
127
|
-
let bestMatchLength = -1;
|
|
128
|
-
|
|
129
|
-
for (const [routeKey] of Tach.indexedRoutes) {
|
|
130
|
-
|
|
131
|
-
const routeSegs = routeKey.split('/')
|
|
132
|
-
|
|
133
|
-
routeSegs.pop()
|
|
134
|
-
|
|
135
|
-
const isMatch = Tach.pathsMatch(routeSegs, paths.slice(0, routeSegs.length));
|
|
136
|
-
|
|
137
|
-
if (isMatch && routeSegs.length > bestMatchLength) {
|
|
138
|
-
bestMatchKey = routeKey;
|
|
139
|
-
bestMatchLength = routeSegs.length;
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
if (bestMatchKey) {
|
|
144
|
-
const routeMap = Tach.indexedRoutes.get(bestMatchKey)!
|
|
145
|
-
handler = routeMap.get(request.method);
|
|
146
|
-
|
|
147
|
-
for (const [key] of routeMap) {
|
|
148
|
-
if (Tach.allMethods.includes(key)) allowedMethods.push(key);
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
params = paths.slice(bestMatchLength);
|
|
152
|
-
|
|
153
|
-
const slugMap = Tach.routeSlugs.get(bestMatchKey) ?? new Map<string, number>()
|
|
154
|
-
|
|
155
|
-
slugMap.forEach((idx, key) => slugs.set(key, paths[idx]))
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
Tach.headers = { ...Tach.headers, "Access-Control-Allow-Methods": allowedMethods.join(',') };
|
|
159
|
-
|
|
160
|
-
if (!handler) throw new Error(`Route ${request.method} ${url.pathname} not found`, { cause: 404 });
|
|
161
|
-
|
|
162
|
-
return { handler, params: Tach.parseParams(params), slugs }
|
|
163
|
-
},
|
|
164
|
-
|
|
165
|
-
formatDate() {
|
|
166
|
-
return new Date().toISOString().replace('T', ' ').replace('Z', '')
|
|
167
|
-
},
|
|
168
|
-
|
|
169
|
-
formatMsg(...msg: any[]) {
|
|
170
|
-
|
|
171
|
-
const formatted: string[] = []
|
|
172
|
-
|
|
173
|
-
for(const arg of msg) {
|
|
174
|
-
|
|
175
|
-
if(arg instanceof Set) formatted.push(JSON.stringify(Array.from(arg), null, 2))
|
|
176
|
-
|
|
177
|
-
else if(arg instanceof Map) formatted.push(JSON.stringify(Object.fromEntries(arg), null, 2))
|
|
178
|
-
|
|
179
|
-
else if(arg instanceof FormData) {
|
|
180
|
-
const formEntries: Record<string, any> = {}
|
|
181
|
-
arg.forEach((val, key) => formEntries[key] = val)
|
|
182
|
-
formatted.push(JSON.stringify(formEntries, null, 2))
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
else if(Array.isArray(arg) || (typeof arg === 'object' && !Array.isArray(arg)) || (typeof arg === 'object' && arg !== null)) formatted.push(JSON.stringify(arg, null, 2))
|
|
186
|
-
|
|
187
|
-
else formatted.push(arg)
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
return formatted.join('\n\n')
|
|
191
|
-
},
|
|
192
|
-
|
|
193
|
-
configLogger() {
|
|
194
|
-
|
|
195
|
-
const reset = '\x1b[0m'
|
|
196
|
-
|
|
197
|
-
console.info = (...args: any[]) => {
|
|
198
|
-
const info = `[${Tach.formatDate()}]\x1b[32m INFO${reset} (${process.pid}) ${Tach.formatMsg(...args)}`
|
|
199
|
-
console.log(info)
|
|
200
|
-
if(Tach.context.getStore()) {
|
|
201
|
-
const logWriter = Tach.context.getStore()
|
|
202
|
-
if(logWriter && Tach.dbPath && Tach.saveLogs) logWriter.push({ date: Date.now(), msg: `${info.replace(reset, '').replace('\x1b[32m', '')}\n`, type: "info" })
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
console.error = (...args: any[]) => {
|
|
207
|
-
const err = `[${Tach.formatDate()}]\x1b[31m ERROR${reset} (${process.pid}) ${Tach.formatMsg(...args)}`
|
|
208
|
-
console.log(err)
|
|
209
|
-
if(Tach.context.getStore()) {
|
|
210
|
-
const logWriter = Tach.context.getStore()
|
|
211
|
-
if(logWriter && Tach.dbPath && Tach.saveLogs) logWriter.push({ date: Date.now(), msg: `${err.replace(reset, '').replace('\x1b[31m', '')}\n`, type: "error" })
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
console.debug = (...args: any[]) => {
|
|
216
|
-
const bug = `[${Tach.formatDate()}]\x1b[36m DEBUG${reset} (${process.pid}) ${Tach.formatMsg(...args)}`
|
|
217
|
-
console.log(bug)
|
|
218
|
-
if(Tach.context.getStore()) {
|
|
219
|
-
const logWriter = Tach.context.getStore()
|
|
220
|
-
if(logWriter && Tach.dbPath && Tach.saveLogs) logWriter.push({ date: Date.now(), msg: `${bug.replace(reset, '').replace('\x1b[36m', '')}\n`, type: "debug" })
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
console.warn = (...args: any[]) => {
|
|
225
|
-
const warn = `[${Tach.formatDate()}]\x1b[33m WARN${reset} (${process.pid}) ${Tach.formatMsg(...args)}`
|
|
226
|
-
console.log(warn)
|
|
227
|
-
if(Tach.context.getStore()) {
|
|
228
|
-
const logWriter = Tach.context.getStore()
|
|
229
|
-
if(logWriter && Tach.dbPath && Tach.saveLogs) logWriter.push({ date: Date.now(), msg: `${warn.replace(reset, '').replace('\x1b[33m', '')}\n`, type: "warn" })
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
console.trace = (...args: any[]) => {
|
|
234
|
-
const trace = `[${Tach.formatDate()}]\x1b[35m TRACE${reset} (${process.pid}) ${Tach.formatMsg(...args)}`
|
|
235
|
-
console.log(trace)
|
|
236
|
-
if(Tach.context.getStore()) {
|
|
237
|
-
const logWriter = Tach.context.getStore()
|
|
238
|
-
if(logWriter && Tach.dbPath && Tach.saveLogs) logWriter.push({ date: Date.now(), msg: `${trace.replace(reset, '').replace('\x1b[35m', '')}\n`, type: "trace" })
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
},
|
|
242
|
-
|
|
243
|
-
async logRequest(request: Request, status: number, context: _HTTPContext, data: any = null) {
|
|
244
|
-
|
|
245
|
-
if(Tach.dbPath && Tach.saveRequests) {
|
|
246
|
-
|
|
247
|
-
const url = new URL(request.url)
|
|
248
|
-
const date = Date.now()
|
|
249
|
-
const duration = date - (context.requestTime ?? 0)
|
|
250
|
-
|
|
251
|
-
await Silo.putData(Tach.requestTableName, { ipAddress: context.ipAddress, url: `${url.pathname}${url.search}`, method: request.method, status, duration, date, size: data ? String(data).length : 0, data })
|
|
252
|
-
}
|
|
253
|
-
},
|
|
254
|
-
|
|
255
|
-
async processRequest(request: Request, context: _HTTPContext) {
|
|
256
|
-
|
|
257
|
-
const { handler, params, slugs } = Tach.getHandler(request)
|
|
258
|
-
|
|
259
|
-
if(slugs.size > 0) context.slugs = slugs
|
|
260
|
-
|
|
261
|
-
const body = await request.blob()
|
|
262
|
-
|
|
263
|
-
let data: Blob | Record<string, any> | undefined
|
|
264
|
-
|
|
265
|
-
if(body.size > 0) {
|
|
266
|
-
|
|
267
|
-
if(body.type.includes('form')) data = Tach.parseKVParams(await body.formData())
|
|
268
|
-
else {
|
|
269
|
-
try {
|
|
270
|
-
data = await body.json()
|
|
271
|
-
} catch {
|
|
272
|
-
data = body
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
const searchParams = new URL(request.url).searchParams
|
|
278
|
-
|
|
279
|
-
let queryParams: Record<string, any> | undefined;
|
|
280
|
-
|
|
281
|
-
if(searchParams.size > 0) queryParams = Tach.parseKVParams(searchParams)
|
|
282
|
-
|
|
283
|
-
if(params.length > 0 && !queryParams && !data) {
|
|
284
|
-
|
|
285
|
-
const res = await handler(...params, context)
|
|
286
|
-
|
|
287
|
-
await Tach.logRequest(request, 200, context)
|
|
288
|
-
|
|
289
|
-
return res
|
|
290
|
-
|
|
291
|
-
} else if(params.length === 0 && queryParams && !data) {
|
|
292
|
-
|
|
293
|
-
const res = await handler(queryParams, context)
|
|
294
|
-
|
|
295
|
-
await Tach.logRequest(request, 200, context)
|
|
296
|
-
|
|
297
|
-
return res
|
|
298
|
-
|
|
299
|
-
} else if(params.length === 0 && !queryParams && data) {
|
|
300
|
-
|
|
301
|
-
const res = await handler(data, context)
|
|
302
|
-
|
|
303
|
-
await Tach.logRequest(request, 200, context, await body.text())
|
|
304
|
-
|
|
305
|
-
return res
|
|
306
|
-
|
|
307
|
-
} else if(params.length > 0 && queryParams && !data) {
|
|
308
|
-
|
|
309
|
-
const res = await handler(...params, queryParams, context)
|
|
310
|
-
|
|
311
|
-
await Tach.logRequest(request, 200, context)
|
|
312
|
-
|
|
313
|
-
return res
|
|
314
|
-
|
|
315
|
-
} else if(params.length > 0 && !queryParams && data) {
|
|
316
|
-
|
|
317
|
-
const res = await handler(...params, data, context)
|
|
318
|
-
|
|
319
|
-
await Tach.logRequest(request, 200, context, await body.text())
|
|
320
|
-
|
|
321
|
-
return res
|
|
322
|
-
|
|
323
|
-
} else if(params.length === 0 && data && queryParams) {
|
|
324
|
-
|
|
325
|
-
const res = await handler(queryParams, data, context)
|
|
326
|
-
|
|
327
|
-
await Tach.logRequest(request, 200, context, await body.text())
|
|
328
|
-
|
|
329
|
-
return res
|
|
330
|
-
|
|
331
|
-
} else if(params.length > 0 && data && queryParams) {
|
|
332
|
-
|
|
333
|
-
const res = await handler(...params, queryParams, data, context)
|
|
334
|
-
|
|
335
|
-
await Tach.logRequest(request, 200, context, await body.text())
|
|
336
|
-
|
|
337
|
-
return res
|
|
338
|
-
|
|
339
|
-
} else {
|
|
340
|
-
|
|
341
|
-
const res = await handler(context)
|
|
342
|
-
|
|
343
|
-
await Tach.logRequest(request, 200, context)
|
|
344
|
-
|
|
345
|
-
return res
|
|
346
|
-
}
|
|
347
|
-
},
|
|
348
|
-
|
|
349
|
-
isAsyncIterator(data: any) {
|
|
350
|
-
return typeof data === "object" && Object.hasOwn(data, Symbol.asyncIterator)
|
|
351
|
-
},
|
|
352
|
-
|
|
353
|
-
hasFunctions(data: any) {
|
|
354
|
-
return typeof data === "object" && (Object.keys(data).some((elem) => typeof elem === "function") || Object.values(data).some((elem) => typeof elem === "function"))
|
|
355
|
-
},
|
|
356
|
-
|
|
357
|
-
processResponse(status: number, data?: any) {
|
|
358
|
-
|
|
359
|
-
const headers = Tach.headers
|
|
360
|
-
|
|
361
|
-
if(data instanceof Set) return Response.json(Array.from(data), { status, headers })
|
|
362
|
-
|
|
363
|
-
if(data instanceof Map) return Response.json(Object.fromEntries(data), { status, headers })
|
|
364
|
-
|
|
365
|
-
if(data instanceof FormData || data instanceof Blob) return new Response(data, { status, headers })
|
|
366
|
-
|
|
367
|
-
if(typeof data === "object" && !Array.isArray(data) && !Tach.isAsyncIterator(data) && !Tach.hasFunctions(data)) return Response.json(data, { status, headers })
|
|
368
|
-
|
|
369
|
-
if((typeof data === "object" && Array.isArray(data)) || data instanceof Array) return Response.json(data, { status, headers })
|
|
370
|
-
|
|
371
|
-
if(typeof data === "number" || typeof data === "boolean") return Response.json(data, { status, headers })
|
|
372
|
-
|
|
373
|
-
return new Response(data, { status, headers })
|
|
374
|
-
},
|
|
375
|
-
|
|
376
|
-
async logError(e: Error, ipAddress: string, url: URL, method: string, logs: _log[], startTime?: number) {
|
|
377
|
-
|
|
378
|
-
const path = url.pathname
|
|
379
|
-
|
|
380
|
-
if(logs.length > 0 && Tach.saveLogs && Tach.dbPath) await Promise.all(logs.map(log => {
|
|
381
|
-
return Silo.putData(Tach.logsTableName, { ipAddress, path, method, ...log })
|
|
382
|
-
}))
|
|
383
|
-
|
|
384
|
-
if(Tach.dbPath && Tach.saveErrors) await Silo.putData(Tach.errorsTableName, { ipAddress, date: Date.now(),path, method, error: e.message })
|
|
385
|
-
|
|
386
|
-
console.error(`"${method} ${path}" ${e.cause as number ?? 500} ${startTime ? `- ${Date.now() - startTime}ms` : ''} - ${e.message.length} byte(s)`)
|
|
387
|
-
},
|
|
388
|
-
|
|
389
|
-
watchFiles() {
|
|
390
|
-
|
|
391
|
-
if(Tach.inDevelopment) {
|
|
392
|
-
|
|
393
|
-
watch(Tach.routesPath, { recursive: true }, async (ev, filename) => {
|
|
394
|
-
delete import.meta.require.cache[`${Tach.routesPath}/${filename}`]
|
|
395
|
-
if(!filename?.split('/').some((path) => path.startsWith('_'))) await Tach.validateRoutes(filename!)
|
|
396
|
-
})
|
|
397
|
-
}
|
|
398
|
-
},
|
|
399
|
-
|
|
400
|
-
async fetch(req: Request, server: Server) {
|
|
401
|
-
|
|
402
|
-
if(Tach.proxyMod) {
|
|
403
|
-
|
|
404
|
-
const middleware = (Tach.proxyMod as any).default
|
|
405
|
-
|
|
406
|
-
if(middleware) return await middleware(req, Tach.proxy)
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
return await Tach.proxy(req, server)
|
|
410
|
-
},
|
|
411
|
-
|
|
412
|
-
async validateRoutes(route?: string) {
|
|
413
|
-
|
|
414
|
-
const staticPaths: string[] = []
|
|
415
|
-
|
|
416
|
-
const validateRoute = async (route: string) => {
|
|
417
|
-
|
|
418
|
-
const paths = route.split('/')
|
|
419
|
-
|
|
420
|
-
const pattern = /[<>|\[\]]/
|
|
421
|
-
|
|
422
|
-
const slugs = new Map<string, number>()
|
|
423
|
-
|
|
424
|
-
if(pattern.test(paths[0]) || pattern.test(paths[paths.length - 1])) throw new Error(`Invalid route ${route}`)
|
|
425
|
-
|
|
426
|
-
paths.forEach((path, idx) => {
|
|
427
|
-
|
|
428
|
-
if(pattern.test(path) && (pattern.test(paths[idx - 1]) || pattern.test(paths[idx + 1]))) {
|
|
429
|
-
throw new Error(`Invalid route ${route}`)
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
if(pattern.test(path)) slugs.set(path, idx)
|
|
433
|
-
})
|
|
434
|
-
|
|
435
|
-
const staticPath = paths.filter((path) => !pattern.test(path)).join(',')
|
|
436
|
-
|
|
437
|
-
if(staticPaths.includes(staticPath)) throw new Error(`Duplicate route ${route}`)
|
|
438
|
-
|
|
439
|
-
staticPaths.push(staticPath)
|
|
440
|
-
|
|
441
|
-
const module = await import(`${Tach.routesPath}/${route}`)
|
|
442
|
-
|
|
443
|
-
const controller = (new module.default() as any).constructor
|
|
444
|
-
|
|
445
|
-
const methodFuncs = new Map<string, Function>()
|
|
446
|
-
|
|
447
|
-
for(const method of Tach.allMethods) {
|
|
448
|
-
|
|
449
|
-
if(controller[method]) {
|
|
450
|
-
|
|
451
|
-
methodFuncs.set(method, controller[method])
|
|
452
|
-
}
|
|
453
|
-
}
|
|
454
|
-
|
|
455
|
-
Tach.indexedRoutes.set(route, methodFuncs)
|
|
456
|
-
|
|
457
|
-
if(slugs.size > 0) Tach.routeSlugs.set(route, slugs)
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
if(route) return await validateRoute(route)
|
|
461
|
-
|
|
462
|
-
const routes = Array.from(new Glob(`**/*/index.{ts,js}`).scanSync({ cwd: Tach.routesPath }))
|
|
463
|
-
|
|
464
|
-
for(const route of routes) await validateRoute(route)
|
|
465
|
-
|
|
466
|
-
const proxy = Array.from(new Glob(`**/proxy.{ts,js}`).scanSync({ cwd: Tach.routesPath }))
|
|
467
|
-
|
|
468
|
-
if(proxy[0]) Tach.proxyMod = await import(`${Tach.routesPath}/${proxy[0]}`)
|
|
469
|
-
},
|
|
470
|
-
|
|
471
|
-
parseParams(input: string[]) {
|
|
472
|
-
|
|
473
|
-
const params: (string | boolean | number | null | undefined)[] = []
|
|
474
|
-
|
|
475
|
-
for(const param of input) {
|
|
476
|
-
|
|
477
|
-
const num = Number(param)
|
|
478
|
-
|
|
479
|
-
if(!Number.isNaN(num)) params.push(num)
|
|
480
|
-
|
|
481
|
-
else if(param === 'true') params.push(true)
|
|
482
|
-
|
|
483
|
-
else if(param === 'false') params.push(false)
|
|
484
|
-
|
|
485
|
-
else if(param === 'null') params.push(null)
|
|
486
|
-
|
|
487
|
-
else if(param === 'undefined') params.push(undefined)
|
|
488
|
-
|
|
489
|
-
else params.push(param)
|
|
490
|
-
}
|
|
491
|
-
|
|
492
|
-
return params
|
|
493
|
-
},
|
|
494
|
-
|
|
495
|
-
parseKVParams(input: URLSearchParams | FormData) {
|
|
496
|
-
|
|
497
|
-
const params: Record<string, any> = {}
|
|
498
|
-
|
|
499
|
-
for(const [key, val] of input) {
|
|
500
|
-
|
|
501
|
-
if(typeof val === "string") {
|
|
502
|
-
|
|
503
|
-
try {
|
|
504
|
-
|
|
505
|
-
params[key] = JSON.parse(val)
|
|
506
|
-
|
|
507
|
-
} catch {
|
|
508
|
-
|
|
509
|
-
const num = Number(val)
|
|
510
|
-
|
|
511
|
-
if(!Number.isNaN(num)) params[key] = num
|
|
512
|
-
|
|
513
|
-
else if(val === 'true') params[key] = true
|
|
514
|
-
|
|
515
|
-
else if(val === 'false') params[key] = false
|
|
516
|
-
|
|
517
|
-
else if(typeof val === "string" && val.includes(',')) params[key] = Tach.parseParams(val.split(','))
|
|
518
|
-
|
|
519
|
-
else if(val === 'null') params[key] = null
|
|
520
|
-
|
|
521
|
-
if(params[key] === undefined) params[key] = val
|
|
522
|
-
}
|
|
523
|
-
|
|
524
|
-
} else params[key] = val
|
|
525
|
-
}
|
|
526
|
-
|
|
527
|
-
return params
|
|
528
|
-
}
|
|
529
|
-
}
|
|
530
|
-
|
|
531
|
-
try {
|
|
532
|
-
|
|
533
|
-
await Tach.validateRoutes()
|
|
534
|
-
|
|
535
|
-
Tach.watchFiles()
|
|
536
|
-
|
|
537
|
-
Tach.configLogger()
|
|
538
|
-
|
|
539
|
-
} catch(e) {
|
|
540
|
-
console.log(`Tach.ts --> ${e}`)
|
|
541
|
-
}
|
|
542
|
-
|
|
543
|
-
export default Tach
|
package/src/Yon.ts
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bun
|
|
2
|
-
import Tach from "./Tach.js"
|
|
3
|
-
import Silo from "@vyckr/byos";
|
|
4
|
-
|
|
5
|
-
try {
|
|
6
|
-
|
|
7
|
-
const start = Date.now()
|
|
8
|
-
|
|
9
|
-
const server = Bun.serve({ fetch: Tach.fetch, async error(req) {
|
|
10
|
-
|
|
11
|
-
if(Tach.dbPath && Tach.saveStats) await Silo.putData(Tach.statsTableName, { cpu: process.cpuUsage(), memory: process.memoryUsage(), date: Date.now() })
|
|
12
|
-
|
|
13
|
-
return Response.json({ detail: req.message }, { status: req.cause as number ?? 500, headers: Tach.headers })
|
|
14
|
-
},
|
|
15
|
-
development: Tach.inDevelopment,
|
|
16
|
-
port: process.env.PORT || 8000
|
|
17
|
-
})
|
|
18
|
-
|
|
19
|
-
process.on('SIGINT', () => process.exit(0))
|
|
20
|
-
|
|
21
|
-
console.info(`Live Server is running on http://${server.hostname}:${server.port} (Press CTRL+C to quit) - StartUp Time: ${Date.now() - start}ms`)
|
|
22
|
-
|
|
23
|
-
} catch(e) {
|
|
24
|
-
if(e instanceof Error) console.error(e.message)
|
|
25
|
-
}
|