@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,31 @@
1
+ export function Logger() {
2
+
3
+ const formatDate = () => new Date().toISOString().replace('T', ' ').replace('Z', '')
4
+
5
+ const reset = '\x1b[0m'
6
+
7
+ console.info = (...args: any[]) => {
8
+ const info = `[${formatDate()}]\x1b[32m INFO${reset} (${process.pid})`
9
+ console.log(info, ...args)
10
+ }
11
+
12
+ console.error = (...args: any[]) => {
13
+ const err = `[${formatDate()}]\x1b[31m ERROR${reset} (${process.pid})`
14
+ console.log(err, ...args)
15
+ }
16
+
17
+ console.debug = (...args: any[]) => {
18
+ const bug = `[${formatDate()}]\x1b[36m DEBUG${reset} (${process.pid})`
19
+ console.log(bug, ...args)
20
+ }
21
+
22
+ console.warn = (...args: any[]) => {
23
+ const warn = `[${formatDate()}]\x1b[33m WARN${reset} (${process.pid})`
24
+ console.log(warn, ...args)
25
+ }
26
+
27
+ console.trace = (...args: any[]) => {
28
+ const trace = `[${formatDate()}]\x1b[35m TRACE${reset} (${process.pid})`
29
+ console.log(trace, ...args)
30
+ }
31
+ }
@@ -0,0 +1,234 @@
1
+ import { Subprocess, ServerWebSocket, BunFile, BunRequest, Server } from "bun";
2
+ import Router, { _ctx } from "../router.js";
3
+ import { rm } from "fs/promises";
4
+
5
+ export default class Tach {
6
+
7
+ static webSockets = new Map<ServerWebSocket<any>, Subprocess<"pipe", "inherit", "pipe">>()
8
+
9
+ static headers = {
10
+ "Access-Control-Allow-Headers": process.env.ALLOW_HEADERS || "",
11
+ "Access-Control-Allow-Origin": process.env.ALLLOW_ORGINS || "",
12
+ "Access-Control-Allow-Credential": process.env.ALLOW_CREDENTIALS || "false",
13
+ "Access-Control-Expose-Headers": process.env.ALLOW_EXPOSE_HEADERS || "",
14
+ "Access-Control-Max-Age": process.env.ALLOW_MAX_AGE || "",
15
+ "Access-Control-Allow-Methods": process.env.ALLOW_METHODS || ""
16
+ }
17
+
18
+ static parseRequest(request: Request) {
19
+
20
+ const req: Record<string, any> = {}
21
+
22
+ req.headers = Object.fromEntries(request.headers)
23
+ req.cache = request.cache
24
+ req.credentials = request.credentials
25
+ req.destination = request.destination
26
+ req.integrity = request.integrity
27
+ req.keepalive = request.keepalive
28
+ req.method = request.method
29
+ req.mode = request.mode
30
+ req.redirect = request.redirect
31
+ req.referrer = request.referrer
32
+ req.referrerPolicy = request.referrerPolicy
33
+ req.url = request.url
34
+
35
+ return req
36
+ }
37
+
38
+ private static getParams(request: BunRequest, route: string) {
39
+
40
+ const url = new URL(request.url)
41
+
42
+ const params = url.pathname.slice(route.length).split('/')
43
+
44
+ return { params: Tach.parseParams(params) }
45
+ }
46
+
47
+ private static parseParams(input: string[]) {
48
+
49
+ const params: (string | boolean | number | null | undefined)[] = []
50
+
51
+ for(const param of input) {
52
+
53
+ const num = Number(param)
54
+
55
+ if(!Number.isNaN(num)) params.push(num)
56
+
57
+ else if(param === 'true') params.push(true)
58
+
59
+ else if(param === 'false') params.push(false)
60
+
61
+ else if(param === 'null') params.push(null)
62
+
63
+ else if(param === 'undefined') params.push(undefined)
64
+
65
+ else params.push(param)
66
+ }
67
+
68
+ return params
69
+ }
70
+
71
+ private static parseKVParams(input: URLSearchParams | FormData) {
72
+
73
+ const params: Record<string, any> = {}
74
+
75
+ for(const [key, val] of input) {
76
+
77
+ if(typeof val === "string") {
78
+
79
+ try {
80
+
81
+ params[key] = JSON.parse(val)
82
+
83
+ } catch {
84
+
85
+ const num = Number(val)
86
+
87
+ if(!Number.isNaN(num)) params[key] = num
88
+
89
+ else if(val === 'true') params[key] = true
90
+
91
+ else if(val === 'false') params[key] = false
92
+
93
+ else if(typeof val === "string" && val.includes(',')) params[key] = Tach.parseParams(val.split(','))
94
+
95
+ else if(val === 'null') params[key] = null
96
+
97
+ if(params[key] === undefined) params[key] = val
98
+ }
99
+
100
+ } else params[key] = val
101
+ }
102
+
103
+ return params
104
+ }
105
+
106
+ static processRequest(request: BunRequest, route: string, ctx: _ctx) {
107
+
108
+ const { params } = Tach.getParams(request, route)
109
+
110
+ ctx.slugs = request.params
111
+
112
+ const searchParams = new URL(request.url).searchParams
113
+
114
+ let queryParams: Record<string, any> | undefined;
115
+
116
+ if(searchParams.size > 0) queryParams = Tach.parseKVParams(searchParams)
117
+
118
+ ctx.params = params
119
+ ctx.query = queryParams
120
+
121
+ return { ctx }
122
+ }
123
+
124
+ private static async processResponse(cmd: string[], input: _ctx) {
125
+
126
+ const proc = Bun.spawn({
127
+ cmd,
128
+ stdout: 'inherit',
129
+ stderr: "pipe",
130
+ stdin: "pipe"
131
+ })
132
+
133
+ proc.stdin.write(JSON.stringify(input))
134
+ proc.stdin.end()
135
+
136
+ let exitCode = await proc.exited
137
+
138
+ if(exitCode !== 0 && proc.stderr.length > 0) {
139
+ exitCode = exitCode < 0 || (exitCode > 0 && exitCode < 100) || exitCode > 599 ? 500 : exitCode
140
+ return { status: exitCode, body: proc.stderr.toString() }
141
+ }
142
+
143
+ exitCode = exitCode === 0 ? 200 : exitCode
144
+
145
+ return { status: exitCode, body: Bun.file(`/tmp/${proc.pid}`) }
146
+ }
147
+
148
+ private static async serveRequest(handler: string, ctx: _ctx) {
149
+
150
+ const res = await Tach.processResponse([handler], ctx) as { status: number, body?: BunFile }
151
+
152
+ const size = res.body ? res.body.size : 0
153
+
154
+ const response = new Response(res.body, { status: res.status, headers: Tach.headers })
155
+
156
+ return { response, size }
157
+ }
158
+
159
+ static createServerRoutes() {
160
+
161
+ for(const [route, methods] of Router.allRoutes) {
162
+
163
+ const serverRoute = async (request?: BunRequest, server?: Server) => {
164
+
165
+ const start = Date.now()
166
+
167
+ let res: Response
168
+ let bodySize: number
169
+ const path = new URL(request!.url).pathname
170
+ const body = `/tmp/${Bun.randomUUIDv7()}`
171
+
172
+ try {
173
+
174
+ const accept = request!.headers.get('accept') || ''
175
+
176
+ if(accept.includes('text/html')) {
177
+ return new Response(await Bun.file(`${import.meta.dir}/../client/dev.html`).text(), { status: 200, headers: { 'Content-Type': 'text/html' } })
178
+ }
179
+
180
+ request!.blob().then(async blob => {
181
+ if(blob.size > 0) await Bun.write(body, blob)
182
+ })
183
+
184
+ const { handler, ctx } = Router.processRequest(request!, route, {
185
+ request: Router.parseRequest(request!),
186
+ ipAddress: server?.requestIP(request!) ? server.requestIP(request!)!.address : '0.0.0.0',
187
+ body
188
+ })
189
+
190
+ const { response, size } = await Tach.serveRequest(handler, ctx!)
191
+
192
+ res = response
193
+ bodySize = size
194
+
195
+ } catch(err) {
196
+
197
+ const error = err as Error
198
+
199
+ bodySize = error.message.length
200
+
201
+ res = Response.json({ error: error.message }, { status: error.cause as number, headers: Tach.headers })
202
+
203
+ } finally {
204
+
205
+ Bun.file(body).exists().then(async exists => {
206
+ if(exists) await rm(body, { recursive: true })
207
+ })
208
+ }
209
+
210
+ const status = res.status
211
+ const method = request!.method
212
+ const duration = Date.now() - start
213
+
214
+ console.info(`${path} - ${method} - ${status} - ${duration}ms - ${bodySize} byte(s)`)
215
+
216
+ return res
217
+ }
218
+
219
+ for(const method of methods) {
220
+
221
+ if(method !== 'HTML' && method !== 'SOCKET') {
222
+
223
+ if(!Router.reqRoutes[route]) {
224
+ Router.reqRoutes[route] = {}
225
+ Router.reqRoutes[`${route}/*`] = {}
226
+ }
227
+
228
+ Router.reqRoutes[route][method] = serverRoute
229
+ Router.reqRoutes[`${route}/*`][method] = serverRoute
230
+ }
231
+ }
232
+ }
233
+ }
234
+ }
@@ -0,0 +1,110 @@
1
+ import { test, beforeAll, expect, describe } from 'bun:test'
2
+
3
+ beforeAll(async () => {
4
+ new Worker('./tests/worker.ts').postMessage(`./src/serve.ts`)
5
+ await Bun.sleep(1000)
6
+ })
7
+
8
+ describe('/', () => {
9
+
10
+ test('GET', async () => {
11
+
12
+ const res = await fetch('http://localhost:8080')
13
+
14
+ console.log(await res.json())
15
+
16
+ expect(res.status).toEqual(200)
17
+ })
18
+
19
+ test('POST', async () => {
20
+
21
+ const res = await fetch('http://localhost:8080', {
22
+ method: 'POST'
23
+ })
24
+
25
+ console.log(await res.json())
26
+
27
+ expect(res.status).toEqual(200)
28
+ })
29
+
30
+ test('DELETE', async () => {
31
+
32
+ const res = await fetch('http://localhost:8080', {
33
+ method: 'DELETE'
34
+ })
35
+
36
+ console.log(await res.json())
37
+
38
+ expect(res.status).toEqual(200)
39
+ })
40
+ })
41
+
42
+
43
+ describe('/api', () => {
44
+
45
+ test('GET', async () => {
46
+
47
+ const res = await fetch('http://localhost:8080/api')
48
+
49
+ console.log(await res.json())
50
+
51
+ expect(res.status).toEqual(200)
52
+ })
53
+
54
+ test('POST', async () => {
55
+
56
+ const res = await fetch('http://localhost:8080/api', {
57
+ method: 'POST'
58
+ })
59
+
60
+ console.log(await res.json())
61
+
62
+ expect(res.status).toEqual(200)
63
+ })
64
+
65
+ test('PUT', async () => {
66
+
67
+ const res = await fetch('http://localhost:8080/api', {
68
+ method: 'PUT'
69
+ })
70
+
71
+ console.log(await res.json())
72
+
73
+ expect(res.status).toEqual(200)
74
+ })
75
+ })
76
+
77
+
78
+ describe('/api/v2', () => {
79
+
80
+ test('GET', async () => {
81
+
82
+ const res = await fetch('http://localhost:8080/api/v2')
83
+
84
+ console.log(await res.json())
85
+
86
+ expect(res.status).toEqual(200)
87
+ })
88
+
89
+ test('DELETE', async () => {
90
+
91
+ const res = await fetch('http://localhost:8080/api/v2', {
92
+ method: 'DELETE'
93
+ })
94
+
95
+ console.log(await res.json())
96
+
97
+ expect(res.status).toEqual(200)
98
+ })
99
+
100
+ test('PATCH', async () => {
101
+
102
+ const res = await fetch('http://localhost:8080/api/v2/users', {
103
+ method: 'PATCH'
104
+ })
105
+
106
+ console.log(await res.json())
107
+
108
+ expect(res.status).toEqual(200)
109
+ })
110
+ })
@@ -0,0 +1,24 @@
1
+ new Worker('./tests/worker.ts').postMessage(`./src/serve.ts`)
2
+
3
+ await Bun.sleep(1000)
4
+
5
+ const res = await fetch(`http://localhost:8080/`, {
6
+ headers: {
7
+ 'Accept': 'text/html'
8
+ }
9
+ })
10
+
11
+ for await (const chunk of res.body!) {
12
+
13
+ console.log(new TextDecoder().decode(chunk))
14
+ }
15
+
16
+ // const ws = new WebSocket(`ws://localhost:8080`)
17
+
18
+ // ws.onopen = (ev) => {
19
+ // ws.send("Ping")
20
+ // }
21
+
22
+ // ws.onmessage = (ev) => {
23
+ // console.log(JSON.parse(ev.data))
24
+ // }
@@ -0,0 +1,7 @@
1
+ import { $ } from 'bun'
2
+
3
+ declare var self: Worker;
4
+
5
+ self.onmessage = async (event: MessageEvent) => {
6
+ await $`${event.data}`
7
+ };
package/tsconfig.json CHANGED
@@ -11,7 +11,7 @@
11
11
  "noFallthroughCasesInSwitch": true,
