galbe 0.13.0 → 0.14.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,28 +3,30 @@ import type {
3
3
  STIntersection,
4
4
  STJson,
5
5
  STLiteral,
6
+ STNumber,
7
+ STInteger,
6
8
  STObject,
7
- STProps,
8
9
  STSchema,
10
+ STString,
9
11
  STUnion,
10
12
  } from '../../../src/schema'
11
13
 
12
14
  import { Galbe } from '../../../src'
13
- import { walkRoutes, HttpStatus, inferContentType } from '../../../src/util'
15
+ import { walkRoutes, HttpStatus } from '../../../src/util'
14
16
  import { Kind, Optional } from '../../../src/schema'
15
17
 
16
18
  import { OpenAPIV3 } from 'openapi-types'
17
19
 
18
20
  type SchemaType = { type: string; format: string; isJson: boolean }
19
21
 
20
- const schemaToMedia = ({ type, format, isJson }: SchemaType) =>
21
- isJson || (type && ['object', 'number', 'boolean', 'array'].includes(type))
22
+ const schemaToMedia = ({ type, format, isJson }: SchemaType, hasComposite = false) =>
23
+ isJson || hasComposite || (type && ['object', 'number', 'boolean', 'array'].includes(type))
22
24
  ? 'application/json'
23
- : format === 'byte'
24
- ? 'application/octet-stream'
25
- : type === 'string'
26
- ? 'text/plain'
27
- : '*/*'
25
+ : format === 'byte' || format === 'binary'
26
+ ? 'application/octet-stream'
27
+ : type === 'string'
28
+ ? 'text/plain'
29
+ : 'application/json'
28
30
 
