@vyckr/tachyon 0.1.0 → 0.3.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/Dockerfile CHANGED
@@ -16,28 +16,32 @@ RUN unzip bun-lambda-layer.zip -d /tmp
16
16
 
17
17
  WORKDIR /tmp
18
18
 
19
- COPY ./src/Tach.ts .
20
-
21
19
  COPY package.json .
22
20
 
23
21
  RUN bun install
24
22
 
25
- RUN bun build --target=bun Tach.ts --outfile lambda
26
-
27
23
  FROM public.ecr.aws/lambda/provided:al2
28
24
 
29
- COPY --from=build /tmp/lambda ${LAMBDA_TASK_ROOT}
25
+ COPY --from=build /tmp/node_modules ${LAMBDA_TASK_ROOT}/node_modules
26
+
27
+ COPY --from=build /tmp/package.json ${LAMBDA_TASK_ROOT}/package.json
30
28
 
31
29
  COPY --from=build /tmp/bootstrap ${LAMBDA_RUNTIME_DIR}
32
30
 
33
31
  COPY --from=build /tmp/bun /opt
34
32
 
33
+ COPY ./src/Tach.ts ${LAMBDA_TASK_ROOT}
34
+
35
+ COPY ./tsconfig.json ${LAMBDA_TASK_ROOT}
36
+
35
37
  COPY ./src/runtime.ts /opt
36
38
 
37
39
  RUN chmod 777 /opt/bun
38
40
 
39
- RUN chmod 777 ${LAMBDA_TASK_ROOT}/lambda
41
+ RUN chmod 777 /opt/runtime.ts
42
+
43
+ RUN chmod 777 ${LAMBDA_TASK_ROOT}/Tach.ts
40
44
 
41
45
  RUN chmod 777 ${LAMBDA_RUNTIME_DIR}/bootstrap
42
46
 
43
- CMD ["lambda.fetch"]
47
+ CMD ["Tach.fetch"]
package/README.md CHANGED
@@ -60,12 +60,12 @@ Make sure you have set the 'SCHEMA_PATH' if 'SCHEMA' is set to 'STRICT'. The sch
60
60
  ### Requirements
61
61
  - Make sure to have a 'routes' directory in the root of your project
62
62
  - Dynamic routes should be enclosed in square brackets
63
- - The first parameter should NOT be a dynamic route (e.g. /[version]/doc.ts)
64
- - All dynamic routes should be within odd indexes (e.g. /v1/[path]/login/[id]/name.ts)
65
- - The last parameter in the route should not be a dynamic route (e.g. /v1/[path]/login/[id]/name.ts)
63
+ - The first parameter should NOT be a dynamic route (e.g. /[version]/doc/index.ts)
64
+ - All dynamic routes should be within odd indexes (e.g. /v1/[path]/login/[id]/name/index.ts)
65
+ - The last parameter in the route should not be a dynamic route (e.g. /v1/[path]/login/[id]/name/index.ts)
66
66
 
