galbe 0.12.0 → 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 (42) hide show
  1. package/bin/commands/generate/code/openapi.parser.ts +2 -2
  2. package/package.json +6 -1
  3. package/.github/ISSUE_TEMPLATE/bug_report.md +0 -35
  4. package/.github/ISSUE_TEMPLATE/feature_request.md +0 -23
  5. package/.github/workflows/build_test.yml +0 -17
  6. package/.github/workflows/deploy_website.yml +0 -20
  7. package/.github/workflows/release.yml +0 -39
  8. package/.prettierrc +0 -10
  9. package/bun.lock +0 -904
  10. package/bunfig.toml +0 -2
  11. package/docs/CONTRIBUTING.md +0 -105
  12. package/docs/cli.md +0 -343
  13. package/docs/configuration.md +0 -80
  14. package/docs/context.md +0 -104
  15. package/docs/error-handler.md +0 -54
  16. package/docs/getting-started.md +0 -164
  17. package/docs/handler.md +0 -119
  18. package/docs/hooks.md +0 -90
  19. package/docs/plugins.md +0 -146
  20. package/docs/router.md +0 -10
  21. package/docs/routes.md +0 -133
  22. package/docs/schemas.md +0 -327
  23. package/test/hooks.test.ts +0 -200
  24. package/test/parser.test.ts +0 -1358
  25. package/test/plugins.test.ts +0 -239
  26. package/test/requests.test.ts +0 -917
  27. package/test/resources/image.png +0 -0
  28. package/test/resources/object.badSyntax.json +0 -8
  29. package/test/resources/object.json +0 -8
  30. package/test/resources/object.missing.json +0 -6
  31. package/test/resources/static/chameleon.png +0 -0
  32. package/test/resources/static/index.html +0 -13
  33. package/test/resources/static/sub/index.html +0 -13
  34. package/test/resources/static/sub/other.html +0 -13
  35. package/test/resources/test.route.comment.ts +0 -58
  36. package/test/resources/test.route.empty.ts +0 -9
  37. package/test/responses.test.ts +0 -483
  38. package/test/routeFiles.test.ts +0 -239
  39. package/test/router.test.ts +0 -231
  40. package/test/test.utils.ts +0 -109
  41. package/test/types.test.ts +0 -909
  42. 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'