galbe 0.7.0 → 0.9.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.
@@ -3,34 +3,42 @@ import { $ } from 'bun'
3
3
  import { Command, Option } from 'commander'
4
4
  import { resolve, relative, dirname } from 'path'
5
5
  import { tmpdir } from 'os'
6
- import { mkdir, rm } from 'fs/promises'
6
+ import { mkdir, rm, exists } from 'fs/promises'
7
7
 
8
8
  import { CWD, fmtVal, silentExec } from '../util'
9
9
  import { Galbe } from '../../src'
10
- import { defineRoutes } from '../../src/routes'
10
+ import { defineRoutes, GalbeProxy } from '../../src/routes'
11
11
  import { BuildConfig } from 'bun'
12
12
 
13
- const createBuildIndex = async (indexPath: string, g: Galbe) => {
14
- const buildId = crypto.randomUUID()
13
+ const createBuildIndex = async (indexPath: string, g: Galbe, buildId: string) => {
15
14
  const buildPath = resolve(tmpdir(), buildId)
16
- const routes = new Set<string>()
15
+ const routes = new Map<string, { filepath: string, static?: { path: string, root: string } }>()
17
16
  let errors: any[] = []
18
- await defineRoutes({ routes: g?.config?.routes }, g, ({ type, error, filepath }) => {
17
+ // Create GalbeProxy here
18
+ // use it to define routes
19
+ const proxy = new GalbeProxy(g, ({ type, error, filepath, route }) => {
19
20
  if (!filepath) return
20
- routes.add(filepath)
21
+ routes.set(filepath, { filepath, static: route?.static })
21
22
  if (type === 'error') errors.push(error)
22
23
  })
24
+ await defineRoutes({ routes: g?.config?.routes }, proxy)
25
+ // call init on it for plugin initialization
26
+ await proxy.init()
27
+
23
28
  if (errors.length) throw errors
24
29
  await mkdir(buildPath, { recursive: true })
25
- await Bun.write(
26
- resolve(buildPath, 'index.ts'),
27
- `import galbe from '${relative(buildPath, indexPath)}';
28
- ${[...routes].map((r, idx) => `import _${idx} from '${relative(buildPath, r)}'`).join(';\n')}
29
- galbe.meta = ${JSON.stringify(g.meta)};
30
- ${[...routes].map((_, idx) => `_${idx}(galbe)`).join(';\n')}
31
- galbe.listen();
32
- `
33
- )
30
+
31
+ let buildIndex =
32
+ `import galbe from '${relative(buildPath, indexPath)}';\n` +
33
+ `${[...routes.values()].map((r, idx) => `import _${idx} from '${relative(buildPath, r.filepath)}'`).join(';\n')}\n` +
34
+ `Bun.env.BUN_ENV = 'production';\n` +
35
+ `Bun.env.GALBE_BUILD = '${buildId}';\n` +
36
+ `galbe.meta = ${JSON.stringify(g.meta)};\n` +
37
+ `${[...routes].map((_, idx) => `_${idx}(galbe)`).join(';\n')};\n` +
38
+ `galbe.listen();\n`
39
+
40
+ await Bun.write(resolve(buildPath, 'index.ts'), buildIndex)
41
+
34
42
  return resolve(buildPath, 'index.ts')
35
43
  }
36
44
 
@@ -44,10 +52,18 @@ export default (cmd: Command) => {
44
52
  .action(async (index, props) => {
45
53
  const { out, compile, config } = props
46
54
 
55
+ const buildID = crypto.randomUUID()
56
+ const outPath = resolve(CWD, out)
57
+
58
+ Bun.env.GALBE_BUILD = buildID
59
+ Bun.env.GALBE_BUILD_OUT = outPath
60
+
47
61
  const bunfig = config ? (await import(resolve(CWD, config)))?.default || {} : {}
48
62
 
63
+ if(await exists(outPath)) await rm(outPath, { recursive: true })
64
+
49
65
  let error = null
50
- process.stdout.write('📦 \x1b[1;30mBuilding \x1b[36mGalbe\x1b[0m\x1b[1;30m app\x1b[0m')
66
+ Bun.write(Bun.stdout, '📦 \x1b[1;30mBuilding \x1b[36mGalbe\x1b[0m\x1b[1;30m app\x1b[0m')
51
67
  let g: Galbe = await silentExec(async () => {
52
68
  try {
53
69
  const g = (await import(resolve(CWD, index))).default
@@ -63,7 +79,7 @@ export default (cmd: Command) => {
63
79
  }
64
80
  let buildIndex: string = ''
65
81
  try {
66
- buildIndex = await createBuildIndex(index, g)
82
+ buildIndex = await createBuildIndex(index, g, buildID)
67
83
  } catch (errors) {
68
84
  console.log(`\nerror: build errors`)
69
85
  for (let error of errors) console.log(error)
@@ -75,24 +91,25 @@ export default (cmd: Command) => {
75
91
  }
76
92
 
77
93
  const buildConfig: BuildConfig = {
78
- publicPath: `${resolve(CWD, out)}/`,
94
+ publicPath: `${outPath}/`,
79
95
  ...Object.fromEntries(Object.entries(bunfig).filter(([k, v]) => v)),
80
96
  entrypoints: [buildIndex],
81
- outdir: resolve(CWD, out),
97
+ outdir: outPath,
82
98
  sourcemap: 'external',
83
- target: 'bun'
99
+ target: 'bun',
84
100
  }
85
101
 
86
- await rm(resolve(CWD, out), { recursive: true })
87
-
88
102
  let bo = await Bun.build(buildConfig)
89
- if (bo.success) process.stdout.write(' : \x1b[1;30m\x1b[32mdone\x1b[0m\n')
103
+ if (bo.success) Bun.write(Bun.stdout, ' : \x1b[1;30m\x1b[32mdone\x1b[0m\n')
90
104
  else {
91
105
  console.log(`\nerror: build errors`)
92
106
  console.log(...bo.logs)
93
107
  }
94
- if (compile) await $`bun build --compile ${resolve(CWD, out, 'index.js')} --outfile ${resolve(CWD, out, 'app')}`
108
+ if (compile) {
109
+ await $`bun build --compile ${resolve(CWD, out, 'index.js')} --outfile ${outPath}/bin`
110
+ }
95
111
 
96
112
  await rm(dirname(buildIndex), { recursive: true })
113
+ process.exit(0)
97
114
  })
98
115
  }
@@ -20,7 +20,8 @@ export default (cmd: Command) => {
20
20
  })
21
21
  .default(null, fmtVal(defaultPort))
22
22
  )
23
- .addOption(new Option('-w, --watch', 'watch file changes').default(false, fmtVal(false)))
23
+ .addOption(new Option('-w, --watch <dir>', 'watch file changes').default(false, fmtVal(false)))
24
+ .addOption(new Option('-wi, --watchignore <regexp>', 'ignore file changes').default(false, fmtVal(false)))
24
25
  .addOption(new Option('-nc, --noclear', "don't clear on file changes").default(false, fmtVal(false)))
25
26
  .addOption(
26
27
  new Option('-f, --force', 'kills any process running on defined port before strating the server').default(
@@ -29,7 +30,8 @@ export default (cmd: Command) => {
29
30
  )
30
31
  )
31
32
  .action(async (index, props) => {
32
- const { port, watch, noclear, force } = props
33
+ const { port, watch, watchignore, noclear, force } = props
34
+ let watch_dir = typeof watch === 'string' ? watch : CWD
33
35
  const clear = !noclear
34
36
  const indexPath = resolve(CWD, index)
35
37
  let g: Galbe
@@ -38,9 +40,9 @@ export default (cmd: Command) => {
38
40
 
39
41
  if (force) await killPort(port || 3000)
40
42
 
41
- if (watch) {
43
+ if (!!watch) {
42
44
  await watchDir(
43
- CWD,
45
+ watch_dir,
44
46
  async () => {
45
47
  g.stop()
46
48
  if (clear) await $`clear`
@@ -49,11 +51,11 @@ export default (cmd: Command) => {
49
51
  await instanciateRoutes(g)
50
52
  await g.listen(port)
51
53
  },
52
- { ignore: /node_modules/ }
54
+ { ignore: watchignore ? new RegExp(watchignore) : /node_modules/ }
53
55
  )
54
56
  }
55
57
 
56
- if (watch && clear) await $`clear`
58
+ if (!!watch && clear) await $`clear`
57
59
  g = (await import(indexPath)).default
58
60
  await instanciateRoutes(g)
59
61
  await g.listen(port)
@@ -6,7 +6,7 @@ import { resolve, extname } from 'path'
6
6
  import { rm } from 'fs/promises'
7
7
  import { transformSync } from '@swc/core'
8
8
  import { CWD, fmtList, instanciateRoutes, silentExec } from '../../util'
9
- import { $T, Galbe, GalbeCLICommand, Method, Route } from '../../../src'
9
+ import { $T, Galbe, GalbeCLICommand, Method, Route, STResponse } from '../../../src'
10
10
  import { walkRoutes } from '../../../src/util'
11
11
  import { schemaToTypeStr, Optional, STSchema } from '../../../src/schema'
12
12
 
@@ -36,10 +36,10 @@ export default (cmd: Command) => {
36
36
  let pckg: any = {}
37
37
  try {
38
38
  pckg = await Bun.file(resolve(CWD, 'package.json')).json()
39
- } catch (e) {}
39
+ } catch (e) { }
40
40
 
41
41
  let error = null
42
- process.stdout.write('💻 \x1b[1;30mBuilding \x1b[36mGalbe\x1b[0m\x1b[1;30m client\x1b[0m')
42
+ Bun.write(Bun.stdout, '💻 \x1b[1;30mBuilding \x1b[36mGalbe\x1b[0m\x1b[1;30m client\x1b[0m')
43
43
  let g: Galbe = await silentExec(async () => {
44
44
  try {
45
45
  const g = (await import(resolve(CWD, index))).default
@@ -65,6 +65,7 @@ export default (cmd: Command) => {
65
65
  options: [],
66
66
  head: []
67
67
  }
68
+ const types: Record<string, STResponse> = {}
68
69
  let commands: GalbeCLICommand[] = []
69
70
  const metaRoutes = g.meta?.reduce(
70
71
  (routes, c) => ({ ...routes, ...c.routes }),
@@ -73,9 +74,13 @@ export default (cmd: Command) => {
73
74
 
74
75
  walkRoutes(g.router.routes, r => {
75
76
  let meta = metaRoutes?.[r.path]?.[r.method]
77
+ let [_, summary, description] = meta?.head?.match(/^([^\n]*)\n\n(.*)/) || []
78
+ if(!summary) description = meta?.head
76
79
  let route = {
77
80
  ...r,
78
81
  ...(meta?.operationId ? { alias: meta?.operationId } : {}),
82
+ ...(summary ? { summary } : {}),
83
+ ...(description ? { description } : {}),
79
84
  pathT: r.path.replaceAll(/:([^\/]+)/g, '${$1}'),
80
85
  params:
81
86
  Object.fromEntries(
@@ -84,11 +89,11 @@ export default (cmd: Command) => {
84
89
  {
85
90
  ...(r.schema?.params?.[m?.[1]]
86
91
  ? {
87
- type: schemaToTypeStr(r.schema.params[m[1]]),
88
- ...(r.schema.params[m[1]]?.description
89
- ? { description: r.schema.params[m[1]].description as string }
90
- : {})
91
- }
92
+ type: schemaToTypeStr(r.schema.params[m[1]]),
93
+ ...(r.schema.params[m[1]]?.description
94
+ ? { description: r.schema.params[m[1]].description as string }
95
+ : {})
96
+ }
92
97
  : { type: 'string' })
93
98
  }
94
99
  ])
@@ -99,18 +104,22 @@ export default (cmd: Command) => {
99
104
  ...(r.schema.body ? { body: schemaToTypeStr(r.schema.body) } : {}),
100
105
  ...(r.schema.response
101
106
  ? {
102
- response: Object.fromEntries(
103
- Object.entries(r.schema.response).map(([k, v]) => [k === 'default' ? 200 : k, schemaToTypeStr(v)])
104
- )
105
- }
107
+ response: Object.fromEntries(
108
+ Object.entries(r.schema.response).map(([k, v]) => [k === 'default' ? '"default"' : k, schemaToTypeStr(v as STSchema)])
109
+ )
110
+ }
106
111
  : {})
107
112
  }
108
113
  }
114
+ Object.values(r.schema.response || {}).filter(s => s?.id).forEach(s => {
115
+ //@ts-ignore
116
+ types[s.id] = schemaToTypeStr(s)
117
+ })
109
118
  routes[r.method.toLocaleLowerCase()].push(route)
110
119
  if (target === 'cli' && meta?.operationId)
111
120
  commands.push({
112
121
  name: meta.operationId,
113
- description: meta.head,
122
+ description: route.summary || route.description,
114
123
  route,
115
124
  arguments:
116
125
  Object.entries((route?.params || {}) as Record<string, { type: string; description?: string }>)?.map(
@@ -144,7 +153,8 @@ export default (cmd: Command) => {
144
153
  const sandbox = {
145
154
  console,
146
155
  version: pckg?.version || '0.1.0',
147
- routes
156
+ routes,
157
+ types,
148
158
  }
149
159
  createContext(sandbox)
150
160
  let res = script.runInNewContext(sandbox)
@@ -195,6 +205,7 @@ export default (cmd: Command) => {
195
205
  await rm(resolve(CWD, '.galbe'), { recursive: true })
196
206
  }
197
207
 
198
- process.stdout.write(' : \x1b[1;30m\x1b[32mdone\x1b[0m\n')
208
+ Bun.write(Bun.stdout, ' : \x1b[1;30m\x1b[32mdone\x1b[0m\n')
209
+ process.exit(0)
199
210
  })
200
211
  }
@@ -68,6 +68,13 @@ const orderDeps = (deps: Record<string, SchemaEntry>) => {
68
68
  }
69
69
  return Object.fromEntries([...l].map(k => [k, deps[k]]))
70
70
  }
71
+ const serialize = (obj: any) => {
72
+ return JSON.stringify(obj, (k, value) => {
73
+ if (k === 'pattern' && value) return `/${value}/`
74
+ return value
75
+ }).replace(/"\/(.*)\/([gimsuy]*)"/g, '/$1/$2');
76
+ }
77
+
71
78
  const writeCodeFile = async (path: string, content: string, target: 'js' | 'ts') => {
72
79
  if (target === 'js') {
73
80
  content = transformSync(content, {
@@ -88,7 +95,9 @@ const parseOapiSchema = (
88
95
  details: { id?: string; title?: string; description?: string } = {},
89
96
  extra?: { media?: string }
90
97
  ): string => {
91
- if (!os) return `$T.any()`
98
+ if (!os) {
99
+ return `$T.any(${details && Object.keys(details).length ? JSON.stringify(details) : ''})`
100
+ }
92
101
  //@ts-ignore
93
102
  if (os?.$ref) return `%ref:${os.$ref}%`
94
103
  os = os as OpenAPIV3.SchemaObject
@@ -106,11 +115,12 @@ const parseOapiSchema = (
106
115
  } = {
107
116
  ...details,
108
117
  title: os.title,
109
- description: os.description
118
+ description: details.description || os.description
110
119
  }
120
+ if(!os?.type) return `$T.any(${details && Object.keys(details).length ? JSON.stringify(details) : ''})`
111
121
  let resp = ''
112
122
  let hasOptions = Object.values(options).some(v => !!v)
113
- let optArg = hasOptions ? `, ${JSON.stringify(options)}` : ''
123
+ let optArg = hasOptions ? serialize(options) : ''
114
124
  let anyOf = os.oneOf || os.anyOf || os.allOf
115
125
  let required = os.required
116
126
  let nullable = os.nullable
@@ -118,11 +128,11 @@ const parseOapiSchema = (
118
128
  if (anyOf?.length) {
119
129
  if (anyOf.length === 1) resp = parseOapiSchema(anyOf[0] as OpenAPIV3.SchemaObject, details, extra)
120
130
  else {
121
- resp = `$T.union([${anyOf.map(s => parseOapiSchema(s as OpenAPIV3.SchemaObject)).join(',')}], ${JSON.stringify(
131
+ resp = `$T.union([${anyOf.map(s => parseOapiSchema(s as OpenAPIV3.SchemaObject)).join(',')}], ${serialize(
122
132
  options
123
133
  )})`
124
134
  }
125
- } else if (os.type === 'boolean') resp = `$T.boolean(${hasOptions ? JSON.stringify(options) : ''})`
135
+ } else if (os.type === 'boolean') resp = `$T.boolean(${hasOptions ? serialize(options) : ''})`
126
136
  else if (os.type === 'number') {
127
137
  let { max, min, exclusiveMax, exclusiveMin } = {
128
138
  max: os.maximum !== undefined && !os.exclusiveMaximum ? os.maximum : undefined,
@@ -132,7 +142,7 @@ const parseOapiSchema = (
132
142
  }
133
143
  options = { ...options, min, max, exclusiveMax, exclusiveMin }
134
144
  hasOptions = Object.values(options).some(v => !!v)
135
- resp = `$T.number(${hasOptions ? JSON.stringify(options) : ''})`
145
+ resp = `$T.number(${hasOptions ? serialize(options) : ''})`
136
146
  } else if (os.type === 'integer') {
137
147
  let max = os.maximum !== undefined && !os.exclusiveMaximum ? os.maximum : undefined
138
148
  let min = os.minimum !== undefined && !os.exclusiveMinimum ? os.minimum : undefined
@@ -140,38 +150,35 @@ const parseOapiSchema = (
140
150
  let exclusiveMin = os.minimum !== undefined && os.exclusiveMinimum ? os.minimum : undefined
141
151
  options = { ...options, min, max, exclusiveMax, exclusiveMin }
142
152
  hasOptions = Object.values(options).some(v => !!v)
143
- resp = `$T.integer(${hasOptions ? JSON.stringify(options) : ''})`
153
+ resp = `$T.integer(${hasOptions ? serialize(options) : ''})`
144
154
  } else if (os.type === 'string') {
145
- if (os.format === 'binary') resp = `$T.byteArray(${hasOptions ? JSON.stringify(options) : ''})`
155
+ if (os.format === 'binary') resp = `$T.byteArray(${hasOptions ? serialize(options) : ''})`
146
156
  else {
147
157
  let minLength = os.minLength
148
158
  let maxLength = os.maxLength
149
159
  let pattern = os.pattern
150
160
  options = { ...options, minLength, maxLength, pattern }
151
161
  hasOptions = Object.values(options).some(v => !!v)
152
- resp = `$T.string(${hasOptions ? JSON.stringify(options) : ''})`
162
+ resp = `$T.string(${hasOptions ? serialize(options) : ''})`
153
163
  }
154
164
  } else if (os.type === 'array') {
155
165
  let minItems = os.minItems
156
166
  let maxItems = os.maxItems
157
167
  let unique = os.uniqueItems
158
168
  options = { ...options, minItems, maxItems, unique }
159
- resp = `$T.array(${parseOapiSchema(os?.items)}${optArg})`
169
+ resp = `$T.array(${parseOapiSchema(os?.items)}, ${optArg})`
160
170
  } else if (os.type === 'object') {
171
+ let props = Object.entries(os?.properties || {})
172
+ .map(([k, v]) => `"${k}":${parseOapiSchema(v)}`)
173
+ .join(',')
161
174
  if (extra?.media === 'multipart/form-data') {
162
- resp = `$T.multipartForm({${Object.entries(os?.properties || {})
163
- .map(([k, v]) => `${k}:${parseOapiSchema(v)}`)
164
- .join(',')}}${optArg})`
175
+ resp = `$T.multipartForm({${props}}${optArg ? `, ${optArg}`:''})`
165
176
  } else if (extra?.media === 'application/x-www-form-urlencoded') {
166
- resp = `$T.urlForm({${Object.entries(os?.properties || {})
167
- .map(([k, v]) => `${k}:${parseOapiSchema(v)}`)
168
- .join(',')}}${optArg})`
177
+ resp = `$T.urlForm({${props}}${optArg ? `, ${optArg}`:''})`
169
178
  } else {
170
- resp = `$T.object({${Object.entries(os?.properties || {})
171
- .map(([k, v]) => `${k}:${parseOapiSchema(v)}`)
172
- .join(',')}}${optArg})`
179
+ resp = `$T.object({${props}}${optArg ? `, ${optArg}`:''})`
173
180
  }
174
- } else throw new Error(`Unknown schema type ${os.type}`)
181
+ } else throw new Error(`Unknown schema type ${JSON.stringify(os)}`)
175
182
 
176
183
  if (!required && nullable) resp = `$T.nullish(${resp})`
177
184
  else if (!required) resp = `$T.optional(${resp})`
@@ -188,13 +195,21 @@ const buildSchemaIndex = (def: OpenAPIV3.Document) => {
188
195
  let dependsOn = new Set<string>()
189
196
  if (kind === 'schemas') schema = parseOapiSchema(s, { id: k })
190
197
  else if (kind === 'requestBodies' || kind === 'responses') {
191
- let schemas = [
192
- ...new Set(
193
- Object.entries((s as OpenAPIV3.RequestBodyObject).content || {}).map(([media, v]) =>
194
- parseOapiSchema(v.schema, { id: k }, { media })
198
+ let schemas=[] as string[]
199
+ if(!!s.content){
200
+
201
+ schemas = [
202
+ ...new Set(
203
+ Object.entries((s as OpenAPIV3.RequestBodyObject)?.content || {null:{}}).map(([media, v]) => {
204
+ return parseOapiSchema(v.schema, { id: k }, { media })
205
+ })
195
206
  )
196
- )
197
- ]
207
+ ]
208
+ } else {
209
+ schemas = [
210
+ parseOapiSchema(undefined, {id: k, ...s})
211
+ ]
212
+ }
198
213
  schema = schemas.length <= 0 ? '' : schemas.length === 1 ? schemas[0] : `$T.union([${schemas.join(',')}])`
199
214
  }
200
215
  schema = unref(schema, m => {
@@ -225,26 +240,33 @@ const parseEndpointDef = (method: string, path: string, def?: OpenAPIV3.Operatio
225
240
  if (!def) return {}
226
241
  let imports = {}
227
242
  let p = path.replaceAll(/\{([^\}]*)\}/g, ':$1')
228
- let description = def.summary || def.description
243
+ // let description = def.summary || def.description
229
244
  let pathName = path.replaceAll(/\/\{[^\}]*\}/g, 'X').replaceAll(/[^$\w\d_]+([$\w\d_])/g, (_, $1) => $1.toUpperCase())
230
245
  let schemaName = `${method}${pathName}`.replace(/^\w/, c => c.toUpperCase())
231
246
 
232
247
  let meta = '/**\n'
233
- if (description) meta += ` * ${description.replace('\n', '\n * ')}\n`
248
+ if (def.summary) meta += ` * ${def.summary}\n *\n`
249
+ if (def.description) meta += ` * ${def.description.replace('\n', '\n * ')}\n`
234
250
  if (def.operationId) meta += ` * @operationId ${def.operationId}\n`
235
251
  if (def.externalDocs) meta += ` * @externalDocs ${def.externalDocs}\n`
236
252
  if (def.tags) meta += ` * @tags ${def.tags.join(' ')}\n`
237
253
  if (def.deprecated) meta == ' * @deprecated\n'
238
254
  meta += ' */'
239
- let endpoint = `${method}("${p}", ${schemaName}, ctx => {\n throw new Error("Not implemented")\n})`
255
+ let endpoint = `${method}("${p}", ${schemaName}, ctx => {\n throw new NotImplementedError()\n})`
240
256
 
241
257
  let sp = { path: {}, query: {}, header: {}, body: {}, formData: {} } // TODO handle body and formData cases
242
258
  for (let _p of def?.parameters || []) {
243
259
  // @ts-ignore: TODO handle refs cases
244
260
  if (_p.$ref) continue
245
261
  let p = _p as OpenAPIV3.ParameterObject
246
- let o = (s: string) => (p.in !== 'path' && !p.required ? `$T.optional(${s})` : s)
247
- sp[p.in][p.name] = o(parseOapiSchema(p.schema))
262
+ let o = (s: string) => (p.in !== 'path' && !p.required && !/^\$T.optional\(.*\)$/.test(s) ? `$T.optional(${s})` : s)
263
+ sp[p.in][p.name] = o(
264
+ unref(parseOapiSchema(p.schema, {description: p.description }), m => {
265
+ let l = m.split('/')
266
+ imports[l[l.length - 1]] = m
267
+ return l[l.length - 1]
268
+ })
269
+ )
248
270
  }
249
271
 
250
272
  let [schemaParams, schemaQuery, schemaHeaders] = [
@@ -254,34 +276,36 @@ const parseEndpointDef = (method: string, path: string, def?: OpenAPIV3.Operatio
254
276
  ].map(({ g, o }) =>
255
277
  Object.keys(sp[o]).length
256
278
  ? ` ${g}: {${Object.entries(sp[o])
257
- .map(([k, v]) => `${k}:${v}`)
279
+ .map(([k, v]) => `"${k}":${v}`)
258
280
  .join(',')}}`
259
281
  : ''
260
282
  )
261
283
 
262
284
  let body = ''
263
- let _rb = def?.requestBody as OpenAPIV3.ReferenceObject
264
- if (_rb?.$ref) {
265
- body = unref(` body: %ref:${_rb.$ref}%`, m => {
266
- let l = m.split('/')
267
- imports[l[l.length - 1]] = m
268
- return l[l.length - 1]
269
- })
270
- } else {
271
- let rb = def?.requestBody as OpenAPIV3.RequestBodyObject
272
- let o = (s: string) => (!rb.required ? `$T.optional(${s})` : s)
273
- let bs = [
274
- ...new Set(
275
- Object.entries(rb?.content || {}).map(([media, v]) =>
276
- unref(parseOapiSchema(v.schema, undefined, { media }), m => {
277
- let l = m.split('/')
278
- imports[l[l.length - 1]] = m
279
- return l[l.length - 1]
280
- })
285
+ if(!['get', 'options', 'head'].includes(method)){
286
+ let _rb = def?.requestBody as OpenAPIV3.ReferenceObject
287
+ if (_rb?.$ref) {
288
+ body = unref(` body: %ref:${_rb.$ref}%`, m => {
289
+ let l = m.split('/')
290
+ imports[l[l.length - 1]] = m
291
+ return l[l.length - 1]
292
+ })
293
+ } else {
294
+ let rb = def?.requestBody as OpenAPIV3.RequestBodyObject
295
+ let o = (s: string) => (!rb?.required ? `$T.optional(${s})` : s)
296
+ let bs = [
297
+ ...new Set(
298
+ Object.entries(rb?.content || {null:{}}).map(([media, v]) =>
299
+ unref(parseOapiSchema(v.schema, undefined, { media }), m => {
300
+ let l = m.split('/')
301
+ imports[l[l.length - 1]] = m
302
+ return l[l.length - 1]
303
+ })
304
+ )
281
305
  )
282
- )
283
- ]
284
- body = bs.length === 1 ? ` body: ${o(bs[0])}` : bs.length > 1 ? ` body: ${o(`$T.union(${bs.join(',')})`)}` : ''
306
+ ]
307
+ body = bs.length === 1 ? ` body: ${o(bs[0])}` : bs.length > 1 ? ` body: ${o(`$T.union([${bs.join(',')}])`)}` : ''
308
+ }
285
309
  }
286
310
 
287
311
  let resp = ''
@@ -289,7 +313,18 @@ const parseEndpointDef = (method: string, path: string, def?: OpenAPIV3.Operatio
289
313
  let rs = Object.fromEntries(
290
314
  Object.entries(r || {}).map(([status, sv]) => {
291
315
  let entries: string[] = []
292
- let s: string = Number.isInteger(Number(status)) ? status : '200'
316
+ let s: string = Number.isInteger(Number(status)) ? status : 'default'
317
+
318
+ //@ts-ignore
319
+ let rootRef = sv?.$ref ? unref(parseOapiSchema(sv), m => {
320
+ let l = m.split('/')
321
+ imports[l[l.length - 1]] = m
322
+ return l[l.length - 1]
323
+ }) : null
324
+ if (rootRef) {
325
+ return [s, [rootRef]]
326
+ }
327
+
293
328
  for (let [_type, tv] of Object.entries((sv as OpenAPIV3.ResponseObject)?.content || {})) {
294
329
  entries.push(
295
330
  unref(parseOapiSchema(tv.schema), m => {
@@ -306,7 +341,7 @@ const parseEndpointDef = (method: string, path: string, def?: OpenAPIV3.Operatio
306
341
  if (Object.keys(rs).length) {
307
342
  resp = ` response: {${Object.entries(rs)
308
343
  .filter(([_, v]) => v.length)
309
- .map(([s, v]) => `${s}: ${v.length === 1 ? v[0] : v.length > 1 ? `$T.union(${v.join(',')})` : ''}`)
344
+ .map(([s, v]) => `${s}: ${v.length === 1 ? v[0] : v.length > 1 ? `$T.union([${v.join(',')}])` : ''}`)
310
345
  .join(',')}}`
311
346
  } else resp = ''
312
347
 
@@ -394,7 +429,7 @@ const writeFiles = async (
394
429
  })
395
430
  if (decl.length === 0) return ''
396
431
  return `import type { Static } from 'galbe/schema'\nimport { $T } from 'galbe'\n${Object.entries(imports)
397
- .map(([k, v]) => `import { ${v.join(', ')} } from './${typeMap[k]}.schema'\n`)
432
+ .map(([k, v]) => `import { ${[...new Set(v)].join(', ')} } from './${typeMap[k]}.schema'\n`)
398
433
  .join('\n')}\n${decl.join('\n')}\n`
399
434
  }
400
435
 
@@ -461,7 +496,7 @@ const writeFiles = async (
461
496
  `${sDecl.map(d => d).join('\n\n')}\n`
462
497
 
463
498
  let routeFile =
464
- `import type { Galbe } from 'galbe'\n` +
499
+ `import { NotImplementedError, type Galbe } from 'galbe'\n` +
465
500
  `import { ${[...rImports].join(', ')} } from '../schemas${scopeKey}.schema'\n\n` +
466
501
  `export default (g: Galbe) => {\n` +
467
502
  rDecl.map(d => d.replaceAll('\n', '\n ')).join('\n\n') +
@@ -43,20 +43,21 @@ export default (cmd: Command) => {
43
43
 
44
44
  if (!format) format = `openapi:3.0:${inputExt.slice(1)}`
45
45
 
46
- if ((await exists(resolve(CWD, out))) && !force) {
47
- console.log(
48
- `error: output directory ${fmtVal(
49
- out
50
- )} already exists. If you're sure you want to override its content, please remove it before or use the ${fmtVal(
51
- '-F --force'
52
- )} option`
53
- )
54
- process.exit(1)
46
+ if (await exists(resolve(CWD, out))) {
47
+ if(!force){
48
+ console.log(
49
+ `error: output directory ${fmtVal(
50
+ out
51
+ )} already exists. If you're sure you want to override its content, please remove it before or use the ${fmtVal(
52
+ '-F --force'
53
+ )} option`
54
+ )
55
+ process.exit(1)
56
+ }
57
+ await rm(resolve(CWD, out), { recursive: true })
55
58
  }
56
59
 
57
- await rm(resolve(CWD, out), { recursive: true })
58
-
59
- process.stdout.write('💻 \x1b[1;30mGenerating \x1b[36mGalbe\x1b[0m\x1b[1;30m sources\x1b[0m')
60
+ Bun.write(Bun.stdout, '💻 \x1b[1;30mGenerating \x1b[36mGalbe\x1b[0m\x1b[1;30m sources\x1b[0m')
60
61
  try {
61
62
  let match = format.match(/^([^:]*):([^:]*):(.*)$/)
62
63
  if (!match) throw new Error(`error: invalid format ${format}`)
@@ -70,6 +71,6 @@ export default (cmd: Command) => {
70
71
  console.log(`error: ${err.message}`)
71
72
  process.exit(1)
72
73
  }
73
- process.stdout.write(' : \x1b[1;30m\x1b[32mdone\x1b[0m\n')
74
+ Bun.write(Bun.stdout, ' : \x1b[1;30m\x1b[32mdone\x1b[0m\n')
74
75
  })
75
76
  }
@@ -54,7 +54,7 @@ export default (cmd: Command) => {
54
54
  pckg = await Bun.file(resolve(CWD, 'package.json')).json()
55
55
  } catch (e) {}
56
56
 
57
- process.stdout.write(`📖 \x1b[1;30mGenerating ${target.split(':')?.[0]} spec\x1b[0m`)
57
+ Bun.write(Bun.stdout, `📖 \x1b[1;30mGenerating ${target.split(':')?.[0]} spec\x1b[0m`)
58
58
 
59
59
  let error = null
60
60
  let g: Galbe = await silentExec(async () => {
@@ -89,6 +89,6 @@ export default (cmd: Command) => {
89
89
  Bun.write(resolve(CWD, out), tFormat === 'json' ? JSON.stringify(openapiSpec, null, 2) : ymlDump(openapiSpec))
90
90
  }
91
91
 
92
- process.stdout.write(' : \x1b[1;30m\x1b[32mdone\x1b[0m\n')
92
+ Bun.write(Bun.stdout, ' : \x1b[1;30m\x1b[32mdone\x1b[0m\n')
93
93
  })
94
94
  }