starlight-server 1.3.0 → 1.5.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.
Files changed (84) hide show
  1. package/README.md +11 -1
  2. package/dist/demo/index.js +41 -31
  3. package/dist/http/cors.d.ts +33 -0
  4. package/dist/http/cors.js +33 -0
  5. package/dist/http/index.d.ts +1 -0
  6. package/dist/http/index.js +1 -0
  7. package/dist/http/mime-types.js +1 -20
  8. package/dist/http/request.d.ts +18 -2
  9. package/dist/http/request.js +28 -7
  10. package/dist/http/response.d.ts +9 -5
  11. package/dist/http/response.js +16 -8
  12. package/dist/http/server.d.ts +1 -1
  13. package/dist/http/server.js +1 -1
  14. package/dist/index.d.ts +3 -1
  15. package/dist/index.js +3 -2
  16. package/dist/logging.d.ts +4 -3
  17. package/dist/logging.js +7 -19
  18. package/dist/router/helpers.d.ts +6 -0
  19. package/dist/router/helpers.js +24 -0
  20. package/dist/router/index.d.ts +67 -38
  21. package/dist/router/index.js +107 -36
  22. package/dist/router/match-path.d.ts +13 -0
  23. package/dist/router/match-path.js +160 -0
  24. package/dist/swagger/factories.d.ts +143 -0
  25. package/dist/swagger/factories.js +231 -0
  26. package/dist/swagger/index.d.ts +54 -6
  27. package/dist/swagger/index.js +94 -58
  28. package/dist/swagger/specification.d.ts +802 -0
  29. package/dist/swagger/specification.js +130 -0
  30. package/package.json +9 -9
  31. package/src/demo/index.ts +45 -46
  32. package/src/http/cors.ts +59 -0
  33. package/src/http/index.ts +1 -0
  34. package/src/http/mime-types.ts +1 -20
  35. package/src/http/request.ts +38 -10
  36. package/src/http/response.ts +18 -17
  37. package/src/http/server.ts +2 -2
  38. package/src/index.ts +3 -2
  39. package/src/logging.ts +7 -21
  40. package/src/router/helpers.ts +27 -0
  41. package/src/router/index.ts +141 -38
  42. package/src/router/match-path.ts +178 -0
  43. package/src/swagger/factories.ts +345 -0
  44. package/src/swagger/index.ts +113 -80
  45. package/src/swagger/specification.ts +823 -0
  46. package/dist/router/cors.d.ts +0 -24
  47. package/dist/router/cors.js +0 -35
  48. package/dist/router/match.d.ts +0 -23
  49. package/dist/router/match.js +0 -172
  50. package/dist/router/parameters.d.ts +0 -50
  51. package/dist/router/parameters.js +0 -118
  52. package/dist/router/router.d.ts +0 -128
  53. package/dist/router/router.js +0 -97
  54. package/dist/swagger/factory.d.ts +0 -114
  55. package/dist/swagger/factory.js +0 -159
  56. package/dist/swagger/openapi-spec.d.ts +0 -261
  57. package/dist/swagger/openapi-spec.js +0 -5
  58. package/dist/validators/array.d.ts +0 -9
  59. package/dist/validators/array.js +0 -28
  60. package/dist/validators/boolean.d.ts +0 -4
  61. package/dist/validators/boolean.js +0 -28
  62. package/dist/validators/common.d.ts +0 -23
  63. package/dist/validators/common.js +0 -25
  64. package/dist/validators/index.d.ts +0 -20
  65. package/dist/validators/index.js +0 -38
  66. package/dist/validators/number.d.ts +0 -10
  67. package/dist/validators/number.js +0 -30
  68. package/dist/validators/object.d.ts +0 -13
  69. package/dist/validators/object.js +0 -36
  70. package/dist/validators/string.d.ts +0 -11
  71. package/dist/validators/string.js +0 -29
  72. package/src/router/cors.ts +0 -54
  73. package/src/router/match.ts +0 -194
  74. package/src/router/parameters.ts +0 -175
  75. package/src/router/router.ts +0 -234
  76. package/src/swagger/factory.ts +0 -184
  77. package/src/swagger/openapi-spec.ts +0 -312
  78. package/src/validators/array.ts +0 -33
  79. package/src/validators/boolean.ts +0 -23
  80. package/src/validators/common.ts +0 -46
  81. package/src/validators/index.ts +0 -50
  82. package/src/validators/number.ts +0 -36
  83. package/src/validators/object.ts +0 -41
  84. package/src/validators/string.ts +0 -38
