galbe 0.14.0 → 0.15.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.
@@ -125,7 +125,7 @@ export default (cmd: Command) => {
125
125
  FROM information_schema.tables
126
126
  WHERE table_schema = '${schema}'
127
127
  AND table_type = 'BASE TABLE'`)
128
- tables = r.map(r => r.table_name)
128
+ tables = r.map((r: Record<string, string>) => r.table_name)
129
129
  }
130
130
 
131
131
  for (const tableName of tables) {
@@ -133,7 +133,7 @@ export default (cmd: Command) => {
133
133
  FROM information_schema.columns
134
134
  WHERE table_schema = '${schema}' AND table_name = '${tableName}'`)
135
135
  types[tableName] = `type ${toPascalCase(tableName)} = {\n${t
136
- .map(r => ` ${r.column_name}: ${TYPE_MAP?.[r.data_type] ?? 'any'}${r.is_nullable ? ' | null' : ''}`)
136
+ .map((r: Record<string, string>) => ` ${r.column_name}: ${TYPE_MAP?.[r.data_type] ?? 'any'}${r.is_nullable ? ' | null' : ''}`)
137
137
  .join(';\n')}\n}`
138
138
  }
139
139
 
@@ -0,0 +1,185 @@
1
+ // Inlined into the generated client — no exports
2
+
3
+ type GalbeClientConfig = {
4
+ server?: { url?: string }
5
+ headers?: Record<string, string>
6
+ fetch?: (req: Request) => Promise<Response>
7
+ }
8
+
9
+ class GalbeClientError extends Error {
10
+ readonly status: number
11
+ readonly headers: Headers
12
+ readonly body: string
13
+ constructor(status: number, headers: Headers, body: string) {
14
+ super(`HTTP ${status}`)
15
+ this.name = 'GalbeClientError'
16
+ this.status = status
17
+ this.headers = headers
18
+ this.body = body
19
+ }
20
+ }
21
+
22
+ const _parseResponse = async (res: Response): Promise<any> => {
23
+ const ct = res.headers.get('content-type') ?? ''
24
+ if (ct.includes('application/json')) return res.json()
25
+ if (ct.includes('application/octet-stream')) return new Uint8Array(await res.arrayBuffer())
26
+ return res.text()
27
+ }
28
+
29
+ const _buildUrl = (base: string | undefined, path: string, query?: Record<string, any>): string => {
30
+ let url = `${base ?? ''}${path}`
31
+ if (query) {
32
+ const params = new URLSearchParams()
33
+ for (const [k, v] of Object.entries(query)) {
34
+ if (v === undefined || v === null) continue
35
+ if (Array.isArray(v)) for (const item of v) params.append(k, String(item))
36
+ else params.set(k, String(v))
37
+ }
38
+ const qs = params.toString()
39
+ if (qs) url += `?${qs}`
40
+ }
41
+ return url
42
+ }
43
+
44
+ const _formdata = (data: Record<string, string | string[] | Blob>): FormData => {
45
+ const form = new FormData()
46
+ for (const [k, v] of Object.entries(data)) {
47
+ if (Array.isArray(v)) for (const item of v) form.append(k, String(item))
48
+ else form.append(k, v instanceof Blob ? v : String(v))
49
+ }
50
+ return form
51
+ }
52
+
53
+ type _RequestOptions = {
54
+ query?: Record<string, any>
55
+ headers?: Record<string, string>
56
+ contentType?: string
57
+ }
58
+
59
+ const _doFetch = (
60
+ config: GalbeClientConfig,
61
+ method: string,
62
+ path: string,
63
+ body?: any,
64
+ options?: _RequestOptions
65
+ ): Promise<Response> => {
66
+ const url = _buildUrl(config.server?.url, path, options?.query)
67
+ let bodyInit: BodyInit | undefined
68
+ const bodyHeaders: Record<string, string> = {}
69
+
70
+ if (body !== undefined && body !== null) {
71
+ const ct = options?.contentType
72
+ if (ct === 'urlForm') {
73
+ bodyInit = new URLSearchParams(body).toString()
74
+ bodyHeaders['content-type'] = 'application/x-www-form-urlencoded'
75
+ } else if (ct === 'multipart') {
76
+ bodyInit = _formdata(body)
77
+ } else if (ct === 'byteArray' || body instanceof Uint8Array) {
78
+ bodyInit = body
79
+ bodyHeaders['content-type'] = 'application/octet-stream'
80
+ } else if (ct === 'text') {
81
+ bodyInit = String(body)
82
+ bodyHeaders['content-type'] = 'text/plain'
83
+ } else {
84
+ bodyInit = JSON.stringify(body)
85
+ bodyHeaders['content-type'] = 'application/json'
86
+ }
87
+ }
88
+
89
+ const req = new Request(url, {
90
+ method,
91
+ headers: { ...config.headers, ...bodyHeaders, ...options?.headers },
92
+ ...(bodyInit !== undefined ? { body: bodyInit } : {}),
93
+ })
94
+
95
+ return (config.fetch ?? fetch)(req)
96
+ }
97
+
98
+ class GalbeRequest<T, E = any> {
99
+ #promise: Promise<[Response, Response]>
100
+ #main?: Promise<T>
101
+
102
+ constructor(fetchPromise: Promise<Response>) {
103
+ this.#promise = fetchPromise.then(res => [res, res.clone()] as [Response, Response])
104
+ }
105
+
106
+ #getMain(): Promise<T> {
107
+ if (!this.#main) {
108
+ this.#main = this.#promise.then(async ([res]) => {
109
+ if (!res.ok) throw new GalbeClientError(res.status, res.headers, await res.text())
110
+ return _parseResponse(res) as T
111
+ })
112
+ }
113
+ return this.#main
114
+ }
115
+
116
+ then<R1 = T, R2 = never>(
117
+ onfulfilled?: ((value: T) => R1 | PromiseLike<R1>) | null,
118
+ onrejected?: ((reason: any) => R2 | PromiseLike<R2>) | null
119
+ ): Promise<R1 | R2> {
120
+ return this.#getMain().then(onfulfilled, onrejected)
121
+ }
122
+
123
+ catch<R = never>(
124
+ onrejected?: ((reason: any) => R | PromiseLike<R>) | null
125
+ ): Promise<T | R> {
126
+ return this.#getMain().then(undefined, onrejected)
127
+ }
128
+
129
+ finally(onfinally?: (() => void) | null): Promise<T> {
130
+ return this.#getMain().finally(onfinally)
131
+ }
132
+
133
+ async safe(): Promise<{ ok: true; data: T } | { ok: false; error: E }> {
134
+ const [mainRes, cloneRes] = await this.#promise
135
+ if (mainRes.ok) {
136
+ return { ok: true, data: (await _parseResponse(cloneRes)) as T }
137
+ } else {
138
+ const body = await _parseResponse(cloneRes)
139
+ return { ok: false, error: { status: mainRes.status, headers: mainRes.headers, body } as E }
140
+ }
141
+ }
142
+ }
143
+
144
+ const _createRequest = <T, E = any>(
145
+ config: GalbeClientConfig,
146
+ method: string,
147
+ path: string,
148
+ body?: any,
149
+ options?: _RequestOptions
150
+ ): GalbeRequest<T, E> => new GalbeRequest<T, E>(_doFetch(config, method, path, body, options))
151
+
152
+ const _createRawRequest = async (
153
+ config: GalbeClientConfig,
154
+ method: string,
155
+ path: string,
156
+ body?: any,
157
+ options?: _RequestOptions
158
+ ): Promise<any> => {
159
+ const res = await _doFetch(config, method, path, body, options)
160
+ return {
161
+ status: res.status,
162
+ ok: res.ok,
163
+ redirected: res.redirected,
164
+ statusText: res.statusText,
165
+ type: res.type,
166
+ url: res.url,
167
+ headers: res.headers,
168
+ body: {
169
+ json: () => res.json(),
170
+ text: () => res.text(),
171
+ byteArray: () => res.arrayBuffer().then((b: ArrayBuffer) => new Uint8Array(b)),
172
+ stream: (): AsyncGenerator<Uint8Array, void, unknown> => {
173
+ const reader = res.body?.getReader()
174
+ return (async function* () {
175
+ if (!reader) return
176
+ while (true) {
177
+ const { value, done } = await reader.read()
178
+ if (done) break
179
+ yield value!
180
+ }
181
+ })()
182
+ },
183
+ },
184
+ }
185
+ }
@@ -110,7 +110,7 @@ export default class GalbeClient {
110
110
  text: 'text/plain',
111
111
  json: 'application/json',
112
112
  urlForm: 'application/x-www-form-urlencoded',
113
- }[options.contentType],
113
+ }[options.contentType as 'byteArray' | 'text' | 'json' | 'urlForm'],
114
114
  }