29
31
  export const OpenAPISerializer = async (g: Galbe, version = '3.0.3'): Promise<OpenAPIV3.Document> => {
30
32
  let paths: any = {}
@@ -43,66 +45,53 @@ export const OpenAPISerializer = async (g: Galbe, version = '3.0.3'): Promise<Op
43
45
  let kind = schema[Kind]
44
46
  let isJson = false
45
47
 
46
- let pattern = schema?.pattern?.toString()
47
- if (pattern) pattern = pattern.substring(1, pattern.length - 1)
48
-
49
- let minLength = schema?.minLength
50
- let maxLength = schema?.maxLength
51
- let minimum = schema?.min
52
- let maximum = schema?.max
53
- let exclusiveMinimum = schema?.exclusiveMin
54
- let exclusiveMaximum = schema?.exclusiveMax
55
- let minItems = schema?.minItems
56
- let maxItems = schema?.maxItems
57
- let uniqueItems = schema?.unique
58
-
59
48
  if (components.schemas && (schema.id as string) in components.schemas) {
60
49
  //@ts-ignore
61
50
  return { schema: { $ref: `#/components/schemas/${schema.id}` } }
62
51
  }
63
52
 
64
53
  if (kind === 'null') {
65
- s = {
66
- anyOf: ['null'],
67
- }
54
+ // OpenAPI 3.0 has no first-class null type; the canonical workaround
55
+ // is `nullable: true` with `enum: [null]` to mean "must be null".
56
+ s = { nullable: true, enum: [null] }
68
57
  } else if (kind === 'boolean') s = { type: 'boolean' }
69
- else if (kind === 'byteArray') s = { type: 'string', format: 'byte' }
70
- else if (kind === 'number')
71
- s = {
72
- type: 'number',
73
- ...(exclusiveMinimum ? { exclusiveMinimum } : {}),
74
- ...(exclusiveMaximum ? { exclusiveMaximum } : {}),
75
- ...(minimum ? { minimum } : {}),
76
- ...(maximum ? { maximum } : {}),
77
- }
78
- else if (kind === 'integer')
58
+ else if (kind === 'byteArray') s = { type: 'string', format: 'binary' }
59
+ else if (kind === 'number' || kind === 'integer') {
60
+ const n = schema as STNumber | STInteger
79
61
  s = {
80
- type: 'integer',
81
- ...(exclusiveMinimum ? { exclusiveMinimum } : {}),
82
- ...(exclusiveMaximum ? { exclusiveMaximum } : {}),
83
- ...(minimum ? { minimum } : {}),
84
- ...(maximum ? { maximum } : {}),
62
+ type: kind,
63
+ ...(n.exclusiveMin !== undefined ? { exclusiveMinimum: n.exclusiveMin } : {}),
64
+ ...(n.exclusiveMax !== undefined ? { exclusiveMaximum: n.exclusiveMax } : {}),
65
+ ...(n.min !== undefined ? { minimum: n.min } : {}),
66
+ ...(n.max !== undefined ? { maximum: n.max } : {}),
85
67
  }
86
- else if (kind === 'string')
68
+ } else if (kind === 'string') {
69
+ const str = schema as STString
70
+ let pattern = str.pattern?.toString()
71
+ if (pattern) pattern = pattern.substring(1, pattern.length - 1)
87
72
  s = {
88
73
  type: 'string',
74
+ ...(str.format ? { format: str.format } : {}),
89
75
  ...(pattern ? { pattern } : {}),
90
- ...(minLength ? { minLength } : {}),
91
- ...(maxLength ? { maxLength } : {}),
76
+ ...(str.minLength !== undefined ? { minLength: str.minLength } : {}),
77
+ ...(str.maxLength !== undefined ? { maxLength: str.maxLength } : {}),
92
78
  }
93
- else if (kind === 'any') s = { type: 'string' }
79
+ } else if (kind === 'any') s = {}
94
80
  else if (kind === 'literal') {
95
81
  let value = (schema as STLiteral).value
96
82
  s = { type: 'string', enum: [value] }
97
83
  } else if (kind === 'array') {
84
+ const arr = schema as STArray
85
+ // ArrayOptions exposes minLength/maxLength (matching the schema-builder API);
86
+ // OpenAPI calls them minItems/maxItems.
98
87
  s = {
99
88
  type: 'array',
100
- items: schemaToOpenapi((schema as STArray).items).schema,
101
- ...(minItems ? { minItems } : {}),
102
- ...(maxItems ? { maxItems } : {}),
103
- ...(uniqueItems ? { uniqueItems } : {}),
89
+ items: schemaToOpenapi(arr.items).schema,
90
+ ...(arr.minLength !== undefined ? { minItems: arr.minLength } : {}),
91
+ ...(arr.maxLength !== undefined ? { maxItems: arr.maxLength } : {}),
92
+ ...(arr.unique ? { uniqueItems: arr.unique } : {}),
104
93
  }
105
- } else if (kind === 'object') {
94
+ } else if (kind === 'object' || kind === 'multipartForm') {
106
95
  let props = (schema as STObject).props || {}
107
96
  let required = Object.entries(props)
108
97
  .filter(([_, v]) => !v?.[Optional])
@@ -113,41 +102,33 @@ export const OpenAPISerializer = async (g: Galbe, version = '3.0.3'): Promise<Op
113
102
  ...(required.length ? { required } : {}),
114
103
  }
115
104
  } else if (kind === 'json') {
116
- let props = ((schema as STJson).props || {}) as STProps
117
- let type = (schema as STJson).type
118
- if (type === 'unknown') type = 'object'
119
- let required = Object.entries(props)
120
- .filter(([_, v]) => !v?.[Optional])
121
- .map(([k, _]) => k)
105
+ const inner = (schema as STJson).value as STSchema | undefined
122
106
  isJson = true
123
- s = {
124
- type: type,
125
- ...(type === 'object'
126
- ? {
127
- properties: Object.fromEntries(Object.entries(props).map(([k, v]) => [k, schemaToOpenapi(v).schema])),
128
- ...(required.length ? { required } : {}),
129
- }
130
- : {}),
131
- }
132
- } else if (kind === 'union') {
133
- let anyOf: STSchema[] = (schema as STUnion).anyOf
134
- let nullable = anyOf.some(s => s[Kind] === 'null')
135
- anyOf = anyOf.filter(s => s[Kind] !== 'null')
107
+ s = inner ? schemaToOpenapi(inner).schema : { type: 'object' }
108
+ } else if (kind === 'anyOf' || kind === 'oneOf') {
109
+ let members: STSchema[] = (schema as STUnion).members
110
+ let nullable = members.some(s => s[Kind] === 'null')
111
+ members = members.filter(s => s[Kind] !== 'null')
136
112
 
137
- if (anyOf.length === 0) {
113
+ const allStringLiterals =
114
+ members.length > 0 && members.every(e => e[Kind] === 'literal' && typeof (e as STLiteral).value === 'string')
115
+ const useOneOf = kind === 'oneOf'
116
+
117
+ if (members.length === 0) {
138
118
  s = {}
139
- } else if (anyOf.length === 1) {
140
- s = schemaToOpenapi(anyOf[0]).schema
141
- } else if (anyOf.length > 1) {
142
- s = {
143
- anyOf: anyOf.map(e => schemaToOpenapi(e).schema),
144
- }
119
+ } else if (members.length === 1) {
120
+ s = schemaToOpenapi(members[0]).schema
121
+ } else if (allStringLiterals && !useOneOf) {
122
+ s = { type: 'string', enum: members.map(e => (e as STLiteral).value) }
123
+ } else if (members.length > 1) {
124
+ const variants = members.map(e => schemaToOpenapi(e).schema)
125
+ s = useOneOf ? { oneOf: variants } : { anyOf: variants }
145
126
  }
146
127
 
147
128
  //@ts-ignore
148
129
  if (nullable) s.nullable = nullable
149
130
  } else if (kind === 'intersection') {
150
- let allOf: STSchema[] = (schema as STIntersection).allOf
131
+ let allOf: STSchema[] = (schema as STIntersection<any>).allOf
151
132
  if (allOf.length === 0) {
152
133
  s = {}
153
134
  } else if (allOf.length === 1) {
@@ -159,7 +140,13 @@ export const OpenAPISerializer = async (g: Galbe, version = '3.0.3'): Promise<Op
159
140
  }
160
141
  }
161
142
 
162
- s = { title: schema.title, description: schema.description, ...s }
143
+ s = {
144
+ title: schema.title,
145
+ description: schema.description,
146
+ ...s,
147
+ ...(schema?.default !== undefined ? { default: schema.default } : {}),
148
+ ...(schema?.examples !== undefined ? { example: schema.examples } : {}),
149
+ }
163
150
  if (components.schemas && schema.id) {
164
151
  components.schemas[schema.id] = s
165
152
  return { schema: { $ref: `#/components/schemas/${schema.id}` } }
@@ -190,7 +177,6 @@ export const OpenAPISerializer = async (g: Galbe, version = '3.0.3'): Promise<Op
190
177
  deprecated: param.deprecated,
191
178
  schema,
192
179
  }
193
- if (components.parameters && param.id) components.parameters[param.id] = p
194
180
  return p
195
181
  }
