@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.
Files changed (141) hide show
  1. package/.dist/cjs/index.d.ts +1 -0
  2. package/.dist/cjs/index.d.ts.map +1 -0
  3. package/.dist/cjs/src/aot/aotCacheLoader.d.ts +1 -0
  4. package/.dist/cjs/src/aot/aotCacheLoader.d.ts.map +1 -0
  5. package/.dist/cjs/src/callContext.d.ts +1 -0
  6. package/.dist/cjs/src/callContext.d.ts.map +1 -0
  7. package/.dist/cjs/src/constants.d.ts +1 -0
  8. package/.dist/cjs/src/constants.d.ts.map +1 -0
  9. package/.dist/cjs/src/defaultRoutes.d.ts +1 -0
  10. package/.dist/cjs/src/defaultRoutes.d.ts.map +1 -0
  11. package/.dist/cjs/src/dispatch.d.ts +1 -0
  12. package/.dist/cjs/src/dispatch.d.ts.map +1 -0
  13. package/.dist/cjs/src/lib/aotEmitter.d.ts +1 -0
  14. package/.dist/cjs/src/lib/aotEmitter.d.ts.map +1 -0
  15. package/.dist/cjs/src/lib/dispatchError.d.ts +1 -0
  16. package/.dist/cjs/src/lib/dispatchError.d.ts.map +1 -0
  17. package/.dist/cjs/src/lib/handlers.d.ts +1 -0
  18. package/.dist/cjs/src/lib/handlers.d.ts.map +1 -0
  19. package/.dist/cjs/src/lib/headers.d.ts +1 -0
  20. package/.dist/cjs/src/lib/headers.d.ts.map +1 -0
  21. package/.dist/cjs/src/lib/methodsCache.d.ts +1 -0
  22. package/.dist/cjs/src/lib/methodsCache.d.ts.map +1 -0
  23. package/.dist/cjs/src/lib/queryBody.d.ts +1 -0
  24. package/.dist/cjs/src/lib/queryBody.d.ts.map +1 -0
  25. package/.dist/cjs/src/lib/reflection.d.ts +1 -0
  26. package/.dist/cjs/src/lib/reflection.d.ts.map +1 -0
  27. package/.dist/cjs/src/lib/remoteMethods.d.ts +1 -0
  28. package/.dist/cjs/src/lib/remoteMethods.d.ts.map +1 -0
  29. package/.dist/cjs/src/lib/test/aotEmitter-test-router.d.ts +1 -0
  30. package/.dist/cjs/src/lib/test/aotEmitter-test-router.d.ts.map +1 -0
  31. package/.dist/cjs/src/router.d.ts +1 -0
  32. package/.dist/cjs/src/router.d.ts.map +1 -0
  33. package/.dist/cjs/src/routes/client.routes.d.ts +1 -0
  34. package/.dist/cjs/src/routes/client.routes.d.ts.map +1 -0
  35. package/.dist/cjs/src/routes/errors.routes.d.ts +1 -0
  36. package/.dist/cjs/src/routes/errors.routes.d.ts.map +1 -0
  37. package/.dist/cjs/src/routes/mion.routes.d.ts +1 -0
  38. package/.dist/cjs/src/routes/mion.routes.d.ts.map +1 -0
  39. package/.dist/cjs/src/routes/serializer.routes.d.ts +1 -0
  40. package/.dist/cjs/src/routes/serializer.routes.d.ts.map +1 -0
  41. package/.dist/cjs/src/routesFlow.d.ts +1 -0
  42. package/.dist/cjs/src/routesFlow.d.ts.map +1 -0
  43. package/.dist/cjs/src/types/context.d.ts +1 -0
  44. package/.dist/cjs/src/types/context.d.ts.map +1 -0
  45. package/.dist/cjs/src/types/definitions.d.ts +1 -0
  46. package/.dist/cjs/src/types/definitions.d.ts.map +1 -0
  47. package/.dist/cjs/src/types/general.d.ts +1 -0
  48. package/.dist/cjs/src/types/general.d.ts.map +1 -0
  49. package/.dist/cjs/src/types/guards.d.ts +1 -0
  50. package/.dist/cjs/src/types/guards.d.ts.map +1 -0
  51. package/.dist/cjs/src/types/handlers.d.ts +1 -0
  52. package/.dist/cjs/src/types/handlers.d.ts.map +1 -0
  53. package/.dist/cjs/src/types/publicMethods.d.ts +1 -0
  54. package/.dist/cjs/src/types/publicMethods.d.ts.map +1 -0
  55. package/.dist/cjs/src/types/remoteMethods.d.ts +1 -0
  56. package/.dist/cjs/src/types/remoteMethods.d.ts.map +1 -0
  57. package/.dist/esm/index.d.ts +1 -0
  58. package/.dist/esm/index.d.ts.map +1 -0
  59. package/.dist/esm/src/aot/aotCacheLoader.d.ts +1 -0
  60. package/.dist/esm/src/aot/aotCacheLoader.d.ts.map +1 -0
  61. package/.dist/esm/src/callContext.d.ts +1 -0
  62. package/.dist/esm/src/callContext.d.ts.map +1 -0
  63. package/.dist/esm/src/constants.d.ts +1 -0
  64. package/.dist/esm/src/constants.d.ts.map +1 -0
  65. package/.dist/esm/src/defaultRoutes.d.ts +1 -0
  66. package/.dist/esm/src/defaultRoutes.d.ts.map +1 -0
  67. package/.dist/esm/src/dispatch.d.ts +1 -0
  68. package/.dist/esm/src/dispatch.d.ts.map +1 -0
  69. package/.dist/esm/src/lib/aotEmitter.d.ts +1 -0
  70. package/.dist/esm/src/lib/aotEmitter.d.ts.map +1 -0
  71. package/.dist/esm/src/lib/dispatchError.d.ts +1 -0
  72. package/.dist/esm/src/lib/dispatchError.d.ts.map +1 -0
  73. package/.dist/esm/src/lib/handlers.d.ts +1 -0
  74. package/.dist/esm/src/lib/handlers.d.ts.map +1 -0
  75. package/.dist/esm/src/lib/headers.d.ts +1 -0
  76. package/.dist/esm/src/lib/headers.d.ts.map +1 -0
  77. package/.dist/esm/src/lib/methodsCache.d.ts +1 -0
  78. package/.dist/esm/src/lib/methodsCache.d.ts.map +1 -0
  79. package/.dist/esm/src/lib/queryBody.d.ts +1 -0
  80. package/.dist/esm/src/lib/queryBody.d.ts.map +1 -0
  81. package/.dist/esm/src/lib/reflection.d.ts +1 -0
  82. package/.dist/esm/src/lib/reflection.d.ts.map +1 -0
  83. package/.dist/esm/src/lib/remoteMethods.d.ts +1 -0
  84. package/.dist/esm/src/lib/remoteMethods.d.ts.map +1 -0
  85. package/.dist/esm/src/lib/test/aotEmitter-test-router.d.ts +1 -0
  86. package/.dist/esm/src/lib/test/aotEmitter-test-router.d.ts.map +1 -0
  87. package/.dist/esm/src/router.d.ts +1 -0
  88. package/.dist/esm/src/router.d.ts.map +1 -0
  89. package/.dist/esm/src/routes/client.routes.d.ts +1 -0
  90. package/.dist/esm/src/routes/client.routes.d.ts.map +1 -0
  91. package/.dist/esm/src/routes/errors.routes.d.ts +1 -0
  92. package/.dist/esm/src/routes/errors.routes.d.ts.map +1 -0
  93. package/.dist/esm/src/routes/mion.routes.d.ts +1 -0
  94. package/.dist/esm/src/routes/mion.routes.d.ts.map +1 -0
  95. package/.dist/esm/src/routes/serializer.routes.d.ts +1 -0
  96. package/.dist/esm/src/routes/serializer.routes.d.ts.map +1 -0
  97. package/.dist/esm/src/routesFlow.d.ts +1 -0
  98. package/.dist/esm/src/routesFlow.d.ts.map +1 -0
  99. package/.dist/esm/src/types/context.d.ts +1 -0
  100. package/.dist/esm/src/types/context.d.ts.map +1 -0
  101. package/.dist/esm/src/types/definitions.d.ts +1 -0
  102. package/.dist/esm/src/types/definitions.d.ts.map +1 -0
  103. package/.dist/esm/src/types/general.d.ts +1 -0
  104. package/.dist/esm/src/types/general.d.ts.map +1 -0
  105. package/.dist/esm/src/types/guards.d.ts +1 -0
  106. package/.dist/esm/src/types/guards.d.ts.map +1 -0
  107. package/.dist/esm/src/types/handlers.d.ts +1 -0
  108. package/.dist/esm/src/types/handlers.d.ts.map +1 -0
  109. package/.dist/esm/src/types/publicMethods.d.ts +1 -0
  110. package/.dist/esm/src/types/publicMethods.d.ts.map +1 -0
  111. package/.dist/esm/src/types/remoteMethods.d.ts +1 -0
  112. package/.dist/esm/src/types/remoteMethods.d.ts.map +1 -0
  113. package/index.ts +29 -0
  114. package/package.json +9 -5
  115. package/src/aot/aotCacheLoader.ts +15 -0
  116. package/src/callContext.ts +193 -0
  117. package/src/constants.ts +45 -0
  118. package/src/defaultRoutes.ts +37 -0
  119. package/src/dispatch.ts +204 -0
  120. package/src/lib/aotEmitter.ts +140 -0
  121. package/src/lib/dispatchError.ts +60 -0
  122. package/src/lib/handlers.ts +75 -0
  123. package/src/lib/headers.ts +102 -0
  124. package/src/lib/methodsCache.ts +72 -0
  125. package/src/lib/queryBody.ts +40 -0
  126. package/src/lib/reflection.ts +517 -0
  127. package/src/lib/remoteMethods.ts +180 -0
  128. package/src/lib/test/aotEmitter-test-router.ts +40 -0
  129. package/src/router.ts +656 -0
  130. package/src/routes/client.routes.ts +108 -0
  131. package/src/routes/errors.routes.ts +51 -0
  132. package/src/routes/mion.routes.ts +11 -0
  133. package/src/routes/serializer.routes.ts +225 -0
  134. package/src/routesFlow.ts +320 -0
  135. package/src/types/context.ts +119 -0
  136. package/src/types/definitions.ts +53 -0
  137. package/src/types/general.ts +84 -0
  138. package/src/types/guards.ts +71 -0
  139. package/src/types/handlers.ts +49 -0
  140. package/src/types/publicMethods.ts +83 -0
  141. package/src/types/remoteMethods.ts +54 -0
