galbe 0.12.1 → 0.12.2

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 (41) hide show
  1. package/package.json +6 -1
  2. package/.github/ISSUE_TEMPLATE/bug_report.md +0 -35
  3. package/.github/ISSUE_TEMPLATE/feature_request.md +0 -23
  4. package/.github/workflows/build_test.yml +0 -17
  5. package/.github/workflows/deploy_website.yml +0 -20
  6. package/.github/workflows/release.yml +0 -39
  7. package/.prettierrc +0 -10
  8. package/bun.lock +0 -904
  9. package/bunfig.toml +0 -2
  10. package/docs/CONTRIBUTING.md +0 -105
  11. package/docs/cli.md +0 -343
  12. package/docs/configuration.md +0 -80
  13. package/docs/context.md +0 -104
  14. package/docs/error-handler.md +0 -54
  15. package/docs/getting-started.md +0 -164
  16. package/docs/handler.md +0 -119
  17. package/docs/hooks.md +0 -90
  18. package/docs/plugins.md +0 -146
  19. package/docs/router.md +0 -10
  20. package/docs/routes.md +0 -133
  21. package/docs/schemas.md +0 -327
  22. package/test/hooks.test.ts +0 -200
  23. package/test/parser.test.ts +0 -1358
  24. package/test/plugins.test.ts +0 -239
  25. package/test/requests.test.ts +0 -917
  26. package/test/resources/image.png +0 -0
  27. package/test/resources/object.badSyntax.json +0 -8
  28. package/test/resources/object.json +0 -8
  29. package/test/resources/object.missing.json +0 -6
  30. package/test/resources/static/chameleon.png +0 -0
  31. package/test/resources/static/index.html +0 -13
  32. package/test/resources/static/sub/index.html +0 -13
  33. package/test/resources/static/sub/other.html +0 -13
  34. package/test/resources/test.route.comment.ts +0 -58
  35. package/test/resources/test.route.empty.ts +0 -9
  36. package/test/responses.test.ts +0 -483
  37. package/test/routeFiles.test.ts +0 -239
  38. package/test/router.test.ts +0 -231
  39. package/test/test.utils.ts +0 -109
  40. package/test/types.test.ts +0 -909
  41. package/tsconfig.json +0 -23