196
182
 
@@ -211,6 +197,25 @@ export const OpenAPISerializer = async (g: Galbe, version = '3.0.3'): Promise<Op
211
197
  ...(typeof meta?.tag === 'string' ? [meta?.tag] : meta?.tag || []),
212
198
  ]
213
199
  let security: Record<string, any> = []
200
+ let securityExplicitlyEmpty = false
201
+
202
+ const metaSecRaw = meta?.security
203
+ if (metaSecRaw !== undefined) {
204
+ const entries = Array.isArray(metaSecRaw) ? metaSecRaw : [metaSecRaw]
205
+ for (const e of entries) {
206
+ if (typeof e !== 'string') continue
207
+ const trimmed = e.trim()
208
+ if (trimmed === 'none' || trimmed === '') {
209
+ securityExplicitlyEmpty = true
210
+ } else {
211
+ const [name, ...scopes] = trimmed.split(/\s+/)
212
+ security.push({ [name]: scopes })
213
+ if (name === 'bearerAuth' && components.securitySchemes && !components.securitySchemes.bearerAuth) {
214
+ components.securitySchemes.bearerAuth = { type: 'http', scheme: 'bearer' }
215
+ }
216
+ }
217
+ }
218
+ }
214
219
 
215
220
  let pathParam = r.schema?.params
216
221
  ? Object.entries(r.schema?.params as Record<string, STSchema>).map(([k, v]) => parseParam(k, v, 'path'))
@@ -218,15 +223,21 @@ export const OpenAPISerializer = async (g: Galbe, version = '3.0.3'): Promise<Op
218
223
  let queryParam = r.schema?.query
219
224
  ? Object.entries(r.schema?.query as Record<string, STSchema>).map(([k, v]) => parseParam(k, v, 'query'))
220
225
  : []
226
+ const metaSecuritySet = security.length > 0
221
227
  let headerParam = r.schema?.headers
222
228
  ? Object.entries(r.schema?.headers as Record<string, STSchema>)