@@ -0,0 +1,320 @@
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 {
9
+ RpcError,
10
+ SerializerCode,
11
+ SerializerModes,
12
+ StatusCodes,
13
+ HandlerType,
14
+ getNoopJitFns,
15
+ PURE_SERVER_FN_NAMESPACE,
16
+ fromBase64Url,
17
+ } from '@mionjs/core';
18
+ import {serverPureFnsCache} from '@mionjs/core/server-pure-fns';
19
+ import {getRouteExecutionChain, getRouterOptions, startMiddleFns, endMiddleFns} from './router.ts';
20
+ import {RouterOptions} from './types/general.ts';
21
+ import {MethodsExecutionChain, RemoteMethod} from './types/remoteMethods.ts';
22
+ import {RoutesFlowExecutionResult} from './types/context.ts';
23
+ import type {CallContext} from './types/context.ts';
24
+ import type {RoutesFlowQuery, RoutesFlowMapping} from '@mionjs/core';
25
+
26
+ // ############# ROUTES_FLOW CACHE #############
27
+
28
+ /** FILO cache for merged execution chains. Key is the query string, value is the cached chain. */
29
+ const routesFlowCache = new Map<string, MethodsExecutionChain>();
30
+ const cacheOrder: string[] = [];
31
+ /** Cache for mapping RemoteMethods keyed by their unique ID */
32
+ const mappingMethodCache = new Map<string, RemoteMethod>();
33
+
34
+ /** Clears the routesFlow cache and mapping method cache - useful for testing */
35
+ export function clearRoutesFlowCache(): void {
36
+ routesFlowCache.clear();
37
+ cacheOrder.length = 0;
38
+ mappingMethodCache.clear();
39
+ }
40
+
41
+ /** Returns the current routesFlow cache size */
42
+ export function getRoutesFlowCacheSize(): number {
43
+ return routesFlowCache.size;
44
+ }
45
+
46
+ /** Returns a cached routesFlow chain by query string */
47
+ export function getCachedRoutesFlow(query: string): MethodsExecutionChain | undefined {
48
+ return routesFlowCache.get(query);
49
+ }
50
+
51
+ /** Adds a merged chain to the cache with FILO eviction */
52
+ function addToRoutesFlowCache(query: string, chain: MethodsExecutionChain): void {
53
+ const routerOpts = getRouterOptions();
54
+ const maxSize = routerOpts.maxRoutesFlowsCacheSize;
55
+ // Caching disabled
56
+ if (maxSize <= 0) return;
57
+ // Evict oldest entries if cache is full (FILO - First In, Last Out)
58
+ while (cacheOrder.length >= maxSize) {
59
+ const oldestKey = cacheOrder.shift();
60
+ if (oldestKey) routesFlowCache.delete(oldestKey);
61
+ }
62
+ routesFlowCache.set(query, chain);
63
+ cacheOrder.push(query);
64
+ }
65
+
66
+ // ############# QUERY PARSING #############
67
+
68
+ /** Decodes a base64url-encoded JSON routesFlow query string, expects `data=<base64url>` format */
69
+ function decodeRoutesFlowQuery(urlQuery: string): RoutesFlowQuery {
70
+ try {
71
+ const dataParam = urlQuery.startsWith('data=') ? urlQuery.slice(5) : urlQuery;
72
+ const jsonString = fromBase64Url(dataParam);
73
+ return JSON.parse(jsonString) as RoutesFlowQuery;
74
+ } catch (e: any) {
75
+ throw new RpcError({
76
+ statusCode: StatusCodes.UNEXPECTED_ERROR,
77
+ type: 'routesFlow-invalid-query',
78
+ publicMessage: 'RoutesFlow query string is not valid base64url-encoded JSON.',
79
+ errorData: {parseError: e?.message || 'Unknown error'},
80
+ });
81
+ }
82
+ }
83
+
84
+ // ############# ROUTES_FLOW #############
85
+
86
+ /** Builds or retrieves a cached merged execution chain for routesFlow requests */
87
+ export function getRoutesFlowExecutionChain(
88
+ rawRequest: unknown,
89
+ opts: RouterOptions,
90
+ urlQuery?: string
91
+ ): RoutesFlowExecutionResult {
92
+ // Validate urlQuery is provided
93
+ if (!urlQuery) {
94
+ throw new RpcError({
95
+ statusCode: StatusCodes.UNEXPECTED_ERROR,
96
+ type: 'routesFlow-missing-query',
97
+ publicMessage: 'RoutesFlow request requires a query string with route paths.',
98
+ });
99
+ }
100
+
101
+ // Decode base64+JSON query
102
+ const query = decodeRoutesFlowQuery(urlQuery);
103
+ const routePaths = query.routes;
104
+ const mappings = query.mappings;
105
+
106
+ if (!routePaths || routePaths.length === 0) {
107
+ throw new RpcError({
108
+ statusCode: StatusCodes.UNEXPECTED_ERROR,
109
+ type: 'routesFlow-empty-routes',
110
+ publicMessage: 'RoutesFlow request requires at least one route path in query string.',
111
+ });
112
+ }
113
+
114
+ // Convert paths to route IDs (remove leading slash)
115
+ const routeIds = routePaths.map((path) => (path.startsWith('/') ? path.slice(1) : path));
116
+
117
+ // Check cache first
118
+ let executionChain = routesFlowCache.get(urlQuery);
119
+ if (executionChain) return {executionChain, routesFlowRouteIds: routeIds, mappings};
120
+
121
+ // Build merged execution chain
122
+ executionChain = buildMergedExecutionChain(routePaths, rawRequest, opts, mappings);
123
+ addToRoutesFlowCache(urlQuery, executionChain);
124
+ return {executionChain, routesFlowRouteIds: routeIds, mappings};
125
+ }
126
+
127
+ /**
128
+ * Builds a merged execution chain from multiple route paths.
129
+ * The merged chain includes all methods from all routes, with deduplication by ID.
130
+ *
131
+ * The chain is structured as:
132
+ * 1. Start middleFns (e.g., mionDeserializeRequest) - from first route, at the beginning
133
+ * 2. Middle methods (routes and their middleFns) - merged from all routes, with mapping steps inserted
134
+ * 3. End middleFns (e.g., mionSerializeResponse) - from first route, at the end
135
+ *
136
+ * When mappings are provided, mapping steps are inserted after the source route
137
+ * and before the target route to transform output → input.
138
+ */
139
+ function buildMergedExecutionChain(
140
+ routePaths: string[],
141
+ rawRequest: unknown,
142
+ opts: RouterOptions,
143
+ mappings?: RoutesFlowMapping[]
144
+ ): MethodsExecutionChain {
145
+ const seenIds = new Set<string>();
146
+ const middleMethods: RemoteMethod[] = [];
147
+ let resolvedSerializer: SerializerCode | undefined;
148
+ let firstRouteIndex = -1;
149
+ const defaultSerializerCode = SerializerModes[opts.serializer];
150
+
151
+ // Build sets of start and end middleFn IDs for filtering
152
+ const startMiddleFnIds = new Set(startMiddleFns.map((m) => m.id));
153
+ const endMiddleFnIds = new Set(endMiddleFns.map((m) => m.id));
154
+
155
+ // Process each route path
156
+ for (const routePath of routePaths) {
157
+ // Apply path transform if configured
158
+ const transformedPath = opts.pathTransform?.(rawRequest, routePath) || routePath;
159
+ const chain = getRouteExecutionChain(transformedPath);
160
+ if (!chain) {
161
+ throw new RpcError({
162
+ statusCode: StatusCodes.UNEXPECTED_ERROR,
163
+ type: 'routesFlow-route-not-found',
164
+ publicMessage: `Route not found in routesFlow: ${routePath}`,
165
+ errorData: {routePath},
166
+ });
167
+ }
168
+
169
+ // Resolve serializer - use first route's serializer, or fall back to default if conflicting
170
+ if (!resolvedSerializer) {
171
+ resolvedSerializer = chain.serializer;
172
+ // Track the route index from the first route (relative to start middleFns)
173
+ firstRouteIndex = chain.routeIndex;
174
+ } else if (resolvedSerializer !== chain.serializer) {
175
+ resolvedSerializer = defaultSerializerCode;
176
+ }
177
+
178
+ // Add middle methods from this route's chain, deduplicating by ID
179
+ // Skip start and end middleFns - they will be added separately
180
+ for (const method of chain.methods) {
181
+ if (seenIds.has(method.id)) continue;
182
+ if (startMiddleFnIds.has(method.id)) continue;
183
+ if (endMiddleFnIds.has(method.id)) continue;
184
+ seenIds.add(method.id);
185
+ middleMethods.push(method);
186
+ }
187
+ }
188
+
189
+ // Insert mapping methods between source and target routes
190
+ if (mappings && mappings.length > 0) {
191
+ insertMappingMethods(middleMethods, mappings);
192
+ }
193
+
194
+ // Build final chain: start middleFns + middle methods + end middleFns
195
+ const mergedMethods = [...startMiddleFns, ...middleMethods, ...endMiddleFns];
196
+
197
+ return {
198
+ // Use the first route's routeIndex since that's where the first route handler is
199
+ routeIndex: firstRouteIndex,
200
+ methods: mergedMethods,
201
+ serializer: resolvedSerializer ?? defaultSerializerCode,
202
+ };
203
+ }
204
+
205
+ // ############# MAPPING METHODS #############
206
+
207
+ /**
208
+ * Inserts mapping methods into the middleMethods array in the correct position.
209
+ * Each mapping method is inserted after the source route (fromId) and before the target route (toId).
210
+ * Mappings are processed in reverse insertion order to maintain correct indices.
211
+ */
212
+ function insertMappingMethods(middleMethods: RemoteMethod[], mappings: RoutesFlowMapping[]): void {
213
+ // Build a map of route ID → index in middleMethods for quick lookup
214
+ const idToIndex = new Map<string, number>();
215
+ for (let i = 0; i < middleMethods.length; i++) {
216
+ idToIndex.set(middleMethods[i].id, i);
217
+ }
218
+
219
+ // Collect insertions: each mapping creates one insertion point
220
+ const insertions: Array<{index: number; method: RemoteMethod}> = [];
221
+
222
+ for (const mapping of mappings) {
223
+ const fromIndex = idToIndex.get(mapping.fromId);
224
+ const toIndex = idToIndex.get(mapping.toId);
225
+
226
+ if (fromIndex === undefined) {
227
+ throw new RpcError({
228
+ statusCode: StatusCodes.UNEXPECTED_ERROR,
229
+ type: 'routesFlow-mapping-invalid-source',
230
+ publicMessage: `Mapping source route '${mapping.fromId}' not found in routesFlow execution chain.`,
231
+ errorData: {mapping},
232
+ });
233
+ }
234
+ if (toIndex === undefined) {
235
+ throw new RpcError({
236
+ statusCode: StatusCodes.UNEXPECTED_ERROR,
237
+ type: 'routesFlow-mapping-invalid-target',
238
+ publicMessage: `Mapping target route '${mapping.toId}' not found in routesFlow execution chain.`,
239
+ errorData: {mapping},
240
+ });
241
+ }
242
+
243
+ // Validate the pure function exists in the serverPureFnsCache (populated by mion vite plugin)
244
+ if (!serverPureFnsCache[PURE_SERVER_FN_NAMESPACE]?.[mapping.bodyHash]?.fn) {
245
+ throw new RpcError({
246
+ statusCode: StatusCodes.UNEXPECTED_ERROR,
247
+ type: 'routesFlow-mapping-missing-pure-fn',
248
+ publicMessage: `Mapping pure function '${mapping.bodyHash}' not found. Ensure the function is registered on the server.`,
249
+ errorData: {mapping},
250
+ });
251
+ }
252
+
253
+ // Insert after the source route (fromIndex + 1)
254
+ insertions.push({
255
+ index: fromIndex + 1,
256
+ method: createMappingMethod(mapping),
257
+ });
258
+ }
259
+
260
+ // Sort insertions by index descending so splice doesn't shift subsequent indices
261
+ insertions.sort((a, b) => b.index - a.index);
262
+
263
+ for (const {index, method} of insertions) {
264
+ middleMethods.splice(index, 0, method);
265
+ }
266
+ }
267
+
268
+ /** Creates or retrieves a cached RemoteMethod that acts as a raw middleFn to execute a mapping between routes */
269
+ function createMappingMethod(mapping: RoutesFlowMapping): RemoteMethod {
270
+ const id = `mionMapFrom_${mapping.fromId}_${mapping.bodyHash}_to_${mapping.toId}`;
271
+ const cached = mappingMethodCache.get(id);
272
+ if (cached) return cached;
273
+
274
+ const noopJitFns = getNoopJitFns();
275
+ const method = {
276
+ type: HandlerType.rawMiddleFn,
277
+ id,
278
+ isAsync: false,
279
+ hasReturnData: false,
280
+ paramsJitHash: '',
281
+ returnJitHash: '',
282
+ paramsJitFns: noopJitFns,
283
+ returnJitFns: noopJitFns,
284
+ handler: createMappingHandler(mapping),
285
+ options: {runOnError: false, validateParams: false},
286
+ methodCaller: runMappingHandler,
287
+ } as RemoteMethod;
288
+
289
+ mappingMethodCache.set(id, method);
290
+ return method;
291
+ }
292
+
293
+ /** Creates the handler function for a mapping step */
294
+ function createMappingHandler(mapping: RoutesFlowMapping) {
295
+ return (ctx: CallContext) => {
296
+ // Get the output from the source route
297
+ const sourceOutput = ctx.response.body[mapping.fromId];
298
+
299
+ // Resolve and execute the pure function from serverPureFnsCache (populated by mion vite plugin)
300
+ const entry = serverPureFnsCache[PURE_SERVER_FN_NAMESPACE]?.[mapping.bodyHash];
301
+ if (!entry?.fn) {
302
+ throw new RpcError({
303
+ statusCode: StatusCodes.UNEXPECTED_ERROR,
304
+ type: 'routesFlow-mapping-missing-pure-fn',
305
+ publicMessage: `Mapping pure function '${mapping.bodyHash}' not found at runtime.`,
306
+ });
307
+ }
308
+ const mappedValue = entry.fn(sourceOutput);
309
+
310
+ // Replace null at paramIndex in target route's params
311
+ const targetParams = ctx.request.body[mapping.toId] as any[];
312
+ if (targetParams) (targetParams as any[])[mapping.paramIndex] = mappedValue;
313
+ };
314
+ }
315
+
316
+ /** Custom method caller for mapping handlers — only passes the context */
317
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
318
+ async function runMappingHandler(context: CallContext, executable: RemoteMethod, ...args: unknown[]) {
319
+ return executable.handler(context);
320
+ }
@@ -0,0 +1,119 @@
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 {AnyObject, DataViewSerializer, SerializerCode} from '@mionjs/core';
9
+ import type {RpcError} from '@mionjs/core';
10
+ import type {RoutesFlowMapping} from '@mionjs/core';
11
+ import type {MethodsExecutionChain} from './remoteMethods.ts';
12
+
13
+ // ####### Call Context #######
14
+
15
+ // type-call-context-start
16
+ /** The call Context object passed as first parameter to any middleFn or route */
17
+ export interface CallContext<ContextData extends Record<string, any> = any> {
18
+ /** Route's path after internal transformation */
19
+ readonly path: string;
20
+ /** Router's own request object */
21
+ readonly request: MionRequest;
22
+ /** Router's own response object */
23
+ readonly response: MionResponse;
24
+ /** context data between handlers (route/middleFns) and that is not returned in the response. */
25
+ shared: ContextData;
26
+ /** The execution chain of the current route */
27
+ readonly executionChain: MethodsExecutionChain;
28
+ /** Query string from URL, used for routesFlow routes */
29
+ readonly urlQuery?: string;
30
+ /** Route IDs for routesFlow calls, used for binary serialization buffer sizing */
31
+ readonly routesFlowRouteIds?: string[];
32
+ }
33
+ // type-call-context-end
34
+
35
+ // ####### REQUEST & RESPONSE #######
36
+
37
+ export type RawRequestBody = string | ArrayBuffer | Uint8Array | AnyObject;
38
+ /** Response body can be a string, an arrayBuffer, a Uint8Array, or an object (for pre-serialized responses) */
39
+ export type RawResponseBody = string | ArrayBuffer | Uint8Array | AnyObject;
40
+
41
+ // type-mion-request-start
42
+ /** Router's own request object, do not confuse with the underlying raw request */
43
+ export interface MionRequest {
44
+ /** parsed headers */
45
+ readonly headers: Readonly<Omit<MionHeaders, 'append' | 'set' | 'delete'>>;
46
+ /** Raw request body, can be string for json, arrayBuffer for binary or a javascript object in the case of pre-parsed body */
47
+ readonly rawBody: RawRequestBody;
48
+ readonly bodyType: SerializerCode;
49
+ /** parsed request body */
50
+ readonly body: Readonly<AnyObject>;
51
+ /**
52
+ * Unexpected or thrown errors that are not part of the route/handler return type.
53
+ * This includes:
54
+ * - Validation errors (params, headers)
55
+ * - Deserialization/serialization errors
56
+ * - Errors thrown by user code (not returned)
57
+ * - Route not found errors
58
+ * - Any other errors thrown during execution
59
+ *
60
+ * These errors are serialized separately from the route response and sent to the client
61
+ * in the thrownErrors middleFn response, allowing them to be properly deserialized
62
+ * without being part of the route's type signature.
63
+ */
64
+ readonly thrownErrors?: Readonly<Record<string, RpcError<string>>>;
65
+ }
66
+ // type-mion-request-end
67
+
68
+ // type-mion-response-start
69
+ /** Router's own response object, do not confuse with the underlying raw response */
70
+ export interface MionResponse {
71
+ /** response http status code */
72
+ readonly statusCode: number;
73
+ /** response headers */
74
+ readonly headers: Readonly<MionHeaders>;
75
+ /** Raw response body, can be string for json or an arrayBuffer for binary. */
76
+ readonly rawBody: RawResponseBody;
77
+ readonly serializer: SerializerCode;
78
+ /** the router response data, body should not be modified manually so marked as Read Only */
79
+ readonly body: Readonly<ResponseBody>;
80
+ /** response errors: empty if there were no errors during execution */
81
+ readonly hasErrors: boolean;
82
+ readonly binSerializer?: DataViewSerializer | undefined;
83
+ }
84
+ // type-mion-response-end
85
+
86
+ /**
87
+ * Similar to Fetch API Headers.
88
+ * @see https://developer.mozilla.org/en-US/docs/Web/API/Headers
89
+ * Headers names must be case insensitive.
90
+ */
91
+ export interface MionHeaders {
92
+ append(name: string, value: string): void;
93
+ delete(name: string): void;
94
+ set(name: string, value: string): void;
95
+ get(name: string): string | undefined | null;
96
+ has(name: string): boolean;
97
+ entries(): IterableIterator<[string, string]>;
98
+ keys(): IterableIterator<string>;
99
+ values(): IterableIterator<string>;
100
+ }
101
+
102
+ /** Function used to create the context data object on each route call */
103
+ export type ContextDataFactory<ContextData extends Record<string, any>> = () => ContextData;
104
+
105
+ // type-response-body-start
106
+ /** Response body, a record containing the result of each handler or an error. */
107
+ export interface ResponseBody extends Record<string, any> {
108
+ '@thrownErrors'?: Record<string, RpcError<string>>;
109
+ }
110
+ // type-response-body-end
111
+
112
+ /** Result of getRoutesFlowExecutionChain including the route IDs for buffer sizing */
113
+ export interface RoutesFlowExecutionResult {
114
+ executionChain: MethodsExecutionChain;
115
+ /** Route IDs for binary serialization buffer sizing */
116
+ routesFlowRouteIds?: string[];
117
+ /** Mappings from the routesFlow query, used for mapping route outputs to inputs */
118
+ mappings?: RoutesFlowMapping[];
119
+ }
@@ -0,0 +1,53 @@
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 {Handler, HeaderHandler, RawMiddleFnHandler} from './handlers.ts';
9
+ import {
10
+ HeadersMiddleFnOptions,
11
+ HeadersMethod,
12
+ MiddleFnOptions,
13
+ MiddleFnMethod,
14
+ RawMiddleFnOptions,
15
+ RawMethod,
16
+ RouteOptions,
17
+ RouteMethod,
18
+ } from './remoteMethods.ts';
19
+
20
+ // ####### Routes Definitions #######
21
+
22
+ // type-route-def-start
23
+ /** Route definition */
24
+ export type RouteDef<H extends Handler = any> = Pick<RouteMethod<H>, 'type' | 'handler'> & {
25
+ options?: RouteOptions;
26
+ };
27
+ // type-route-def-end
28
+
29
+ // type-middleFn-def-start
30
+ /** MiddleFn definition, a function that middleFns into the ExecutionChain */
31
+ export type MiddleFnDef<H extends Handler = any> = Pick<MiddleFnMethod<H>, 'type' | 'handler'> & {
32
+ options?: MiddleFnOptions;
33
+ };
34
+ // type-middleFn-def-end
35
+
36
+ // type-header-middleFn-def-start
37
+ /** Headers MiddleFn definition, used to handle header params */
38
+ export type HeadersMiddleFnDef<H extends HeaderHandler = any> = Pick<HeadersMethod<H>, 'type' | 'handler'> & {
39
+ options?: HeadersMiddleFnOptions;
40
+ };
41
+ // type-header-middleFn-def-end
42
+
43
+ // type-raw-middleFn-def-start
44
+ /**
45
+ * Raw middleFn, used only to access raw request/response and modify the call context.
46
+ * Can not declare extra parameters.
47
+ */
48
+ export type RawMiddleFnDef<H extends RawMiddleFnHandler = any> = Pick<RawMethod<H>, 'type' | 'handler'> & {
49
+ options?: RawMiddleFnOptions;
50
+ };
51
+ // type-raw-middleFn-def-end
52
+
53
+ export type AnyHandlerDef = RouteDef | MiddleFnDef | HeadersMiddleFnDef | RawMiddleFnDef;
@@ -0,0 +1,84 @@
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 {CoreRouterOptions, SerializerMode} from '@mionjs/core';
9
+ import {ContextDataFactory} from './context.ts';
10
+ import {HeadersMiddleFnDef, MiddleFnDef, RawMiddleFnDef, RouteDef} from './definitions.ts';
11
+ import type {RunTypeOptions} from '@mionjs/run-types';
12
+ // ####### Router Object #######
13
+
14
+ /** A route can be a full route definition or just the handler */
15
+ export type Route = RouteDef;
16
+
17
+ /** A route entry can be a route, a middleFn or sub-routes */
18
+ export type RouterEntry = Routes | MiddleFnDef | RouteDef | RawMiddleFnDef | HeadersMiddleFnDef;
19
+
20
+ /** Data structure to define all the routes, each entry is a route a middleFn or sub-routes */
21
+ export interface Routes {
22
+ [key: string]: RouterEntry;
23
+ }
24
+
25
+ // ####### Router Options #######
26
+
27
+ /** Global Router Options */
28
+ export interface RouterOptions<Req = any, ContextData extends Record<string, any> = any> extends CoreRouterOptions {
29
+ /** basePath for all routes, i.e: api/v1.
30
+ * path separator is added between the prefix and the route */
31
+ basePath: string;
32
+ /** suffix for all routes, i.e: .json.
33
+ * Not path separators is added between the route and the suffix */
34
+ suffix: string;
35
+ /** Transform the path before finding a route */
36
+ pathTransform?: (request: Req, path: string) => string;
37
+ /** factory function to initialize shared call context data */
38
+ contextDataFactory?: ContextDataFactory<ContextData>;
39
+ /**
40
+ * Default serializer mode for response body serialization.
41
+ * Can be overridden per-route using route options.
42
+ * - 'json': Use prepareForJson, platform adapter handles JSON.stringify
43
+ * - 'binary': Use toBinary JIT function for binary serialization
44
+ * - 'stringifyJson': Use stringifyJson JIT function for optimized JSON serialization
45
+ * @default 'stringifyJson'
46
+ */
47
+ serializer: SerializerMode;
48
+ /** run type compiler options for middleFns and routes */
49
+ runTypeOptions: RunTypeOptions;
50
+ /** When true, isType and typeErrors reject objects with unknown/extra properties. Can be overridden per-route. */
51
+ strictTypes?: boolean;
52
+ /** Used to return public data structure when adding routes */
53
+ getPublicRoutesData: boolean;
54
+ /** automatically generate and uuid */
55
+ autoGenerateErrorId: boolean;
56
+ /** client routes are initialized by default */
57
+ skipClientRoutes: boolean;
58
+ /**
59
+ * Enable AOT (Ahead-of-Time) mode.
60
+ * When true, router will use pre-compiled JIT functions from cache
61
+ * and will NOT load @mionjs/run-types package.
62
+ * Throws error if any route/middleFn is missing from AOT cache.
63
+ * @default false
64
+ */
65
+ aot: boolean;
66
+ /**
67
+ * Maximum size of the CallContext pool for reduced memory allocations.
68
+ * When set to a value > 0, CallContext objects are reused from a pool
69
+ * instead of creating new ones for each request. This can improve
70
+ * performance in high-throughput scenarios by reducing GC pressure.
71
+ * Set to 0 to disable pooling.
72
+ * @default 0 (disabled)
73
+ */
74
+ maxContextPoolSize: number;
75
+ /**
76
+ * Maximum size of the routesFlow execution chain cache.
77
+ * Caches merged execution chains for routesFlow requests to avoid
78
+ * recalculating them on every request with the same route combination.
79
+ * Uses FILO (First In, Last Out) eviction when cache is full.
80
+ * Set to 0 to disable caching.
81
+ * @default 100
82
+ */
83
+ maxRoutesFlowsCacheSize: number;
84
+ }
@@ -0,0 +1,71 @@
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 {HeadersMiddleFnDef, MiddleFnDef, RawMiddleFnDef, RouteDef} from './definitions.ts';
9
+ import {Route, RouterEntry, Routes} from './general.ts';
10
+ import {RawMethod} from './remoteMethods.ts';
11
+ import {HeadersMethod} from './remoteMethods.ts';
12
+ import {RouteMethod} from './remoteMethods.ts';
13
+ import {RemoteMethod} from './remoteMethods.ts';
14
+ import {HandlerType} from '@mionjs/core';
15
+
16
+ // ####### type guards #######
17
+
18
+ export function isRouteDef(entry: RouterEntry): entry is RouteDef {
19
+ return entry.type === HandlerType.route;
20
+ }
21
+
22
+ export function isMiddleFnDef(entry: RouterEntry): entry is MiddleFnDef {
23
+ return entry.type === HandlerType.middleFn;
24
+ }
25
+
26
+ export function isRawMiddleFnDef(entry: RouterEntry): entry is RawMiddleFnDef {
27
+ return entry.type === HandlerType.rawMiddleFn;
28
+ }
29
+
30
+ export function isHeadersMiddleFnDef(entry: RouterEntry): entry is HeadersMiddleFnDef {
31
+ return entry.type === HandlerType.headersMiddleFn;
32
+ }
33
+
34
+ export function isAnyMiddleFnDef(entry: RouterEntry): entry is HeadersMiddleFnDef | MiddleFnDef | RawMiddleFnDef {
35
+ return isMiddleFnDef(entry) || isRawMiddleFnDef(entry) || isHeadersMiddleFnDef(entry);
36
+ }
37
+
38
+ export function isRoute(entry: RouterEntry): entry is Route {
39
+ return entry.type === HandlerType.route;
40
+ }
41
+
42
+ export function isRoutes(entry: RouterEntry | Routes): entry is Route {
43
+ return typeof entry === 'object';
44
+ }
45
+
46
+ export function isExecutable(entry: RemoteMethod | {pathPointer: string[]}): entry is RemoteMethod {
47
+ return (
48
+ typeof (entry as RemoteMethod)?.id === 'string' &&
49
+ ((entry as any).routes === 'undefined' || typeof (entry as RemoteMethod).handler === 'function')
50
+ );
51
+ }
52
+ export function isRawExecutable(entry: RemoteMethod): entry is RawMethod {
53
+ return entry.type === HandlerType.rawMiddleFn;
54
+ }
55
+
56
+ export function isPublicExecutable(entry: RemoteMethod): entry is RemoteMethod {
57
+ return (
58
+ entry.hasReturnData ||
59
+ entry.type === HandlerType.route ||
60
+ !!entry.paramNames?.length ||
61
+ !!(entry as HeadersMethod).headersParam?.headerNames?.length
62
+ );
63
+ }
64
+
65
+ export function isHeaderExecutable(entry: RemoteMethod): entry is HeadersMethod {
66
+ return entry.type === HandlerType.headersMiddleFn;
67
+ }
68
+
69
+ export function isRouteExecutable(entry: RemoteMethod): entry is RouteMethod {
70
+ return entry.type === HandlerType.route;
71
+ }