@mionjs/router 0.8.6 → 0.8.8
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/.dist/cjs/index.d.ts +1 -0
- package/.dist/cjs/index.d.ts.map +1 -0
- package/.dist/cjs/src/aot/aotCacheLoader.d.ts +1 -0
- package/.dist/cjs/src/aot/aotCacheLoader.d.ts.map +1 -0
- package/.dist/cjs/src/callContext.d.ts +1 -0
- package/.dist/cjs/src/callContext.d.ts.map +1 -0
- package/.dist/cjs/src/constants.d.ts +1 -0
- package/.dist/cjs/src/constants.d.ts.map +1 -0
- package/.dist/cjs/src/defaultRoutes.d.ts +1 -0
- package/.dist/cjs/src/defaultRoutes.d.ts.map +1 -0
- package/.dist/cjs/src/dispatch.d.ts +1 -0
- package/.dist/cjs/src/dispatch.d.ts.map +1 -0
- package/.dist/cjs/src/lib/aotEmitter.d.ts +1 -0
- package/.dist/cjs/src/lib/aotEmitter.d.ts.map +1 -0
- package/.dist/cjs/src/lib/dispatchError.d.ts +1 -0
- package/.dist/cjs/src/lib/dispatchError.d.ts.map +1 -0
- package/.dist/cjs/src/lib/handlers.d.ts +1 -0
- package/.dist/cjs/src/lib/handlers.d.ts.map +1 -0
- package/.dist/cjs/src/lib/headers.d.ts +1 -0
- package/.dist/cjs/src/lib/headers.d.ts.map +1 -0
- package/.dist/cjs/src/lib/methodsCache.d.ts +1 -0
- package/.dist/cjs/src/lib/methodsCache.d.ts.map +1 -0
- package/.dist/cjs/src/lib/queryBody.d.ts +1 -0
- package/.dist/cjs/src/lib/queryBody.d.ts.map +1 -0
- package/.dist/cjs/src/lib/reflection.d.ts +1 -0
- package/.dist/cjs/src/lib/reflection.d.ts.map +1 -0
- package/.dist/cjs/src/lib/remoteMethods.d.ts +1 -0
- package/.dist/cjs/src/lib/remoteMethods.d.ts.map +1 -0
- package/.dist/cjs/src/lib/test/aotEmitter-test-router.d.ts +1 -0
- package/.dist/cjs/src/lib/test/aotEmitter-test-router.d.ts.map +1 -0
- package/.dist/cjs/src/router.d.ts +1 -0
- package/.dist/cjs/src/router.d.ts.map +1 -0
- package/.dist/cjs/src/routes/client.routes.d.ts +1 -0
- package/.dist/cjs/src/routes/client.routes.d.ts.map +1 -0
- package/.dist/cjs/src/routes/errors.routes.d.ts +1 -0
- package/.dist/cjs/src/routes/errors.routes.d.ts.map +1 -0
- package/.dist/cjs/src/routes/mion.routes.d.ts +1 -0
- package/.dist/cjs/src/routes/mion.routes.d.ts.map +1 -0
- package/.dist/cjs/src/routes/serializer.routes.d.ts +1 -0
- package/.dist/cjs/src/routes/serializer.routes.d.ts.map +1 -0
- package/.dist/cjs/src/routesFlow.d.ts +1 -0
- package/.dist/cjs/src/routesFlow.d.ts.map +1 -0
- package/.dist/cjs/src/types/context.d.ts +1 -0
- package/.dist/cjs/src/types/context.d.ts.map +1 -0
- package/.dist/cjs/src/types/definitions.d.ts +1 -0
- package/.dist/cjs/src/types/definitions.d.ts.map +1 -0
- package/.dist/cjs/src/types/general.d.ts +1 -0
- package/.dist/cjs/src/types/general.d.ts.map +1 -0
- package/.dist/cjs/src/types/guards.d.ts +1 -0
- package/.dist/cjs/src/types/guards.d.ts.map +1 -0
- package/.dist/cjs/src/types/handlers.d.ts +1 -0
- package/.dist/cjs/src/types/handlers.d.ts.map +1 -0
- package/.dist/cjs/src/types/publicMethods.d.ts +1 -0
- package/.dist/cjs/src/types/publicMethods.d.ts.map +1 -0
- package/.dist/cjs/src/types/remoteMethods.d.ts +1 -0
- package/.dist/cjs/src/types/remoteMethods.d.ts.map +1 -0
- package/.dist/esm/index.d.ts +1 -0
- package/.dist/esm/index.d.ts.map +1 -0
- package/.dist/esm/src/aot/aotCacheLoader.d.ts +1 -0
- package/.dist/esm/src/aot/aotCacheLoader.d.ts.map +1 -0
- package/.dist/esm/src/callContext.d.ts +1 -0
- package/.dist/esm/src/callContext.d.ts.map +1 -0
- package/.dist/esm/src/constants.d.ts +1 -0
- package/.dist/esm/src/constants.d.ts.map +1 -0
- package/.dist/esm/src/defaultRoutes.d.ts +1 -0
- package/.dist/esm/src/defaultRoutes.d.ts.map +1 -0
- package/.dist/esm/src/dispatch.d.ts +1 -0
- package/.dist/esm/src/dispatch.d.ts.map +1 -0
- package/.dist/esm/src/lib/aotEmitter.d.ts +1 -0
- package/.dist/esm/src/lib/aotEmitter.d.ts.map +1 -0
- package/.dist/esm/src/lib/dispatchError.d.ts +1 -0
- package/.dist/esm/src/lib/dispatchError.d.ts.map +1 -0
- package/.dist/esm/src/lib/handlers.d.ts +1 -0
- package/.dist/esm/src/lib/handlers.d.ts.map +1 -0
- package/.dist/esm/src/lib/headers.d.ts +1 -0
- package/.dist/esm/src/lib/headers.d.ts.map +1 -0
- package/.dist/esm/src/lib/methodsCache.d.ts +1 -0
- package/.dist/esm/src/lib/methodsCache.d.ts.map +1 -0
- package/.dist/esm/src/lib/queryBody.d.ts +1 -0
- package/.dist/esm/src/lib/queryBody.d.ts.map +1 -0
- package/.dist/esm/src/lib/reflection.d.ts +1 -0
- package/.dist/esm/src/lib/reflection.d.ts.map +1 -0
- package/.dist/esm/src/lib/remoteMethods.d.ts +1 -0
- package/.dist/esm/src/lib/remoteMethods.d.ts.map +1 -0
- package/.dist/esm/src/lib/test/aotEmitter-test-router.d.ts +1 -0
- package/.dist/esm/src/lib/test/aotEmitter-test-router.d.ts.map +1 -0
- package/.dist/esm/src/router.d.ts +1 -0
- package/.dist/esm/src/router.d.ts.map +1 -0
- package/.dist/esm/src/routes/client.routes.d.ts +1 -0
- package/.dist/esm/src/routes/client.routes.d.ts.map +1 -0
- package/.dist/esm/src/routes/errors.routes.d.ts +1 -0
- package/.dist/esm/src/routes/errors.routes.d.ts.map +1 -0
- package/.dist/esm/src/routes/mion.routes.d.ts +1 -0
- package/.dist/esm/src/routes/mion.routes.d.ts.map +1 -0
- package/.dist/esm/src/routes/serializer.routes.d.ts +1 -0
- package/.dist/esm/src/routes/serializer.routes.d.ts.map +1 -0
- package/.dist/esm/src/routesFlow.d.ts +1 -0
- package/.dist/esm/src/routesFlow.d.ts.map +1 -0
- package/.dist/esm/src/types/context.d.ts +1 -0
- package/.dist/esm/src/types/context.d.ts.map +1 -0
- package/.dist/esm/src/types/definitions.d.ts +1 -0
- package/.dist/esm/src/types/definitions.d.ts.map +1 -0
- package/.dist/esm/src/types/general.d.ts +1 -0
- package/.dist/esm/src/types/general.d.ts.map +1 -0
- package/.dist/esm/src/types/guards.d.ts +1 -0
- package/.dist/esm/src/types/guards.d.ts.map +1 -0
- package/.dist/esm/src/types/handlers.d.ts +1 -0
- package/.dist/esm/src/types/handlers.d.ts.map +1 -0
- package/.dist/esm/src/types/publicMethods.d.ts +1 -0
- package/.dist/esm/src/types/publicMethods.d.ts.map +1 -0
- package/.dist/esm/src/types/remoteMethods.d.ts +1 -0
- package/.dist/esm/src/types/remoteMethods.d.ts.map +1 -0
- package/index.ts +29 -0
- package/package.json +9 -5
- package/src/aot/aotCacheLoader.ts +15 -0
- package/src/callContext.ts +193 -0
- package/src/constants.ts +45 -0
- package/src/defaultRoutes.ts +37 -0
- package/src/dispatch.ts +204 -0
- package/src/lib/aotEmitter.ts +140 -0
- package/src/lib/dispatchError.ts +60 -0
- package/src/lib/handlers.ts +75 -0
- package/src/lib/headers.ts +102 -0
- package/src/lib/methodsCache.ts +72 -0
- package/src/lib/queryBody.ts +40 -0
- package/src/lib/reflection.ts +517 -0
- package/src/lib/remoteMethods.ts +180 -0
- package/src/lib/test/aotEmitter-test-router.ts +40 -0
- package/src/router.ts +656 -0
- package/src/routes/client.routes.ts +108 -0
- package/src/routes/errors.routes.ts +51 -0
- package/src/routes/mion.routes.ts +11 -0
- package/src/routes/serializer.routes.ts +225 -0
- package/src/routesFlow.ts +320 -0
- package/src/types/context.ts +119 -0
- package/src/types/definitions.ts +53 -0
- package/src/types/general.ts +84 -0
- package/src/types/guards.ts +71 -0
- package/src/types/handlers.ts +49 -0
- package/src/types/publicMethods.ts +83 -0
- package/src/types/remoteMethods.ts +54 -0
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/* ########
|
|
2
|
+
* 2023 mion
|
|
3
|
+
* Author: Ma-jerez
|
|
4
|
+
* License: MIT
|
|
5
|
+
* The software is provided "as is", without warranty of any kind.
|
|
6
|
+
* ######## */
|
|
7
|
+
|
|
8
|
+
import {AnyObject, Mutable, RpcError, MION_ROUTES, SerializableMethodsData, SerializerModes} from '@mionjs/core';
|
|
9
|
+
import {
|
|
10
|
+
getMiddleFnExecutable,
|
|
11
|
+
getRouteExecutable,
|
|
12
|
+
isPrivateExecutable,
|
|
13
|
+
getRouterOptions,
|
|
14
|
+
getTotalExecutables,
|
|
15
|
+
getAllExecutablesIds,
|
|
16
|
+
getAnyExecutable,
|
|
17
|
+
} from '../router.ts';
|
|
18
|
+
import {middleFn, route} from '../lib/handlers.ts';
|
|
19
|
+
import {RouterOptions, Routes} from '../types/general.ts';
|
|
20
|
+
import {MiddleFnsCollection} from '../types/publicMethods.ts';
|
|
21
|
+
import {getSerializableMethod, serializeMethodDeps} from '../lib/remoteMethods.ts';
|
|
22
|
+
import {RemoteMethod} from '../types/remoteMethods.ts';
|
|
23
|
+
import {CallContext, MionResponse} from '../types/context.ts';
|
|
24
|
+
|
|
25
|
+
export interface ClientRouteOptions extends RouterOptions {
|
|
26
|
+
getAllRemoteMethodsMaxNumber?: number;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export const defaultClientRouteOptions = {
|
|
30
|
+
getAllRemoteMethodsMaxNumber: 100,
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
// Internal mion routes that should not be exposed to clients
|
|
34
|
+
const mionInternalRoutes = Object.values(MION_ROUTES) as string[];
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Returns the metadata for the given method ids.
|
|
38
|
+
* If getAllRemoteMethods is true, all public methods and middleFns are returned.
|
|
39
|
+
* @mion:route
|
|
40
|
+
*/
|
|
41
|
+
function mionGetRemoteMethodsDataById(
|
|
42
|
+
ctx,
|
|
43
|
+
methodsIds: string[],
|
|
44
|
+
getAllRemoteMethods?: boolean
|
|
45
|
+
): SerializableMethodsData | RpcError<'rpc-metadata-not-found'> {
|
|
46
|
+
const resp: SerializableMethodsData = {
|
|
47
|
+
methods: {},
|
|
48
|
+
deps: {},
|
|
49
|
+
purFnDeps: {},
|
|
50
|
+
};
|
|
51
|
+
const errorData = {};
|
|
52
|
+
const maxMethods =
|
|
53
|
+
getRouterOptions<ClientRouteOptions>().getAllRemoteMethodsMaxNumber ||
|
|
54
|
+
defaultClientRouteOptions.getAllRemoteMethodsMaxNumber;
|
|
55
|
+
const shouldReturnAll = getAllRemoteMethods && getTotalExecutables() <= maxMethods;
|
|
56
|
+
const idsToReturn = shouldReturnAll
|
|
57
|
+
? getAllExecutablesIds().filter(
|
|
58
|
+
(id) => !mionInternalRoutes.includes(id) && !isPrivateExecutable(getAnyExecutable(id) as RemoteMethod)
|
|
59
|
+
)
|
|
60
|
+
: methodsIds;
|
|
61
|
+
idsToReturn.forEach((id) => addRequiredRemoteMethodsToResponse(id, resp, errorData));
|
|
62
|
+
|
|
63
|
+
if (Object.keys(errorData).length)
|
|
64
|
+
return new RpcError({
|
|
65
|
+
type: 'rpc-metadata-not-found',
|
|
66
|
+
publicMessage: 'Errors getting Remote Methods Metadata',
|
|
67
|
+
errorData,
|
|
68
|
+
});
|
|
69
|
+
return resp;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** Middleware wrapper: delegates to mionGetRemoteMethodsDataById when params are provided */
|
|
73
|
+
function mionMethodsMetadata(
|
|
74
|
+
ctx: CallContext,
|
|
75
|
+
methodsIds?: string[],
|
|
76
|
+
getAllRemoteMethods?: boolean
|
|
77
|
+
): SerializableMethodsData | RpcError<'rpc-metadata-not-found'> | void {
|
|
78
|
+
if (!methodsIds || methodsIds.length === 0) return;
|
|
79
|
+
// Force JSON serialization so optimistic client can parse the response
|
|
80
|
+
(ctx.response as Mutable<MionResponse>).serializer = SerializerModes.stringifyJson;
|
|
81
|
+
return mionGetRemoteMethodsDataById(ctx, methodsIds, getAllRemoteMethods);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function addRequiredRemoteMethodsToResponse(id: string, resp: SerializableMethodsData, errorData: AnyObject): void {
|
|
85
|
+
const {methods, deps, purFnDeps} = resp;
|
|
86
|
+
if (methods[id]) return;
|
|
87
|
+
if (mionInternalRoutes.includes(id)) return;
|
|
88
|
+
const executable = getMiddleFnExecutable(id) || getRouteExecutable(id);
|
|
89
|
+
if (!executable) {
|
|
90
|
+
errorData[id] = `Remote Method ${id} not found`;
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
if (isPrivateExecutable(executable)) return;
|
|
94
|
+
const method = getSerializableMethod(executable as RemoteMethod);
|
|
95
|
+
methods[id] = method;
|
|
96
|
+
method.middleFnIds?.forEach((middleFnId) => addRequiredRemoteMethodsToResponse(middleFnId, resp, errorData));
|
|
97
|
+
serializeMethodDeps(method, deps, purFnDeps);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export const mionClientMiddleFns = {
|
|
101
|
+
[MION_ROUTES.methodsMetadata]: middleFn(mionMethodsMetadata, {runOnError: true}),
|
|
102
|
+
} as const satisfies MiddleFnsCollection;
|
|
103
|
+
|
|
104
|
+
export const mionClientRoutes = {
|
|
105
|
+
// Client routes always use stringifyJson serialization to avoid mutating data as is cached
|
|
106
|
+
// These routes are used by the client to fetch metadata and must work regardless of router's default serialization
|
|
107
|
+
[MION_ROUTES.methodsMetadataById]: route(mionGetRemoteMethodsDataById, {serializer: 'stringifyJson'}),
|
|
108
|
+
} as const satisfies Routes;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/* ########
|
|
2
|
+
* 2023 mion
|
|
3
|
+
* Author: Ma-jerez
|
|
4
|
+
* License: MIT
|
|
5
|
+
* The software is provided "as is", without warranty of any kind.
|
|
6
|
+
* ######## */
|
|
7
|
+
|
|
8
|
+
import type {Routes} from '../types/general.ts';
|
|
9
|
+
import type {CallContext} from '../types/context.ts';
|
|
10
|
+
import {RpcError, MION_ROUTES, StatusCodes} from '@mionjs/core';
|
|
11
|
+
import {route} from '../lib/handlers.ts';
|
|
12
|
+
|
|
13
|
+
export const mionErrorsRoutes = {
|
|
14
|
+
/**
|
|
15
|
+
* !IMPORTANT!
|
|
16
|
+
* This is declared as route mostly to reuse existing router serialization/deserialization functionality.
|
|
17
|
+
* But "@thrownErrors" is expected to be a field in response body that contain all thrown errors from other executables.
|
|
18
|
+
* thrown Errors are not strongly typed and are all serialized/deserialized as RpcError<string>.
|
|
19
|
+
* this also prevents users to register a route with the same name.
|
|
20
|
+
*/
|
|
21
|
+
[MION_ROUTES.thrownErrors]: route((ctx: CallContext): Record<string, RpcError<string>> => {
|
|
22
|
+
return ctx.request.thrownErrors || {};
|
|
23
|
+
}),
|
|
24
|
+
/**
|
|
25
|
+
* Route that handles not-found scenarios when a requested route doesn't exist.
|
|
26
|
+
* This route is registered as an internal mion route.
|
|
27
|
+
* The route is called by dispatch logic when no matching route is found.
|
|
28
|
+
* Throws an RpcError that will be caught and stored in thrownErrors by the router.
|
|
29
|
+
*/
|
|
30
|
+
[MION_ROUTES.notFound]: route((ctx: CallContext): RpcError<'route-not-found'> => {
|
|
31
|
+
throw new RpcError({
|
|
32
|
+
statusCode: StatusCodes.NOT_FOUND,
|
|
33
|
+
publicMessage: `Route not found`,
|
|
34
|
+
type: 'route-not-found',
|
|
35
|
+
});
|
|
36
|
+
}),
|
|
37
|
+
/**
|
|
38
|
+
* Platform error route for strongly typing platform/adapter errors.
|
|
39
|
+
* Platform errors occur before reaching the router or outside the router
|
|
40
|
+
* and are platform/adapter related (e.g., HTTP server errors, connection issues).
|
|
41
|
+
* This route is used for serialization/deserialization of platform errors.
|
|
42
|
+
* This also prevents users to register a route with the same name.
|
|
43
|
+
*/
|
|
44
|
+
[MION_ROUTES.platformError]: route((_ctx: CallContext): RpcError<string> => {
|
|
45
|
+
// Platform errors are passed through context, this route is for type serialization
|
|
46
|
+
return new RpcError({
|
|
47
|
+
publicMessage: 'Platform error',
|
|
48
|
+
type: 'platform-error',
|
|
49
|
+
});
|
|
50
|
+
}),
|
|
51
|
+
} as const satisfies Routes;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import {Routes} from '../types/general.ts';
|
|
2
|
+
import {PublicApi} from '../types/publicMethods.ts';
|
|
3
|
+
import {mionClientRoutes} from './client.routes.ts';
|
|
4
|
+
import {mionErrorsRoutes} from './errors.routes.ts';
|
|
5
|
+
|
|
6
|
+
export const mionRoutes = {
|
|
7
|
+
...mionClientRoutes,
|
|
8
|
+
...mionErrorsRoutes,
|
|
9
|
+
} as const satisfies Routes;
|
|
10
|
+
|
|
11
|
+
export type MionRoutes = PublicApi<typeof mionRoutes>;
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
/* ########
|
|
2
|
+
* 2023 mion
|
|
3
|
+
* Author: Ma-jerez
|
|
4
|
+
* License: MIT
|
|
5
|
+
* The software is provided "as is", without warranty of any kind.
|
|
6
|
+
* ######## */
|
|
7
|
+
|
|
8
|
+
import {MionResponse, MionRequest, CallContext, ResponseBody} from '../types/context.ts';
|
|
9
|
+
import {RouterOptions} from '../types/general.ts';
|
|
10
|
+
import {MiddleFnsCollection, MayReturnError} from '../types/publicMethods.ts';
|
|
11
|
+
import {
|
|
12
|
+
AnyObject,
|
|
13
|
+
Mutable,
|
|
14
|
+
MION_ROUTES,
|
|
15
|
+
StatusCodes,
|
|
16
|
+
serializeBinaryBody as coreSerializeBinaryBody,
|
|
17
|
+
deserializeBinaryBody as coreDeserializeBinaryBody,
|
|
18
|
+
SerializerModes,
|
|
19
|
+
} from '@mionjs/core';
|
|
20
|
+
import {rawMiddleFn} from '../lib/handlers.ts';
|
|
21
|
+
import {getRouteExecutableFromPath, getRouteExecutable} from '../router.ts';
|
|
22
|
+
import {RpcError} from '@mionjs/core';
|
|
23
|
+
import {RemoteMethod} from '../types/remoteMethods.ts';
|
|
24
|
+
import {onExecutableError} from '../lib/dispatchError.ts';
|
|
25
|
+
|
|
26
|
+
// ############# PUBLIC METHODS #############
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Deserializes the request body and stores it in the request body property.
|
|
30
|
+
* This method is called before any other middleFn or route handler.
|
|
31
|
+
* For binary requests, the body is parsed lazily per-method in dispatch.ts.
|
|
32
|
+
* @mion:middleFn
|
|
33
|
+
*/
|
|
34
|
+
export function deserializeRequestBody(context: CallContext): MayReturnError {
|
|
35
|
+
if (!context.request.rawBody) return; // empty body
|
|
36
|
+
let parsedBody: any;
|
|
37
|
+
switch (context.request.bodyType) {
|
|
38
|
+
case SerializerModes.stringifyJson: // jit stringify json
|
|
39
|
+
try {
|
|
40
|
+
parsedBody = JSON.parse(context.request.rawBody as string);
|
|
41
|
+
} catch (err: any) {
|
|
42
|
+
throw new RpcError({
|
|
43
|
+
statusCode: StatusCodes.UNEXPECTED_ERROR,
|
|
44
|
+
type: 'parsing-json-request-error',
|
|
45
|
+
publicMessage: `Invalid json request body: ${err?.message || 'unknown parsing error.'}`,
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
break;
|
|
49
|
+
case SerializerModes.binary: {
|
|
50
|
+
// binary
|
|
51
|
+
const rawBody = context.request.rawBody as Uint8Array;
|
|
52
|
+
const {body} = coreDeserializeBinaryBody(context.path, rawBody, false);
|
|
53
|
+
parsedBody = body;
|
|
54
|
+
break;
|
|
55
|
+
}
|
|
56
|
+
case SerializerModes.json: // Object (pre-parsed body from platforms like Google Cloud Functions where Express auto-parses JSON)
|
|
57
|
+
parsedBody = context.request.rawBody;
|
|
58
|
+
break;
|
|
59
|
+
default:
|
|
60
|
+
throw new Error(`Invalid body type ${context.request.bodyType}`);
|
|
61
|
+
}
|
|
62
|
+
if (parsedBody) {
|
|
63
|
+
if (Array.isArray(parsedBody)) {
|
|
64
|
+
// when the body is an array we assume it's a single route call and we have to reconstruct the body
|
|
65
|
+
// http://my-api.com/route1 [p1, p2, p3] => {route1: [p1, p2, p3]}
|
|
66
|
+
parsedBody = {[getRouteExecutableFromPath(context.path).id]: parsedBody};
|
|
67
|
+
}
|
|
68
|
+
if (typeof parsedBody !== 'object')
|
|
69
|
+
throw new RpcError({
|
|
70
|
+
statusCode: StatusCodes.UNEXPECTED_ERROR,
|
|
71
|
+
type: 'invalid-request-body',
|
|
72
|
+
publicMessage: 'Wrong request body. Expecting a body containing the route name and parameters.',
|
|
73
|
+
});
|
|
74
|
+
(context.request as Mutable<MionRequest>).body = parsedBody;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Serializes the response body and stores it in the response rawBody property.
|
|
80
|
+
* This method is called after any other middleFn or route handler.
|
|
81
|
+
* @mion:middleFn
|
|
82
|
+
*/
|
|
83
|
+
export function serializeResponseBody(context: CallContext, opts: RouterOptions): MayReturnError {
|
|
84
|
+
const response = context.response as Mutable<MionResponse>;
|
|
85
|
+
const respBody: AnyObject = response.body;
|
|
86
|
+
const bodyType = context.response.serializer;
|
|
87
|
+
const thrownErrors = context.request.thrownErrors as Record<string, RpcError<string>> | undefined;
|
|
88
|
+
// Add thrownErrors to response body before the serializer runs
|
|
89
|
+
if (thrownErrors) (response.body as Mutable<AnyObject>)['@thrownErrors'] = thrownErrors;
|
|
90
|
+
switch (bodyType) {
|
|
91
|
+
case SerializerModes.stringifyJson: {
|
|
92
|
+
// json - use stringifyJson JIT function
|
|
93
|
+
response.headers.set('content-type', 'application/json; charset=utf-8');
|
|
94
|
+
const body = stringifyBody(context, context.executionChain.methods, respBody);
|
|
95
|
+
response.rawBody = body;
|
|
96
|
+
break;
|
|
97
|
+
}
|
|
98
|
+
case SerializerModes.json: {
|
|
99
|
+
// pre-serialized object - only prepare for JSON, don't stringify
|
|
100
|
+
// Platform adapters will handle the actual JSON stringification
|
|
101
|
+
// prepareForJson mutates response.body in place, so we don't set rawBody
|
|
102
|
+
response.headers.set('content-type', 'application/json; charset=utf-8');
|
|
103
|
+
prepareBodyForJson(context, context.executionChain.methods, respBody);
|
|
104
|
+
break;
|
|
105
|
+
}
|
|
106
|
+
case SerializerModes.binary: {
|
|
107
|
+
// binary - use toBinary JIT function
|
|
108
|
+
response.headers.set('content-type', 'application/octet-stream');
|
|
109
|
+
serializeBinaryBody(context, context.executionChain.methods, respBody);
|
|
110
|
+
break;
|
|
111
|
+
}
|
|
112
|
+
default:
|
|
113
|
+
throw new Error(`Invalid body type ${context.request.bodyType}`);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** Serializes response body to binary format using the core serializeBinaryBody function */
|
|
118
|
+
function serializeBinaryBody(context: CallContext, executionChain: RemoteMethod[], respBody: ResponseBody): void {
|
|
119
|
+
const response = context.response as Mutable<MionResponse>;
|
|
120
|
+
// For routesFlow, use routesFlowRouteIds from context for proper buffer sizing
|
|
121
|
+
const {serializer, buffer} = coreSerializeBinaryBody(
|
|
122
|
+
context.path,
|
|
123
|
+
executionChain,
|
|
124
|
+
respBody,
|
|
125
|
+
true,
|
|
126
|
+
context.routesFlowRouteIds
|
|
127
|
+
);
|
|
128
|
+
response.binSerializer = serializer;
|
|
129
|
+
response.rawBody = new Uint8Array(buffer);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function stringifyBody(context: CallContext, executionChain: RemoteMethod[], respBody: ResponseBody): string {
|
|
133
|
+
const props: string[] = [];
|
|
134
|
+
for (let i = 0; i < executionChain.length; i++) {
|
|
135
|
+
const method = executionChain[i];
|
|
136
|
+
const returnValue = respBody[method.id];
|
|
137
|
+
if (!method.hasReturnData || typeof returnValue === 'undefined') continue;
|
|
138
|
+
try {
|
|
139
|
+
const jsonValue = stringifyHandlerReturnValue(method, returnValue);
|
|
140
|
+
if (!jsonValue) continue;
|
|
141
|
+
props.push(`${JSON.stringify(method.id)}:${jsonValue}`);
|
|
142
|
+
} catch (e: any) {
|
|
143
|
+
onStringifyExecutableError(context, method, e);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Serialize thrownErrors if they exist
|
|
148
|
+
const thrownErrors = respBody['@thrownErrors'];
|
|
149
|
+
if (thrownErrors) {
|
|
150
|
+
const method = getRouteExecutable(MION_ROUTES.thrownErrors)!;
|
|
151
|
+
try {
|
|
152
|
+
const jsonValue = stringifyHandlerReturnValue(method, thrownErrors);
|
|
153
|
+
if (jsonValue) props.push(`${JSON.stringify(method.id)}:${jsonValue}`);
|
|
154
|
+
} catch (e: any) {
|
|
155
|
+
onStringifyExecutableError(context, method, e);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
return `{${props.join(',')}}`;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function onStringifyExecutableError(context: CallContext, method: RemoteMethod, e: any) {
|
|
162
|
+
const err = new RpcError({
|
|
163
|
+
statusCode: StatusCodes.UNEXPECTED_ERROR,
|
|
164
|
+
type: 'json-stringify-response-error',
|
|
165
|
+
publicMessage: `Failed to stringify return value for handler ${method.id}, expected response type: ${method.returnJitFns.stringifyJson.typeName}`,
|
|
166
|
+
originalError: e,
|
|
167
|
+
errorData: {methodId: method.id},
|
|
168
|
+
});
|
|
169
|
+
onExecutableError(context, method, err);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function stringifyHandlerReturnValue(method: RemoteMethod, returnValue: any): string {
|
|
173
|
+
if (!method.hasReturnData) return '';
|
|
174
|
+
// id data does not require custom encoding then we use native json
|
|
175
|
+
if (method.returnJitFns.prepareForJson.isNoop) JSON.stringify(returnValue);
|
|
176
|
+
return method.returnJitFns.stringifyJson.fn(returnValue);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function prepareBodyForJson(context: CallContext, executionChain: RemoteMethod[], respBody: ResponseBody): void {
|
|
180
|
+
// prepareForJson mutates the response body in place
|
|
181
|
+
for (let i = 0; i < executionChain.length; i++) {
|
|
182
|
+
const method = executionChain[i];
|
|
183
|
+
const returnValue = respBody[method.id];
|
|
184
|
+
if (!method.hasReturnData || typeof returnValue === 'undefined') continue;
|
|
185
|
+
try {
|
|
186
|
+
const preparedValue = prepareHandlerReturnValue(method, returnValue);
|
|
187
|
+
if (preparedValue !== undefined) (respBody as Mutable<ResponseBody>)[method.id] = preparedValue;
|
|
188
|
+
} catch (e: any) {
|
|
189
|
+
onPrepareForJsonExecutableError(context, method, e);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
// Prepare thrownErrors if they exist
|
|
193
|
+
const thrownErrors = respBody['@thrownErrors'];
|
|
194
|
+
if (thrownErrors) {
|
|
195
|
+
const method = getRouteExecutable(MION_ROUTES.thrownErrors)!;
|
|
196
|
+
try {
|
|
197
|
+
const preparedValue = prepareHandlerReturnValue(method, thrownErrors);
|
|
198
|
+
if (preparedValue !== undefined) (respBody as Mutable<ResponseBody>)[method.id] = preparedValue;
|
|
199
|
+
} catch (e: any) {
|
|
200
|
+
onPrepareForJsonExecutableError(context, method, e);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function onPrepareForJsonExecutableError(context: CallContext, method: RemoteMethod, e: any) {
|
|
206
|
+
const err = new RpcError({
|
|
207
|
+
statusCode: StatusCodes.UNEXPECTED_ERROR,
|
|
208
|
+
type: 'prepare-for-json-response-error',
|
|
209
|
+
publicMessage: `Failed to prepare return value for JSON for handler ${method.id}, expected response type: ${method.returnJitFns.prepareForJson.typeName}`,
|
|
210
|
+
originalError: e,
|
|
211
|
+
errorData: {methodId: method.id},
|
|
212
|
+
});
|
|
213
|
+
onExecutableError(context, method, err);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function prepareHandlerReturnValue(method: RemoteMethod, returnValue: any): any {
|
|
217
|
+
if (!method.hasReturnData) return undefined;
|
|
218
|
+
if (method.returnJitFns.prepareForJson.isNoop) return returnValue;
|
|
219
|
+
return method.returnJitFns.prepareForJson.fn(returnValue);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
export const serializerMiddleFns = {
|
|
223
|
+
mionDeserializeRequest: rawMiddleFn(deserializeRequestBody, {runOnError: true}),
|
|
224
|
+
mionSerializeResponse: rawMiddleFn(serializeResponseBody, {runOnError: true}),
|
|
225
|
+
} satisfies MiddleFnsCollection;
|