@@ -1,239 +0,0 @@
1
- import { expect, test, describe } from 'bun:test'
2
- import { defineRoutes, GalbeProxy, metaAnalysis } from '../src/routes'
3
- import { Galbe } from '../src'
4
-
5
- describe('routeFiles', () => {
6
- test('meta analysis, empty', async () => {
7
- let meta = await metaAnalysis('./test/resources/test.route.empty.ts')
8
- expect(meta).toEqual({
9
- header: {},
10
- routes: {
11
- '/one': {
12
- get: {}
13
- },
14
- '/two': {
15
- post: {}
16
- },
17
- '/three': {
18
- put: {}
19
- }
20
- }
21
- })
22
- })
23
-
24
- test('meta analysis, comments', async () => {
25
- let meta = await metaAnalysis('./test/resources/test.route.comment.ts')
26
- expect(meta).toEqual({
27
- header: {
28
- head: 'description\nmultiline',
29
- tag: 'test'
30
- },
31
- routes: {
32
- '/test/:param1': {
33
- get: {
34
- head: 'This part\nhere',
35
- tags: 'tag1, tag2, tag3',
36
- summary: 'short summary',
37
- description: 'longer description example',
38
- deprecated: true,
39
- param: ['{path} param1 description', '{query} param2 description']
40
- }
41
- },
42
- '/test': {
43
- post: {
44
- tags: 'tag1, tag2, tag3',
45
- summary: 'short summary',
46
- description: 'longer description example',
47
- body: 'body descripton'
48
- },
49
- put: {
50
- tags: 'tag1, tag2',
51
- other: 'Hello Mom!'
52
- },
53
- patch: {
54
- head: 'patch method'
55
- },
56
- options: {
57
- head: 'options method'
58
- },
59
- delete: {
60
- head: 'delete method'
61
- },
62
- head: {
63
- head: 'head method'
64
- }
65
- }
66
- }
67
- })
68
- })
69
-
70
- test('define routes, no route', async () => {
71
- const k = new GalbeProxy(new Galbe())
72
- await defineRoutes({}, k)
73
- expect(k.router.routes).toEqual({
74
- routes: {}
75
- })
76
- })
77
-
78
- test('define routes, no route (false)', async () => {
79
- const k = new GalbeProxy(new Galbe({ routes: false }))
80
- await defineRoutes({}, k)
81
- expect(k.router.routes).toEqual({
82
- routes: {}
83
- })
84
- })
85
-
86
- test('define routes, no route found', async () => {
87
- const k = new GalbeProxy(new Galbe())
88
- await defineRoutes({ routes: 'unexisting_route' }, k)
89
- expect(k.router.routes).toEqual({
90
- routes: {}
91
- })
92
- })
93
-
94
- test('define routes, route.empty', async () => {
95
- const k = new GalbeProxy(new Galbe())
96
-
97
- await defineRoutes({ routes: 'test/resources/test.route.empty.ts' }, k)
98
-
99
- const r = k.router.routes
100
- expect(r?.children?.one?.routes.get).toMatchObject({
101
- method: 'get',
102
- path: '/one',
103
- handler: () => {}
104
- })
105
- expect(r?.children?.two?.routes.post).toMatchObject({
106
- method: 'post',
107
- path: '/two',
108
- handler: () => {}
109
- })
110
- expect(r?.children?.three?.routes.put).toMatchObject({
111
- method: 'put',
112
- path: '/three',
113
- handler: () => {}
114
- })
115
- expect(k.meta).toMatchObject([
116
- {
117
- header: {},
118
- routes: {
119
- '/one': {
120
- get: {}
121
- },
122
- '/two': {
123
- post: {}
124
- },
125
- '/three': {
126
- put: {}
127
- }
128
- }
129
- }
130
- ])
131
- expect(k.meta?.[0].file).toMatch(/test\.route\.empty\.ts$/)
132
- })
133
-
134
- test('define routes, all', async () => {
135
- const k = new GalbeProxy(new Galbe())
136
-
137
- await defineRoutes({ routes: ['test/resources/test.route.*.ts'] }, k)
138
-
139
- const r = k.router.routes
140
- expect(r?.children?.one?.routes.get).toMatchObject({
141
- method: 'get',
142
- path: '/one',
143
- handler: () => {}
144
- })
145
- expect(r?.children?.two?.routes.post).toMatchObject({
146
- method: 'post',
147
- path: '/two',
148
- handler: () => {}
149
- })
150
- expect(r?.children?.three?.routes.put).toMatchObject({
151
- method: 'put',
152
- path: '/three',
153
- handler: () => {}
154
- })
155
- expect(r?.children?.test?.param?.routes.get).toMatchObject({
156
- method: 'get',
157
- path: '/test/:param1',
158
- handler: () => {}
159
- })
160
- expect(r?.children?.test?.routes.post).toMatchObject({
161
- method: 'post',
162
- path: '/test',
163
- handler: () => {}
164
- })
165
- expect(r?.children?.test?.routes.put).toMatchObject({
166
- method: 'put',
167
- path: '/test',
168
- handler: () => {}
169
- })
170
- expect(r?.children?.test?.routes.patch).toMatchObject({
171
- method: 'patch',
172
- path: '/test',
173
- handler: () => {}
174
- })
175
- expect(r?.children?.test?.routes.options).toMatchObject({
176
- method: 'options',
177
- path: '/test',
178
- handler: () => {}
179
- })
180
- expect(r?.children?.test?.routes.delete).toMatchObject({
181
- method: 'delete',
182
- path: '/test',
183
- handler: () => {}
184
- })
185
- expect(r?.children?.test?.routes.head).toMatchObject({
186
- method: 'head',
187
- path: '/test',
188
- handler: () => {}
189
- })
190
- expect(k.meta?.sort((a, b) => (a.file < b.file ? 1 : -1))).toMatchObject([
191
- {
192
- header: {},
193
- routes: {
194
- '/one': {
195
- get: {}
196
- },
197
- '/two': {
198
- post: {}
199
- },
200
- '/three': {
201
- put: {}
202
- }
203
- }
204
- },
205
- {
206
- header: {
207
- head: 'description\nmultiline',
208
- tag: 'test'
209
- },
210
- routes: {
211
- '/test/:param1': {
212
- get: {
213
- head: 'This part\nhere',
214
- tags: 'tag1, tag2, tag3',
215
- summary: 'short summary',
216
- description: 'longer description example',
217
- deprecated: true,
218
- param: ['{path} param1 description', '{query} param2 description']
219
- }
220
- },
221
- '/test': {
222
- post: {
223
- tags: 'tag1, tag2, tag3',
224
- summary: 'short summary',
225
- description: 'longer description example',
226
- body: 'body descripton'
227
- },
228
- put: {
229
- tags: 'tag1, tag2',
230
- other: 'Hello Mom!'
231
- }
232
- }
233
- }
234
- }
235
- ])
236
- expect(k.meta?.[0].file).toMatch(/test\.route\..*$/)
237
- expect(k.meta?.[1].file).toMatch(/test\.route\..*$/)
238
- })
239
- })
@@ -1,231 +0,0 @@
1
- import { expect, test, describe } from 'bun:test'
2
- import { Galbe, MethodNotAllowedError, NotFoundError, type RouteNode } from '../src'
3
-
4
- describe('router', () => {
5
- test('empty', async () => {
6
- const galbe = new Galbe()
7
- const router = galbe.router
8
-
9
- expect(router.prefix).toBe('')
10
- expect(router.routes).toEqual({ routes: {} })
11
- })
12
-
13
- test('routes, bad syntax', async () => {
14
- const galbe = new Galbe()
15
-
16
- const invalidPaths = ['.', '/@', '/-ta', '/test/-ta', '/hell@/w0rld', '/last-', '../', './x', '/hello?']
17
-
18
- for (const p of invalidPaths) {
19
- try {
20
- galbe.get(p, () => {})
21
- expect.unreachable()
22
- } catch (err) {
23
- expect(err).toBeInstanceOf(SyntaxError)
24
- }
25
- }
26
- })
27
-
28
- test('chaining routes', async () => {
29
- const galbe = new Galbe()
30
- const router = galbe.router
31
-
32
- expect(router.routes).toEqual({ routes: {} })
33
-
34
- galbe.get('/', () => {})
35
- let r: RouteNode | undefined = router.routes
36
-
37
- expect(r?.routes.get?.method).toBe('get')
38
- expect(r?.routes.get?.path).toBe('/')
39
- expect(r?.param).toBeUndefined()
40
- expect(r?.children).toBeUndefined()
41
-
42
- const mockHandler = () => {}
43
- galbe.get('/test', mockHandler)
44
- r = r?.children?.test
45
-
46
- expect(r?.routes.get?.method).toBe('get')
47
- expect(r?.routes.get?.path).toBe('/test')
48
- expect(r?.param).toBeUndefined()
49
- expect(r?.children).toBeUndefined()
50
- expect(r?.routes.get?.handler).toBe(mockHandler)
51
-
52
- const mockHandler2 = () => {}
53
- galbe.get('/test/:foo', mockHandler2)
54
- r = r?.param
55
-
56
- expect(r?.routes.get?.method).toBe('get')
57
- expect(r?.routes.get?.path).toBe('/test/:foo')
58
- expect(r?.param).toBeUndefined()
59
- expect(r?.children).toBeUndefined()
60
- expect(r?.routes.get?.handler).toBe(mockHandler2)
61
-
62
- const mockHandler3 = () => {}
63
- galbe.get('/test/:foo/bar', mockHandler3)
64
-
65
- expect(r?.children).toHaveProperty('bar')
66
- expect(r?.param).toBeUndefined()
67
- expect(r?.children?.bar?.routes.get?.handler).toBe(mockHandler3)
68
- })
69
-
70
- test('redefining root', async () => {
71
- const galbe = new Galbe()
72
- const router = galbe.router
73
-
74
- const [h1, h2, h3] = [() => {}, () => {}, () => {}]
75
-
76
- galbe.get('/', h1)
77
- galbe.get('/test', h2)
78
- galbe.get('/', h3)
79
-
80
- let r: RouteNode | undefined = router.routes
81
- expect(router.prefix).toBe('')
82
- expect(r?.routes.get?.method).toBe('get')
83
- expect(r?.routes.get?.path).toBe('/')
84
- expect(r?.routes.get?.handler).toBe(h3)
85
- expect(r?.children?.test?.routes.get?.method).toBe('get')
86
- expect(r?.children?.test?.routes.get?.handler).toBe(h2)
87
-
88
- const [h4, h5] = [() => {}, () => {}]
89
-
90
- galbe.get('/foo/bar', h4)
91
- galbe.get('/foo', h5)
92
-
93
- r = router?.routes?.children?.foo
94
- expect(r?.routes.get?.method).toBe('get')
95
- expect(r?.routes.get?.path).toBe('/foo')
96
- expect(r?.routes.get?.handler).toBe(h5)
97
- expect(r?.children?.bar?.routes.get?.method).toBe('get')
98
- expect(r?.children?.bar?.routes.get?.path).toBe('/foo/bar')
99
- expect(r?.children?.bar?.routes.get?.handler).toBe(h4)
100
- })
101
-
102
- test('find route', async () => {
103
- const galbe = new Galbe()
104
- const router = galbe.router
105
-
106
- const [h1, h3, h4] = [() => {}, () => {}, () => {}, () => {}]
107
-
108
- galbe.get('/', h1)
109
- galbe.get('/test/foo', h3)
110
- galbe.get('/test/foo/bar', h4)
111
-
112
- let r1 = router.find('get', '/')
113
- expect(r1.path).toBe('/')
114
- expect(r1.handler).toBe(h1)
115
- let r2 = router.find('get', '/test/foo')
116
- expect(r2.path).toBe('/test/foo')
117
- expect(r2.handler).toBe(h3)
118
- let r3 = router.find('get', '/test/foo/bar')
119
- expect(r3.path).toBe('/test/foo/bar')
120
- expect(r3.handler).toBe(h4)
121
-
122
- try {
123
- router.find('get', '/test')
124
- expect.unreachable()
125
- } catch (err: any) {
126
- expect(err).toBeInstanceOf(NotFoundError)
127
- expect(err.status).toBe(404)
128
- expect(err.payload).toBe('Not Found')
129
- }
130
-
131
- try {
132
- router.find('get', '/test/bar/bar')
133
- expect.unreachable()
134
- } catch (err: any) {
135
- expect(err).toBeInstanceOf(NotFoundError)
136
- expect(err.status).toBe(404)
137
- expect(err.payload).toBe('Not Found')
138
- }
139
- })
140
-
141
- test('find route param', async () => {
142
- const galbe = new Galbe()
143
- const router = galbe.router
144
-
145
- const [h1, h2] = [() => {}, () => {}]
146
-
147
- galbe.get('/test/:foo', h1)
148
- galbe.get('/test/test', h2)
149
-
150
- let r1 = router.find('get', '/test/42')
151
- expect(r1.path).toBe('/test/:foo')
152
- expect(r1.handler).toBe(h1)
153
-
154
- let r2 = router.find('get', '/test/test')
155
- expect(r2.path).toBe('/test/test')
156
- expect(r2.handler).toBe(h2)
157
- })
158
-
159
- test('wildcard routes', async () => {
160
- const galbe = new Galbe()
161
- const router = galbe.router
162
-
163
- const [h1, h2, h3, h4] = [() => {}, () => {}, () => {}, () => {}]
164
-
165
- galbe.get('/test/foo/*', h1)
166
- galbe.get('/test/foo/bar', h2)
167
- galbe.get('/test/foo/:p/bar', h3)
168
-
169
- galbe.get('/test/foo/*/lol', h4)
170
-
171
- let r1 = router.find('get', '/test/foo/bar/42')
172
- expect(r1.path).toBe('/test/foo/*')
173
- expect(r1.handler).toBe(h1)
174
-
175
- let r3 = router.find('get', '/test/foo/bar/bar')
176
- expect(r3.path).toBe('/test/foo/:p/bar')
177
- expect(r3.handler).toBe(h3)
178
-
179
- let r4 = router.find('get', '/test/foo/foo')
180
- expect(r4.path).toBe('/test/foo/*')
181
- expect(r4.handler).toBe(h1)
182
-
183
- let r5 = router.find('get', '/test/foo/toto/lol')
184
- expect(r5.path).toBe('/test/foo/*/lol')
185
- expect(r5.handler).toBe(h4)
186
- })
187
-
188
- test('method not allowed', async () => {
189
- const galbe = new Galbe()
190
- const router = galbe.router
191
-
192
- const [h1, h2] = [() => {}, () => {}]
193
-
194
- galbe.put('/test/foo', h1)
195
- galbe.post('/test/bar/*', h2)
196
-
197
- let r1 = router.find('put', '/test/foo')
198
- expect(r1.path).toBe('/test/foo')
199
- expect(r1.handler).toBe(h1)
200
- try {
201
- router.find('get', '/test/foo')
202
- expect.unreachable()
203
- } catch (err) {
204
- expect(err).toBeInstanceOf(MethodNotAllowedError)
205
- }
206
-
207
- let r2 = router.find('post', '/test/bar/tender')
208
- expect(r2.path).toBe('/test/bar/*')
209
- expect(r2.handler).toBe(h2)
210
- try {
211
- router.find('delete', '/test/bar/toto/titi')
212
- expect.unreachable()
213
- } catch (err) {
214
- expect(err).toBeInstanceOf(MethodNotAllowedError)
215
- }
216
- })
217
-
218
- test('trailing slash routes', async () => {
219
- const galbe = new Galbe()
220
- const router = galbe.router
221
-
222
- const handler = () => {}
223
- galbe.get('/test/', handler)
224
-
225
- expect(router.routes.children?.test?.routes.get?.handler).toBe(handler)
226
-
227
- const r = router.find('get', '/test')
228
- expect(r.path).toBe('/test/')
229
- expect(r.handler).toBe(handler)
230
- })
231
- })
@@ -1,109 +0,0 @@
1
- import { $T, type Context } from '../src'
2
-
3
- export type Case = {
4
- body?: any
5
- type?: string
6
- schema: string
7
- expected: { status: number; type?: string; resp?: any }
8
- }
9
-
10
- export const formdata = (data: Record<string, string | string[] | Blob>): FormData => {
11
- const form = new FormData()
12
- for (const [k, v] of Object.entries(data)) {
13
- if (Array.isArray(v)) for (const v2 of v) form.append(k, v2)
14
- else form.append(k, v)
15
- }
16
- return form
17
- }
18
- export const fileHash = async (ba: any) =>
19
- Array.from(new Uint8Array(await crypto.subtle.digest('SHA-256', ba)))
20
- .map(byte => byte.toString(16).padStart(2, '0'))
21
- .join('')
22
- export const decoder = new TextDecoder()
23
-
24
- export const schema_objectBase = {
25
- ba: $T.byteArray(),
26
- string: $T.string(),
27
- number: $T.number(),
28
- bool: $T.boolean(),
29
- any: $T.any(),
30
- optional: $T.optional($T.any()),
31
- }
32
- export const schema_object = {
33
- ...schema_objectBase,
34
- object: $T.object($T.any()),
35
- array: $T.array(),
36
- null: $T.null(),
37
- nullable: $T.nullable($T.string()),
38
- nullish: $T.nullish($T.number()),
39
- }
40
-
41
- export const isAsyncIterator = (obj: any) => {
42
- if (Object(obj) !== obj) return false
43
- const method = obj[Symbol.asyncIterator]
44
- if (typeof method != 'function') return false
45
- const aIter = method.call(obj)
46
- return aIter === obj
47
- }
48
-
49
- const parseAsyncIterator = async (body: any): Promise<any> => {
50
- const chunks = []
51
- let type
52
- for await (const chunk of body) {
53
- if (chunk instanceof Uint8Array) type = 'byteArray'
54
- else if (typeof chunk === 'string') type = 'string'
55
- // @ts-ignore
56
- chunks.push(chunk)
57
- }
58
- // @ts-ignore
59
- if (type === 'byteArray') return new Uint8Array(chunks.map(c => Array.from(c)).flat())
60
- if (type === 'string') return chunks.join('')
61
- return chunks
62
- }
63
- const parseBody = async (body: any): Promise<any> =>
64
- typeof body === 'object' && body !== null && !Array.isArray(body)
65
- ? Object.fromEntries(
66
- await Promise.all(
67
- Object.entries(body).map(async ([k, v]) => {
68
- if (v instanceof Uint8Array) return [k, await fileHash(v)]
69
- else return [k, await parseBody(v)]
70
- })
71
- )
72
- )
73
- : body
74
- export const handleBody = async (ctx: any) => {
75
- if (ctx.body === undefined) {
76
- return { type: 'undefined' }
77
- }
78
- if (ctx?.body instanceof ReadableStream) {
79
- let content = ''
80
- for await (const chunk of ctx.body) {
81
- if (typeof chunk === 'string') content += chunk
82
- else content += decoder.decode(chunk)
83
- }
84
- return { type: 'ReadableStream', content }
85
- }
86
- if (Array.isArray(ctx.body)) {
87
- return { type: 'array', content: ctx.body }
88
- }
89
- if (ctx?.body instanceof Uint8Array) {
90
- return { type: 'byteArray', content: decoder.decode(ctx.body) }
91
- }
92
- if (isAsyncIterator(ctx.body)) {
93
- return { type: 'AsyncIterator', content: await parseAsyncIterator(ctx.body) }
94
- } else {
95
- return { type: typeof ctx.body, content: await parseBody(ctx.body) }
96
- }
97
- }
98
- export const handleUrlFormStream = async (ctx: Context) => {
99
- let resp: Record<string, any> = {}
100
- // @ts-ignore
101
- for await (const [k, v] of ctx.body) {
102
- if (k in resp) {
103
- resp[k] = Array.isArray(resp[k]) ? [...resp[k], v] : [resp[k], v]
104
- } else resp[k] = v
105
- }
106
- return { type: 'object', content: resp }
107
- }
108
-
109
- export const EMPTY_BA_STR = 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855'