@@ -1,96 +1,129 @@
1
1
  import fsPromise from 'node:fs/promises'
2
2
  import path from 'node:path'
3
+ import { clearSlash } from '@anjianshi/utils'
4
+ import { isFileExists } from '@anjianshi/utils/env-node/index.js'
5
+ import defaultsDeep from 'lodash/defaultsDeep.js'
3
6
  import { getAbsoluteFSPath } from 'swagger-ui-dist'
4
- import { HTTPError } from '@/http/index.js'
5
- import type { Router, PathParameters, BaseContext } from '@/router/index.js'
6
- import { normalizePath } from '@/router/match.js'
7
- import * as factory from './factory.js'
8
- import type { OpenAPI } from './openapi-spec.js'
7
+ import { type ResponseUtils } from '@/http/index.js'
8
+ import * as factories from './factories.js'
9
+ import { type DocumentOptions, type OperationOptions } from './factories.js'
10
+ import type {
11
+ OpenAPI,
12
+ Schema,
13
+ Response,
14
+ Parameter,
15
+ RequestBody,
16
+ Method,
17
+ LowerMethod,
18
+ Operation,
19
+ } from './specification.js'
9
20
 
10
- export function registerSwaggerRoute<Ctx extends BaseContext>(
11
- router: Router<Ctx>,
12
- endpoint: string = '/swagger',
13
- customFields: OpenAPICustomFields = { info: { title: 'API Document', version: '0.0.1' } },
14
- swaggerOptions?: Record<string, unknown>,
15
- ) {
16
- router.register({
17
- category: 'swagger',
18
- method: 'GET',
19
- path: normalizePath(endpoint) + '/*',
20
- handler: pageRoute.bind(null, swaggerOptions),
21
- })
22
- router.register({
23
- category: 'swagger',
24
- method: 'GET',
25
- path: normalizePath(endpoint) + '/api-swagger.json',
26
- handler: (context: Ctx) => apiRoute(router, customFields, context),
27
- })
28
- }
21
+ export * from './specification.js'
29
22
 
30
23
  /**
31
- * swagger 页面路由
24
+ * OpenAPI 文档输出成 Swagger 页面
32
25
  */