67
67
  ```typescript
68
- // routes/v1/[collection]/doc.ts
68
+ // routes/v1/[collection]/doc/index.ts
69
69
  import Silo from "@vyckr/byos"
70
70
  imoprt { VALIDATE } from "../utils/decorators"
71
71
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vyckr/tachyon",
3
- "version": "0.1.0",
3
+ "version": "0.3.0",
4
4
  "devDependencies": {
5
5
  "@types/node": "^20.4.2",
6
6
  "bun-types": "^1.1.21"
@@ -1,4 +1,4 @@
1
- import { VALIDATE } from "../../_utils/validation.js";
1
+ import { VALIDATE } from "../../../utils/validation.js";
2
2
 
3
3
  export default class Doc {
4
4
 
@@ -1,4 +1,4 @@
1
- import { VALIDATE } from "../../_utils/validation.js";
1
+ import { VALIDATE } from "../../../utils/validation.js";
2
2
 
3
3
  export default class Docs {
4
4
 
@@ -1,4 +1,4 @@
1
- import { VALIDATE } from "../../../../_utils/validation.js";
1
+ import { VALIDATE } from "../../../../../utils/validation.js";
2
2
 
3
3
  export default class Docs {
4
4
 
@@ -1,4 +1,4 @@
1
- import { VALIDATE } from "../../../_utils/validation.js";
1
+ import { VALIDATE } from "../../../../utils/validation.js";
2
2
 
3
3
  export default class {
4
4
 
@@ -1,4 +1,4 @@
1
- import { VALIDATE } from "../../../_utils/validation.js";
1
+ import { VALIDATE } from "../../../../utils/validation.js";
2
2
 
3
3
  export default class {
4
4
 
@@ -0,0 +1,8 @@
1
+ export default async function middleware(request: Request, next: (request: Request) => Promise<Response>) {
2
+
3
+ console.log("Within Proxy")
4
+
5
+ const response = await next(request)
6
+
7
+ return response
8
+ }
package/src/Tach.ts CHANGED
@@ -1,6 +1,5 @@
1
1
  import { AsyncLocalStorage } from "node:async_hooks";
2
2
  import { watch } from "node:fs";
3
- import { exists } from "node:fs/promises";
4
3
  import Silo from "@vyckr/byos";
5
4
  import { Glob, Server } from "bun";
6
5
 
@@ -39,18 +38,73 @@ const Tach = {
39
38
 
40
39
  routesPath: process.env.LAMBDA_TASK_ROOT ? `${process.env.LAMBDA_TASK_ROOT}/routes` : `${process.cwd()}/routes`,
41
40
 
42
- hasMiddleware: await exists(`${process.env.LAMBDA_TASK_ROOT || process.cwd()}/routes/_middleware.ts`) || await exists(`${process.env.LAMBDA_TASK_ROOT || process.cwd()}/routes/_middleware.js`) ,
41
+ proxyMod: null,
43
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
+
44
98
  pathsMatch(routeSegs: string[], pathSegs: string[]) {
45
99
 
46
100
  if (routeSegs.length !== pathSegs.length) {
47
101
  return false;
48
102
  }
49
103
 
50
- const slugs = Tach.routeSlugs.get(`${routeSegs.join('/')}.ts`) || Tach.routeSlugs.get(`${routeSegs.join('/')}.js`) || new Map<string, number>()
104
+ const slugs = Tach.routeSlugs.get(`${routeSegs.join('/')}/index.ts`) || Tach.routeSlugs.get(`${routeSegs.join('/')}/index.js`) || new Map<string, number>()
51
105
 
52
106
  for (let i = 0; i < routeSegs.length; i++) {
53
- if (!slugs.has(routeSegs[i]) && routeSegs[i].replace('.ts', '').replace('.js', '') !== pathSegs[i]) {
107
+ if (!slugs.has(routeSegs[i]) && routeSegs[i] !== pathSegs[i]) {
54
108
  return false;
55
109
  }
56
110
  }
@@ -73,7 +127,11 @@ const Tach = {
73
127
  let bestMatchLength = -1;
74
128
 
75
129
  for (const [routeKey] of Tach.indexedRoutes) {
76
- const routeSegs = routeKey.split('/').map(seg => seg.replace('.ts', '').replace('.js', ''));
130
+
131
+ const routeSegs = routeKey.split('/')
132
+
133
+ routeSegs.pop()
134
+
77
135
  const isMatch = Tach.pathsMatch(routeSegs, paths.slice(0, routeSegs.length));
78
136
 
79
137
  if (isMatch && routeSegs.length > bestMatchLength) {
@@ -110,43 +168,35 @@ const Tach = {
110
168
 
111
169
  formatMsg(...msg: any[]) {
112
170
 
113
- if(msg instanceof Set) return "\n" + JSON.stringify(Array.from(msg), null, 2)
114
-
115
- else if(msg instanceof Map) return "\n" + JSON.stringify(Object.fromEntries(msg), null, 2)
116
-
117
- else if(msg instanceof FormData) {
118
- const formEntries: Record<string, any> = {}
119
- msg.forEach((val, key) => formEntries[key] = val)
120
- return "\n" + JSON.stringify(formEntries, null, 2)
121
- }
122
-
123
- else if(Array.isArray(msg)
124
- || (typeof msg === 'object' && !Array.isArray(msg))
125
- || (typeof msg === 'object' && msg !== null)) return "\n" + JSON.stringify(msg, null, 2)
171
+ const formatted: string[] = []
126
172
 
127
- return msg
128
- },
129
-
130
- configLogger() {
173
+ for(const arg of msg) {
131
174
 
132
- const logger = console.log
175
+ if(arg instanceof Set) formatted.push(JSON.stringify(Array.from(arg), null, 2))
133
176
 
134
- function log(...args: any[]): void {
177
+ else if(arg instanceof Map) formatted.push(JSON.stringify(Object.fromEntries(arg), null, 2))
135
178
 
136
- if (!args.length) {
137
- return;
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))
138
183
  }
139
184
 
140
- const messages = args.map(arg => Bun.inspect(arg).replace(/\n/g, "\r"));
185
+ else if(Array.isArray(arg) || (typeof arg === 'object' && !Array.isArray(arg)) || (typeof arg === 'object' && arg !== null)) formatted.push(JSON.stringify(arg, null, 2))
141
186
 
142
- logger(...messages);
187
+ else formatted.push(arg)
143
188
  }
144
189
 
190
+ return formatted.join('\n\n')
191
+ },
192
+
193
+ configLogger() {
194
+
145
195
  const reset = '\x1b[0m'
146
196
 
147
197
  console.info = (...args: any[]) => {
148
198
  const info = `[${Tach.formatDate()}]\x1b[32m INFO${reset} (${process.pid}) ${Tach.formatMsg(...args)}`
149
- log(info)
199
+ console.log(info)
150
200
  if(Tach.context.getStore()) {
151
201
  const logWriter = Tach.context.getStore()
152
202
  if(logWriter && Tach.dbPath && Tach.saveLogs) logWriter.push({ date: Date.now(), msg: `${info.replace(reset, '').replace('\x1b[32m', '')}\n`, type: "info" })
@@ -155,7 +205,7 @@ const Tach = {
155
205
 
156
206
  console.error = (...args: any[]) => {
157
207
  const err = `[${Tach.formatDate()}]\x1b[31m ERROR${reset} (${process.pid}) ${Tach.formatMsg(...args)}`
158
- log(err)
208
+ console.log(err)
159
209
  if(Tach.context.getStore()) {
160
210
  const logWriter = Tach.context.getStore()
161
211
  if(logWriter && Tach.dbPath && Tach.saveLogs) logWriter.push({ date: Date.now(), msg: `${err.replace(reset, '').replace('\x1b[31m', '')}\n`, type: "error" })
@@ -164,7 +214,7 @@ const Tach = {
164
214
 
165
215
  console.debug = (...args: any[]) => {
166
216
  const bug = `[${Tach.formatDate()}]\x1b[36m DEBUG${reset} (${process.pid}) ${Tach.formatMsg(...args)}`
167
- log(bug)
217
+ console.log(bug)
168
218
  if(Tach.context.getStore()) {
169
219
  const logWriter = Tach.context.getStore()
170
220
  if(logWriter && Tach.dbPath && Tach.saveLogs) logWriter.push({ date: Date.now(), msg: `${bug.replace(reset, '').replace('\x1b[36m', '')}\n`, type: "debug" })
@@ -173,7 +223,7 @@ const Tach = {
173
223
 
174
224
  console.warn = (...args: any[]) => {
175
225
  const warn = `[${Tach.formatDate()}]\x1b[33m WARN${reset} (${process.pid}) ${Tach.formatMsg(...args)}`
176
- log(warn)
226
+ console.log(warn)
177
227
  if(Tach.context.getStore()) {
178
228
  const logWriter = Tach.context.getStore()
179
229
  if(logWriter && Tach.dbPath && Tach.saveLogs) logWriter.push({ date: Date.now(), msg: `${warn.replace(reset, '').replace('\x1b[33m', '')}\n`, type: "warn" })
@@ -182,7 +232,7 @@ const Tach = {
182
232
 
183
233
  console.trace = (...args: any[]) => {
184
234
  const trace = `[${Tach.formatDate()}]\x1b[35m TRACE${reset} (${process.pid}) ${Tach.formatMsg(...args)}`
185
- log(trace)
235
+ console.log(trace)
186
236
  if(Tach.context.getStore()) {
187
237
  const logWriter = Tach.context.getStore()
188
238
  if(logWriter && Tach.dbPath && Tach.saveLogs) logWriter.push({ date: Date.now(), msg: `${trace.replace(reset, '').replace('\x1b[35m', '')}\n`, type: "trace" })
@@ -230,19 +280,9 @@ const Tach = {
230
280
 
231
281
  if(searchParams.size > 0) queryParams = Tach.parseKVParams(searchParams)
232
282
 
233
- const middlewarePath = await exists(`${Tach.routesPath}/_middleware.ts`) ? `${Tach.routesPath}/_middleware.ts` : `${Tach.routesPath}/_middleware.js`
234
-
235
283
  if(params.length > 0 && !queryParams && !data) {
236
284
 
237
- let res = undefined
238
-
239
- if(Tach.hasMiddleware) {
240
-
241
- const middleware = (await import(middlewarePath)).default
242
-
243
- res = await middleware(async () => handler(...params, context))
244
-
245
- } else res = await handler(...params, context)
285
+ const res = await handler(...params, context)
246
286
 
247
287
  await Tach.logRequest(request, 200, context)
248
288
 
@@ -250,15 +290,7 @@ const Tach = {
250
290
 
251
291
  } else if(params.length === 0 && queryParams && !data) {
252
292
 
253
- let res = undefined
254
-
255
- if(Tach.hasMiddleware) {
256
-
257
- const middleware = (await import(middlewarePath)).default
258
-
259
- res = await middleware(async () => handler(queryParams, context))
260
-
261
- } else res = await handler(queryParams, context)
293
+ const res = await handler(queryParams, context)
262
294
 
263
295
  await Tach.logRequest(request, 200, context)
264
296
 
@@ -266,15 +298,7 @@ const Tach = {
266
298
 
267
299
  } else if(params.length === 0 && !queryParams && data) {
268
300
 
269
- let res = undefined
270
-
271
- if(Tach.hasMiddleware) {
272
-
273
- const middleware = (await import(middlewarePath)).default
274
-
275
- res = await middleware(async () => handler(data, context))
276
-
277
- } else res = await handler(data, context)
301
+ const res = await handler(data, context)
278
302
 
279
303
  await Tach.logRequest(request, 200, context, await body.text())
280
304
 
@@ -282,15 +306,7 @@ const Tach = {
282
306
 
283
307
  } else if(params.length > 0 && queryParams && !data) {
284
308
 
285
- let res = undefined
286
-
287
- if(Tach.hasMiddleware) {
288
-
289
- const middleware = (await import(middlewarePath)).default
290
-
291
- res = await middleware(async () => handler(...params, queryParams, context))
292
-
293
- } else res = await handler(...params, queryParams, context)
309
+ const res = await handler(...params, queryParams, context)
294
310
 
295
311
  await Tach.logRequest(request, 200, context)
296
312
 
@@ -298,15 +314,7 @@ const Tach = {
298
314
 
299
315
  } else if(params.length > 0 && !queryParams && data) {
300
316
 
301
- let res = undefined
302
-
303
- if(Tach.hasMiddleware) {
304
-
305
- const middleware = (await import(middlewarePath)).default
306
-
307
- res = await middleware(async () => handler(...params, data, context))
308
-
309
- } else res = await handler(...params, data, context)
317
+ const res = await handler(...params, data, context)
310
318
 
311
319
  await Tach.logRequest(request, 200, context, await body.text())
312
320
 
@@ -314,15 +322,7 @@ const Tach = {
314
322
 
315
323
  } else if(params.length === 0 && data && queryParams) {
316
324
 
317
- let res = undefined
318
-
319
- if(Tach.hasMiddleware) {
320
-
321
- const middleware = (await import(middlewarePath)).default
322
-
323
- res = await middleware(async () => handler(queryParams, data, context))
324
-
325
- } else res = await handler(queryParams, data, context)
325
+ const res = await handler(queryParams, data, context)
326
326
 
327
327
  await Tach.logRequest(request, 200, context, await body.text())
328
328
 
@@ -330,15 +330,7 @@ const Tach = {
330
330
 
331
331
  } else if(params.length > 0 && data && queryParams) {
332
332
 
333
- let res = undefined
334
-
335
- if(Tach.hasMiddleware) {
336
-
337
- const middleware = (await import(middlewarePath)).default
338
-
339
- res = await middleware(async () => handler(...params, queryParams, data, context))
340
-
341
- } else res = await handler(...params, queryParams, data, context)
333
+ const res = await handler(...params, queryParams, data, context)
342
334
 
343
335
  await Tach.logRequest(request, 200, context, await body.text())
344
336
 
@@ -346,15 +338,7 @@ const Tach = {
346
338
 
347
339
  } else {
348
340
 
349
- let res = undefined
350
-
351
- if(Tach.hasMiddleware) {
352
-
353
- const middleware = (await import(middlewarePath)).default
354
-
355
- res = await middleware(async () => handler(context))
356
-
357
- } else res = await handler(context)
341
+ const res = await handler(context)
358
342
 
359
343
  await Tach.logRequest(request, 200, context)
360
344
 
@@ -415,57 +399,14 @@ const Tach = {
415
399
 
416
400
  async fetch(req: Request, server: Server) {
417
401
 
418
- const request = req.clone()
419
-
420
- const logs: _log[] = []
421
-
422
- const url = new URL(req.url)
423
-
424
- const startTime = Date.now()
402
+ if(Tach.proxyMod) {
425
403
 
426
- const ipAddress = server.requestIP ? server.requestIP(req)!.address : '0.0.0.0'
404
+ const middleware = (Tach.proxyMod as any).default
427
405
 
428
- return await Tach.context.run(logs, async () => {
429
-
430
- let res: Response
431
-
432
- try {
433
-
434
- const data = await Tach.processRequest(req, { ipAddress, request: req, requestTime: startTime, logs, slugs: new Map<string, any>() })
435
-
436
- res = Tach.processResponse(200, data)
437
-
438
- if(logs.length > 0 && Tach.saveLogs && Tach.dbPath) await Promise.all(logs.map(log => {
439
- return Silo.putData(Tach.logsTableName, { ipAddress, path: url.pathname, method: req.method, ...log })
440
- }))
441
-
442
- if(!Tach.isAsyncIterator(data)) {
443
-
444
- const status = res.status
445
- const response_size = typeof data !== "undefined" ? String(data).length : 0
446
- const url = new URL(req.url)
447
- const method = req.method
448
- const date = Date.now()
449
- const duration = date - startTime
450
-
451
- console.info(`"${method} ${url.pathname}" ${status} - ${duration}ms - ${response_size} byte(s)`)
452
-
453
- if(Tach.dbPath && Tach.saveStats) await Silo.putData(Tach.statsTableName, { ipAddress, cpu: process.cpuUsage(), memory: process.memoryUsage(), date: Date.now() })
454
- }
455
-
456
- } catch(e) {
457
-
458
- const method = request.method
459
-
460
- await Tach.logError(e as Error, ipAddress, url, method, logs, startTime)
461
-
462
- if(Tach.dbPath && Tach.saveStats) await Silo.putData(Tach.statsTableName, { ipAddress, cpu: process.cpuUsage(), memory: process.memoryUsage(), date: Date.now() })
406
+ if(middleware) return await middleware(req, Tach.proxy)
407
+ }
463
408
 
464
- res = Response.json({ detail: (e as Error).message }, { status: (e as Error).cause as number ?? 500, headers: Tach.headers })
465
- }
466
-
467
- return res
468
- })
409
+ return await Tach.proxy(req, server)
469
410
  },
470
411
 
471
412
  async validateRoutes(route?: string) {
@@ -480,19 +421,17 @@ const Tach = {
480
421
 
481
422
  const slugs = new Map<string, number>()
482
423
 
424
+ if(pattern.test(paths[0]) || pattern.test(paths[paths.length - 1])) throw new Error(`Invalid route ${route}`)
425
+
483
426
  paths.forEach((path, idx) => {
484
427
 
485
- if(pattern.test(path) && (idx % 2 === 0 || paths[idx].includes('.ts') || paths[idx].includes('.js'))) {
428
+ if(pattern.test(path) && (pattern.test(paths[idx - 1]) || pattern.test(paths[idx + 1]))) {
486
429
  throw new Error(`Invalid route ${route}`)
487
430
  }
488
431
 
489
432
  if(pattern.test(path)) slugs.set(path, idx)
490
433
  })
491
434
 
492
- const idx = paths.findIndex((path) => pattern.test(path))
493
-
494
- if(idx > -1 && (idx % 2 === 0 || paths[idx].includes('.ts') || paths[idx].includes('.js'))) throw new Error(`Invalid route ${route}`)
495
-
496
435
  const staticPath = paths.filter((path) => !pattern.test(path)).join(',')
497
436
 
498
437
  if(staticPaths.includes(staticPath)) throw new Error(`Duplicate route ${route}`)
@@ -520,11 +459,13 @@ const Tach = {
520
459
 
521
460
  if(route) return await validateRoute(route)
522
461
 
523
- const files = Array.from(new Glob(`**/*.{ts,js}`).scanSync({ cwd: Tach.routesPath }))
462
+ const routes = Array.from(new Glob(`**/*/index.{ts,js}`).scanSync({ cwd: Tach.routesPath }))
463
+
464
+ for(const route of routes) await validateRoute(route)
524
465
 
525
- const routes = files.filter((route) => !route.split('/').some((path) => path.startsWith('_')))
466
+ const proxy = Array.from(new Glob(`**/proxy.{ts,js}`).scanSync({ cwd: Tach.routesPath }))
526
467
 
527
- for(const route of routes) await validateRoute(route)
468
+ if(proxy[0]) Tach.proxyMod = await import(`${Tach.routesPath}/${proxy[0]}`)
528
469
  },
529
470
 
530
471
  parseParams(input: string[]) {
File without changes