galbe 0.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.
@@ -0,0 +1,19 @@
1
+ name: Build & Test
2
+ on:
3
+ push:
4
+ pull_request:
5
+ jobs:
6
+ build:
7
+ name: Build & Test
8
+ runs-on: ubuntu-latest
9
+ steps:
10
+ - uses: actions/checkout@v4
11
+ - uses: oven-sh/setup-bun@v1
12
+ with:
13
+ bun-version: latest
14
+ - name: Install
15
+ run: bun install
16
+ - name: Build
17
+ run: bun run build
18
+ - name: Test
19
+ run: bun test
@@ -0,0 +1,20 @@
1
+ name: Redeploy website
2
+ on:
3
+ push:
4
+ branches: [main]
5
+ paths:
6
+ - 'docs/**'
7
+ jobs:
8
+ redeploy_website:
9
+ name: Update website with new doc
10
+ runs-on: ubuntu-latest
11
+ steps:
12
+ - name: Trigger website deployment
13
+ run: |
14
+ curl -L \
15
+ -X POST \
16
+ -H "Accept: application/vnd.github+json" \
17
+ -H "Authorization: Bearer ${{ secrets.GH_WEBSITE_TOKEN }}" \
18
+ -H "X-GitHub-Api-Version: 2022-11-28" \
19
+ https://api.github.com/repos/pierre-cm/galbe-website/actions/workflows/ci.yml/dispatches \
20
+ -d '{"ref":"main"}'
package/.prettierrc ADDED
@@ -0,0 +1,10 @@
1
+ tabWidth: 2
2
+ useTabs: false
3
+ trailingComma: none
4
+ semi: false
5
+ singleQuote: true
6
+ bracketSpacing: true
7
+ arrowParens: avoid
8
+ printWidth: 120
9
+ jsxSingleQuote: true
10
+ endOfLine: lf
package/README.md ADDED
@@ -0,0 +1,39 @@
1
+ # Todos
2
+
3
+ - [x] Params and QueryParams as {} instead of Type.Object({})
4
+ - [x] Body validator (/ parser?)
5
+ - [x] Hooks
6
+ - [x] Export limited version of Typebox
7
+ - [x] Headers
8
+ - [x] Details
9
+ - [x] Build
10
+
11
+ - Own Framework
12
+
13
+ - [x] Types
14
+ - [x] errorHandler
15
+ - [x] plugin use()
16
+ - [ ] jsdoc
17
+ - [x] on the fly body request validation (REST handle parsing of Multipart arrays + Multipart static )
18
+
19
+ - [ ] Unit tests
20
+
21
+ - Todo api example
22
+
23
+ - [x] Create
24
+ - [x] GetList
25
+ - [x] Get
26
+ - [x] Update
27
+ - [x] Delete
28
+ - [ ] Bulk create from file
29
+ - [ ] Dl as file
30
+
31
+ - Hooks
32
+
33
+ - [ ] Headers authentication pos/update/delete
34
+
35
+ - [ ] Documentation
36
+
37
+ - [ ] create-kadre
38
+
39
+ - [ ] Website
package/bin/cli.ts ADDED
@@ -0,0 +1,114 @@
1
+ #!/usr/bin/env bun
2
+
3
+ import { $ } from 'bun'
4
+ import type { RouteMeta } from '../src/routes'
5
+ import { program } from 'commander'
6
+ import { relative, resolve } from 'path'
7
+ import { mkdir, readdir, rm } from 'fs/promises'
8
+ import { metaAnalysis } from '../src/routes'
9
+ import { randomUUID } from 'crypto'
10
+ import { glob } from 'glob'
11
+ import { Galbe } from '../src'
12
+
13
+ const ROOT = process.cwd()
14
+ const BUILD_ID = randomUUID()
15
+
16
+ Bun.env.FORCE_COLOR = '1'
17
+
18
+ const parseRoutes = async (routes?: string | string[]): Promise<{ path: string; meta: RouteMeta }[]> => {
19
+ if (!routes) return []
20
+ let files: { path: string; meta: RouteMeta }[] = []
21
+ if (typeof routes === 'string') {
22
+ let filePaths = await glob(routes, { cwd: ROOT, withFileTypes: true, ignore: 'node_modules/**' })
23
+ for (const file of filePaths.map(f => ({ name: f.name, path: f.path, type: f.getType() }))) {
24
+ const path = `${file.path}/${file.name}`
25
+ if (file.type === 'File') files.push({ path, meta: await metaAnalysis(path) })
26
+ else if (file.type === 'Directory') {
27
+ files = files.concat(
28
+ await Promise.all(
29
+ (
30
+ await readdir(`${file.path}/${file.name}`)
31
+ ).map(async f => ({
32
+ path: `${file.path}/${f}`,
33
+ meta: await metaAnalysis(`${file.path}/${f}`)
34
+ }))
35
+ )
36
+ )
37
+ }
38
+ }
39
+ }
40
+ if (Array.isArray(routes)) for (const r of routes) files = files.concat(await parseRoutes(r))
41
+ return files
42
+ }
43
+
44
+ const createBuildIndex = async (indexPath: string, routes: { path: string; meta: RouteMeta }[]) => {
45
+ const buildPath = resolve(ROOT, '.galbe', 'build', BUILD_ID)
46
+ await mkdir(buildPath, { recursive: true })
47
+ await Bun.write(
48
+ resolve(buildPath, 'index.ts'),
49
+ `import galbe from '${relative(buildPath, indexPath)}';
50
+ ${routes.map((r, idx) => `import _${idx} from '${relative(buildPath, r.path)}'`).join(';\n')}
51
+ ${routes
52
+ .map(
53
+ (r, idx) => `galbe.routesMetadata = {...${JSON.stringify(r.meta)}}
54
+ _${idx}(galbe)`
55
+ )
56
+ .join(';\n')}
57
+ galbe.listen();
58
+ `
59
+ )
60
+ return resolve(buildPath, 'index.ts')
61
+ }
62
+
63
+ program.name('galbe').description('CLI to execute galbe utilities').version('0.1.0')
64
+
65
+ program
66
+ .command('dev')
67
+ .description('Run a dev server running your galbe API')
68
+ .argument('<string>', 'filename')
69
+ .option('-p, --port <number>', 'port number', '')
70
+ .action(async (fileName, props) => {
71
+ const { port } = props
72
+ const devRoot = resolve(ROOT, '.galbe', 'dev')
73
+ await mkdir(devRoot, { recursive: true })
74
+ await Bun.write(
75
+ resolve(devRoot, 'index.ts'),
76
+ `import galbe from '${relative(devRoot, fileName)}';galbe.listen(${port});`
77
+ )
78
+ process.on('SIGINT', async () => {
79
+ await rm(resolve(ROOT, '.galbe', 'dev'), { recursive: true })
80
+ })
81
+
82
+ await $`BUN_ENV=development bun run --watch ${resolve(devRoot, 'index.ts')}`.cwd(ROOT)
83
+ })
84
+
85
+ program
86
+ .command('build')
87
+ .description('Build your galbe API')
88
+ .argument('<string>', 'filename')
89
+ .option('-o, --out <string>', 'output file', '')
90
+ .option('-c, --compile', 'standalone executable', false)
91
+ .action(async (fileName, props) => {
92
+ const { out, compile } = props
93
+ const g: Galbe = (await import(resolve(ROOT, fileName))).default
94
+ const routes = await parseRoutes(g?.config?.routes)
95
+ const buildIndex = await createBuildIndex(fileName, routes)
96
+
97
+ const cmds = [
98
+ 'bun',
99
+ 'build',
100
+ buildIndex,
101
+ '--target',
102
+ 'bun',
103
+ ...(compile ? ['--compile', '--outfile', out ? out : 'api'] : ['--outdir', out ? out : 'dist'])
104
+ ].filter(c => c)
105
+ Bun.spawn(cmds, {
106
+ cwd: ROOT,
107
+ stdout: 'inherit',
108
+ async onExit() {
109
+ await rm(resolve(ROOT, '.galbe', 'build', BUILD_ID), { recursive: true })
110
+ }
111
+ })
112
+ })
113
+
114
+ program.parse()
package/bun.lockb ADDED
Binary file
@@ -0,0 +1 @@
1
+ TODO
@@ -0,0 +1 @@
1
+ TODO: Routes documentation
@@ -0,0 +1,5 @@
1
+ ### Create a project
2
+
3
+ ```shell
4
+ bun create pierre-cm/create-galbe
5
+ ```
package/package.json ADDED
@@ -0,0 +1,49 @@
1
+ {
2
+ "name": "galbe",
3
+ "version": "0.1.0",
4
+ "description": "Fast, lightweight and highly customizable JavaScript web framework based on Bun",
5
+ "author": "Pierre Caillaud M (https://github.com/pierre-cm)",
6
+ "type": "module",
7
+ "bin": "./bin/cli.ts",
8
+ "main": "./dist/index.js",
9
+ "types": "./dist/index.d.ts",
10
+ "exports": {
11
+ ".": "./dist/index.js",
12
+ "./*": "./dist/*.js"
13
+ },
14
+ "repository": {
15
+ "type": "git",
16
+ "url": "https://github.com/pierre-cm/galbe"
17
+ },
18
+ "bugs": "https://github.com/pierre-cm/galbe/issues",
19
+ "homepage": "https://galbe.dev",
20
+ "keywords": [
21
+ "bun",
22
+ "http",
23
+ "web",
24
+ "framework",
25
+ "server",
26
+ "api"
27
+ ],
28
+ "license": "MIT",
29
+ "scripts": {
30
+ "build": "bun ./scripts/build.ts",
31
+ "clean": "rm -rf dist",
32
+ "test": "bun test",
33
+ "postinstall": "bun run ./scripts/postinstall.ts"
34
+ },
35
+ "devDependencies": {
36
+ "@types/bun": "^1.0.4"
37
+ },
38
+ "peerDependencies": {
39
+ "typescript": "^5.0.0"
40
+ },
41
+ "dependencies": {
42
+ "@sinclair/typebox": "^0.31.28",
43
+ "@swc/core": "^1.3.107",
44
+ "@swc/wasm": "^1.4.0",
45
+ "acorn": "^8.11.2",
46
+ "acorn-walk": "^8.3.0",
47
+ "commander": "^11.1.0"
48
+ }
49
+ }
@@ -0,0 +1,14 @@
1
+ import { $ } from 'bun'
2
+
3
+ await Bun.build({
4
+ entrypoints: ['./src/index.ts'],
5
+ outdir: './dist',
6
+ minify: true,
7
+ target: 'bun',
8
+ sourcemap: 'external',
9
+ external: ['bun']
10
+ })
11
+
12
+ await $`bun x tsc --outdir ./dist`
13
+
14
+ process.exit()
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env bun
2
+
3
+ const COMMIT_MSG = await Bun.file(Bun.argv[2]).text()
4
+ const COMMIT_EMOJI = { feat: '✨', fix: 'šŸ”§', doc: 'šŸ“š', chore: '🧹' }
5
+
6
+ const newCommitMsg = COMMIT_MSG.replace(/^(feat|fix|doc|chore):(.*)/, (_, type, msg) => {
7
+ return `${COMMIT_EMOJI[type]} ${type}:${msg}`
8
+ })
9
+
10
+ await Bun.write(Bun.argv[2], newCommitMsg)
@@ -0,0 +1,9 @@
1
+ import { $ } from 'bun'
2
+ import { existsSync } from 'node:fs'
3
+
4
+ if (existsSync('.git')) {
5
+ console.log('Setting up dev environment')
6
+ Bun.write('.git/hooks/prepare-commit-msg', Bun.file('scripts/hooks/prepare-commit-msg'))
7
+ await $`chmod +x .git/hooks/prepare-commit-msg`
8
+ console.log('done')
9
+ }
package/src/index.ts ADDED
@@ -0,0 +1,353 @@
1
+ import type { Server } from 'bun'
2
+ import type {
3
+ ArrayOptions,
4
+ NumericOptions,
5
+ ObjectOptions,
6
+ SchemaOptions,
7
+ Static,
8
+ StringOptions,
9
+ TAny,
10
+ TArray,
11
+ TBoolean,
12
+ TInteger,
13
+ TLiteral,
14
+ TLiteralValue,
15
+ TNever,
16
+ TNumber,
17
+ TObject,
18
+ TOptional,
19
+ TProperties,
20
+ TSchema,
21
+ TString,
22
+ TUnion
23
+ } from '@sinclair/typebox'
24
+ import type { RouteFileMeta } from './routes'
25
+ import type {
26
+ GalbeConfig,
27
+ Method,
28
+ Schema,
29
+ Hook,
30
+ Handler,
31
+ Endpoint,
32
+ Context,
33
+ ErrorHandler,
34
+ GalbePlugin,
35
+ TStream,
36
+ TMultipartProperties,
37
+ TMultipartForm,
38
+ TUrlFormProperties,
39
+ TUrlForm,
40
+ TBody,
41
+ TByteArray,
42
+ TStreamable,
43
+ MultipartFormData
44
+ } from './types'
45
+
46
+ import { TypeClone, Kind, TypeBuilder, Optional, TypeGuard } from '@sinclair/typebox'
47
+ import server from './server'
48
+ import { GalbeRouter } from './router'
49
+ import { defineRoutes } from './routes'
50
+ import { Stream } from './types'
51
+ import { logRoute } from './util'
52
+
53
+ const overloadDiscriminer = <H extends TProperties, P extends TProperties, Q extends TProperties, B extends TBody>(
54
+ galbe: Galbe,
55
+ method: Method,
56
+ path: string,
57
+ arg2: Schema<H, P, Q, B> | Hook<Schema<H, P, Q, B>>[] | Handler<Schema<H, P, Q, B>>,
58
+ arg3?: Hook<Schema<H, P, Q, B>>[] | Handler<Schema<H, P, Q, B>>,
59
+ arg4?: Handler<Schema<H, P, Q, B>>
60
+ ) => {
61
+ const defaultSchema = {}
62
+ if (typeof arg2 === 'function') {
63
+ return galbeMethod(galbe, method, path, defaultSchema, undefined, arg2)
64
+ } else {
65
+ if (Array.isArray(arg2)) {
66
+ if (typeof arg3 === 'function') return galbeMethod(galbe, method, path, defaultSchema, arg2, arg3)
67
+ } else {
68
+ if (Array.isArray(arg3) && arg4) return galbeMethod(galbe, method, path, arg2, arg3, arg4)
69
+ else if (typeof arg3 === 'function') return galbeMethod(galbe, method, path, arg2, undefined, arg3)
70
+ }
71
+ }
72
+ throw new Error('Undefined endpoint signature')
73
+ }
74
+ const galbeMethod = <H extends TProperties, P extends TProperties, Q extends TProperties, B extends TBody>(
75
+ _galbe: Galbe,
76
+ method: Method,
77
+ path: string,
78
+ schema: Schema<H, P, Q, B> | undefined,
79
+ hooks: Hook<Schema<H, P, Q, B>>[] | undefined,
80
+ handler: Handler<Schema<H, P, Q, B>>
81
+ ) => {
82
+ schema = schema ?? {}
83
+ hooks = hooks || []
84
+ const context: Context<typeof schema> = {
85
+ headers: {} as Static<TObject<Exclude<(typeof schema)['headers'], undefined>>>,
86
+ params: {} as Static<TObject<Exclude<(typeof schema)['params'], undefined>>>,
87
+ query: {} as Static<TObject<Exclude<(typeof schema)['query'], undefined>>>,
88
+ body: {} as Static<Exclude<(typeof schema)['body'], undefined>>,
89
+ request: {} as Request,
90
+ state: {},
91
+ set: {} as {
92
+ headers: {
93
+ [header: string]: string
94
+ }
95
+ status?: number
96
+ redirect?: string
97
+ }
98
+ }
99
+ //@ts-ignore
100
+ return {
101
+ method,
102
+ path,
103
+ schema,
104
+ context,
105
+ hooks,
106
+ handler
107
+ }
108
+ }
109
+
110
+ export class TypeboxTypeBuilder extends TypeBuilder {
111
+ /** `[Json]` Creates an Optional property */
112
+ public Optional<T extends TSchema>(schema: T): TOptional<T> {
113
+ return { ...TypeClone.Type(schema), [Optional]: 'Optional' }
114
+ }
115
+ /** `[Json]` Creates an Any type */
116
+ public Any(options: SchemaOptions = {}): TAny {
117
+ return this.Create({ ...options, [Kind]: 'Any' })
118
+ }
119
+ /** `[Json]` Creates an Array type */
120
+ public Array<T extends TSchema>(schema: T, options: ArrayOptions = {}): TArray<T> {
121
+ return this.Create({ ...options, [Kind]: 'Array', type: 'array', items: TypeClone.Type(schema) })
122
+ }
123
+ /** `[Json]` Creates a Boolean type */
124
+ public Boolean(options: SchemaOptions = {}): TBoolean {
125
+ return this.Create({ ...options, [Kind]: 'Boolean', type: 'boolean' })
126
+ }
127
+ /** `[Json]` Creates an Integer type */
128
+ public Integer(options: NumericOptions<number> = {}): TInteger {
129
+ return this.Create({ ...options, [Kind]: 'Integer', type: 'integer' })
130
+ }
131
+ /** `[Json]` Creates a Literal type */
132
+ public Literal<T extends TLiteralValue>(value: T, options: SchemaOptions = {}): TLiteral<T> {
133
+ return this.Create({
134
+ ...options,
135
+ [Kind]: 'Literal',
136
+ const: value,
137
+ type: typeof value as 'string' | 'number' | 'boolean'
138
+ })
139
+ }
140
+ /** `[Json]` Creates a Number type */
141
+ public Number(options: NumericOptions<number> = {}): TNumber {
142
+ return this.Create({ ...options, [Kind]: 'Number', type: 'number' })
143
+ }
144
+ /** `[Json]` Creates an Object type */
145
+ public Object<T extends TProperties>(properties: T, options: ObjectOptions = {}): TObject<T> {
146
+ const propertyKeys = Object.getOwnPropertyNames(properties)
147
+ const optionalKeys = propertyKeys.filter(key => TypeGuard.TOptional(properties[key]))
148
+ const requiredKeys = propertyKeys.filter(name => !optionalKeys.includes(name))
149
+ const clonedAdditionalProperties = TypeGuard.TSchema(options.additionalProperties)
150
+ ? { additionalProperties: TypeClone.Type(options.additionalProperties) }
151
+ : {}
152
+ const clonedProperties = propertyKeys.reduce(
153
+ (acc, key) => ({ ...acc, [key]: TypeClone.Type(properties[key]) }),
154
+ {} as TProperties
155
+ )
156
+ return requiredKeys.length > 0
157
+ ? this.Create({
158
+ ...options,
159
+ ...clonedAdditionalProperties,
160
+ [Kind]: 'Object',
161
+ type: 'object',
162
+ properties: clonedProperties,
163
+ required: requiredKeys
164
+ })
165
+ : this.Create({
166
+ ...options,
167
+ ...clonedAdditionalProperties,
168
+ [Kind]: 'Object',
169
+ type: 'object',
170
+ properties: clonedProperties
171
+ })
172
+ }
173
+ /** `[Json]` Creates a String type */
174
+ public String(options: StringOptions = {}): TString {
175
+ return this.Create({ ...options, [Kind]: 'String', type: 'string' })
176
+ }
177
+ /** `[Json]` Creates a Union type */
178
+ public Union(anyOf: [], options?: SchemaOptions): TNever
179
+ /** `[Json]` Creates a Union type */
180
+ public Union<T extends [TSchema]>(anyOf: [...T], options?: SchemaOptions): T[0]
181
+ /** `[Json]` Creates a Union type */
182
+ public Union<T extends TSchema[]>(anyOf: [...T], options?: SchemaOptions): TUnion<T>
183
+ /** `[Json]` Creates a Union type */
184
+ public Union(union: TSchema[], options: SchemaOptions = {}) {
185
+ // prettier-ignore
186
+ return (() => {
187
+ const anyOf = union
188
+ if (anyOf.length === 0) throw new Error("Union type must decalre at least one schema")
189
+ if (anyOf.length === 1) return this.Create(TypeClone.Type(anyOf[0], options))
190
+ const clonedAnyOf = TypeClone.Rest(anyOf)
191
+ return this.Create({ ...options, [Kind]: 'Union', anyOf: clonedAnyOf })
192
+ })()
193
+ }
194
+ }
195
+
196
+ class GalbeTypeBuilder extends TypeboxTypeBuilder {
197
+ /** `[Galbe]` Creates an Stream type */
198
+ public Stream<T extends TUrlForm>(
199
+ schema: T
200
+ ): Omit<TStream<T>, 'static'> & { static: AsyncGenerator<[string, string | number | boolean]>; params: unknown[] }
201
+ public Stream<T extends TMultipartForm>(
202
+ schema: T
203
+ ): Omit<TStream<T>, 'static'> & { static: AsyncGenerator<MultipartFormData, void, unknown>; params: unknown[] }
204
+ public Stream<T extends TByteArray>(
205
+ schema: T
206
+ ): Omit<TStream<T>, 'static'> & { static: AsyncGenerator<Uint8Array>; params: unknown[] }
207
+ public Stream<T extends TString>(
208
+ schema: T
209
+ ): Omit<TStream<T>, 'static'> & { static: AsyncGenerator<string>; params: unknown[] }
210
+ public Stream<T extends TStreamable>(schema: T): TStream<T> {
211
+ return {
212
+ ...TypeClone.Type(schema),
213
+ [Stream]: 'Stream'
214
+ }
215
+ }
216
+ /** `[Galbe]` Creates an ByteArray type */
217
+ public ByteArray(): TByteArray {
218
+ return this.Create({ [Kind]: 'ByteArray', type: 'byteArray', params: {} })
219
+ }
220
+ /** `[Galbe]` Creates an MultipartForm type */
221
+ public MultipartForm<T extends TMultipartProperties>(properties?: T): TMultipartForm {
222
+ if (!properties) return this.Create({ [Kind]: 'MultipartForm', type: 'multipartForm' })
223
+ const propertyKeys = Object.getOwnPropertyNames(properties)
224
+ const clonedProperties = propertyKeys.reduce(
225
+ //@ts-ignore
226
+ (acc, key) => ({ ...acc, [key]: TypeClone.Type(properties[key]) }),
227
+ {} as TProperties
228
+ )
229
+ return this.Create({
230
+ [Kind]: 'MultipartForm',
231
+ type: 'multipartForm',
232
+ properties: clonedProperties
233
+ })
234
+ }
235
+ /** `[Galbe]` Creates an UrlForm type */
236
+ public UrlForm<T extends TUrlFormProperties>(properties?: T): TUrlForm {
237
+ if (!properties) return this.Create({ [Kind]: 'UrlForm', type: 'urlForm' })
238
+ const propertyKeys = Object.getOwnPropertyNames(properties)
239
+ const clonedProperties = propertyKeys.reduce(
240
+ //@ts-ignore
241
+ (acc, key) => ({ ...acc, [key]: TypeClone.Type(properties[key]) }),
242
+ {} as TProperties
243
+ )
244
+ return this.Create({ [Kind]: 'UrlForm', type: 'urlForm', properties: clonedProperties })
245
+ }
246
+ }
247
+
248
+ export const T = new GalbeTypeBuilder()
249
+
250
+ export { RequestError } from './types'
251
+
252
+ const indexRoutes: { method: string; path: string }[] = []
253
+ /**
254
+ * #### Galbe Server
255
+ * Instanciate a Galbe web server
256
+ *
257
+ * ---
258
+ * @example
259
+ * ```typescript
260
+ * import { Galbe } from 'galbe'
261
+ * import config from "./galbe.config"
262
+ *
263
+ * export default new Galbe(config)
264
+ * ```
265
+ */
266
+ export class Galbe {
267
+ config: GalbeConfig
268
+ meta?: Array<RouteFileMeta> = []
269
+ router: GalbeRouter
270
+ errorHandler?: ErrorHandler
271
+ listening: boolean = false
272
+ #prepare: boolean = false
273
+ server?: Server
274
+ plugins: GalbePlugin[] = []
275
+ constructor(config?: GalbeConfig) {
276
+ this.config = config ?? {}
277
+ this.router = new GalbeRouter(this.config?.basePath || '')
278
+ }
279
+ private add(route: any) {
280
+ this.router.add(route)
281
+ if (Bun.env.BUN_ENV === 'development') {
282
+ if (!this.#prepare) indexRoutes.push({ method: route.method, path: route.path })
283
+ else logRoute(route)
284
+ }
285
+ }
286
+ async use(plugin: GalbePlugin) {
287
+ this.plugins.push(plugin)
288
+ }
289
+ async listen(port?: number) {
290
+ port = port || this.config?.port || 3000
291
+ this.config.port = port
292
+ if (this.listening) this.stop()
293
+ if (Bun.env.BUN_ENV === 'development') {
294
+ this.#prepare = true
295
+ console.log('šŸ—ļø \x1b[1;30mConstructing routes\x1b[0m')
296
+ for (const r of indexRoutes) logRoute(r)
297
+ await defineRoutes(this.config || {}, this)
298
+ console.log('\nāœ… \x1b[1;30mdone\x1b[0m')
299
+ this.server = await server(this, port)
300
+ const url = `http://localhost:${port}${this.config?.basePath || ''}`
301
+ console.log(`\n\x1b[1;30mšŸš€ API running at\x1b[0m \x1b[4;34m${url}\x1b[0m`)
302
+ } else {
303
+ this.server = await server(this, port)
304
+ }
305
+ this.listening = true
306
+ this.#prepare = false
307
+ return this.server
308
+ }
309
+ stop() {
310
+ this.server?.stop(true)
311
+ }
312
+ onError(handler: ErrorHandler) {
313
+ this.errorHandler = handler
314
+ }
315
+ get: Endpoint = <H extends TProperties, P extends TProperties, Q extends TProperties, B extends TBody>(
316
+ path: string,
317
+ arg2: Schema<H, P, Q, B> | Hook<Schema<H, P, Q, B>>[] | Handler<Schema<H, P, Q, B>>,
318
+ arg3?: Hook<Schema<H, P, Q, B>>[] | Handler<Schema<H, P, Q, B>>,
319
+ arg4?: Handler<Schema<H, P, Q, B>>
320
+ ) => this.add(overloadDiscriminer(this, 'get', path, arg2, arg3, arg4))
321
+ post: Endpoint = <H extends TProperties, P extends TProperties, Q extends TProperties, B extends TBody>(
322
+ path: string,
323
+ arg2: Schema<H, P, Q, B> | Hook<Schema<H, P, Q, B>>[] | Handler<Schema<H, P, Q, B>>,
324
+ arg3?: Hook<Schema<H, P, Q, B>>[] | Handler<Schema<H, P, Q, B>>,
325
+ arg4?: Handler<Schema<H, P, Q, B>>
326
+ ) => this.add(overloadDiscriminer(this, 'post', path, arg2, arg3, arg4))
327
+ put: Endpoint = <H extends TProperties, P extends TProperties, Q extends TProperties, B extends TBody>(
328
+ path: string,
329
+ arg2: Schema<H, P, Q, B> | Hook<Schema<H, P, Q, B>>[] | Handler<Schema<H, P, Q, B>>,
330
+ arg3?: Hook<Schema<H, P, Q, B>>[] | Handler<Schema<H, P, Q, B>>,
331
+ arg4?: Handler<Schema<H, P, Q, B>>
332
+ ) => this.add(overloadDiscriminer(this, 'put', path, arg2, arg3, arg4))
333
+ patch: Endpoint = <H extends TProperties, P extends TProperties, Q extends TProperties, B extends TBody>(
334
+ path: string,
335
+ arg2: Schema<H, P, Q, B> | Hook<Schema<H, P, Q, B>>[] | Handler<Schema<H, P, Q, B>>,
336
+ arg3?: Hook<Schema<H, P, Q, B>>[] | Handler<Schema<H, P, Q, B>>,
337
+ arg4?: Handler<Schema<H, P, Q, B>>
338
+ ) => this.add(overloadDiscriminer(this, 'patch', path, arg2, arg3, arg4))
339
+ delete: Endpoint = <H extends TProperties, P extends TProperties, Q extends TProperties, B extends TBody>(
340
+ path: string,
341
+ arg2: Schema<H, P, Q, B> | Hook<Schema<H, P, Q, B>>[] | Handler<Schema<H, P, Q, B>>,
342
+ arg3?: Hook<Schema<H, P, Q, B>>[] | Handler<Schema<H, P, Q, B>>,
343
+ arg4?: Handler<Schema<H, P, Q, B>>
344
+ ) => this.add(overloadDiscriminer(this, 'delete', path, arg2, arg3, arg4))
345
+ options: Endpoint = <H extends TProperties, P extends TProperties, Q extends TProperties, B extends TBody>(
346
+ path: string,
347
+ arg2: Schema<H, P, Q, B> | Hook<Schema<H, P, Q, B>>[] | Handler<Schema<H, P, Q, B>>,
348
+ arg3?: Hook<Schema<H, P, Q, B>>[] | Handler<Schema<H, P, Q, B>>,
349
+ arg4?: Handler<Schema<H, P, Q, B>>
350
+ ) => this.add(overloadDiscriminer(this, 'options', path, arg2, arg3, arg4))
351
+ }
352
+
353
+ export * from './types'