@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.
- package/LICENSE +21 -21
- package/README.md +613 -613
- package/dist/tsconfig.publish.tsbuildinfo +1 -1
- package/package.json +11 -11
- package/src/client/GGHttpSchema.createClient.ts +106 -106
- package/src/index-browser.ts +12 -12
- package/src/index-node.ts +38 -38
- package/src/rpc/GGHttpRouteRPC.ts +42 -42
- package/src/rpc/RpcRequest/GGRpcRequestBuilder.ts +91 -91
- package/src/rpc/RpcRequest/GGRpcRequestParser.ts +100 -100
- package/src/rpc/RpcResponse/GGRpcResponseBuilder.ts +84 -84
- package/src/rpc/RpcResponse/GGRpcResponseParser.ts +23 -23
- package/src/schema/GGHttpSchema.ts +115 -115
- package/src/schema/httpSchema.ts +99 -99
- package/src/server/GGHttp.ts +27 -27
- package/src/server/GGHttpMetrics.ts +31 -31
- package/src/server/GGHttpSchema.startServer.ts +161 -161
- package/src/server/GGHttpServer.ts +133 -133
- package/src/server/GG_HTTP_REQUEST.ts +12 -12
- package/src/server/GG_HTTP_SERVER.ts +4 -4
|
@@ -1,107 +1,107 @@
|
|
|
1
|
-
import {ANY_ERROR, ERROR, GGContractApiDefinition, GGContractClient, GGContractExecutor, GGContractImplementation, GGPromise, OK, SERVER_ERROR} from "@grest-ts/schema"
|
|
2
|
-
import {ClientHttpRouteToRpcTransformClientCodec, GGHttpCodec, GGHttpSchema} from "../schema/GGHttpSchema";
|
|
3
|
-
import {isBrowser} from "@grest-ts/common";
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
declare module "../schema/GGHttpSchema" {
|
|
7
|
-
interface GGHttpSchema<TContract extends GGContractApiDefinition, TContext = {}> {
|
|
8
|
-
createClient(config?: GGHttpClientConfig): GGContractClient<TContract>
|
|
9
|
-
}
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export interface GGHttpClientConfig {
|
|
13
|
-
url?: string;
|
|
14
|
-
timeout?: number;
|
|
15
|
-
noValidation?: boolean
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
GGHttpSchema.prototype.createClient = function <TContract extends GGContractApiDefinition, TContext>(
|
|
19
|
-
this: GGHttpSchema<TContract, TContext>,
|
|
20
|
-
config?: GGHttpClientConfig
|
|
21
|
-
): GGContractClient<TContract> {
|
|
22
|
-
return createClient(this, config);
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export function createClient<TContract extends GGContractApiDefinition, TContext>(
|
|
26
|
-
httpSchema: GGHttpSchema<TContract, TContext>,
|
|
27
|
-
config?: GGHttpClientConfig
|
|
28
|
-
): GGContractClient<TContract> {
|
|
29
|
-
config ??= {};
|
|
30
|
-
config.timeout ??= 15000;
|
|
31
|
-
|
|
32
|
-
if (config.url === undefined && isBrowser()) {
|
|
33
|
-
throw new Error("Must define URL for GGHttpClient when running in browser! Use empty string for same-origin requests.");
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
const pathPrefix = "/" + httpSchema.pathPrefix + "/";
|
|
37
|
-
|
|
38
|
-
const transportImplementation: any = {}
|
|
39
|
-
for (const methodName of Object.keys(httpSchema.codec)) {
|
|
40
|
-
|
|
41
|
-
const contractFunction = httpSchema.contract.methods[methodName];
|
|
42
|
-
const noValidation = config?.noValidation === true;
|
|
43
|
-
|
|
44
|
-
const codec: GGHttpCodec = httpSchema.codec[methodName];
|
|
45
|
-
const wireFormat: ClientHttpRouteToRpcTransformClientCodec = codec.createForClient({
|
|
46
|
-
pathPrefix: pathPrefix,
|
|
47
|
-
contract: contractFunction,
|
|
48
|
-
middlewares: httpSchema.apiMiddlewares
|
|
49
|
-
})
|
|
50
|
-
|
|
51
|
-
const implementation = async (data?: unknown): Promise<OK<unknown> | ANY_ERROR> => {
|
|
52
|
-
try {
|
|
53
|
-
let baseUrl: string = config.url;
|
|
54
|
-
if (baseUrl === undefined) {
|
|
55
|
-
try {
|
|
56
|
-
const {GG_DISCOVERY} = await import(/* @vite-ignore */ '@grest-ts/discovery');
|
|
57
|
-
baseUrl = await GG_DISCOVERY.get().discoverApi(httpSchema.name);
|
|
58
|
-
} catch (err) {
|
|
59
|
-
throw new SERVER_ERROR({displayMessage: "Service discovery failed", originalError: err});
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
// ---------------------------------------------
|
|
64
|
-
// Input validation
|
|
65
|
-
const validatedInput = noValidation ? data : GGContractExecutor.parseInput(contractFunction.input, data)
|
|
66
|
-
|
|
67
|
-
// ---------------------------------------------
|
|
68
|
-
// Execution
|
|
69
|
-
const fetchRequest = await wireFormat.createRequest(validatedInput);
|
|
70
|
-
const controller = new AbortController();
|
|
71
|
-
const timeoutId = setTimeout(() => controller.abort(), config.timeout);
|
|
72
|
-
const wireResponse = await fetch(baseUrl + fetchRequest.url, {
|
|
73
|
-
method: fetchRequest.method,
|
|
74
|
-
signal: controller.signal,
|
|
75
|
-
headers: fetchRequest.headers,
|
|
76
|
-
body: fetchRequest.body
|
|
77
|
-
}).finally(() => clearTimeout(timeoutId));
|
|
78
|
-
const resData = await wireFormat.parseResponse(wireResponse);
|
|
79
|
-
|
|
80
|
-
// ---------------------------------------------
|
|
81
|
-
// Response handling
|
|
82
|
-
const schema = GGContractExecutor.getResponseSchema(contractFunction, resData);
|
|
83
|
-
if (schema) {
|
|
84
|
-
resData.data = noValidation ? resData.data : GGContractExecutor.parseOutputData(schema, resData.data);
|
|
85
|
-
} else if (resData.data !== undefined) {
|
|
86
|
-
resData.data = undefined
|
|
87
|
-
}
|
|
88
|
-
if (resData.success === true) {
|
|
89
|
-
return resData as OK<unknown>;
|
|
90
|
-
} else {
|
|
91
|
-
return GGContractExecutor.createErrorObj(resData, contractFunction.errors);
|
|
92
|
-
}
|
|
93
|
-
// ---------------------------------------------
|
|
94
|
-
} catch (error) {
|
|
95
|
-
return ERROR.fromUnknown(error);
|
|
96
|
-
}
|
|
97
|
-
};
|
|
98
|
-
|
|
99
|
-
transportImplementation[methodName] = (data?: unknown) => {
|
|
100
|
-
return new GGPromise(implementation(data))
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
}
|
|
104
|
-
// @TODO Next line is just so test would register it correctly.
|
|
105
|
-
httpSchema.contract.implement(transportImplementation as GGContractImplementation<TContract>)
|
|
106
|
-
return transportImplementation;
|
|
1
|
+
import {ANY_ERROR, ERROR, GGContractApiDefinition, GGContractClient, GGContractExecutor, GGContractImplementation, GGPromise, OK, SERVER_ERROR} from "@grest-ts/schema"
|
|
2
|
+
import {ClientHttpRouteToRpcTransformClientCodec, GGHttpCodec, GGHttpSchema} from "../schema/GGHttpSchema";
|
|
3
|
+
import {isBrowser} from "@grest-ts/common";
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
declare module "../schema/GGHttpSchema" {
|
|
7
|
+
interface GGHttpSchema<TContract extends GGContractApiDefinition, TContext = {}> {
|
|
8
|
+
createClient(config?: GGHttpClientConfig): GGContractClient<TContract>
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface GGHttpClientConfig {
|
|
13
|
+
url?: string;
|
|
14
|
+
timeout?: number;
|
|
15
|
+
noValidation?: boolean
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
GGHttpSchema.prototype.createClient = function <TContract extends GGContractApiDefinition, TContext>(
|
|
19
|
+
this: GGHttpSchema<TContract, TContext>,
|
|
20
|
+
config?: GGHttpClientConfig
|
|
21
|
+
): GGContractClient<TContract> {
|
|
22
|
+
return createClient(this, config);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function createClient<TContract extends GGContractApiDefinition, TContext>(
|
|
26
|
+
httpSchema: GGHttpSchema<TContract, TContext>,
|
|
27
|
+
config?: GGHttpClientConfig
|
|
28
|
+
): GGContractClient<TContract> {
|
|
29
|
+
config ??= {};
|
|
30
|
+
config.timeout ??= 15000;
|
|
31
|
+
|
|
32
|
+
if (config.url === undefined && isBrowser()) {
|
|
33
|
+
throw new Error("Must define URL for GGHttpClient when running in browser! Use empty string for same-origin requests.");
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const pathPrefix = "/" + httpSchema.pathPrefix + "/";
|
|
37
|
+
|
|
38
|
+
const transportImplementation: any = {}
|
|
39
|
+
for (const methodName of Object.keys(httpSchema.codec)) {
|
|
40
|
+
|
|
41
|
+
const contractFunction = httpSchema.contract.methods[methodName];
|
|
42
|
+
const noValidation = config?.noValidation === true;
|
|
43
|
+
|
|
44
|
+
const codec: GGHttpCodec = httpSchema.codec[methodName];
|
|
45
|
+
const wireFormat: ClientHttpRouteToRpcTransformClientCodec = codec.createForClient({
|
|
46
|
+
pathPrefix: pathPrefix,
|
|
47
|
+
contract: contractFunction,
|
|
48
|
+
middlewares: httpSchema.apiMiddlewares
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
const implementation = async (data?: unknown): Promise<OK<unknown> | ANY_ERROR> => {
|
|
52
|
+
try {
|
|
53
|
+
let baseUrl: string = config.url;
|
|
54
|
+
if (baseUrl === undefined) {
|
|
55
|
+
try {
|
|
56
|
+
const {GG_DISCOVERY} = await import(/* @vite-ignore */ '@grest-ts/discovery');
|
|
57
|
+
baseUrl = await GG_DISCOVERY.get().discoverApi(httpSchema.name);
|
|
58
|
+
} catch (err) {
|
|
59
|
+
throw new SERVER_ERROR({displayMessage: "Service discovery failed", originalError: err});
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// ---------------------------------------------
|
|
64
|
+
// Input validation
|
|
65
|
+
const validatedInput = noValidation ? data : GGContractExecutor.parseInput(contractFunction.input, data)
|
|
66
|
+
|
|
67
|
+
// ---------------------------------------------
|
|
68
|
+
// Execution
|
|
69
|
+
const fetchRequest = await wireFormat.createRequest(validatedInput);
|
|
70
|
+
const controller = new AbortController();
|
|
71
|
+
const timeoutId = setTimeout(() => controller.abort(), config.timeout);
|
|
72
|
+
const wireResponse = await fetch(baseUrl + fetchRequest.url, {
|
|
73
|
+
method: fetchRequest.method,
|
|
74
|
+
signal: controller.signal,
|
|
75
|
+
headers: fetchRequest.headers,
|
|
76
|
+
body: fetchRequest.body
|
|
77
|
+
}).finally(() => clearTimeout(timeoutId));
|
|
78
|
+
const resData = await wireFormat.parseResponse(wireResponse);
|
|
79
|
+
|
|
80
|
+
// ---------------------------------------------
|
|
81
|
+
// Response handling
|
|
82
|
+
const schema = GGContractExecutor.getResponseSchema(contractFunction, resData);
|
|
83
|
+
if (schema) {
|
|
84
|
+
resData.data = noValidation ? resData.data : GGContractExecutor.parseOutputData(schema, resData.data);
|
|
85
|
+
} else if (resData.data !== undefined) {
|
|
86
|
+
resData.data = undefined
|
|
87
|
+
}
|
|
88
|
+
if (resData.success === true) {
|
|
89
|
+
return resData as OK<unknown>;
|
|
90
|
+
} else {
|
|
91
|
+
return GGContractExecutor.createErrorObj(resData, contractFunction.errors);
|
|
92
|
+
}
|
|
93
|
+
// ---------------------------------------------
|
|
94
|
+
} catch (error) {
|
|
95
|
+
return ERROR.fromUnknown(error);
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
transportImplementation[methodName] = (data?: unknown) => {
|
|
100
|
+
return new GGPromise(implementation(data))
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
}
|
|
104
|
+
// @TODO Next line is just so test would register it correctly.
|
|
105
|
+
httpSchema.contract.implement(transportImplementation as GGContractImplementation<TContract>)
|
|
106
|
+
return transportImplementation;
|
|
107
107
|
}
|
package/src/index-browser.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
// API Schema
|
|
2
|
-
export * from "./schema/GGHttpSchema";
|
|
3
|
-
export * from "./schema/httpSchema";
|
|
4
|
-
export * from "./rpc/GGHttpRouteRPC";
|
|
5
|
-
export * from "./rpc/RpcRequest/GGRpcRequestBuilder";
|
|
6
|
-
export * from "./rpc/RpcResponse/GGRpcResponseParser";
|
|
7
|
-
|
|
8
|
-
// Client
|
|
9
|
-
export * from "./client/GGHttpSchema.createClient";
|
|
10
|
-
|
|
11
|
-
// Extensions
|
|
12
|
-
import "./client/GGHttpSchema.createClient";
|
|
1
|
+
// API Schema
|
|
2
|
+
export * from "./schema/GGHttpSchema";
|
|
3
|
+
export * from "./schema/httpSchema";
|
|
4
|
+
export * from "./rpc/GGHttpRouteRPC";
|
|
5
|
+
export * from "./rpc/RpcRequest/GGRpcRequestBuilder";
|
|
6
|
+
export * from "./rpc/RpcResponse/GGRpcResponseParser";
|
|
7
|
+
|
|
8
|
+
// Client
|
|
9
|
+
export * from "./client/GGHttpSchema.createClient";
|
|
10
|
+
|
|
11
|
+
// Extensions
|
|
12
|
+
import "./client/GGHttpSchema.createClient";
|
package/src/index-node.ts
CHANGED
|
@@ -1,38 +1,38 @@
|
|
|
1
|
-
// Register server RPC codec factory (must be before other exports that use it)
|
|
2
|
-
import {_registerRpcServerCodecFactory} from "./rpc/GGHttpRouteRPC";
|
|
3
|
-
import {GGRpcResponseBuilder} from "./rpc/RpcResponse/GGRpcResponseBuilder";
|
|
4
|
-
import {GGRpcRequestParser} from "./rpc/RpcRequest/GGRpcRequestParser";
|
|
5
|
-
_registerRpcServerCodecFactory((method, path, config) => ({
|
|
6
|
-
parseRequest: new GGRpcRequestParser(method, path, config).parseRequest,
|
|
7
|
-
sendResponse: new GGRpcResponseBuilder(config).sendResponse
|
|
8
|
-
}));
|
|
9
|
-
|
|
10
|
-
// Metrics
|
|
11
|
-
export * from "./server/GGHttpMetrics";
|
|
12
|
-
|
|
13
|
-
// Context
|
|
14
|
-
export * from "./server/GG_HTTP_REQUEST";
|
|
15
|
-
|
|
16
|
-
// API Schema
|
|
17
|
-
export * from "./schema/GGHttpSchema";
|
|
18
|
-
export * from "./schema/httpSchema";
|
|
19
|
-
export * from "./rpc/GGHttpRouteRPC";
|
|
20
|
-
export * from "./rpc/RpcRequest/GGRpcRequestBuilder";
|
|
21
|
-
export * from "./rpc/RpcRequest/GGRpcRequestParser";
|
|
22
|
-
export * from "./rpc/RpcResponse/GGRpcResponseBuilder";
|
|
23
|
-
export * from "./rpc/RpcResponse/GGRpcResponseParser";
|
|
24
|
-
|
|
25
|
-
// Server (convenience builder)
|
|
26
|
-
export * from "./server/GGHttp";
|
|
27
|
-
export * from "./server/GGHttp";
|
|
28
|
-
export * from "./server/GGHttpServer";
|
|
29
|
-
export * from "./server/GG_HTTP_SERVER";
|
|
30
|
-
|
|
31
|
-
// Client
|
|
32
|
-
export * from "./client/GGHttpSchema.createClient";
|
|
33
|
-
export * from "./server/GGHttpSchema.startServer";
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
// Extensions
|
|
37
|
-
import "./client/GGHttpSchema.createClient";
|
|
38
|
-
import "./server/GGHttpSchema.startServer";
|
|
1
|
+
// Register server RPC codec factory (must be before other exports that use it)
|
|
2
|
+
import {_registerRpcServerCodecFactory} from "./rpc/GGHttpRouteRPC";
|
|
3
|
+
import {GGRpcResponseBuilder} from "./rpc/RpcResponse/GGRpcResponseBuilder";
|
|
4
|
+
import {GGRpcRequestParser} from "./rpc/RpcRequest/GGRpcRequestParser";
|
|
5
|
+
_registerRpcServerCodecFactory((method, path, config) => ({
|
|
6
|
+
parseRequest: new GGRpcRequestParser(method, path, config).parseRequest,
|
|
7
|
+
sendResponse: new GGRpcResponseBuilder(config).sendResponse
|
|
8
|
+
}));
|
|
9
|
+
|
|
10
|
+
// Metrics
|
|
11
|
+
export * from "./server/GGHttpMetrics";
|
|
12
|
+
|
|
13
|
+
// Context
|
|
14
|
+
export * from "./server/GG_HTTP_REQUEST";
|
|
15
|
+
|
|
16
|
+
// API Schema
|
|
17
|
+
export * from "./schema/GGHttpSchema";
|
|
18
|
+
export * from "./schema/httpSchema";
|
|
19
|
+
export * from "./rpc/GGHttpRouteRPC";
|
|
20
|
+
export * from "./rpc/RpcRequest/GGRpcRequestBuilder";
|
|
21
|
+
export * from "./rpc/RpcRequest/GGRpcRequestParser";
|
|
22
|
+
export * from "./rpc/RpcResponse/GGRpcResponseBuilder";
|
|
23
|
+
export * from "./rpc/RpcResponse/GGRpcResponseParser";
|
|
24
|
+
|
|
25
|
+
// Server (convenience builder)
|
|
26
|
+
export * from "./server/GGHttp";
|
|
27
|
+
export * from "./server/GGHttp";
|
|
28
|
+
export * from "./server/GGHttpServer";
|
|
29
|
+
export * from "./server/GG_HTTP_SERVER";
|
|
30
|
+
|
|
31
|
+
// Client
|
|
32
|
+
export * from "./client/GGHttpSchema.createClient";
|
|
33
|
+
export * from "./server/GGHttpSchema.startServer";
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
// Extensions
|
|
37
|
+
import "./client/GGHttpSchema.createClient";
|
|
38
|
+
import "./server/GGHttpSchema.startServer";
|
|
@@ -1,42 +1,42 @@
|
|
|
1
|
-
import {HttpMethod} from "@grest-ts/common"
|
|
2
|
-
import {ClientHttpRouteToRpcTransformClientCodec, ClientHttpRouteToRpcTransformClientConfig, ClientHttpRouteToRpcTransformServerCodec, ClientHttpRouteToRpcTransformServerConfig, GGHttpCodec} from "../schema/GGHttpSchema"
|
|
3
|
-
import {GGRpcRequestBuilder} from "./RpcRequest/GGRpcRequestBuilder";
|
|
4
|
-
import {GGRpcResponseParser} from "./RpcResponse/GGRpcResponseParser";
|
|
5
|
-
|
|
6
|
-
export type GGRpcServerCodecFactory = (method: HttpMethod, path: string, config: ClientHttpRouteToRpcTransformServerConfig) => ClientHttpRouteToRpcTransformServerCodec;
|
|
7
|
-
|
|
8
|
-
let _serverCodecFactory: GGRpcServerCodecFactory | undefined;
|
|
9
|
-
|
|
10
|
-
export function _registerRpcServerCodecFactory(factory: GGRpcServerCodecFactory): void {
|
|
11
|
-
_serverCodecFactory = factory;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export const GGRpc = {
|
|
15
|
-
GET: (path: string) => new GGHttpRpcCodec("GET", path),
|
|
16
|
-
DELETE: (path: string) => new GGHttpRpcCodec("DELETE", path),
|
|
17
|
-
POST: (path: string) => new GGHttpRpcCodec("POST", path),
|
|
18
|
-
PUT: (path: string) => new GGHttpRpcCodec("PUT", path),
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
class GGHttpRpcCodec implements GGHttpCodec {
|
|
22
|
-
|
|
23
|
-
public readonly method: HttpMethod
|
|
24
|
-
public readonly path: string
|
|
25
|
-
|
|
26
|
-
constructor(method: HttpMethod, path: string) {
|
|
27
|
-
this.method = method
|
|
28
|
-
this.path = path
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
public createForClient(config: ClientHttpRouteToRpcTransformClientConfig): ClientHttpRouteToRpcTransformClientCodec {
|
|
32
|
-
return {
|
|
33
|
-
createRequest: new GGRpcRequestBuilder(this.method, this.path, config).createRequest,
|
|
34
|
-
parseResponse: new GGRpcResponseParser(config).parseResponse
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
public createForServer(config: ClientHttpRouteToRpcTransformServerConfig): ClientHttpRouteToRpcTransformServerCodec {
|
|
39
|
-
if (!_serverCodecFactory) throw new Error("Server RPC codec not available. Ensure @grest-ts/http server entry is imported.");
|
|
40
|
-
return _serverCodecFactory(this.method, this.path, config);
|
|
41
|
-
}
|
|
42
|
-
}
|
|
1
|
+
import {HttpMethod} from "@grest-ts/common"
|
|
2
|
+
import {ClientHttpRouteToRpcTransformClientCodec, ClientHttpRouteToRpcTransformClientConfig, ClientHttpRouteToRpcTransformServerCodec, ClientHttpRouteToRpcTransformServerConfig, GGHttpCodec} from "../schema/GGHttpSchema"
|
|
3
|
+
import {GGRpcRequestBuilder} from "./RpcRequest/GGRpcRequestBuilder";
|
|
4
|
+
import {GGRpcResponseParser} from "./RpcResponse/GGRpcResponseParser";
|
|
5
|
+
|
|
6
|
+
export type GGRpcServerCodecFactory = (method: HttpMethod, path: string, config: ClientHttpRouteToRpcTransformServerConfig) => ClientHttpRouteToRpcTransformServerCodec;
|
|
7
|
+
|
|
8
|
+
let _serverCodecFactory: GGRpcServerCodecFactory | undefined;
|
|
9
|
+
|
|
10
|
+
export function _registerRpcServerCodecFactory(factory: GGRpcServerCodecFactory): void {
|
|
11
|
+
_serverCodecFactory = factory;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const GGRpc = {
|
|
15
|
+
GET: (path: string) => new GGHttpRpcCodec("GET", path),
|
|
16
|
+
DELETE: (path: string) => new GGHttpRpcCodec("DELETE", path),
|
|
17
|
+
POST: (path: string) => new GGHttpRpcCodec("POST", path),
|
|
18
|
+
PUT: (path: string) => new GGHttpRpcCodec("PUT", path),
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
class GGHttpRpcCodec implements GGHttpCodec {
|
|
22
|
+
|
|
23
|
+
public readonly method: HttpMethod
|
|
24
|
+
public readonly path: string
|
|
25
|
+
|
|
26
|
+
constructor(method: HttpMethod, path: string) {
|
|
27
|
+
this.method = method
|
|
28
|
+
this.path = path
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
public createForClient(config: ClientHttpRouteToRpcTransformClientConfig): ClientHttpRouteToRpcTransformClientCodec {
|
|
32
|
+
return {
|
|
33
|
+
createRequest: new GGRpcRequestBuilder(this.method, this.path, config).createRequest,
|
|
34
|
+
parseResponse: new GGRpcResponseParser(config).parseResponse
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
public createForServer(config: ClientHttpRouteToRpcTransformServerConfig): ClientHttpRouteToRpcTransformServerCodec {
|
|
39
|
+
if (!_serverCodecFactory) throw new Error("Server RPC codec not available. Ensure @grest-ts/http server entry is imported.");
|
|
40
|
+
return _serverCodecFactory(this.method, this.path, config);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -1,91 +1,91 @@
|
|
|
1
|
-
import type {HttpMethod} from "@grest-ts/common";
|
|
2
|
-
import {GGContractMethod} from "@grest-ts/schema";
|
|
3
|
-
import {ClientHttpRouteToRpcTransformClientConfig, GGHttpFetchRequest, GGHttpTransportMiddleware} from "../../schema/GGHttpSchema";
|
|
4
|
-
|
|
5
|
-
export class GGRpcRequestBuilder {
|
|
6
|
-
|
|
7
|
-
public readonly contract: GGContractMethod
|
|
8
|
-
public readonly middlewares: readonly GGHttpTransportMiddleware[]
|
|
9
|
-
public readonly method: HttpMethod;
|
|
10
|
-
public readonly pathTemplate: string;
|
|
11
|
-
public readonly pathParams: string[];
|
|
12
|
-
public readonly pathPrefix: string;
|
|
13
|
-
public readonly hasBody: boolean;
|
|
14
|
-
|
|
15
|
-
constructor(
|
|
16
|
-
method: HttpMethod,
|
|
17
|
-
pathTemplate: string,
|
|
18
|
-
config: ClientHttpRouteToRpcTransformClientConfig
|
|
19
|
-
) {
|
|
20
|
-
this.method = method
|
|
21
|
-
this.pathTemplate = pathTemplate
|
|
22
|
-
this.pathPrefix = config.pathPrefix
|
|
23
|
-
this.contract = config.contract
|
|
24
|
-
this.middlewares = config.middlewares
|
|
25
|
-
this.pathParams = (pathTemplate.match(/:(\w+)/g) || []).map(m => m.slice(1))
|
|
26
|
-
this.hasBody = method === "POST" || method === "PUT" || method === "PATCH"
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
public createRequest = (data: unknown): GGHttpFetchRequest => {
|
|
30
|
-
let result: GGHttpFetchRequest;
|
|
31
|
-
if (this.hasBody) {
|
|
32
|
-
result = {
|
|
33
|
-
url: this.pathPrefix + this.buildPath(data),
|
|
34
|
-
method: this.method,
|
|
35
|
-
headers: {'Content-Type': 'application/json'},
|
|
36
|
-
body: this.buildBody(data)
|
|
37
|
-
}
|
|
38
|
-
} else {
|
|
39
|
-
result = {
|
|
40
|
-
url: this.pathPrefix + this.buildPath(data) + this.buildQueryString(data as Record<string, unknown>),
|
|
41
|
-
method: this.method,
|
|
42
|
-
headers: {},
|
|
43
|
-
body: undefined
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
this.middlewares?.forEach(mw => mw.updateRequest?.(result))
|
|
47
|
-
return result
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
private buildPath(data: unknown) {
|
|
51
|
-
let path: string = this.pathTemplate;
|
|
52
|
-
if (this.pathParams.length > 0 && typeof data === "object" && data) {
|
|
53
|
-
for (let i = 0; i < this.pathParams.length; i++) {
|
|
54
|
-
const p = this.pathParams[i]
|
|
55
|
-
const val = (data as Record<string, unknown>)[p]
|
|
56
|
-
path = path.replace(':' + p, encodeURIComponent(val === undefined || val === null ? "" : String(val)))
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
return path;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
private buildBody(data: unknown) {
|
|
63
|
-
if (data === undefined || data === null) {
|
|
64
|
-
return undefined;
|
|
65
|
-
}
|
|
66
|
-
if (this.contract.input?.def.hasNonJsonData) {
|
|
67
|
-
throw new Error("Schema contains non-JSON data (e.g. files). Use GGRpc.MULTIPART_POST instead of GGRpc.POST for this route.")
|
|
68
|
-
} else {
|
|
69
|
-
return JSON.stringify(data)
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
private buildQueryString(data: unknown): string {
|
|
74
|
-
if (data && typeof data === "object") {
|
|
75
|
-
const params = new URLSearchParams()
|
|
76
|
-
for (const [key, value] of Object.entries(data)) {
|
|
77
|
-
if (value !== undefined && value !== null) {
|
|
78
|
-
if (this.pathParams.length > 0 && this.pathParams.includes(key)) {
|
|
79
|
-
continue;
|
|
80
|
-
}
|
|
81
|
-
params.append(key, String(value))
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
const query = params.toString()
|
|
85
|
-
if (query) {
|
|
86
|
-
return "?" + query
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
return ""
|
|
90
|
-
}
|
|
91
|
-
}
|
|
1
|
+
import type {HttpMethod} from "@grest-ts/common";
|
|
2
|
+
import {GGContractMethod} from "@grest-ts/schema";
|
|
3
|
+
import {ClientHttpRouteToRpcTransformClientConfig, GGHttpFetchRequest, GGHttpTransportMiddleware} from "../../schema/GGHttpSchema";
|
|
4
|
+
|
|
5
|
+
export class GGRpcRequestBuilder {
|
|
6
|
+
|
|
7
|
+
public readonly contract: GGContractMethod
|
|
8
|
+
public readonly middlewares: readonly GGHttpTransportMiddleware[]
|
|
9
|
+
public readonly method: HttpMethod;
|
|
10
|
+
public readonly pathTemplate: string;
|
|
11
|
+
public readonly pathParams: string[];
|
|
12
|
+
public readonly pathPrefix: string;
|
|
13
|
+
public readonly hasBody: boolean;
|
|
14
|
+
|
|
15
|
+
constructor(
|
|
16
|
+
method: HttpMethod,
|
|
17
|
+
pathTemplate: string,
|
|
18
|
+
config: ClientHttpRouteToRpcTransformClientConfig
|
|
19
|
+
) {
|
|
20
|
+
this.method = method
|
|
21
|
+
this.pathTemplate = pathTemplate
|
|
22
|
+
this.pathPrefix = config.pathPrefix
|
|
23
|
+
this.contract = config.contract
|
|
24
|
+
this.middlewares = config.middlewares
|
|
25
|
+
this.pathParams = (pathTemplate.match(/:(\w+)/g) || []).map(m => m.slice(1))
|
|
26
|
+
this.hasBody = method === "POST" || method === "PUT" || method === "PATCH"
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
public createRequest = (data: unknown): GGHttpFetchRequest => {
|
|
30
|
+
let result: GGHttpFetchRequest;
|
|
31
|
+
if (this.hasBody) {
|
|
32
|
+
result = {
|
|
33
|
+
url: this.pathPrefix + this.buildPath(data),
|
|
34
|
+
method: this.method,
|
|
35
|
+
headers: {'Content-Type': 'application/json'},
|
|
36
|
+
body: this.buildBody(data)
|
|
37
|
+
}
|
|
38
|
+
} else {
|
|
39
|
+
result = {
|
|
40
|
+
url: this.pathPrefix + this.buildPath(data) + this.buildQueryString(data as Record<string, unknown>),
|
|
41
|
+
method: this.method,
|
|
42
|
+
headers: {},
|
|
43
|
+
body: undefined
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
this.middlewares?.forEach(mw => mw.updateRequest?.(result))
|
|
47
|
+
return result
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
private buildPath(data: unknown) {
|
|
51
|
+
let path: string = this.pathTemplate;
|
|
52
|
+
if (this.pathParams.length > 0 && typeof data === "object" && data) {
|
|
53
|
+
for (let i = 0; i < this.pathParams.length; i++) {
|
|
54
|
+
const p = this.pathParams[i]
|
|
55
|
+
const val = (data as Record<string, unknown>)[p]
|
|
56
|
+
path = path.replace(':' + p, encodeURIComponent(val === undefined || val === null ? "" : String(val)))
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return path;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
private buildBody(data: unknown) {
|
|
63
|
+
if (data === undefined || data === null) {
|
|
64
|
+
return undefined;
|
|
65
|
+
}
|
|
66
|
+
if (this.contract.input?.def.hasNonJsonData) {
|
|
67
|
+
throw new Error("Schema contains non-JSON data (e.g. files). Use GGRpc.MULTIPART_POST instead of GGRpc.POST for this route.")
|
|
68
|
+
} else {
|
|
69
|
+
return JSON.stringify(data)
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
private buildQueryString(data: unknown): string {
|
|
74
|
+
if (data && typeof data === "object") {
|
|
75
|
+
const params = new URLSearchParams()
|
|
76
|
+
for (const [key, value] of Object.entries(data)) {
|
|
77
|
+
if (value !== undefined && value !== null) {
|
|
78
|
+
if (this.pathParams.length > 0 && this.pathParams.includes(key)) {
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
params.append(key, String(value))
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
const query = params.toString()
|
|
85
|
+
if (query) {
|
|
86
|
+
return "?" + query
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return ""
|
|
90
|
+
}
|
|
91
|
+
}
|