12
12
  "noImplicitReturns": true,
13
13
  "forceConsistentCasingInFileNames": true,
14
- "types": ["bun-types", "node", "@vyckr/byos"],
14
+ "types": ["@types/bun", "@types/node", "@types/deno"],
15
15
  "isolatedModules": true
16
16
  }
17
17
  }
package/Dockerfile DELETED
@@ -1,47 +0,0 @@
1
- FROM oven/bun:latest AS build
2
-
3
- RUN apt-get update && apt-get install -y unzip git
4
-
5
- WORKDIR /app
6
-
7
- RUN git clone https://github.com/oven-sh/bun.git
8
-
9
- WORKDIR /app/bun/packages/bun-lambda
10
-
11
- RUN bun install
12
-
13
- RUN bun run build-layer
14
-
15
- RUN unzip bun-lambda-layer.zip -d /tmp
16
-
17
- WORKDIR /tmp
18
-
19
- COPY package.json .
20
-
21
- RUN bun install
22
-
23
- FROM public.ecr.aws/lambda/provided:al2
24
-
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
28
-
29
- COPY --from=build /tmp/bootstrap ${LAMBDA_RUNTIME_DIR}
30
-
31
- COPY --from=build /tmp/bun /opt
32
-
33
- COPY ./src/Tach.ts ${LAMBDA_TASK_ROOT}
34
-
35
- COPY ./tsconfig.json ${LAMBDA_TASK_ROOT}
36
-
37
- COPY ./src/runtime.ts /opt
38
-
39
- RUN chmod 777 /opt/bun
40
-
41
- RUN chmod 777 /opt/runtime.ts
42
-
43
- RUN chmod 777 ${LAMBDA_TASK_ROOT}/Tach.ts
44
-
45
- RUN chmod 777 ${LAMBDA_RUNTIME_DIR}/bootstrap
46
-
47
- CMD ["Tach.fetch"]
package/bun.lockb DELETED
Binary file
@@ -1,28 +0,0 @@
1
- import { VALIDATE } from "../../../utils/validation.js";
2
-
3
- export default class Doc {
4
-
5
- @VALIDATE([{ type: "object" }])
6
- static async GET({ request }: _HTTPContext) {
7
-
8
- console.log(request.url)
9
- }
10
-
11
- @VALIDATE([{ type: "object" }, { type: "object" }])
12
- static async POST({ request }: _HTTPContext) {
13
-
14
- console.log(request.url)
15
- }
16
-
17
- @VALIDATE([{ type: "object" }, { type: "object" }])
18
- static async PATCH({ request }: _HTTPContext) {
19
-
20
- console.log(request.url)
21
- }
22
-
23
- @VALIDATE([{ type: "string" }, { type: "object" }])
24
- static async DELETE({ request }: _HTTPContext) {
25
-
26
- console.log(request.url)
27
- }
28
- }
@@ -1,28 +0,0 @@
1
- import { VALIDATE } from "../../../utils/validation.js";
2
-
3
- export default class Docs {
4
-
5
- @VALIDATE([{ type: "object" }])
6
- static async GET({ request }: _HTTPContext) {
7
-
8
- console.log(request.url)
9
- }
10
-
11
- @VALIDATE([{ type: "object" }])
12
- static async POST({ request }: _HTTPContext) {
13
-
14
- console.log(request.url)
15
- }
16
-
17
- @VALIDATE([{ type: "object" }])
18
- static async PATCH({ request }: _HTTPContext) {
19
-
20
- console.log(request.url)
21
- }
22
-
23
- @VALIDATE([{ type: "object" }])
24
- static async DELETE({ request }: _HTTPContext) {
25
-
26
- console.log(request.url)
27
- }
28
- }
@@ -1,10 +0,0 @@
1
- import { VALIDATE } from "../../../../../utils/validation.js";
2
-
3
- export default class Docs {
4
-
5
- @VALIDATE([{ type: "object" }])
6
- static async GET({ slugs }: _HTTPContext) {
7
-
8
- console.info(slugs)
9
- }
10
- }
@@ -1,17 +0,0 @@
1
- export default class Schema {
2
-
3
- static async POST({ request }: _HTTPContext) {
4
-
5
- console.log(request.url)
6
- }
7
-
8
- static async PATCH({ request }: _HTTPContext) {
9
-
10
- console.log(request.url)
11
- }
12
-
13
- static DELETE({ request }: _HTTPContext) {
14
-
15
- console.log(request.url)
16
- }
17
- }
@@ -1,28 +0,0 @@
1
- import { VALIDATE } from "../../../../utils/validation.js";
2
-
3
- export default class {
4
-
5
- @VALIDATE([{ type: "object" }])
6
- static GET({ request }: _HTTPContext) {
7
-
8
- return {
9
-
10
- async *[Symbol.asyncIterator]() {
11
-
12
- yield request.url
13
- }
14
- }
15
- }
16
-
17
- @VALIDATE([{ type: "object" }])
18
- static DELETE({ request }: _HTTPContext) {
19
-
20
- return {
21
-
22
- async *[Symbol.asyncIterator]() {
23
-
24
- yield request.url
25
- }
26
- }
27
- }
28
- }
@@ -1,28 +0,0 @@
1
- import { VALIDATE } from "../../../../utils/validation.js";
2
-
3
- export default class {
4
-
5
- @VALIDATE([{ type: "object" }])
6
- static GET({ request }: _HTTPContext) {
7
-
8
- return {
9
-
10
- async *[Symbol.asyncIterator]() {
11
-
12
- yield request.url
13
- }
14
- }
15
- }
16
-
17
- @VALIDATE([{ type: "object", default: {} }, { type: "object" }])
18
- static DELETE({ request }: _HTTPContext) {
19
-
20
- return {
21
-
22
- async *[Symbol.asyncIterator]() {
23
-
24
- yield request.url
25
- }
26
- }
27
- }
28
- }
package/routes/proxy.ts DELETED
@@ -1,8 +0,0 @@
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
- }
@@ -1,36 +0,0 @@
1
- export const VALIDATE = (input: any[]) => (cls: Object, funcName: string, propDesc: PropertyDescriptor) => {
2
-
3
- const originalFunc: Function = propDesc.value
4
-
5
- propDesc.value = async function(...args: any[]) {
6
-
7
- const params = [...input]
8
-
9
- if(params.length !== args.length) {
10
-
11
- do {
12
-
13
- const idx = params.findLastIndex(x => x.default !== undefined)
14
-
15
- if(idx === -1) break
16
-
17
- const [ param ] = params.splice(idx, 1)
18
-
19
- args.unshift(param.default)
20
-
21
- } while(true)
22
-
23
- if(input.length !== args.length) throw new Error(`Invalid number of arguments for ${funcName}`)
24
- }
25
-
26
- for(let i = 0; i < input.length; i++) {
27
-
28
- if(typeof args[i] !== input[i].type) {
29
-
30
- throw new Error(`Invalid argument type for ${funcName}`)
31
- }
32
- }
33
-
34
- return originalFunc.apply(this, args)
35
- }
36
- }