@grest-ts/http 0.0.6 → 0.0.7

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.
@@ -1,100 +1,100 @@
1
- import type http from "http";
2
- import type {HttpMethod} from "@grest-ts/common";
3
- import {GGContractExecutor, GGContractMethod} from "@grest-ts/schema";
4
- import {ClientHttpRouteToRpcTransformServerConfig, GGHttpRequest, GGHttpTransportMiddleware} from "../../schema/GGHttpSchema";
5
- import type {GGHttpServerMiddleware} from "../../server/GGHttpSchema.startServer";
6
-
7
- export class GGRpcRequestParser {
8
-
9
- private readonly pathTemplate: string;
10
- private readonly pathParams: string[];
11
- private readonly hasBody: boolean;
12
- protected readonly contract: GGContractMethod
13
- private readonly apiMiddlewares: readonly GGHttpTransportMiddleware[]
14
- private readonly serverMiddlewares: readonly GGHttpServerMiddleware[]
15
-
16
- constructor(
17
- method: HttpMethod,
18
- pathTemplate: string,
19
- config: ClientHttpRouteToRpcTransformServerConfig
20
- ) {
21
- this.pathTemplate = pathTemplate
22
- this.pathParams = (pathTemplate.match(/:(\w+)/g) || []).map(m => m.slice(1))
23
- this.hasBody = method === "POST" || method === "PUT" || method === "PATCH"
24
- this.contract = config.contract
25
- this.apiMiddlewares = config.apiMiddlewares
26
- this.serverMiddlewares = config.serverMiddlewares
27
- }
28
-
29
- public parseRequest = async (req: http.IncomingMessage): Promise<unknown> => {
30
- const url = req.url || '/'
31
- const qIndex = url.indexOf('?')
32
- const queryArgs = this.parseQueryString(qIndex === -1 ? '' : url.substring(qIndex + 1))
33
- if (this.apiMiddlewares?.length > 0) {
34
- const mwQuery: GGHttpRequest = {headers: req.headers, queryArgs: queryArgs}
35
- this.apiMiddlewares?.forEach(mw => mw.parseRequest?.(mwQuery))
36
- }
37
- for (const mw of this.serverMiddlewares ?? []) await mw.process?.();
38
-
39
- let input: unknown;
40
- if (this.hasBody) {
41
- input = await this.parseBody(req);
42
- } else if (this.pathParams.length > 0) {
43
- input = {
44
- ...this.extractPathParams(qIndex === -1 ? url : url.substring(0, qIndex)),
45
- ...queryArgs
46
- };
47
- } else {
48
- input = queryArgs;
49
- }
50
- return GGContractExecutor.parseInput(this.contract.input, input);
51
- }
52
-
53
- private parseQueryString(rawQuery: string): Record<string, string | string[]> {
54
- const result: Record<string, string | string[]> = {}
55
- if (rawQuery) {
56
- const params = new URLSearchParams(rawQuery)
57
- for (const [key, value] of params.entries()) {
58
- result[key] = value
59
- }
60
- }
61
- return result;
62
- }
63
-
64
- private extractPathParams(actualPath: string): Record<string, string> {
65
- const result: Record<string, string> = {}
66
- const templateParts = this.pathTemplate.split('/').filter(p => p)
67
- const actualParts = actualPath.split('/').filter(p => p)
68
- const offset = actualParts.length - templateParts.length
69
- if (offset >= 0) {
70
- for (let i = 0; i < templateParts.length; i++) {
71
- const templatePart = templateParts[i]
72
- if (templatePart.startsWith(':')) {
73
- const paramName = templatePart.slice(1)
74
- result[paramName] = decodeURIComponent(actualParts[offset + i] || '')
75
- }
76
- }
77
- }
78
- return result;
79
- }
80
-
81
- private async parseBody(req: http.IncomingMessage): Promise<unknown> {
82
- const contentType = req.headers['content-type'] || ''
83
- const isMultipart = contentType.toLowerCase().startsWith('multipart/form-data')
84
- if (isMultipart) {
85
- throw new Error("Received multipart request on a JSON-only route. Use GGRpc.MULTIPART_POST for routes with file uploads.")
86
- } else {
87
- const rawBody: Buffer = await new Promise((resolve, reject) => {
88
- const chunks: Buffer[] = []
89
- req.on('data', (chunk: Buffer) => chunks.push(chunk))
90
- req.on('end', () => resolve(Buffer.concat(chunks)))
91
- req.on('error', reject)
92
- });
93
- if (rawBody && rawBody.length > 0) {
94
- return JSON.parse(rawBody.toString('utf-8'))
95
- } else {
96
- return undefined
97
- }
98
- }
99
- }
100
- }
1
+ import type http from "http";
2
+ import type {HttpMethod} from "@grest-ts/common";
3
+ import {GGContractExecutor, GGContractMethod} from "@grest-ts/schema";
4
+ import {ClientHttpRouteToRpcTransformServerConfig, GGHttpRequest, GGHttpTransportMiddleware} from "../../schema/GGHttpSchema";
5
+ import type {GGHttpServerMiddleware} from "../../server/GGHttpSchema.startServer";
6
+
7
+ export class GGRpcRequestParser {
8
+
9
+ private readonly pathTemplate: string;
10
+ private readonly pathParams: string[];
11
+ private readonly hasBody: boolean;
12
+ protected readonly contract: GGContractMethod
13
+ private readonly apiMiddlewares: readonly GGHttpTransportMiddleware[]
14
+ private readonly serverMiddlewares: readonly GGHttpServerMiddleware[]
15
+
16
+ constructor(
17
+ method: HttpMethod,
18
+ pathTemplate: string,
19
+ config: ClientHttpRouteToRpcTransformServerConfig
20
+ ) {
21
+ this.pathTemplate = pathTemplate
22
+ this.pathParams = (pathTemplate.match(/:(\w+)/g) || []).map(m => m.slice(1))
23
+ this.hasBody = method === "POST" || method === "PUT" || method === "PATCH"
24
+ this.contract = config.contract
25
+ this.apiMiddlewares = config.apiMiddlewares
26
+ this.serverMiddlewares = config.serverMiddlewares
27
+ }
28
+
29
+ public parseRequest = async (req: http.IncomingMessage): Promise<unknown> => {
30
+ const url = req.url || '/'
31
+ const qIndex = url.indexOf('?')
32
+ const queryArgs = this.parseQueryString(qIndex === -1 ? '' : url.substring(qIndex + 1))
33
+ if (this.apiMiddlewares?.length > 0) {
34
+ const mwQuery: GGHttpRequest = {headers: req.headers, queryArgs: queryArgs}
35
+ this.apiMiddlewares?.forEach(mw => mw.parseRequest?.(mwQuery))
36
+ }
37
+ for (const mw of this.serverMiddlewares ?? []) await mw.process?.();
38
+
39
+ let input: unknown;
40
+ if (this.hasBody) {
41
+ input = await this.parseBody(req);
42
+ } else if (this.pathParams.length > 0) {
43
+ input = {
44
+ ...this.extractPathParams(qIndex === -1 ? url : url.substring(0, qIndex)),
45
+ ...queryArgs
46
+ };
47
+ } else {
48
+ input = queryArgs;
49
+ }
50
+ return GGContractExecutor.parseInput(this.contract.input, input);
51
+ }
52
+
53
+ private parseQueryString(rawQuery: string): Record<string, string | string[]> {
54
+ const result: Record<string, string | string[]> = {}
55
+ if (rawQuery) {
56
+ const params = new URLSearchParams(rawQuery)
57
+ for (const [key, value] of params.entries()) {
58
+ result[key] = value
59
+ }
60
+ }
61
+ return result;
62
+ }
63
+
64
+ private extractPathParams(actualPath: string): Record<string, string> {
65
+ const result: Record<string, string> = {}
66
+ const templateParts = this.pathTemplate.split('/').filter(p => p)
67
+ const actualParts = actualPath.split('/').filter(p => p)
68
+ const offset = actualParts.length - templateParts.length
69
+ if (offset >= 0) {
70
+ for (let i = 0; i < templateParts.length; i++) {
71
+ const templatePart = templateParts[i]
72
+ if (templatePart.startsWith(':')) {
73
+ const paramName = templatePart.slice(1)
74
+ result[paramName] = decodeURIComponent(actualParts[offset + i] || '')
75
+ }
76
+ }
77
+ }
78
+ return result;
79
+ }
80
+
81
+ private async parseBody(req: http.IncomingMessage): Promise<unknown> {
82
+ const contentType = req.headers['content-type'] || ''
83
+ const isMultipart = contentType.toLowerCase().startsWith('multipart/form-data')
84
+ if (isMultipart) {
85
+ throw new Error("Received multipart request on a JSON-only route. Use GGRpc.MULTIPART_POST for routes with file uploads.")
86
+ } else {
87
+ const rawBody: Buffer = await new Promise((resolve, reject) => {
88
+ const chunks: Buffer[] = []
89
+ req.on('data', (chunk: Buffer) => chunks.push(chunk))
90
+ req.on('end', () => resolve(Buffer.concat(chunks)))
91
+ req.on('error', reject)
92
+ });
93
+ if (rawBody && rawBody.length > 0) {
94
+ return JSON.parse(rawBody.toString('utf-8'))
95
+ } else {
96
+ return undefined
97
+ }
98
+ }
99
+ }
100
+ }
@@ -1,84 +1,84 @@
1
- import type http from "http";
2
- import {ANY_ERROR, ERROR, GGContractExecutor, GGContractMethod, GGDebugData, GGErrorData, GGSchema, OK} from "@grest-ts/schema";
3
- import {ClientHttpRouteToRpcTransformServerConfig} from "../../schema/GGHttpSchema";
4
-
5
-
6
- export class GGRpcResponseBuilder {
7
-
8
- protected readonly contract: GGContractMethod
9
-
10
- constructor(config: ClientHttpRouteToRpcTransformServerConfig) {
11
- this.contract = config.contract;
12
- }
13
-
14
- public sendResponse = async (res: http.ServerResponse, rpcResult: ERROR<string, unknown> | OK<unknown>): Promise<void> => {
15
- let json: string;
16
- let statusCode: number;
17
- if (rpcResult.success === true) {
18
- statusCode = 200;
19
- json = '{' +
20
- '"success":true,' +
21
- '"type":"OK"' +
22
- this.makeDataStr(this.contract.success, rpcResult) +
23
- "}"
24
- } else {
25
- json = this.makeError(GGContractExecutor.getResponseSchema(this.contract, rpcResult), rpcResult);
26
- statusCode = rpcResult.statusCode;
27
- }
28
- res.writeHead(statusCode, {
29
- 'Content-Type': 'application/json',
30
- 'Content-Length': Buffer.byteLength(json)
31
- });
32
- res.end(json)
33
- }
34
-
35
- private makeError(schema: GGSchema<any> | undefined, rpcResult: ERROR<string, unknown>) {
36
- return '{' +
37
- '"success":false,' +
38
- '"type":' + JSON.stringify(rpcResult.type) + ',' +
39
- '"statusCode":' + Number(rpcResult.statusCode) +
40
- this.makeErrorCtx(rpcResult.context, rpcResult.getDebugContext()) +
41
- this.makeDataStr(schema, rpcResult) +
42
- "}";
43
- }
44
-
45
- private makeDataStr(schema: GGSchema<any>, data: OK<any> | ANY_ERROR): string {
46
- if (schema) {
47
- GGContractExecutor.assertResponse(schema, data);
48
- const dataStr = schema.unsafeStringify(data.data)
49
- return dataStr ? ',"data":' + dataStr + "" : "";
50
- } else {
51
- return "";
52
- }
53
- }
54
-
55
- private makeErrorCtx(ctx: GGErrorData, debugContext?: GGDebugData): string {
56
- if (ctx) {
57
- let str = "";
58
- str += ctx?.displayMessage ? '"displayMessage":' + JSON.stringify(ctx.displayMessage) + '' : "";
59
- str += ctx?.timestamp ? (str ? "," : "") + '"timestamp":' + Number(ctx.timestamp) : "";
60
- str += ctx?.ref ? (str ? "," : "") + '"ref":' + JSON.stringify(ctx.ref) + '' : "";
61
- if (process.env.NODE_ENV !== "production" && debugContext) {
62
- if (debugContext?.debugMessage) {
63
- str += (str ? "," : "") + '"debugMessage":' + JSON.stringify(debugContext.debugMessage);
64
- }
65
- if (debugContext?.debugData !== undefined) {
66
- str += (str ? "," : "") + '"debugData":' + JSON.stringify(debugContext.debugData);
67
- }
68
- if (debugContext?.originalError) {
69
- const origErr = debugContext.originalError;
70
- const errInfo = origErr instanceof ERROR
71
- ? origErr.toJSON()
72
- : origErr instanceof Error
73
- ? {message: origErr.message, stack: origErr.stack?.split("\n")}
74
- : {message: String(origErr)};
75
- str += (str ? "," : "") + '"originalError":' + JSON.stringify(errInfo);
76
- }
77
- }
78
- if (str) {
79
- return ',"context":{' + str + '}';
80
- }
81
- }
82
- return "";
83
- }
84
- }
1
+ import type http from "http";
2
+ import {ANY_ERROR, ERROR, GGContractExecutor, GGContractMethod, GGDebugData, GGErrorData, GGSchema, OK} from "@grest-ts/schema";
3
+ import {ClientHttpRouteToRpcTransformServerConfig} from "../../schema/GGHttpSchema";
4
+
5
+
6
+ export class GGRpcResponseBuilder {
7
+
8
+ protected readonly contract: GGContractMethod
9
+
10
+ constructor(config: ClientHttpRouteToRpcTransformServerConfig) {
11
+ this.contract = config.contract;
12
+ }
13
+
14
+ public sendResponse = async (res: http.ServerResponse, rpcResult: ERROR<string, unknown> | OK<unknown>): Promise<void> => {
15
+ let json: string;
16
+ let statusCode: number;
17
+ if (rpcResult.success === true) {
18
+ statusCode = 200;
19
+ json = '{' +
20
+ '"success":true,' +
21
+ '"type":"OK"' +
22
+ this.makeDataStr(this.contract.success, rpcResult) +
23
+ "}"
24
+ } else {
25
+ json = this.makeError(GGContractExecutor.getResponseSchema(this.contract, rpcResult), rpcResult);
26
+ statusCode = rpcResult.statusCode;
27
+ }
28
+ res.writeHead(statusCode, {
29
+ 'Content-Type': 'application/json',
30
+ 'Content-Length': Buffer.byteLength(json)
31
+ });
32
+ res.end(json)
33
+ }
34
+
35
+ private makeError(schema: GGSchema<any> | undefined, rpcResult: ERROR<string, unknown>) {
36
+ return '{' +
37
+ '"success":false,' +
38
+ '"type":' + JSON.stringify(rpcResult.type) + ',' +
39
+ '"statusCode":' + Number(rpcResult.statusCode) +
40
+ this.makeErrorCtx(rpcResult.context, rpcResult.getDebugContext()) +
41
+ this.makeDataStr(schema, rpcResult) +
42
+ "}";
43
+ }
44
+
45
+ private makeDataStr(schema: GGSchema<any>, data: OK<any> | ANY_ERROR): string {
46
+ if (schema) {
47
+ GGContractExecutor.assertResponse(schema, data);
48
+ const dataStr = schema.unsafeStringify(data.data)
49
+ return dataStr ? ',"data":' + dataStr + "" : "";
50
+ } else {
51
+ return "";
52
+ }
53
+ }
54
+
55
+ private makeErrorCtx(ctx: GGErrorData, debugContext?: GGDebugData): string {
56
+ if (ctx) {
57
+ let str = "";
58
+ str += ctx?.displayMessage ? '"displayMessage":' + JSON.stringify(ctx.displayMessage) + '' : "";
59
+ str += ctx?.timestamp ? (str ? "," : "") + '"timestamp":' + Number(ctx.timestamp) : "";
60
+ str += ctx?.ref ? (str ? "," : "") + '"ref":' + JSON.stringify(ctx.ref) + '' : "";
61
+ if (process.env.NODE_ENV !== "production" && debugContext) {
62
+ if (debugContext?.debugMessage) {
63
+ str += (str ? "," : "") + '"debugMessage":' + JSON.stringify(debugContext.debugMessage);
64
+ }
65
+ if (debugContext?.debugData !== undefined) {
66
+ str += (str ? "," : "") + '"debugData":' + JSON.stringify(debugContext.debugData);
67
+ }
68
+ if (debugContext?.originalError) {
69
+ const origErr = debugContext.originalError;
70
+ const errInfo = origErr instanceof ERROR
71
+ ? origErr.toJSON()
72
+ : origErr instanceof Error
73
+ ? {message: origErr.message, stack: origErr.stack?.split("\n")}
74
+ : {message: String(origErr)};
75
+ str += (str ? "," : "") + '"originalError":' + JSON.stringify(errInfo);
76
+ }
77
+ }
78
+ if (str) {
79
+ return ',"context":{' + str + '}';
80
+ }
81
+ }
82
+ return "";
83
+ }
84
+ }
@@ -1,23 +1,23 @@
1
- import {ERROR_JSON, OK, SERVER_ERROR} from "@grest-ts/schema";
2
- import {ClientHttpRouteToRpcTransformClientConfig} from "../../schema/GGHttpSchema";
3
-
4
- export class GGRpcResponseParser {
5
-
6
- constructor(config: ClientHttpRouteToRpcTransformClientConfig) {
7
-
8
- }
9
-
10
- parseResponse = async (response: Response): Promise<OK<unknown> | ERROR_JSON<string, unknown>> => {
11
- const txt = await response.text();
12
- try {
13
- const json = txt ? JSON.parse(txt) : {};
14
- if (typeof json === "object" && "success" in json && "type" in json) {
15
- return json
16
- } else {
17
- return new SERVER_ERROR({displayMessage: "Invalid response format!", debugData: {json: json}});
18
- }
19
- } catch (err) {
20
- return new SERVER_ERROR({displayMessage: "Failed to parse JSON", originalError: err, debugData: {text: txt}});
21
- }
22
- }
23
- }
1
+ import {ERROR_JSON, OK, SERVER_ERROR} from "@grest-ts/schema";
2
+ import {ClientHttpRouteToRpcTransformClientConfig} from "../../schema/GGHttpSchema";
3
+
4
+ export class GGRpcResponseParser {
5
+
6
+ constructor(config: ClientHttpRouteToRpcTransformClientConfig) {
7
+
8
+ }
9
+
10
+ parseResponse = async (response: Response): Promise<OK<unknown> | ERROR_JSON<string, unknown>> => {
11
+ const txt = await response.text();
12
+ try {
13
+ const json = txt ? JSON.parse(txt) : {};
14
+ if (typeof json === "object" && "success" in json && "type" in json) {
15
+ return json
16
+ } else {
17
+ return new SERVER_ERROR({displayMessage: "Invalid response format!", debugData: {json: json}});
18
+ }
19
+ } catch (err) {
20
+ return new SERVER_ERROR({displayMessage: "Failed to parse JSON", originalError: err, debugData: {text: txt}});
21
+ }
22
+ }
23
+ }
@@ -1,115 +1,115 @@
1
- import {ERROR, ERROR_JSON, GGContractApiDefinition, GGContractClass, GGContractMethod, OK} from "@grest-ts/schema";
2
- import type {HttpMethod} from "@grest-ts/common";
3
- import type http from "http";
4
- import type {GGHttpServerMiddleware} from "../server/GGHttpSchema.startServer";
5
-
6
- export class GGHttpSchema<TContract extends GGContractApiDefinition, TContext> {
7
-
8
- public readonly name: string
9
- public readonly pathPrefix: string
10
- public readonly apiMiddlewares: readonly GGHttpTransportMiddleware[]
11
- public readonly codec: Record<keyof TContract, GGHttpCodec>
12
- public readonly contract: GGContractClass<TContract> | null = null
13
-
14
- constructor(
15
- pathPrefix: string,
16
- contract: GGContractClass<TContract>,
17
- wireCodec: Record<keyof TContract, GGHttpCodec>,
18
- middlewares: readonly GGHttpTransportMiddleware[] = []
19
- ) {
20
- this.name = contract.name
21
- this.pathPrefix = pathPrefix
22
- this.apiMiddlewares = middlewares
23
- this.codec = wireCodec
24
- this.contract = contract
25
- Object.freeze(this.apiMiddlewares)
26
- Object.freeze(this.codec)
27
- Object.freeze(this)
28
- }
29
- }
30
-
31
- // --------------------------------------------------------------------------------------------------------
32
- // Client codec
33
- // --------------------------------------------------------------------------------------------------------
34
-
35
- export interface ClientHttpRouteToRpcTransformClientConfig {
36
- pathPrefix: string,
37
- contract: GGContractMethod,
38
- middlewares: readonly GGHttpTransportMiddleware[]
39
- }
40
-
41
- export interface ClientHttpRouteToRpcTransformClientCodec {
42
- createRequest: (data: unknown) => GGHttpFetchRequest | Promise<GGHttpFetchRequest>
43
- parseResponse: (response: Response) => Promise<OK<unknown> | ERROR_JSON<string, unknown>>
44
- }
45
-
46
- export interface GGHttpFetchRequest {
47
- url: string;
48
- method: HttpMethod;
49
- headers: Record<string, string>;
50
- body: string | FormData | undefined;
51
- }
52
-
53
- // --------------------------------------------------------------------------------------------------------
54
- // Server codec
55
- // --------------------------------------------------------------------------------------------------------
56
-
57
- export interface ClientHttpRouteToRpcTransformServerConfig {
58
- contract: GGContractMethod,
59
- apiMiddlewares: readonly GGHttpTransportMiddleware[],
60
- serverMiddlewares: readonly GGHttpServerMiddleware[]
61
- }
62
-
63
- export interface ClientHttpRouteToRpcTransformServerCodec {
64
- parseRequest: (req: http.IncomingMessage) => Promise<unknown>,
65
- sendResponse: (res: http.ServerResponse, rpcResult: ERROR<string, unknown> | OK<unknown>) => Promise<void>
66
- }
67
-
68
- // --------------------------------------------------------------------------------------------------------
69
- // Codec
70
- // --------------------------------------------------------------------------------------------------------
71
-
72
- export interface GGHttpCodec {
73
- readonly method: HttpMethod;
74
- readonly path: string;
75
-
76
- createForClient(config: ClientHttpRouteToRpcTransformClientConfig): ClientHttpRouteToRpcTransformClientCodec
77
-
78
- createForServer(config: ClientHttpRouteToRpcTransformServerConfig): ClientHttpRouteToRpcTransformServerCodec
79
-
80
- }
81
-
82
- // --------------------------------------------------------------------------------------------------------
83
- // Middleware
84
- // --------------------------------------------------------------------------------------------------------
85
-
86
- export interface GGHttpTransportMiddleware {
87
- /**
88
- * Client-side: modify outgoing request (add headers, etc.)
89
- */
90
- updateRequest?(req: GGHttpRequest): void;
91
-
92
- /**
93
- * Server-side: parse incoming request (extract context from headers, etc.)
94
- */
95
- parseRequest?(req: GGHttpRequest): void;
96
-
97
- /**
98
- * Server-side: modify outgoing response
99
- */
100
- updateResponse?(res: GGHttpResponse): void;
101
-
102
- /**
103
- * Client-side: parse incoming response
104
- */
105
- parseResponse?(res: GGHttpResponse): void;
106
- }
107
-
108
- export interface GGHttpRequest {
109
- headers?: Record<string, string | string[]>;
110
- queryArgs?: Record<string, string | string[]>;
111
- }
112
-
113
- export interface GGHttpResponse {
114
- headers: Record<string, string | string[]>;
115
- }
1
+ import {ERROR, ERROR_JSON, GGContractApiDefinition, GGContractClass, GGContractMethod, OK} from "@grest-ts/schema";
2
+ import type {HttpMethod} from "@grest-ts/common";
3
+ import type http from "http";
4
+ import type {GGHttpServerMiddleware} from "../server/GGHttpSchema.startServer";
5
+
6
+ export class GGHttpSchema<TContract extends GGContractApiDefinition, TContext> {
7
+
8
+ public readonly name: string
9
+ public readonly pathPrefix: string
10
+ public readonly apiMiddlewares: readonly GGHttpTransportMiddleware[]
11
+ public readonly codec: Record<keyof TContract, GGHttpCodec>
12
+ public readonly contract: GGContractClass<TContract> | null = null
13
+
14
+ constructor(
15
+ pathPrefix: string,
16
+ contract: GGContractClass<TContract>,
17
+ wireCodec: Record<keyof TContract, GGHttpCodec>,
18
+ middlewares: readonly GGHttpTransportMiddleware[] = []
19
+ ) {
20
+ this.name = contract.name
21
+ this.pathPrefix = pathPrefix
22
+ this.apiMiddlewares = middlewares
23
+ this.codec = wireCodec
24
+ this.contract = contract
25
+ Object.freeze(this.apiMiddlewares)
26
+ Object.freeze(this.codec)
27
+ Object.freeze(this)
28
+ }
29
+ }
30
+
31
+ // --------------------------------------------------------------------------------------------------------
32
+ // Client codec
33
+ // --------------------------------------------------------------------------------------------------------
34
+
35
+ export interface ClientHttpRouteToRpcTransformClientConfig {
36
+ pathPrefix: string,
37
+ contract: GGContractMethod,
38
+ middlewares: readonly GGHttpTransportMiddleware[]
39
+ }
40
+
41
+ export interface ClientHttpRouteToRpcTransformClientCodec {
42
+ createRequest: (data: unknown) => GGHttpFetchRequest | Promise<GGHttpFetchRequest>
43
+ parseResponse: (response: Response) => Promise<OK<unknown> | ERROR_JSON<string, unknown>>
44
+ }
45
+
46
+ export interface GGHttpFetchRequest {
47
+ url: string;
48
+ method: HttpMethod;
49
+ headers: Record<string, string>;
50
+ body: string | FormData | undefined;
51
+ }
52
+
53
+ // --------------------------------------------------------------------------------------------------------
54
+ // Server codec
55
+ // --------------------------------------------------------------------------------------------------------
56
+
57
+ export interface ClientHttpRouteToRpcTransformServerConfig {
58
+ contract: GGContractMethod,
59
+ apiMiddlewares: readonly GGHttpTransportMiddleware[],
60
+ serverMiddlewares: readonly GGHttpServerMiddleware[]
61
+ }
62
+
63
+ export interface ClientHttpRouteToRpcTransformServerCodec {
64
+ parseRequest: (req: http.IncomingMessage) => Promise<unknown>,
65
+ sendResponse: (res: http.ServerResponse, rpcResult: ERROR<string, unknown> | OK<unknown>) => Promise<void>
66
+ }
67
+
68
+ // --------------------------------------------------------------------------------------------------------
69
+ // Codec
70
+ // --------------------------------------------------------------------------------------------------------
71
+
72
+ export interface GGHttpCodec {
73
+ readonly method: HttpMethod;
74
+ readonly path: string;
75
+
76
+ createForClient(config: ClientHttpRouteToRpcTransformClientConfig): ClientHttpRouteToRpcTransformClientCodec
77
+
78
+ createForServer(config: ClientHttpRouteToRpcTransformServerConfig): ClientHttpRouteToRpcTransformServerCodec
79
+
80
+ }
81
+
82
+ // --------------------------------------------------------------------------------------------------------
83
+ // Middleware
84
+ // --------------------------------------------------------------------------------------------------------
85
+
86
+ export interface GGHttpTransportMiddleware {
87
+ /**
88
+ * Client-side: modify outgoing request (add headers, etc.)
89
+ */
90
+ updateRequest?(req: GGHttpRequest): void;
91
+
92
+ /**
93
+ * Server-side: parse incoming request (extract context from headers, etc.)
94
+ */
95
+ parseRequest?(req: GGHttpRequest): void;
96
+
97
+ /**
98
+ * Server-side: modify outgoing response
99
+ */
100
+ updateResponse?(res: GGHttpResponse): void;
101
+
102
+ /**
103
+ * Client-side: parse incoming response
104
+ */
105
+ parseResponse?(res: GGHttpResponse): void;
106
+ }
107
+
108
+ export interface GGHttpRequest {
109
+ headers?: Record<string, string | string[]>;
110
+ queryArgs?: Record<string, string | string[]>;
111
+ }
112
+
113
+ export interface GGHttpResponse {
114
+ headers: Record<string, string | string[]>;
115
+ }