starlight-server 1.6.2 → 1.6.4

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.
@@ -36,6 +36,8 @@ export declare class Request {
36
36
  constructor(nodeRequest: NodeRequest, logger: Logger, bodyOptions: BodyOptions);
37
37
  /**
38
38
  * 获取 HTTP Header,支持任意大小写形式:content-type / Content-Type
39
+ * 若 loose 为 true,固定返回一个字符串。有多个值时返回第一个,header 不存在时返回空字符串。
39
40
  */
40
- getHeader<const K extends string>(key: K): http.IncomingHttpHeaders[Lowercase<K>];
41
+ getHeader<const K extends string>(key: K, loose?: false): http.IncomingHttpHeaders[Lowercase<K>];
42
+ getHeader<const K extends string>(key: K, loose: true): string;
41
43
  }
@@ -52,10 +52,17 @@ export class Request {
52
52
  this.headers = nodeRequest.headers;
53
53
  this.body = new RequestBody(nodeRequest, bodyOptions);
54
54
  }
55
- /**
56
- * 获取 HTTP Header,支持任意大小写形式:content-type / Content-Type
57
- */
58
- getHeader(key) {
59
- return this.headers[key.toLowerCase()];
55
+ getHeader(key, loose) {
56
+ const value = this.headers[key.toLowerCase()];
57
+ if (loose === true) {
58
+ if (typeof value === 'string')
59
+ return value;
60
+ if (Array.isArray(value) && value.length > 0)
61
+ return value[1];
62
+ return '';
63
+ }
64
+ else {
65
+ return value;
66
+ }
60
67
  }
61
68
  }
