@mionjs/router 0.8.7 → 0.8.10
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
package/src/router.ts
ADDED
|
@@ -0,0 +1,656 @@
|
|
|
1
|
+
/* ########
|
|
2
|
+
* 2022 mion
|
|
3
|
+
* Author: Ma-jerez
|
|
4
|
+
* License: MIT
|
|
5
|
+
* The software is provided "as is", without warranty of any kind.
|
|
6
|
+
* ######## */
|
|
7
|
+
|
|
8
|
+
/** Lightweight path join for error messages (avoids Node's 'path' module for edge compatibility) */
|
|
9
|
+
import type {Route, RouterOptions, Routes, RouterEntry} from './types/general.ts';
|
|
10
|
+
import type {
|
|
11
|
+
RemoteMethod,
|
|
12
|
+
MethodsExecutionChain,
|
|
13
|
+
RawMethod,
|
|
14
|
+
HeadersMethod,
|
|
15
|
+
MiddleFnMethod,
|
|
16
|
+
RouteMethod,
|
|
17
|
+
} from './types/remoteMethods.ts';
|
|
18
|
+
import type {PublicApi, PrivateDef, MiddleFnsCollection} from './types/publicMethods.ts';
|
|
19
|
+
import type {HeadersMiddleFnDef, MiddleFnDef, RawMiddleFnDef} from './types/definitions.ts';
|
|
20
|
+
import {DEFAULT_ROUTE_OPTIONS, MAX_ROUTE_NESTING, WORKFLOW_KEY} from './constants.ts';
|
|
21
|
+
import {
|
|
22
|
+
isRawMiddleFnDef,
|
|
23
|
+
isHeadersMiddleFnDef,
|
|
24
|
+
isExecutable,
|
|
25
|
+
isMiddleFnDef,
|
|
26
|
+
isRoute,
|
|
27
|
+
isRoutes,
|
|
28
|
+
isAnyMiddleFnDef,
|
|
29
|
+
isPublicExecutable,
|
|
30
|
+
} from './types/guards.ts';
|
|
31
|
+
import {
|
|
32
|
+
HandlerType,
|
|
33
|
+
SerializerModes,
|
|
34
|
+
SerializerCode,
|
|
35
|
+
SerializerMode,
|
|
36
|
+
isTestEnv,
|
|
37
|
+
isMionCompileMode,
|
|
38
|
+
isMionAOTEmitMode,
|
|
39
|
+
resetRoutesCache,
|
|
40
|
+
} from '@mionjs/core';
|
|
41
|
+
import {getRawMethodReflection, getHandlerReflection, ensureBinaryJitFns} from './lib/reflection.ts';
|
|
42
|
+
import {serializerMiddleFns} from './routes/serializer.routes.ts';
|
|
43
|
+
import {getRouterItemId, getRoutePath, getENV, MION_ROUTES, routesCache} from '@mionjs/core';
|
|
44
|
+
import {setErrorOptions} from '@mionjs/core';
|
|
45
|
+
import {getPublicApi, resetRemoteMethodsMetadata} from './lib/remoteMethods.ts';
|
|
46
|
+
import {addToPersistedMethods, getPersistedMethod, resetPersistedMethods} from './lib/methodsCache.ts';
|
|
47
|
+
import {mionClientRoutes, mionClientMiddleFns} from './routes/client.routes.ts';
|
|
48
|
+
import {mionErrorsRoutes} from './routes/errors.routes.ts';
|
|
49
|
+
import {clearRoutesFlowCache} from './routesFlow.ts';
|
|
50
|
+
import {clearContextPool} from './callContext.ts';
|
|
51
|
+
|
|
52
|
+
type RouterKeyEntryList = [string, RouterEntry][];
|
|
53
|
+
type RoutesWithId = {
|
|
54
|
+
pathPointer: string[];
|
|
55
|
+
routes: Routes;
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
// ############# PRIVATE STATE #############
|
|
59
|
+
|
|
60
|
+
const mionInternalRoutes = Object.values(MION_ROUTES) as string[];
|
|
61
|
+
const flatRouter: Map<string, MethodsExecutionChain> = new Map(); // Main Router
|
|
62
|
+
const middleFnsById: Map<string, MiddleFnMethod | HeadersMethod | RawMethod> = new Map();
|
|
63
|
+
const routesById: Map<string, RouteMethod> = new Map();
|
|
64
|
+
const rawMiddleFnsById: Map<string, RawMethod> = new Map();
|
|
65
|
+
const middleFnNames: Set<string> = new Set();
|
|
66
|
+
const routeNames: Set<string> = new Set();
|
|
67
|
+
let complexity = 0;
|
|
68
|
+
let routerOptions: RouterOptions = {...DEFAULT_ROUTE_OPTIONS};
|
|
69
|
+
let isRouterInitialized = false;
|
|
70
|
+
let allExecutablesIds: string[] | undefined;
|
|
71
|
+
let platformConfig: Record<string, unknown> | undefined;
|
|
72
|
+
|
|
73
|
+
/** Global middleFns to be run before and after any other middleFns or routes set using `registerRoutes` */
|
|
74
|
+
const defaultStartMiddleFns = {
|
|
75
|
+
mionDeserializeRequest: serializerMiddleFns.mionDeserializeRequest,
|
|
76
|
+
};
|
|
77
|
+
const defaultEndMiddleFns = {
|
|
78
|
+
...mionClientMiddleFns,
|
|
79
|
+
mionSerializeResponse: serializerMiddleFns.mionSerializeResponse,
|
|
80
|
+
};
|
|
81
|
+
let startMiddleFnsDef: MiddleFnsCollection = {...defaultStartMiddleFns};
|
|
82
|
+
let endMiddleFnsDef: MiddleFnsCollection = {...defaultEndMiddleFns};
|
|
83
|
+
export let startMiddleFns: RemoteMethod[] = [];
|
|
84
|
+
export let endMiddleFns: RemoteMethod[] = [];
|
|
85
|
+
|
|
86
|
+
// ############# PUBLIC METHODS #############
|
|
87
|
+
|
|
88
|
+
export const getRouteExecutionChain = (path: string) => flatRouter.get(path);
|
|
89
|
+
export const getRouteEntries = () => flatRouter.entries();
|
|
90
|
+
export const geRoutesSize = () => flatRouter.size;
|
|
91
|
+
export const getRouteExecutable = (id: string) => routesById.get(id);
|
|
92
|
+
export const getMiddleFnExecutable = (id: string) => middleFnsById.get(id);
|
|
93
|
+
export const geMiddleFnsSize = () => middleFnsById.size;
|
|
94
|
+
export const getComplexity = () => complexity;
|
|
95
|
+
export const getRouterOptions = <Opts extends RouterOptions>(): Readonly<Opts> => routerOptions as Opts;
|
|
96
|
+
export const getAnyExecutable = (id: string) => routesById.get(id) || middleFnsById.get(id) || rawMiddleFnsById.get(id);
|
|
97
|
+
|
|
98
|
+
/** Sets platform adapter config and notifies the parent process (Vite plugin) that the server is ready.
|
|
99
|
+
* Called automatically by platform adapters. Sends an IPC message containing both the
|
|
100
|
+
* serializable router config and the platform adapter config. */
|
|
101
|
+
export function setPlatformConfig(config: Record<string, unknown>): void {
|
|
102
|
+
platformConfig = config;
|
|
103
|
+
if (isMionAOTEmitMode() && typeof process.send === 'function') {
|
|
104
|
+
const routerConfig = Object.fromEntries(Object.entries(routerOptions).filter(([, v]) => typeof v !== 'function'));
|
|
105
|
+
try {
|
|
106
|
+
process.send({type: 'mion-platform-ready', routerConfig, platformConfig: config});
|
|
107
|
+
} catch (err) {
|
|
108
|
+
console.error('[mion] Failed to send platform-ready IPC:', err);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** Returns the platform adapter config set by setPlatformConfig(). */
|
|
114
|
+
export const getPlatformConfig = (): Readonly<Record<string, unknown>> | undefined => platformConfig;
|
|
115
|
+
|
|
116
|
+
export const resetRouter = () => {
|
|
117
|
+
flatRouter.clear();
|
|
118
|
+
middleFnsById.clear();
|
|
119
|
+
routesById.clear();
|
|
120
|
+
rawMiddleFnsById.clear();
|
|
121
|
+
middleFnNames.clear();
|
|
122
|
+
routeNames.clear();
|
|
123
|
+
complexity = 0;
|
|
124
|
+
routerOptions = {...DEFAULT_ROUTE_OPTIONS};
|
|
125
|
+
startMiddleFnsDef = {...defaultStartMiddleFns};
|
|
126
|
+
endMiddleFnsDef = {...defaultEndMiddleFns};
|
|
127
|
+
startMiddleFns = [];
|
|
128
|
+
endMiddleFns = [];
|
|
129
|
+
isRouterInitialized = false;
|
|
130
|
+
allExecutablesIds = undefined;
|
|
131
|
+
platformConfig = undefined;
|
|
132
|
+
resetRemoteMethodsMetadata();
|
|
133
|
+
resetPersistedMethods();
|
|
134
|
+
resetRoutesCache();
|
|
135
|
+
clearContextPool();
|
|
136
|
+
clearRoutesFlowCache();
|
|
137
|
+
// Note: We intentionally do NOT call resetJitFnCaches() here because:
|
|
138
|
+
// 1. JIT function caches are global and should persist across router resets
|
|
139
|
+
// 2. The serializableClassRegistry (cleared by resetJitFnCaches) is needed for
|
|
140
|
+
// serialization/deserialization of classes like RpcError
|
|
141
|
+
// resetJitFnCaches() should only be called in specific test scenarios that need
|
|
142
|
+
// to test AOT cache loading behavior
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
// simpler router initialization
|
|
146
|
+
export async function initMionRouter<R extends Routes>(routes: R, opts?: Partial<RouterOptions>): Promise<PublicApi<R>> {
|
|
147
|
+
await initRouter(opts);
|
|
148
|
+
const publicApi = await registerRoutes(routes);
|
|
149
|
+
// Emit AOT caches once after ALL routes (error, client, user) are registered
|
|
150
|
+
await emitAOTCaches();
|
|
151
|
+
return publicApi;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Initializes the Router.
|
|
156
|
+
* @param application
|
|
157
|
+
* @param contextDataFactory a factory function that returns an object to be shared in the `callContext.shared`
|
|
158
|
+
* @param routerOptions
|
|
159
|
+
* @returns
|
|
160
|
+
*/
|
|
161
|
+
export async function initRouter(opts?: Partial<RouterOptions>): Promise<Readonly<RouterOptions>> {
|
|
162
|
+
if (isRouterInitialized) throw new Error('Router has already been initialized');
|
|
163
|
+
routerOptions = {...routerOptions, ...opts};
|
|
164
|
+
validateSharedDataFactory(routerOptions);
|
|
165
|
+
Object.freeze(routerOptions);
|
|
166
|
+
setErrorOptions(routerOptions);
|
|
167
|
+
if (routerOptions.aot) await loadAOTCaches();
|
|
168
|
+
isRouterInitialized = true;
|
|
169
|
+
await registerRoutes({...mionErrorsRoutes});
|
|
170
|
+
if (!routerOptions.skipClientRoutes) await registerRoutes({...mionClientRoutes});
|
|
171
|
+
if (!isTestEnv()) console.log('mion router initialized', {routerOptions});
|
|
172
|
+
return routerOptions;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export async function registerRoutes<R extends Routes>(routes: R): Promise<PublicApi<R>> {
|
|
176
|
+
if (!isRouterInitialized) throw new Error('initRouter should be called first');
|
|
177
|
+
startMiddleFns = await getExecutablesFromMiddleFnsCollection(startMiddleFnsDef);
|
|
178
|
+
endMiddleFns = await getExecutablesFromMiddleFnsCollection(endMiddleFnsDef);
|
|
179
|
+
const binaryMiddlewares = new Set<string>();
|
|
180
|
+
await recursiveFlatRoutes(routes, [], [], [], binaryMiddlewares, 0);
|
|
181
|
+
if (binaryMiddlewares.size > 0) await compileBinaryForMiddleware(binaryMiddlewares);
|
|
182
|
+
if (shouldFullGenerateSpec()) {
|
|
183
|
+
return getPublicApi(routes);
|
|
184
|
+
}
|
|
185
|
+
return {} as PublicApi<R>;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/** Add middleFns at the start af the ExecutionChain, adds them before any other existing start middleFns by default */
|
|
189
|
+
export function addStartMiddleFns(middleFnsDef: MiddleFnsCollection, appendBeforeExisting = true) {
|
|
190
|
+
if (isRouterInitialized) throw new Error('Can not add start middleFns after the router has been initialized');
|
|
191
|
+
if (appendBeforeExisting) {
|
|
192
|
+
startMiddleFnsDef = {...middleFnsDef, ...startMiddleFnsDef};
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
startMiddleFnsDef = {...startMiddleFnsDef, ...middleFnsDef};
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/** Add middleFns at the end af the ExecutionChain, adds them after any other existing end middleFns by default */
|
|
199
|
+
export function addEndMiddleFns(middleFnsDef: MiddleFnsCollection, prependAfterExisting = true) {
|
|
200
|
+
if (isRouterInitialized) throw new Error('Can not add end middleFns after the router has been initialized');
|
|
201
|
+
if (prependAfterExisting) {
|
|
202
|
+
endMiddleFnsDef = {...endMiddleFnsDef, ...middleFnsDef};
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
endMiddleFnsDef = {...middleFnsDef, ...endMiddleFnsDef};
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
export function isPrivateDefinition(entry: RouterEntry, id: string): entry is PrivateDef {
|
|
209
|
+
if (isRoute(entry)) return false;
|
|
210
|
+
if (isRawMiddleFnDef(entry)) return true;
|
|
211
|
+
try {
|
|
212
|
+
const executable = getMiddleFnExecutable(id) || getRouteExecutable(id);
|
|
213
|
+
if (!executable)
|
|
214
|
+
throw new Error(`Route or MiddleFn ${id} not found. Please check you have called router.registerRoutes first.`);
|
|
215
|
+
return isPrivateExecutable(executable);
|
|
216
|
+
} catch {
|
|
217
|
+
// error thrown because entry is a Routes object and does not have any handler
|
|
218
|
+
return false;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
export function isPrivateExecutable(executable: RemoteMethod): boolean {
|
|
223
|
+
if (executable.type === HandlerType.rawMiddleFn) return true;
|
|
224
|
+
if (executable.type === HandlerType.route) return false;
|
|
225
|
+
const hasPublicParams = !!executable.paramNames?.length;
|
|
226
|
+
const hasHeaderParams = !!(executable as HeadersMethod).headersParam?.headerNames?.length;
|
|
227
|
+
return !hasPublicParams && !hasHeaderParams && !executable.hasReturnData;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
export function getTotalExecutables(): number {
|
|
231
|
+
return routesById.size + middleFnsById.size + rawMiddleFnsById.size;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
export function getAllExecutablesIds(): string[] {
|
|
235
|
+
if (allExecutablesIds) return allExecutablesIds;
|
|
236
|
+
allExecutablesIds = [...routesById.keys(), ...middleFnsById.keys(), ...rawMiddleFnsById.keys()];
|
|
237
|
+
return allExecutablesIds;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// used by codegen
|
|
241
|
+
export function shouldFullGenerateSpec(): boolean {
|
|
242
|
+
return routerOptions.getPublicRoutesData || getENV('GENERATE_ROUTER_SPEC') === 'true' || isMionCompileMode();
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
export function getRouteExecutableFromPath(path: string): RouteMethod {
|
|
246
|
+
const executionChain = flatRouter.get(path);
|
|
247
|
+
if (!executionChain) {
|
|
248
|
+
// Return the not-found route executable
|
|
249
|
+
return getAnyExecutable(MION_ROUTES.notFound) as RouteMethod;
|
|
250
|
+
}
|
|
251
|
+
return executionChain.methods[executionChain.routeIndex] as RouteMethod;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// ############# PRIVATE METHODS #############
|
|
255
|
+
|
|
256
|
+
async function loadAOTCaches() {
|
|
257
|
+
const loader = await import('./aot/aotCacheLoader.ts');
|
|
258
|
+
return loader.loadRouterAOTCaches();
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
async function emitAOTCaches() {
|
|
262
|
+
if (!isMionAOTEmitMode()) return;
|
|
263
|
+
// Dynamic import resolves relative to this source file.
|
|
264
|
+
// This only runs via vite-node (MION_COMPILE=buildOnly|childProcess), which always resolves from source.
|
|
265
|
+
const aotEmitter = await import('./lib/aotEmitter.ts');
|
|
266
|
+
return aotEmitter.emitAOTCaches();
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Optimized algorithm to flatten the routes object into a list of Executable objects.
|
|
271
|
+
* @param routes
|
|
272
|
+
* @param currentPointer current pointer in the routes object i.e. ['users', 'get']
|
|
273
|
+
* @param preMiddleFns middleFns one level up preceding current pointer
|
|
274
|
+
* @param postMiddleFns middleFns one level up following the current pointer
|
|
275
|
+
* @param nestLevel
|
|
276
|
+
*/
|
|
277
|
+
async function recursiveFlatRoutes(
|
|
278
|
+
routes: Routes,
|
|
279
|
+
currentPointer: string[] = [],
|
|
280
|
+
preMiddleFns: RemoteMethod[] = [],
|
|
281
|
+
postMiddleFns: RemoteMethod[] = [],
|
|
282
|
+
binaryMiddlewares: Set<string> = new Set(),
|
|
283
|
+
nestLevel = 0
|
|
284
|
+
) {
|
|
285
|
+
if (nestLevel > MAX_ROUTE_NESTING)
|
|
286
|
+
throw new Error('Too many nested routes, you can only nest routes ${MAX_ROUTE_NESTING} levels');
|
|
287
|
+
|
|
288
|
+
const entries = Object.entries(routes);
|
|
289
|
+
if (entries.length === 0)
|
|
290
|
+
throw new Error(
|
|
291
|
+
`Invalid route: ${currentPointer.length ? joinPath(...currentPointer) : '*'}. Can Not define empty routes`
|
|
292
|
+
);
|
|
293
|
+
|
|
294
|
+
let minus1Props: ReturnType<typeof getRouteEntryProperties> | null = null;
|
|
295
|
+
for (let index = 0; index < entries.length; index++) {
|
|
296
|
+
const [key, item] = entries[index];
|
|
297
|
+
// create the executable items
|
|
298
|
+
const newPointer = [...currentPointer, key];
|
|
299
|
+
let routeEntry: RemoteMethod | RoutesWithId;
|
|
300
|
+
if (typeof key !== 'string' || !isNaN(key as any))
|
|
301
|
+
throw new Error(`Invalid route: ${joinPath(...newPointer)}. Numeric route names are not allowed`);
|
|
302
|
+
if (key.includes(',')) throw new Error(`Invalid route: ${joinPath(...newPointer)}. Route names cannot contain commas.`);
|
|
303
|
+
if (key === WORKFLOW_KEY)
|
|
304
|
+
throw new Error(`Invalid route: ${joinPath(...newPointer)}. '${WORKFLOW_KEY}' is a reserved mion route name.`);
|
|
305
|
+
|
|
306
|
+
// generates a middleFn
|
|
307
|
+
if (isAnyMiddleFnDef(item)) {
|
|
308
|
+
routeEntry = await getExecutableFromAnyMiddleFn(item, newPointer, nestLevel);
|
|
309
|
+
if (middleFnNames.has(routeEntry.id))
|
|
310
|
+
throw new Error(
|
|
311
|
+
`Invalid middleFn: ${joinPath(...newPointer)}. Naming collision, Naming collision, duplicated middleFn.`
|
|
312
|
+
);
|
|
313
|
+
middleFnNames.add(routeEntry.id);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// generates a route
|
|
317
|
+
else if (isRoute(item)) {
|
|
318
|
+
routeEntry = await getExecutableFromRoute(item, newPointer, nestLevel);
|
|
319
|
+
if (routeNames.has(routeEntry.id))
|
|
320
|
+
throw new Error(`Invalid route: ${joinPath(...newPointer)}. Naming collision, duplicated route`);
|
|
321
|
+
routeNames.add(routeEntry.id);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// generates structure required to go one level down
|
|
325
|
+
else if (isRoutes(item)) {
|
|
326
|
+
routeEntry = {
|
|
327
|
+
pathPointer: newPointer,
|
|
328
|
+
routes: item,
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// throws an error if the route is invalid
|
|
333
|
+
else {
|
|
334
|
+
const itemType = typeof item;
|
|
335
|
+
throw new Error(`Invalid route: ${joinPath(...newPointer)}. Type <${itemType}> is not a valid route.`);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// recurse into sublevels
|
|
339
|
+
minus1Props = await recursiveCreateExecutionChain(
|
|
340
|
+
routeEntry,
|
|
341
|
+
newPointer,
|
|
342
|
+
preMiddleFns,
|
|
343
|
+
postMiddleFns,
|
|
344
|
+
binaryMiddlewares,
|
|
345
|
+
nestLevel,
|
|
346
|
+
index,
|
|
347
|
+
entries,
|
|
348
|
+
minus1Props
|
|
349
|
+
);
|
|
350
|
+
|
|
351
|
+
complexity++;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
async function recursiveCreateExecutionChain(
|
|
356
|
+
routeEntry: RemoteMethod | RoutesWithId,
|
|
357
|
+
currentPointer: string[],
|
|
358
|
+
preMiddleFns: RemoteMethod[],
|
|
359
|
+
postMiddleFns: RemoteMethod[],
|
|
360
|
+
binaryMiddlewares: Set<string>,
|
|
361
|
+
nestLevel: number,
|
|
362
|
+
index: number,
|
|
363
|
+
routeKeyedEntries: RouterKeyEntryList,
|
|
364
|
+
minus1Props: ReturnType<typeof getRouteEntryProperties> | null
|
|
365
|
+
) {
|
|
366
|
+
const minus1 = getEntry(index - 1, routeKeyedEntries);
|
|
367
|
+
const plus1 = getEntry(index + 1, routeKeyedEntries);
|
|
368
|
+
const props = getRouteEntryProperties(minus1, routeEntry, plus1);
|
|
369
|
+
|
|
370
|
+
if (props.isBetweenRoutes && minus1Props) {
|
|
371
|
+
props.preLevelMiddleFns = minus1Props.preLevelMiddleFns;
|
|
372
|
+
props.postLevelMiddleFns = minus1Props.postLevelMiddleFns;
|
|
373
|
+
} else {
|
|
374
|
+
for (let i = 0; i < routeKeyedEntries.length; i++) {
|
|
375
|
+
const [k, entry] = routeKeyedEntries[i];
|
|
376
|
+
complexity++;
|
|
377
|
+
if (!isAnyMiddleFnDef(entry)) continue;
|
|
378
|
+
const newPointer = [...currentPointer.slice(0, -1), k];
|
|
379
|
+
const executable = await getExecutableFromAnyMiddleFn(entry, newPointer, nestLevel);
|
|
380
|
+
if (i < index) props.preLevelMiddleFns.push(executable);
|
|
381
|
+
if (i > index) props.postLevelMiddleFns.push(executable);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
const isExec = isExecutable(routeEntry);
|
|
385
|
+
|
|
386
|
+
if (isExec && props.isRoute) {
|
|
387
|
+
const path = getRoutePath(routeEntry.pointer, routerOptions);
|
|
388
|
+
const routeMethod = routeEntry as RouteMethod;
|
|
389
|
+
const levelMethods = [
|
|
390
|
+
...preMiddleFns,
|
|
391
|
+
...props.preLevelMiddleFns,
|
|
392
|
+
routeEntry,
|
|
393
|
+
...props.postLevelMiddleFns,
|
|
394
|
+
...postMiddleFns,
|
|
395
|
+
];
|
|
396
|
+
const methods = [...startMiddleFns, ...levelMethods, ...endMiddleFns];
|
|
397
|
+
const executionChain: MethodsExecutionChain = {
|
|
398
|
+
routeIndex: startMiddleFns.length + preMiddleFns.length + props.preLevelMiddleFns.length,
|
|
399
|
+
methods,
|
|
400
|
+
serializer: getSerializerCodeFromMode(routeMethod.options.serializer),
|
|
401
|
+
};
|
|
402
|
+
const middleFnIds = getPublicMiddleFnIds(methods);
|
|
403
|
+
// add middleware functions deps, so can be serialized with the router
|
|
404
|
+
if (middleFnIds.length) routeMethod.middleFnIds = middleFnIds;
|
|
405
|
+
flatRouter.set(path, executionChain);
|
|
406
|
+
// Collect middleware that needs binary JIT functions for retroactive compilation
|
|
407
|
+
if (routeMethod.options.serializer === 'binary') {
|
|
408
|
+
for (const method of methods) {
|
|
409
|
+
if (method.type === HandlerType.middleFn || method.type === HandlerType.headersMiddleFn) {
|
|
410
|
+
binaryMiddlewares.add(method.id);
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
} else if (!isExec) {
|
|
415
|
+
await recursiveFlatRoutes(
|
|
416
|
+
routeEntry.routes,
|
|
417
|
+
routeEntry.pathPointer,
|
|
418
|
+
[...preMiddleFns, ...props.preLevelMiddleFns],
|
|
419
|
+
[...props.postLevelMiddleFns, ...postMiddleFns],
|
|
420
|
+
binaryMiddlewares,
|
|
421
|
+
nestLevel + 1
|
|
422
|
+
);
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
return props;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
async function getExecutableFromAnyMiddleFn(
|
|
429
|
+
middleFn: MiddleFnDef | HeadersMiddleFnDef | RawMiddleFnDef,
|
|
430
|
+
middleFnPointer: string[],
|
|
431
|
+
nestLevel: number
|
|
432
|
+
) {
|
|
433
|
+
if (isRawMiddleFnDef(middleFn)) return getExecutableFromRawMiddleFn(middleFn, middleFnPointer, nestLevel);
|
|
434
|
+
return getExecutableFromMiddleFn(middleFn, middleFnPointer, nestLevel);
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
export async function getExecutableFromMiddleFn(
|
|
438
|
+
middleFn: MiddleFnDef | HeadersMiddleFnDef,
|
|
439
|
+
middleFnPointer: string[],
|
|
440
|
+
nestLevel: number
|
|
441
|
+
): Promise<MiddleFnMethod | HeadersMethod> {
|
|
442
|
+
const isHeader = isHeadersMiddleFnDef(middleFn);
|
|
443
|
+
// todo fix header id should be same as any other one and then maybe map from id to header name
|
|
444
|
+
const middleFnId = getRouterItemId(middleFnPointer);
|
|
445
|
+
const existing = middleFnsById.get(middleFnId);
|
|
446
|
+
if (existing) return existing as MiddleFnMethod;
|
|
447
|
+
|
|
448
|
+
type MixedMiddleFn = (Omit<MiddleFnMethod, 'type'> | Omit<HeadersMethod, 'type'>) & {
|
|
449
|
+
type: typeof HandlerType.middleFn | typeof HandlerType.headersMiddleFn;
|
|
450
|
+
};
|
|
451
|
+
|
|
452
|
+
const compiledMethod = getPersistedMethod(middleFnId, middleFn.handler);
|
|
453
|
+
let executable: MixedMiddleFn;
|
|
454
|
+
if (compiledMethod) {
|
|
455
|
+
executable = compiledMethod as MixedMiddleFn;
|
|
456
|
+
} else {
|
|
457
|
+
const reflectionData = await getHandlerReflection(
|
|
458
|
+
middleFn.handler,
|
|
459
|
+
middleFnId,
|
|
460
|
+
routerOptions,
|
|
461
|
+
middleFn.options ?? {},
|
|
462
|
+
isHeader,
|
|
463
|
+
middleFn.options?.strictTypes
|
|
464
|
+
);
|
|
465
|
+
executable = {
|
|
466
|
+
id: middleFnId,
|
|
467
|
+
type: isHeader ? HandlerType.headersMiddleFn : HandlerType.middleFn,
|
|
468
|
+
nestLevel,
|
|
469
|
+
handler: middleFn.handler,
|
|
470
|
+
pointer: middleFnPointer,
|
|
471
|
+
...reflectionData,
|
|
472
|
+
options: {
|
|
473
|
+
runOnError: !!middleFn.options?.runOnError,
|
|
474
|
+
validateParams: middleFn.options?.validateParams ?? true,
|
|
475
|
+
validateReturn: middleFn.options?.validateReturn ?? false,
|
|
476
|
+
description: middleFn.options?.description,
|
|
477
|
+
strictTypes: middleFn.options?.strictTypes ?? routerOptions.strictTypes,
|
|
478
|
+
},
|
|
479
|
+
};
|
|
480
|
+
addToPersistedMethods(middleFnId, executable);
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
middleFnsById.set(middleFnId, executable as any);
|
|
484
|
+
routesCache.setMethodJitFns(middleFnId, executable as any);
|
|
485
|
+
return executable as any;
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
export async function getExecutableFromRawMiddleFn(
|
|
489
|
+
middleFn: RawMiddleFnDef,
|
|
490
|
+
middleFnPointer: string[],
|
|
491
|
+
nestLevel: number
|
|
492
|
+
): Promise<RawMethod> {
|
|
493
|
+
const middleFnId = getRouterItemId(middleFnPointer);
|
|
494
|
+
const existing = rawMiddleFnsById.get(middleFnId);
|
|
495
|
+
if (existing) return existing as RawMethod;
|
|
496
|
+
const reflectionData = await getRawMethodReflection(middleFn.handler, middleFnId, routerOptions);
|
|
497
|
+
const executable: RawMethod = {
|
|
498
|
+
id: middleFnId,
|
|
499
|
+
type: HandlerType.rawMiddleFn,
|
|
500
|
+
nestLevel,
|
|
501
|
+
handler: middleFn.handler,
|
|
502
|
+
pointer: middleFnPointer,
|
|
503
|
+
...reflectionData,
|
|
504
|
+
options: {
|
|
505
|
+
runOnError: !!middleFn.options?.runOnError,
|
|
506
|
+
validateParams: false,
|
|
507
|
+
validateReturn: false,
|
|
508
|
+
description: middleFn.options?.description,
|
|
509
|
+
},
|
|
510
|
+
};
|
|
511
|
+
rawMiddleFnsById.set(middleFnId, executable);
|
|
512
|
+
routesCache.setMethodJitFns(middleFnId, executable as any);
|
|
513
|
+
return executable;
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
/** Retroactively compiles binary JIT functions for middleware in the path of binary routes */
|
|
517
|
+
async function compileBinaryForMiddleware(binaryMiddlewareIds: Set<string>): Promise<void> {
|
|
518
|
+
for (const id of binaryMiddlewareIds) {
|
|
519
|
+
const method = middleFnsById.get(id);
|
|
520
|
+
if (method) await ensureBinaryJitFns(method as MiddleFnMethod);
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
export async function getExecutableFromRoute(route: Route, routePointer: string[], nestLevel: number): Promise<RouteMethod> {
|
|
525
|
+
const routeId = getRouterItemId(routePointer);
|
|
526
|
+
const existing = routesById.get(routeId);
|
|
527
|
+
if (existing) return existing as RouteMethod;
|
|
528
|
+
|
|
529
|
+
const compiledMethod = getPersistedMethod(routeId, route.handler);
|
|
530
|
+
let executable: RouteMethod;
|
|
531
|
+
if (compiledMethod) {
|
|
532
|
+
executable = compiledMethod as RouteMethod;
|
|
533
|
+
} else {
|
|
534
|
+
const resolvedRouteOptions = {...route.options, serializer: route.options?.serializer ?? routerOptions.serializer};
|
|
535
|
+
const reflectionData = await getHandlerReflection(
|
|
536
|
+
route.handler,
|
|
537
|
+
routeId,
|
|
538
|
+
routerOptions,
|
|
539
|
+
resolvedRouteOptions,
|
|
540
|
+
false,
|
|
541
|
+
route.options?.strictTypes
|
|
542
|
+
);
|
|
543
|
+
executable = {
|
|
544
|
+
id: routeId,
|
|
545
|
+
type: HandlerType.route,
|
|
546
|
+
nestLevel,
|
|
547
|
+
handler: route.handler,
|
|
548
|
+
pointer: routePointer,
|
|
549
|
+
...reflectionData,
|
|
550
|
+
options: {
|
|
551
|
+
runOnError: false,
|
|
552
|
+
validateParams: route.options?.validateParams ?? true,
|
|
553
|
+
validateReturn: route.options?.validateReturn ?? false,
|
|
554
|
+
description: route.options?.description,
|
|
555
|
+
serializer: route.options?.serializer ?? routerOptions.serializer,
|
|
556
|
+
isMutation: route.options?.isMutation,
|
|
557
|
+
strictTypes: route.options?.strictTypes ?? routerOptions.strictTypes,
|
|
558
|
+
},
|
|
559
|
+
};
|
|
560
|
+
addToPersistedMethods(routeId, executable);
|
|
561
|
+
}
|
|
562
|
+
routesById.set(routeId, executable);
|
|
563
|
+
routesCache.setMethodJitFns(routeId, executable as any);
|
|
564
|
+
return executable;
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
/** Returns IDs of public middleware methods from the execution chain, excluding internal mion routes. */
|
|
568
|
+
function getPublicMiddleFnIds(methods: RemoteMethod[]): string[] {
|
|
569
|
+
const ids = methods
|
|
570
|
+
.filter((exec) => isPublicExecutable(exec))
|
|
571
|
+
.map((exec) => getRouterItemId(exec.pointer))
|
|
572
|
+
.filter((mfId) => {
|
|
573
|
+
if (mionInternalRoutes.includes(mfId)) return false;
|
|
574
|
+
const exec = getMiddleFnExecutable(mfId);
|
|
575
|
+
return exec && isPublicExecutable(exec);
|
|
576
|
+
});
|
|
577
|
+
return ids;
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
function getEntry(index: number, keyEntryList: RouterKeyEntryList) {
|
|
581
|
+
return keyEntryList[index]?.[1];
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
function getRouteEntryProperties(
|
|
585
|
+
minus1: RouterEntry | undefined,
|
|
586
|
+
zero: RemoteMethod | RoutesWithId,
|
|
587
|
+
plus1: RouterEntry | undefined
|
|
588
|
+
) {
|
|
589
|
+
const minus1IsRoute = minus1 && isRoute(minus1);
|
|
590
|
+
const zeroIsRoute = (zero as RemoteMethod).type === HandlerType.route;
|
|
591
|
+
const plus1IsRoute = plus1 && isRoute(plus1);
|
|
592
|
+
|
|
593
|
+
const isExec = !!(zero as RemoteMethod).handler;
|
|
594
|
+
|
|
595
|
+
return {
|
|
596
|
+
isBetweenRoutes: minus1IsRoute && zeroIsRoute && plus1IsRoute,
|
|
597
|
+
isExecutable: isExec,
|
|
598
|
+
isRoute: zeroIsRoute,
|
|
599
|
+
preLevelMiddleFns: [] as RemoteMethod[],
|
|
600
|
+
postLevelMiddleFns: [] as RemoteMethod[],
|
|
601
|
+
};
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
async function getExecutablesFromMiddleFnsCollection(
|
|
605
|
+
middleFnsDef: MiddleFnsCollection
|
|
606
|
+
): Promise<(RawMethod | MiddleFnMethod | HeadersMethod)[]> {
|
|
607
|
+
const results: (RawMethod | MiddleFnMethod | HeadersMethod)[] = [];
|
|
608
|
+
for (const [key, middleFn] of Object.entries(middleFnsDef)) {
|
|
609
|
+
if (isRawMiddleFnDef(middleFn)) {
|
|
610
|
+
results.push(await getExecutableFromRawMiddleFn(middleFn, [key], 0));
|
|
611
|
+
} else if (isHeadersMiddleFnDef(middleFn) || isMiddleFnDef(middleFn)) {
|
|
612
|
+
results.push(await getExecutableFromMiddleFn(middleFn, [key], 0));
|
|
613
|
+
} else {
|
|
614
|
+
throw new Error(`Invalid middleFn: ${key}. Invalid middleFn definition`);
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
return results;
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
/**
|
|
621
|
+
* Validates that a contextDataFactory returns a valid context data object.
|
|
622
|
+
* @param contextDataFactory The factory function to validate
|
|
623
|
+
* @throws Error if the factory doesn't return a plain object with at least one property
|
|
624
|
+
*/
|
|
625
|
+
function validateSharedDataFactory(opts?: Partial<RouterOptions>): void {
|
|
626
|
+
if (!opts?.contextDataFactory) return;
|
|
627
|
+
const testSharedData = opts.contextDataFactory();
|
|
628
|
+
if (
|
|
629
|
+
typeof testSharedData !== 'object' ||
|
|
630
|
+
Array.isArray(testSharedData) ||
|
|
631
|
+
testSharedData === null ||
|
|
632
|
+
Object.keys(testSharedData).length === 0
|
|
633
|
+
) {
|
|
634
|
+
throw new Error('contextDataFactory must return a plain object with at least one property');
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
/** Maps serializer mode string to response body type code */
|
|
639
|
+
function getSerializerCodeFromMode(mode: SerializerMode | undefined): SerializerCode {
|
|
640
|
+
switch (mode) {
|
|
641
|
+
case 'binary':
|
|
642
|
+
return SerializerModes.binary;
|
|
643
|
+
case 'stringifyJson':
|
|
644
|
+
return SerializerModes.stringifyJson;
|
|
645
|
+
case 'optimistic':
|
|
646
|
+
return SerializerModes.stringifyJson;
|
|
647
|
+
case 'json':
|
|
648
|
+
default:
|
|
649
|
+
return SerializerModes.json;
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
/** Path replacement as is not available in edge runtime */
|
|
654
|
+
function joinPath(...parts: string[]): string {
|
|
655
|
+
return parts.filter(Boolean).join('/');
|
|
656
|
+
}
|