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.
- package/bin/commands/build.ts +2 -2
- package/bin/commands/generate/cli/index.ts +21 -14
- package/bin/commands/generate/cli/targets/cac.ts +188 -63
- package/bin/commands/generate/client.ts +520 -110
- package/bin/commands/generate/model.ts +2 -2
- package/bin/res/client.runtime.ts +185 -0
- package/bin/res/client.template.ts +1 -1
- package/package.json +1 -1
- package/src/extras.ts +1 -1
- package/src/server.ts +1 -1
- package/src/types.ts +25 -1
|
@@ -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
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?: (
|
|
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
|
}
|