33
- async function pageRoute(
34
- swaggerOptions: Record<string, unknown> | undefined, // Example: { defaultModelsExpandDepth: 1, defaultModelExpandDepth: 1 }
35
- { response }: BaseContext,
36
- pathParameters: PathParameters,
37
- ) {
38
- const file = (pathParameters['*'] ?? '') || 'index.html'
26
+ export class Swagger {
27
+ /**
28
+ * Swagger 页面配置
29
+ * 支持的配置项见:https://swagger.io/docs/open-source-tools/swagger-ui/usage/configuration/
30
+ */
31
+ readonly uiConfig: Record<string, unknown> | null
39
32
 
40
- const swaggerDir = getAbsoluteFSPath()
41
- const abspath = path.join(swaggerDir, file)
42
- if (!cache.has(abspath)) {
43
- try {
44
- const stat = await fsPromise.stat(abspath)
45
- if (!stat.isFile()) throw new HTTPError(404)
46
- } catch (e) {
47
- throw new HTTPError(404)
48
- }
49
- const content = await fsPromise.readFile(abspath)
50
- const replacemented = replacement(abspath, content, swaggerOptions)
51
- cache.set(abspath, replacemented)
33
+ /**
34
+ * OpenAPI 文档内容
35
+ */
36
+ readonly document: OpenAPI
37
+
38
+ constructor(uiConfig?: Record<string, unknown>, document?: DocumentOptions) {
39
+ this.uiConfig = uiConfig ?? null
40
+ this.document = factories.makeDocument(document)
52
41
  }
53
- response.text(cache.get(abspath)!)
54
- }
55
42
 
56
- const cache = new Map<string, Buffer | string>()
43
+ /**
44
+ * 注册 Operation
45
+ */
46
+ registerOperation(method: Method, path: string, operation: Operation | OperationOptions) {
47
+ if (factories.isOperationOptions(operation)) operation = factories.makeOperation(operation)
57
48
 
58
- function replacement(abspath: string, content: Buffer, swaggerOptions?: Record<string, unknown>) {
59
- if (/swagger-initializer.js/.exec(abspath)) {
60
- let formatted = content
61
- .toString()
62
- .replace('https://petstore.swagger.io/v2/swagger.json', './api-swagger.json')
63
- if (swaggerOptions) {
64
- formatted = formatted.replace(
65
- 'SwaggerUIBundle({',
66
- 'SwaggerUIBundle({\n ' + JSON.stringify(swaggerOptions).slice(1, -1) + ',\n',
67
- )
68
- }
69
- return formatted
49
+ defaultsDeep(this.document.paths, { [path]: {} })
50
+ const pathItem = this.document.paths[path]!
51
+ if ('$ref' in pathItem) throw new Error('接口文档注册失败,目标路径已注册为 Reference')
52
+ pathItem[method.toLowerCase() as LowerMethod] = operation
70
53
  }
71
- return content
72
- }
73
54
 
74
- /**
75
- * swagger API 路由
76
- */
77
- type OpenAPICustomFields = Omit<OpenAPI, 'openapi' | 'paths' | 'components'>
55
+ /**
56
+ * 注册 OpenAPI components 内容(以便后续引用)
57
+ */
58
+ registerSchema(name: string, content: Schema) {
59
+ defaultsDeep(this.document, { components: { schemas: {} } })
60
+ this.document.components!.schemas![name] = content
61
+ }
62
+ registerResponse(name: string, content: Response) {
63
+ defaultsDeep(this.document, { components: { responses: {} } })
64
+ this.document.components!.responses![name] = content
65
+ }
66
+ registerParameter(name: string, content: Parameter) {
67
+ defaultsDeep(this.document, { components: { parameters: {} } })
68
+ this.document.components!.parameters![name] = content
69
+ }
70
+ registerRequestBody(name: string, content: RequestBody) {
71
+ defaultsDeep(this.document, { components: { requestBodies: {} } })
72
+ this.document.components!.requestBodies![name] = content
73
+ }
74
+
75
+ /**
76
+ * factories
77
+ */
78
+ operation = factories.makeOperation
79
+ query = factories.makeQuery
80
+ header = factories.makeHeader
81
+ body = factories.makeBody
82
+ responses = factories.makeResponses
83
+ response = factories.makeResponse
84
+ string = factories.makeString
85
+ number = factories.makeNumber
86
+ integer = factories.makeInteger
87
+ boolean = factories.makeBoolean
88
+ null = factories.makeNull
89
+ array = factories.makeArray
90
+ object = factories.makeObject
91
+ nullable = factories.makeNullable
92
+ maySuccess = factories.makeMaySuccess
93
+ ref = factories.makeReference
78
94
 
79
- function apiRoute<Ctx extends BaseContext>(
80
- router: Router<Ctx>,
81
- customFields: OpenAPICustomFields,
82
- { response }: BaseContext,
83
- ) {
84
- const swaggerJSON: OpenAPI = {
85
- ...customFields,
86
- openapi: '3.1.0',
87
- paths: factory.paths(router.routes),
88
- components: {
89
- schemas: Object.entries(router.responseReferences).reduce(
90
- (schemas, [name, type]) => ({ ...schemas, [name]: factory.responseSchema(type) }),
91
- {},
92
- ),
93
- },
95
+ /**
96
+ * 输出 Swagger 内容
97
+ * Swagger 内容包含 HTML、各类资源文件和接口数据 JSON,都通过此方法输出,此方法通过 filepath 判断要输出什么。
98
+ */
99
+ private readonly fileCache = new Map<string, string>()
100
+ async output(response: ResponseUtils, filepath: string = '') {
101
+ filepath = clearSlash(filepath)
102
+
103
+ // 输出接口 JSON 数据
104
+ if (filepath === 'api-swagger.json') return void response.json(this.document)
105
+
106
+ // 输出 Swagger 静态文件内容
107
+ const swaggerDir = getAbsoluteFSPath()
108
+ const abspath = path.join(swaggerDir, filepath || 'index.html')
109
+ if (!this.fileCache.has(abspath)) {
110
+ if (!(await isFileExists(abspath))) return void response.error(404)
111
+
112
+ let content = (await fsPromise.readFile(abspath)).toString()
113
+ if (filepath.endsWith('swagger-initializer.js')) {
114
+ content = content.replace(
115
+ 'https://petstore.swagger.io/v2/swagger.json',
116
+ './api-swagger.json'
117
+ )
118
+ if (this.uiConfig) {
119
+ content = content.replace(
120
+ 'SwaggerUIBundle({',
121
+ 'SwaggerUIBundle({\n ' + JSON.stringify(this.uiConfig).slice(1, -1) + ',\n'
122
+ )
123
+ }
124
+ }
125
+ this.fileCache.set(abspath, content)
126
+ }
127
+ response.file(this.fileCache.get(abspath)!, abspath)
94
128
  }
95
- response.json(swaggerJSON)
96
129
  }