115
115
  : {}),
116
116
  ...(options?.headers || {}),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "galbe",
3
- "version": "0.14.0",
3
+ "version": "0.15.0",
4
4
  "description": "Fast, lightweight and highly customizable JavaScript web framework based on Bun",
5
5
  "author": "Pierre Caillaud M (https://github.com/pierre-cm)",
6
6
  "type": "module",
package/src/extras.ts CHANGED
@@ -1,2 +1,2 @@
1
1
  export { OpenAPISerializer } from './extras/spec/openapi.serializer'
2
- export type { GalbeCLICommand, GalbeCLIOptions } from './types'
2
+ export type { GalbeCLICommand, GalbeCLIOptions, GalbeClientRoute, GalbeClientOptions } from './types'
package/src/server.ts CHANGED
@@ -171,7 +171,7 @@ export default async (galbe: Galbe, port?: number, hostname?: string) => {
171
171
 
172
172
  const parsedResponse = responseParser(response, context as Context, cookies, schema.response)
173
173
 
174
- if (galbe.config?.responseValidator?.enabled !== false && schema.response)
174
+ if (galbe.config?.responseValidator?.enabled !== false && schema.response && !(response instanceof Response))
175
175
  validateResponse(response, schema.response, parsedResponse.status || 200)
176
176
 
177
177
  for (const p of pluginsCb.afterHandle) {
package/src/types.ts CHANGED
@@ -484,5 +484,29 @@ export type GalbeCLIOptions = {
484
484
  args: Record<string, string>,
485
485
  options: Record<string, any>
486
486
  ) => MaybePromise<Request>
487
- responseFormatter?: (res: Response) => MaybePromise<string>
487
+ responseFormatter?: (
488
+ res: Response,
489
+ command: GalbeCLICommand,
490
+ args: Record<string, string>,
491
+ options: Record<string, any>
492
+ ) => MaybePromise<string>
493
+ }
494
+
495
+ export type GalbeClientRoute = {
496
+ method: string
497
+ path: string
498
+ operationId: string
499
+ autoDerived: boolean
500
+ params: Record<string, { type: string; description?: string }>
501
+ query: Record<string, { type: string; optional: boolean; description?: string }>
502
+ headers: Record<string, { type: string; optional: boolean; description?: string }>
503
+ body: Record<string, STSchema> | null
504
+ response: STResponse | null
505
+ summary?: string
506
+ description?: string
507
+ tags: string[]
508
+ }
509
+
510
+ export type GalbeClientOptions = {
511
+ className?: string
488
512
  }