@@ -8,7 +8,11 @@ import { type NodeResponse } from './types.js';
8
8
  export declare class ResponseUtils {
9
9
  readonly nodeResponse: NodeResponse;
10
10
  readonly logger: Logger;
11
- constructor(nodeResponse: NodeResponse, logger: Logger);
11
+ /** 处理 JSON 输出中的自定义类型 */
12
+ jsonReplacer?: ((key: string, value: unknown) => unknown) | undefined;
13
+ constructor(nodeResponse: NodeResponse, logger: Logger,
14
+ /** 处理 JSON 输出中的自定义类型 */
15
+ jsonReplacer?: ((key: string, value: unknown) => unknown) | undefined);
12
16
  header(name: string, value: string): void;
13
17
  headers(...items: [string, string][]): void;
14
18
  text(content: string | Buffer): void;
@@ -7,9 +7,13 @@ import { HTTPError } from './types.js';
7
7
  export class ResponseUtils {
8
8
  nodeResponse;
9
9
  logger;
10
- constructor(nodeResponse, logger) {
10
+ jsonReplacer;
11
+ constructor(nodeResponse, logger,
12
+ /** 处理 JSON 输出中的自定义类型 */
13
+ jsonReplacer) {
11
14
  this.nodeResponse = nodeResponse;
12
15
  this.logger = logger;
16
+ this.jsonReplacer = jsonReplacer;
13
17
  }
14
18
  header(name, value) {
15
19
  this.nodeResponse.setHeader(name, value);
@@ -25,7 +29,7 @@ export class ResponseUtils {
25
29
  */
26
30
  json(data) {
27
31
  try {
28
- const json = JSON.stringify(data);
32
+ const json = JSON.stringify(data, this.jsonReplacer);
29
33
  this.header('Content-Type', 'application/json; charset=UTF-8');
30
34
  this.nodeResponse.end(json);
31
35
  }
@@ -28,4 +28,6 @@ export declare function startHTTPServer(options: BodyOptions & {
28
28
  handler?: RequestHandler;
29
29
  logger: Logger;
30
30
  port: number;
31
+ /** 处理 JSON 输出中的自定义类型,详见 JSON.stringify() 的 replacer 参数 */
32
+ jsonReplacer?: (key: string, value: unknown) => unknown;
31
33
  }): void;
@@ -30,7 +30,7 @@ export function startHTTPServer(options) {
30
30
  const logMethod = nodeRequest.method ?? 'UNKNOWN';
31
31
  const logUrl = nodeRequest.url ?? '';
32
32
  logger.info('<--', logMethod, logUrl);
33
- const response = new ResponseUtils(nodeResponse, logger);
33
+ const response = new ResponseUtils(nodeResponse, logger, options.jsonReplacer);
34
34
  try {
35
35
  const request = new Request(nodeRequest, logger, bodyOptions);
36
36
  await handler(request, response);
@@ -1,6 +1,12 @@
1
- import { type Validator, validators } from '@anjianshi/utils/validators/index.js';
1
+ import { type Validator } from '@anjianshi/utils/validators/index.js';
2
2
  import { type BasicContext } from './index.js';
3
- export { validators };
4
- export declare function validatePathParameters<T>(this: BasicContext, struct: Record<string, Validator>): T;
5
- export declare function validateQuery<T>(this: BasicContext, struct: Record<string, Validator>): T;
6
- export declare function validateBody<T>(this: BasicContext, struct: Record<string, Validator>): Promise<T>;
3
+ export declare class Helpers {
4
+ readonly context: BasicContext;
5
+ constructor(context: BasicContext);
6
+ validatePathParameters<T>(struct: Record<string, Validator>): T;
7
+ validateQuery<T>(struct: Record<string, Validator>): T;
8
+ validateBody<T>(struct: Record<string, Validator>): Promise<T>;
9
+ success(data?: unknown): void;
10
+ failed(message: string, code?: string | number): void;
11
+ failed<T>(message: string, code: string | number | undefined, data: T): void;
12
+ }
@@ -1,24 +1,36 @@
1
+ import { success, failed } from '@anjianshi/utils';
1
2
  import { validators } from '@anjianshi/utils/validators/index.js';
2
3
  import { HTTPError } from '../index.js';
3
- export { validators };
4
- export function validatePathParameters(struct) {
5
- const result = validators.struct(struct).validate('path', this.pathParameters);
6
- if (result.success)
7
- return result.data;
8
- throw new HTTPError(400, result.message);
9
- }
10
- export function validateQuery(struct) {
11
- const result = validators.struct(struct).validate('query', this.request.query);
12
- if (result.success)
13
- return result.data;
14
- throw new HTTPError(400, result.message);
15
- }
16
- export async function validateBody(struct) {
17
- const body = await this.request.body.json();
18
- if (typeof body !== 'object' || body === null || Array.isArray(body))
19
- throw new HTTPError(400, 'Invalid JSON body, should be an object.');
20
- const result = validators.struct(struct).validate('body', this.request.body);
21
- if (result.success)
22
- return result.data;
23
- throw new HTTPError(400, result.message);
4
+ export class Helpers {
5
+ context;
6
+ constructor(context) {
7
+ this.context = context;
8
+ }
9
+ validatePathParameters(struct) {
10
+ const result = validators.struct(struct).validate('path', this.context.pathParameters);
11
+ if (result.success)
12
+ return result.data;
13
+ throw new HTTPError(400, result.message);
14
+ }
15
+ validateQuery(struct) {
16
+ const result = validators.struct(struct).validate('query', this.context.request.query);
17
+ if (result.success)
18
+ return result.data;
19
+ throw new HTTPError(400, result.message);
20
+ }
21
+ async validateBody(struct) {
22
+ const body = await this.context.request.body.json();
23
+ if (typeof body !== 'object' || body === null || Array.isArray(body))
24
+ throw new HTTPError(400, 'Invalid JSON body, should be an object.');
25
+ const result = validators.struct(struct).validate('body', body);
26
+ if (result.success)
27
+ return result.data;
28
+ throw new HTTPError(400, result.message);
29
+ }
30
+ success(data) {
31
+ this.context.response.json(success(data));
32
+ }
33
+ failed(message, code, data) {
34
+ this.context.response.json(failed(message, code, data));
35
+ }
24
36
  }
@@ -1,18 +1,20 @@
1
- import { type validators } from '@anjianshi/utils/validators/index.js';
1
+ import { validators } from '@anjianshi/utils/validators/index.js';
2
2
  import { type CORSRule } from '../http/cors.js';
3
3
  import { type Request, type ResponseUtils } from '../http/index.js';
4
4
  import { Swagger, type Method } from '../swagger/index.js';
5
- import * as helpers from './helpers.js';
5
+ import { Helpers } from './helpers.js';
6
6
  import { type PathParameters } from './match-path.js';
7
- type WithoutThis<T extends (...args: any[]) => unknown> = (...args: Parameters<T>) => ReturnType<T>;
7
+ type HelpersInst = InstanceType<typeof Helpers>;
8
8
  export interface BasicContext {
9
9
  request: Request;
10
10
  response: ResponseUtils;
11
11
  pathParameters: PathParameters;
12
- validatePathParameters: WithoutThis<typeof helpers.validatePathParameters>;
13
- validateQuery: WithoutThis<typeof helpers.validateQuery>;
14
- validateBody: WithoutThis<typeof helpers.validateBody>;
12
+ validatePathParameters: HelpersInst['validatePathParameters'];
13
+ validateQuery: HelpersInst['validateQuery'];
14
+ validateBody: HelpersInst['validateBody'];
15
15
  validators: typeof validators;
16
+ success: HelpersInst['success'];
17
+ failed: HelpersInst['failed'];
16
18
  }
17
19
  /** 使用者可自行补充 Context 定义 */
18
20
  export interface Context extends BasicContext {
@@ -59,7 +61,7 @@ export declare class Router {
59
61
  * route 调用 / context 配置
60
62
  * -------------------------------
61
63
  */
62
- private executor;
64
+ protected executor: (basicContext: BasicContext, route: Route) => Promise<void>;
63
65
  setExecutor(executor: typeof this.executor): void;
64
66
  /**
65
67
  * ----------------------
@@ -1,8 +1,9 @@
1
1
  import { joinPath } from '@anjianshi/utils';
2
+ import { validators } from '@anjianshi/utils/validators/index.js';
2
3
  import { getPreflightRequestMethod, handleCORS } from '../http/cors.js';
3
4
  import { HTTPError } from '../http/index.js';
4
5
  import { Swagger } from '../swagger/index.js';
5
- import * as helpers from './helpers.js';
6
+ import { Helpers } from './helpers.js';
6
7
  import { matchPath } from './match-path.js';
7
8
  export class Router {
8
9
  /**
@@ -95,13 +96,17 @@ export class Router {
95
96
  throw new HTTPError(405); // 有路径匹配的路由,但是没有 method 匹配的
96
97
  }
97
98
  const basicContext = {};
99
+ const helpers = new Helpers(basicContext);
98
100
  Object.assign(basicContext, {
99
101
  request,
100
102
  response,
101
103
  pathParameters: matched.parameters,
102
- validatePathParameters: helpers.validatePathParameters.bind(basicContext),
103
- validateQuery: helpers.validateQuery.bind(basicContext),
104
- validateBody: helpers.validateBody.bind(basicContext),
104
+ validatePathParameters: helpers.validatePathParameters.bind(helpers),
105
+ validateQuery: helpers.validateQuery.bind(helpers),
106
+ validateBody: helpers.validateBody.bind(helpers),
107
+ validators,
108
+ success: helpers.success.bind(helpers),
109
+ failed: helpers.failed.bind(helpers),
105
110
  });
106
111
  await this.executor(basicContext, matched.route);
107
112
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "starlight-server",
3
- "version": "1.6.2",
3
+ "version": "1.6.4",
4
4
  "description": "Simple But Powerful Node.js HTTP Server",
5
5
  "type": "module",
6
6
  "keywords": [
@@ -51,8 +51,9 @@
51
51
  "eslintIgnore": [],
52
52
  "prettier": "@anjianshi/presets-prettier/prettierrc",
53
53
  "scripts": {
54
- "dev": "rimraf dist && (concurrently --raw \"tsc-with-alias --watch\" \"nodemon dist/demo/index.js\")",
54
+ "demo": "rimraf dist && (concurrently --raw \"tsc-with-alias --watch\" \"nodemon dist/demo/index.js\")",
55
55
  "build": "rimraf dist && tsc-with-alias",
56
+ "watch": "rimraf dist && tsc-with-alias --watch",
56
57
  "lint": "tsc --noEmit && eslint './src/**/*'"
57
58
  }
58
59
  }
@@ -42,7 +42,7 @@ export class Request {
42
42
  constructor(
43
43
  readonly nodeRequest: NodeRequest,
44
44
  protected readonly logger: Logger,
45
- bodyOptions: BodyOptions
45
+ bodyOptions: BodyOptions,
46
46
  ) {
47
47
  if (nodeRequest.method === undefined) throw new HTTPError(405)
48
48
  this.method = nodeRequest.method.toUpperCase()
@@ -65,8 +65,21 @@ export class Request {
65
65
 
66
66
  /**
67
67
  * 获取 HTTP Header,支持任意大小写形式:content-type / Content-Type
68
+ * 若 loose 为 true,固定返回一个字符串。有多个值时返回第一个,header 不存在时返回空字符串。
68
69
  */
69
- getHeader<const K extends string>(key: K): http.IncomingHttpHeaders[Lowercase<K>] {
70
- return this.headers[key.toLowerCase()]
70
+ getHeader<const K extends string>(key: K, loose?: false): http.IncomingHttpHeaders[Lowercase<K>]
71
+ getHeader<const K extends string>(key: K, loose: true): string
72
+ getHeader<const K extends string>(
73
+ key: K,
74
+ loose?: boolean,
75
+ ): http.IncomingHttpHeaders[Lowercase<K>] | string {
76
+ const value = this.headers[key.toLowerCase()]
77
+ if (loose === true) {
78
+ if (typeof value === 'string') return value
79
+ if (Array.isArray(value) && value.length > 0) return value[1]!
80
+ return ''
81
+ } else {
82
+ return value
83
+ }
71
84
  }
72
85
  }
@@ -7,7 +7,12 @@ import { HTTPError, type NodeResponse } from './types.js'
7
7
  * Encapsulate functions for outputting response content.
8
8
  */
9
9
  export class ResponseUtils {
10
- constructor(readonly nodeResponse: NodeResponse, readonly logger: Logger) {}
10
+ constructor(
11
+ readonly nodeResponse: NodeResponse,
12
+ readonly logger: Logger,
13
+ /** 处理 JSON 输出中的自定义类型 */
14
+ public jsonReplacer?: (key: string, value: unknown) => unknown,
15
+ ) {}
11
16
 
12
17
  header(name: string, value: string) {
13
18
  this.nodeResponse.setHeader(name, value)
@@ -26,7 +31,7 @@ export class ResponseUtils {
26
31
  */
27
32
  json(data: unknown) {
28
33
  try {
29
- const json = JSON.stringify(data)
34
+ const json = JSON.stringify(data, this.jsonReplacer)
30
35
  this.header('Content-Type', 'application/json; charset=UTF-8')
31
36
  this.nodeResponse.end(json)
32
37
  } catch (e) {
@@ -33,7 +33,9 @@ export function startHTTPServer(
33
33
  handler?: RequestHandler
34
34
  logger: Logger
35
35
  port: number
36
- }
36
+ /** 处理 JSON 输出中的自定义类型,详见 JSON.stringify() 的 replacer 参数 */
37
+ jsonReplacer?: (key: string, value: unknown) => unknown
38
+ },
37
39
  ) {
38
40
  const { handler = placeholderHandler, logger, port, ...bodyOptions } = options
39
41
 
@@ -43,7 +45,7 @@ export function startHTTPServer(
43
45
  const logUrl = nodeRequest.url ?? ''
44
46
  logger.info('<--', logMethod, logUrl)
45
47
 
46
- const response = new ResponseUtils(nodeResponse, logger)
48
+ const response = new ResponseUtils(nodeResponse, logger, options.jsonReplacer)
47
49
  try {
48
50
  const request = new Request(nodeRequest, logger, bodyOptions)
49
51
  await handler(request, response)
@@ -1,27 +1,40 @@
1
+ import { success, failed } from '@anjianshi/utils'
1
2
  import { type Validator, validators } from '@anjianshi/utils/validators/index.js'
2
3
  import { HTTPError } from '@/index.js'
3
4
  import { type BasicContext } from './index.js'
4
5
 
5
- export { validators }
6
+ export class Helpers {
7
+ constructor(readonly context: BasicContext) {}
6
8
 
7
- export function validatePathParameters<T>(this: BasicContext, struct: Record<string, Validator>) {
8
- const result = validators.struct(struct).validate('path', this.pathParameters)
9
- if (result.success) return result.data as T
10
- throw new HTTPError(400, result.message)
11
- }
9
+ validatePathParameters<T>(struct: Record<string, Validator>) {
10
+ const result = validators.struct(struct).validate('path', this.context.pathParameters)
11
+ if (result.success) return result.data as T
12
+ throw new HTTPError(400, result.message)
13
+ }
12
14
 
13
- export function validateQuery<T>(this: BasicContext, struct: Record<string, Validator>) {
14
- const result = validators.struct(struct).validate('query', this.request.query)
15
- if (result.success) return result.data as T
16
- throw new HTTPError(400, result.message)
17
- }
15
+ validateQuery<T>(struct: Record<string, Validator>) {
16
+ const result = validators.struct(struct).validate('query', this.context.request.query)
17
+ if (result.success) return result.data as T
18
+ throw new HTTPError(400, result.message)
19
+ }
20
+
21
+ async validateBody<T>(struct: Record<string, Validator>) {
22
+ const body = await this.context.request.body.json()
23
+ if (typeof body !== 'object' || body === null || Array.isArray(body))
24
+ throw new HTTPError(400, 'Invalid JSON body, should be an object.')
25
+
26
+ const result = validators.struct(struct).validate('body', body)
27
+ if (result.success) return result.data as T
28
+ throw new HTTPError(400, result.message)
29
+ }
18
30
 
19
- export async function validateBody<T>(this: BasicContext, struct: Record<string, Validator>) {
20
- const body = await this.request.body.json()
21
- if (typeof body !== 'object' || body === null || Array.isArray(body))
22
- throw new HTTPError(400, 'Invalid JSON body, should be an object.')
31
+ success(data?: unknown) {
32
+ this.context.response.json(success(data))
33
+ }
23
34
 
24
- const result = validators.struct(struct).validate('body', this.request.body)
25
- if (result.success) return result.data as T
26
- throw new HTTPError(400, result.message)
35
+ failed(message: string, code?: string | number): void
36
+ failed<T>(message: string, code: string | number | undefined, data: T): void
37
+ failed<T>(message: string, code?: string | number, data?: T) {
38
+ this.context.response.json(failed(message, code, data))
39
+ }
27
40
  }
@@ -1,20 +1,22 @@
1
1
  import { joinPath } from '@anjianshi/utils'
2
- import { type validators } from '@anjianshi/utils/validators/index.js'
2
+ import { validators } from '@anjianshi/utils/validators/index.js'
3
3
  import { getPreflightRequestMethod, handleCORS, type CORSRule } from '@/http/cors.js'
4
4
  import { HTTPError, type Request, type ResponseUtils } from '@/http/index.js'
5
5
  import { Swagger, type Method } from '@/swagger/index.js'
6
- import * as helpers from './helpers.js'
6
+ import { Helpers } from './helpers.js'
7
7
  import { matchPath, type PathParameters } from './match-path.js'
8
8
 
9
- type WithoutThis<T extends (...args: any[]) => unknown> = (...args: Parameters<T>) => ReturnType<T>
9
+ type HelpersInst = InstanceType<typeof Helpers>
10
10
  export interface BasicContext {
11
11
  request: Request
12
12
  response: ResponseUtils
13
13
  pathParameters: PathParameters
14
- validatePathParameters: WithoutThis<typeof helpers.validatePathParameters>
15
- validateQuery: WithoutThis<typeof helpers.validateQuery>
16
- validateBody: WithoutThis<typeof helpers.validateBody>
14
+ validatePathParameters: HelpersInst['validatePathParameters']
15
+ validateQuery: HelpersInst['validateQuery']
16
+ validateBody: HelpersInst['validateBody']
17
17
  validators: typeof validators
18
+ success: HelpersInst['success']
19
+ failed: HelpersInst['failed']
18
20
  }
19
21
 
20
22
  /** 使用者可自行补充 Context 定义 */
@@ -96,7 +98,7 @@ export class Router {
96
98
  * route 调用 / context 配置
97
99
  * -------------------------------
98
100
  */
99
- private executor = async (basicContext: BasicContext, route: Route) => {
101
+ protected executor = async (basicContext: BasicContext, route: Route) => {
100
102
  await route.handler(basicContext as Context)
101
103
  }
102
104
  setExecutor(executor: typeof this.executor) {
@@ -132,13 +134,17 @@ export class Router {
132
134
  }
133
135
 
134
136
  const basicContext = {} as BasicContext
137
+ const helpers = new Helpers(basicContext)
135
138
  Object.assign(basicContext, {
136
139
  request,
137
140
  response,
138
141
  pathParameters: matched.parameters,
139
- validatePathParameters: helpers.validatePathParameters.bind(basicContext),
140
- validateQuery: helpers.validateQuery.bind(basicContext),
141
- validateBody: helpers.validateBody.bind(basicContext),
142
+ validatePathParameters: helpers.validatePathParameters.bind(helpers),
143
+ validateQuery: helpers.validateQuery.bind(helpers),
144
+ validateBody: helpers.validateBody.bind(helpers),
145
+ validators,
146
+ success: helpers.success.bind(helpers),
147
+ failed: helpers.failed.bind(helpers),
142
148
  })
143
149
  await this.executor(basicContext, matched.route)
144
150
  }