223
229
  .map(([k, v]) => {
224
230
  let p = parseParam(k, v, 'header')
225
231
  if (k.match(/authorization/i)) {
226
232
  // TODO: handle other auth methods
227
- if (v.pattern && v?.pattern?.toString() === '/^Bearer /') {
228
- security.push({ bearerAuth: [] })
229
- components.securitySchemes = { bearerAuth: { type: 'http', scheme: 'bearer' } }
233
+ const str = v as STString
234
+ if (str.pattern && str.pattern.toString() === '/^Bearer /') {
235
+ if (!metaSecuritySet) security.push({ bearerAuth: [] })
236
+ const scheme: OpenAPIV3.HttpSecurityScheme = { type: 'http', scheme: 'bearer' }
237
+ if (typeof str.format === 'string') scheme.bearerFormat = str.format
238
+ if (typeof str.description === 'string') scheme.description = str.description
239
+ if (!components.securitySchemes) components.securitySchemes = {}
240
+ components.securitySchemes.bearerAuth = scheme
230
241
  return null
231
242
  }
232
243
  }
@@ -246,7 +257,7 @@ export const OpenAPISerializer = async (g: Galbe, version = '3.0.3'): Promise<Op
246
257
  Object.entries(r.schema.body).map(([bodyType, schema]) => {
247
258
  const s = schema.description
248
259
  const isDefined = typeof s === 'string' && s !== ''
249
- if (s?.[Optional] === false) required = true
260
+ if (!schema?.[Optional]) required = true
250
261
  if (isDefined) {
251
262
  if (description === undefined) {
252
263
  description = s
@@ -254,8 +265,8 @@ export const OpenAPISerializer = async (g: Galbe, version = '3.0.3'): Promise<Op
254
265
  conflictDescription = true
255
266
  }
256
267
  }
257
- description = conflictDescription ? undefined : description ?? undefined
258
- return [inferContentType(bodyType), { schema: schemaToOpenapi(schema).schema }]
268
+ description = conflictDescription ? undefined : (description ?? undefined)
269
+ return [bodyType, { schema: schemaToOpenapi(schema).schema }]
259
270
  })
260
271
  )
261
272
  requestBody = {
@@ -270,17 +281,74 @@ export const OpenAPISerializer = async (g: Galbe, version = '3.0.3'): Promise<Op
270
281
  Object.entries(r.schema.response).map(([status, v]) => {
271
282
  if (!v) return []
272
283
  let s = status as keyof typeof HttpStatus | 'default'
273
- let { schema, isJson } = schemaToOpenapi(v)
274
- let { type, format } = resolveRef(schema)
275
- let media = schemaToMedia({ type, format, isJson } as SchemaType)
276
- let response: OpenAPIV3.ResponseObject = {
277
- description: v.description || HttpStatus[s as keyof typeof HttpStatus] || 'Response',
278
- content: { [media]: { schema: schema } },
284
+ const isContentMap = !(v as any)[Kind]
285
+ const explicitHeaders = (v as any)?.responseHeaders as Record<string, STSchema> | undefined
286
+ let response: OpenAPIV3.ResponseObject
287
+
288
+ if (isContentMap) {
289
+ // STResponseContent iterate body-type keys
290
+ const cm = v as any
291
+ const desc = cm.description || HttpStatus[s as keyof typeof HttpStatus] || 'Response'
292
+ const content: Record<string, { schema: any; example?: any; examples?: Record<string, any> }> = {}
293
+ for (const [key, bodySchema] of Object.entries(cm)) {
294
+ if (key === 'description' || key === 'responseHeaders' || key === 'example' || key === 'examples') continue
295
+ const { schema: oaSchema } = schemaToOpenapi(bodySchema as STSchema)
296
+ content[key] = { schema: oaSchema }
297
+ const ex = (bodySchema as any)?.examples
298
+ const exSingle = (bodySchema as any)?.example
299
+ if (ex && Object.keys(ex).length) content[key].examples = ex
300
+ if (exSingle !== undefined) content[key].example = exSingle
301
+ }
302
+ response = { description: desc, ...(Object.keys(content).length ? { content } : {}) }
303
+ } else {
304
+ const statusNum = Number(s)
305
+ const noBodyStatus = statusNum === 204 || statusNum === 304 || (statusNum >= 100 && statusNum < 200)
306
+ const noContent = noBodyStatus && (v as any)[Kind] === 'null'
307
+ if (noContent) {
308
+ response = {
309
+ description: (v as any).description || HttpStatus[s as keyof typeof HttpStatus] || 'Response',
310
+ }
311
+ } else {
312
+ let { schema, isJson } = schemaToOpenapi(v as STSchema)
313
+ let resolved = resolveRef(schema)
314
+ let { type, format } = resolved
315
+ let hasComposite = !!(resolved as any)?.allOf || !!(resolved as any)?.anyOf || !!(resolved as any)?.oneOf
316
+ const mediaType = schemaToMedia({ type, format, isJson } as SchemaType, hasComposite)
317
+ const explicitExamples = (v as any)?.examples as Record<string, any> | undefined
318
+ const explicitExample = (v as any)?.example
319
+ const content: Record<string, { schema: typeof schema; example?: any; examples?: Record<string, any> }> = {
320
+ [mediaType]: { schema: { ...schema } },
321
+ }
322
+ if (explicitExamples && Object.keys(explicitExamples).length) content[mediaType].examples = explicitExamples
323
+ if (explicitExample !== undefined) content[mediaType].example = explicitExample
324
+ response = {
325
+ description: (v as any).description || HttpStatus[s as keyof typeof HttpStatus] || 'Response',
326
+ content,
327
+ }
328
+ }
329
+ const respSchema = r.schema.response?.[s] as any
330
+ const respId = respSchema?._responseId
331
+ if (components.responses && respId) {
332
+ components.responses[respId as string] = response
333
+ //@ts-ignore
334
+ response = { $ref: `#/components/responses/${respId}` }
335
+ }
279
336
  }
280
- if (components.responses && r.schema.response?.[s]?.id) {
281
- components.responses[r.schema.response?.[s]?.id as string] = response
282
- //@ts-ignore
283
- response = { $ref: `#/components/responses/${r.schema.response?.[s].id}` }
337
+
338
+ if (explicitHeaders && Object.keys(explicitHeaders).length) {
339
+ response.headers = {}
340
+ for (const [hName, hSchema] of Object.entries(explicitHeaders)) {
341
+ const isRequired = !(hSchema as any)?.[Optional]
342
+ const stripped = { ...(hSchema as any), [Optional]: false } as STSchema
343
+ const { schema: hSer } = schemaToOpenapi(stripped)
344
+ const headerObj: OpenAPIV3.HeaderObject = {
345
+ ...((hSchema as any)?.description ? { description: (hSchema as any).description } : {}),
346
+ ...(isRequired ? { required: true } : {}),
347
+ schema: hSer,
348
+ }
349
+ if ((hSer as any)?.description) delete (hSer as any).description
350
+ response.headers[hName] = headerObj
351
+ }
284
352
  }
285
353
  return [s, response]
286
354
  })
@@ -290,24 +358,91 @@ export const OpenAPISerializer = async (g: Galbe, version = '3.0.3'): Promise<Op
290
358
  default: { description: HttpStatus[200] },
291
359
  }
292
360
  }
293
- let summary = meta?.head.match(/^([^\n]+)/)?.[1]
361
+ const head: string = meta?.head ?? ''
362
+ let summary: string | undefined
363
+ let description: string | undefined
364
+ if (head) {
365
+ const firstBlank = head.indexOf('\n\n')
366
+ if (firstBlank === -1) {
367
+ const nl = head.indexOf('\n')
368
+ summary = (nl === -1 ? head : head.slice(0, nl)).trim() || undefined
369
+ } else {
370
+ summary = head.slice(0, firstBlank).trim() || undefined
371
+ description = head.slice(firstBlank + 2).trim() || undefined
372
+ }
373
+ }
294
374
  paths[path][r.method] = {
295
375
  tags: tags.length ? tags : undefined,
296
- summary: summary,
376
+ summary,
377
+ description,
297
378
  operationId: meta?.operationId,
298
379
  parameters: parameters.length ? parameters : undefined,
299
380
  requestBody,
300
381
  responses,
301
- ...(security.length ? { security } : {}),
382
+ ...(security.length ? { security } : securityExplicitlyEmpty ? { security: [] } : {}),
302
383
  deprecated: meta?.deprecated ? true : undefined,
303
384
  }
304
385
  })
305
386
 
387
+ // Promote parameters that appear with the exact same shape on more than
388
+ // one operation into components.parameters and replace each occurrence
389
+ // with a $ref. Naming: the parameter's `name`, capitalised; collisions
390
+ // with different shapes get suffixed.
391
+ const stableStringify = (v: any): string => {
392
+ if (v === null || typeof v !== 'object') return JSON.stringify(v)
393
+ if (Array.isArray(v)) return `[${v.map(stableStringify).join(',')}]`
394
+ const keys = Object.keys(v).sort()
395
+ return `{${keys.map(k => `${JSON.stringify(k)}:${stableStringify(v[k])}`).join(',')}}`
396
+ }
397
+ const paramHashes = new Map<string, { count: number; param: any }>()
398
+ for (const path of Object.values(paths) as any[]) {
399
+ for (const m of Object.keys(path)) {
400
+ if (m === 'parameters') continue
401
+ for (const p of path[m]?.parameters || []) {
402
+ if (p.$ref) continue
403
+ const key = stableStringify(p)
404
+ const entry = paramHashes.get(key)
405
+ if (entry) entry.count++
406
+ else paramHashes.set(key, { count: 1, param: p })
407
+ }
408
+ }
409
+ }
410
+ const cap = (s: string) => (s ? s[0].toUpperCase() + s.slice(1) : s)
411
+ const promoted = new Map<string, string>() // hash -> component name
412
+ const usedNames = new Set<string>(Object.keys(components.parameters || {}))
413
+ for (const [hash, { count, param }] of paramHashes) {
414
+ if (count < 2) continue
415
+ let base = cap(String(param.name || 'Param')).replace(/[^A-Za-z0-9]/g, '')
416
+ let name = base
417
+ let i = 2
418
+ while (usedNames.has(name)) name = `${base}${i++}`
419
+ usedNames.add(name)
420
+ promoted.set(hash, name)
421
+ components.parameters![name] = param
422
+ }
423
+ if (promoted.size) {
424
+ for (const path of Object.values(paths) as any[]) {
425
+ for (const m of Object.keys(path)) {
426
+ if (m === 'parameters') continue
427
+ const op = path[m]
428
+ if (!op?.parameters) continue
429
+ op.parameters = op.parameters.map((p: any) => {
430
+ if (p.$ref) return p
431
+ const name = promoted.get(stableStringify(p))
432
+ return name ? { $ref: `#/components/parameters/${name}` } : p
433
+ })
434
+ }
435
+ }
436
+ }
437
+
306
438
  //@ts-ignore
307
- components = Object.entries(components).reduce((p, [k, v]) => {
308
- if (Object.keys(v).length) p[k] = v
309
- return p
310
- }, {} as Record<string, OpenAPIV3.ComponentsObject>)
439
+ components = Object.entries(components).reduce(
440
+ (p, [k, v]) => {
441
+ if (Object.keys(v).length) p[k] = v
442
+ return p
443
+ },
444
+ {} as Record<string, OpenAPIV3.ComponentsObject>
445
+ )
311
446
  return {
312
447
  openapi: version,
313
448
  info: {
package/src/extras.ts CHANGED
@@ -1 +1,2 @@
1
1
  export { OpenAPISerializer } from './extras/spec/openapi.serializer'
2
+ export type { GalbeCLICommand, GalbeCLIOptions } from './types'
package/src/index.ts CHANGED
@@ -8,6 +8,7 @@ import type {
8
8
  Handler,
9
9
  Endpoint,
10
10
  Context,
11
+ ContextSet,
11
12
  ErrorHandler,
12
13
  GalbePlugin,
13
14
  STBody,
@@ -86,14 +87,9 @@ const galbeMethod = <
86
87
  query: {} as Static<STObject<Exclude<(typeof schema)['query'], undefined>>>,
87
88
  body: ['get', 'options', 'head'].includes(method) ? null : ({} as unknown as STBodyValue),
88
89
  request: {} as Request,
90
+ cookies: {} as Record<string, string>,
89
91
  state: {},
90
- set: {} as {
91
- headers: {
92
- 'set-cookie': string[]
93
- [header: string]: string | string[]
94
- }
95
- status?: number
96
- },
92
+ set: {} as ContextSet,
97
93
  }
98
94
  return {
99
95
  method,
@@ -109,6 +105,7 @@ const galbeMethod = <
109
105
  export const $T = new SchemaType()
110
106
 
111
107
  export { RequestError } from './types'
108
+ export type { STResponseContent, STResponseBodyKey, STResponseEntry } from './types'
112
109
 
113
110
  export const config = (config: GalbeConfig) => config
114
111