@molecule/api-resource 1.0.6 → 1.1.0

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/README.md CHANGED
@@ -3,7 +3,7 @@ AUTO-GENERATED — DO NOT EDIT THIS FILE.
3
3
  Generated by `mlcl sync-docs` from the package's src/index.ts JSDoc + mlcl/registry.json.
4
4
  Edits here are overwritten on the next commit (molecule's pre-commit hook regenerates).
5
5
  To change this document, edit the module-level JSDoc in src/index.ts.
6
- Generated: 2026-10-23T21:27:14.472Z
6
+ Generated: 2026-11-06T22:08:33.809Z
7
7
  -->
8
8
 
9
9
  # @molecule/api-resource
@@ -30,6 +30,33 @@ res.status(statusCode).send(body)
30
30
  Monkey-patching `res.json` (or `res.send`/`res.end`) to re-shape a handler's answer intercepts
31
31
  nothing the handler produces, so the request it was meant to answer is left unanswered and hangs.
32
32
 
33
+ **A different request or response envelope is configuration.** Pass
34
+ `mapRequest` and/or `mapResponse` as the second argument to `createRequestHandler`.
35
+ Both may be asynchronous. The existing handler still validates and authorizes
36
+ the adapted input. A response mapper sees the handler's errors and partial
37
+ responses too: preserve them and its headers instead of treating every result
38
+ as a success. `adaptHandler(handler, options)` offers the same mappings when
39
+ another HTTP adapter will send the structured response.
40
+ A request mapper can return `{ response: { statusCode, body } }` to answer
41
+ before calling the handler, for example when extra validation fails or a
42
+ filter lookup finds nothing. That response still passes through mapResponse.
43
+
44
+ ```typescript
45
+ import { createRequestHandler } from '@molecule/api-resource'
46
+ import { handlers } from '@molecule/api-resource-user'
47
+
48
+ const register = createRequestHandler(handlers.create({ name: 'User', tableName: 'users' }), {
49
+ // Return overrides. The adapter retains framework methods and getters.
50
+ mapRequest: (req) => ({ body: req.body?.account }),
51
+ mapResponse: (result) =>
52
+ result.statusCode === 201
53
+ ? { ...result, body: { account: result.body.props, accessToken: result.body.accessToken } }
54
+ : result,
55
+ })
56
+ // Keep the route's existing rate limiter and authorization around register.
57
+ // No mapping is inferred: a flat-body API keeps createRequestHandler(handler).
58
+ ```
59
+
33
60
  > **Auto-generated, AI-first package reference** for the [molecule.dev](https://molecule.dev) ecosystem.
34
61
  > It is written to be read by coding agents as much as by people, and is generated from this
35
62
  > package's source — edit `src/index.ts` JSDoc, not this file.
@@ -114,6 +141,38 @@ interface Response {
114
141
  }
115
142
  ```
116
143
 
144
+ #### `RequestHandlerOptions`
145
+
146
+ Per-route wire-format adapters. Validation and authorization still belong to
147
+ the original handler. Omit these options to retain its native contract.
148
+
149
+ ```typescript
150
+ interface RequestHandlerOptions {
151
+ /**
152
+ * Return the fields to replace before the handler validates the request.
153
+ * The adapter preserves the original request's methods and getters, and does
154
+ * not mutate it. Returning `{ body }` or `{ query }` is sufficient, including
155
+ * frameworks whose request properties have read-only accessors.
156
+ * Return `{ response: { statusCode, headers?, body } }` to answer before the
157
+ * handler runs. That result still goes through mapResponse, with the original request.
158
+ */
159
+ mapRequest?: (
160
+ req: MoleculeRequest,
161
+ res: MoleculeResponse,
162
+ ) => RequestMappingResult | Promise<RequestMappingResult>
163
+ /**
164
+ * Adapt the returned status, headers or body. This sees error and partial
165
+ * responses too: narrow the success shape before changing it, and preserve
166
+ * headers with `{ ...response, body }`. It never runs for fall-through.
167
+ */
168
+ mapResponse?: (
169
+ response: Response,
170
+ req: MoleculeRequest,
171
+ res: MoleculeResponse,
172
+ ) => Response | Promise<Response>
173
+ }
174
+ ```
175
+
117
176
  #### `MoleculeRequest`
118
177
 
119
178
  Framework-agnostic HTTP request interface. Structurally compatible with Express's
@@ -258,6 +317,16 @@ type Handler = (
258
317
  - `req` — The incoming HTTP request.
259
318
  - `res` — The HTTP response (available but handlers should return a Response instead of calling `res` directly).
260
319
 
320
+ #### `RequestMappingResult`
321
+
322
+ Request overrides, or an explicit response that stops before the handler.
323
+ The response form supports extra validation and unresolved external filters
324
+ without writing data or inventing a placeholder identifier.
325
+
326
+ ```typescript
327
+ type RequestMappingResult = Partial<MoleculeRequest> | { response: Response }
328
+ ```
329
+
261
330
  #### `MoleculeNextFunction`
262
331
 
263
332
  Framework-agnostic next function. Call with no arguments to pass to the next
@@ -307,11 +376,14 @@ export const create = ({ name, tableName, schema }: types.Resource) => {
307
376
  ```
308
377
 
309
378
  ```typescript
310
- function create({
379
+ function create<
380
+ CreateProps extends Record<string, unknown> = Record<string, unknown>,
381
+ CreatedProps extends types.Props & CreateProps = types.Props & CreateProps,
382
+ >({
311
383
  name,
312
384
  tableName,
313
385
  schema,
314
- }: types.Resource<unknown>): ({
386
+ }: types.Resource): ({
315
387
  props: createProps,
316
388
  id,
317
389
  }: {
@@ -330,7 +402,22 @@ function create({
330
402
 
331
403
  **Returns:** A curried async function that accepts `{ props, id? }` and returns a `{ statusCode, body }` response.
332
404
 
333
- #### `createRequestHandler(handler)`
405
+ #### `adaptHandler(handler, options)`
406
+
407
+ Compose wire-format adapters around a structured resource handler without
408
+ sending its response. The response mapper receives the adapted request.
409
+ Exceptions propagate to the caller's normal error boundary.
410
+
411
+ ```typescript
412
+ function adaptHandler(handler: Handler, options?: RequestHandlerOptions): Handler
413
+ ```
414
+
415
+ - `handler` — The existing resource handler, including its checks.
416
+ - `options` — Request and response mappings for this route only.
417
+
418
+ **Returns:** A structured handler that keeps null/undefined fall-through intact.
419
+
420
+ #### `createRequestHandler(handler, options)`
334
421
 
335
422
  Wraps a resource handler into Express-compatible middleware. Calls the handler,
336
423
  then applies `statusCode`, `headers`, and `body` to the response. If the handler
@@ -340,10 +427,12 @@ i18n-translated error messages.
340
427
  ```typescript
341
428
  function createRequestHandler(
342
429
  handler: Handler,
430
+ options?: RequestHandlerOptions,
343
431
  ): (req: MoleculeRequest, res: MoleculeResponse, next: MoleculeNextFunction) => Promise<void>
344
432
  ```
345
433
 
346
434
  - `handler` — The resource handler function to wrap.
435
+ - `options` — Optional wire-format mappings; neither mutates the shared handler.
347
436
 
348
437
  **Returns:** An Express-compatible middleware function `(req, res, next) => void`.
349
438
 
@@ -372,7 +461,13 @@ export const del = ({ name, tableName, schema }: types.Resource) => {
372
461
  ```
373
462
 
374
463
  ```typescript
375
- function del({ name, tableName }: DeletedResource): ({ id }: { id: Props['id'] }) => Promise<
464
+ function del<
465
+ Props extends types.Props = types.Props,
466
+ DeletedResource extends types.Resource<Props> = types.Resource<Props>,
467
+ >({
468
+ name,
469
+ tableName,
470
+ }: DeletedResource): ({ id }: { id: Props['id'] }) => Promise<
376
471
  | { statusCode: number; body: { error: string; errorKey: string; props?: undefined } }
377
472
  | {
378
473
  statusCode: number
@@ -394,7 +489,10 @@ Creates a request handler to be used to query resources.
394
489
  Uses the bonded DataStore from `@molecule/api-database` for database-agnostic operations.
395
490
 
396
491
  ```typescript
397
- function query({
492
+ function query<
493
+ Props extends types.Props = types.Props,
494
+ QueriedResource extends types.Resource = types.Resource,
495
+ >({
398
496
  tableName,
399
497
  }: QueriedResource): (
400
498
  req: MoleculeRequest,
@@ -432,7 +530,10 @@ export const read = ({ name, tableName, schema }: types.Resource) => {
432
530
  ```
433
531
 
434
532
  ```typescript
435
- function read({
533
+ function read<
534
+ ReadProps extends types.Props = types.Props,
535
+ ReadResource extends types.Resource<ReadProps> = types.Resource<ReadProps>,
536
+ >({
436
537
  tableName,
437
538
  }: ReadResource): ({
438
539
  id,
@@ -504,11 +605,18 @@ export const update = ({ name, tableName, schema }: types.Resource) => {
504
605
  ```
505
606
 
506
607
  ```typescript
507
- function update({
608
+ function update<
609
+ UpdateProps extends Record<string, unknown> = Record<string, unknown>,
610
+ UpdatedProps extends Pick<types.Props, `updatedAt`> & UpdateProps = Pick<
611
+ types.Props,
612
+ `updatedAt`
613
+ > &
614
+ UpdateProps,
615
+ >({
508
616
  name,
509
617
  tableName,
510
618
  schema,
511
- }: types.Resource<unknown>): ({
619
+ }: types.Resource): ({
512
620
  id,
513
621
  props: updateProps,
514
622
  }: {
@@ -597,9 +705,7 @@ Creates a Zod schema for the standard `{ statusCode, body }` response
597
705
  returned by resource CRUD operations.
598
706
 
599
707
  ```typescript
600
- function responseSchema(
601
- propsSchema: T,
602
- ): z.ZodTypeAny<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>
708
+ function responseSchema<T extends z.ZodTypeAny>(propsSchema: T): z.ZodTypeAny
603
709
  ```
604
710
 
605
711
  - `propsSchema` — The Zod schema for the resource's props (e.g. `userPropsSchema`).
@@ -612,9 +718,7 @@ Creates a Zod schema that validates a resource descriptor object
612
718
  (`{ name, tableName, schema }`).
613
719
 
614
720
  ```typescript
615
- function resourceSchema(
616
- _propsSchema: T,
617
- ): z.ZodTypeAny<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>
721
+ function resourceSchema<T extends z.ZodTypeAny>(_propsSchema: T): z.ZodTypeAny
618
722
  ```
619
723
 
620
724
  - `_propsSchema` — The Zod schema for the resource's props (used for type inference only).
@@ -627,10 +731,7 @@ Validates data against a Zod schema, throwing an `Error` with a
627
731
  human-readable message listing all validation failures if invalid.
628
732
 
629
733
  ```typescript
630
- function validate(
631
- schema: z.ZodSchema<T, unknown, z.core.$ZodTypeInternals<T, unknown>>,
632
- data: unknown,
633
- ): T
734
+ function validate<T>(schema: z.ZodSchema<T>, data: unknown): T
634
735
  ```
635
736
 
636
737
  - `schema` — The Zod schema to validate against.
@@ -644,8 +745,8 @@ Validates data against a Zod schema without throwing. Returns a discriminated
644
745
  union: `{ success: true, data }` on success or `{ success: false, error }` on failure.
645
746
 
646
747
  ```typescript
647
- function safeParse(
648
- schema: z.ZodSchema<T, unknown, z.core.$ZodTypeInternals<T, unknown>>,
748
+ function safeParse<T>(
749
+ schema: z.ZodSchema<T>,
649
750
  data: unknown,
650
751
  ): { success: true; data: T } | { success: false; error: z.ZodError }
651
752
  ```
@@ -661,9 +762,9 @@ Creates a schema where all fields are optional, suitable for validating
661
762
  partial update payloads where only changed fields are sent.
662
763
 
663
764
  ```typescript
664
- function partial(
665
- schema: z.ZodObject<T, z.core.$strip>,
666
- ): z.ZodObject<{ [K in keyof T]: z.ZodOptional<T[K]> }, z.core.$strip>
765
+ function partial<T extends z.ZodRawShape>(
766
+ schema: z.ZodObject<T>,
767
+ ): z.ZodObject<{ [K in keyof T]: z.ZodOptional<T[K]> }>
667
768
  ```
668
769
 
669
770
  - `schema` — The Zod object schema to make partial.
@@ -676,10 +777,10 @@ Creates a schema with only the specified fields, useful for create payloads
676
777
  where only a subset of the full props should be provided by the client.
677
778
 
678
779
  ```typescript
679
- function pick(
680
- schema: z.ZodObject<T, z.core.$strip>,
780
+ function pick<T extends z.ZodRawShape, K extends keyof T>(
781
+ schema: z.ZodObject<T>,
681
782
  keys: readonly K[],
682
- ): z.ZodObject<Pick<T, K>, z.core.$strip>
783
+ ): z.ZodObject<Pick<T, K>>
683
784
  ```
684
785
 
685
786
  - `schema` — The Zod object schema to pick from.
@@ -839,8 +940,17 @@ const getValidProps: <Props>({
839
940
  schema,
840
941
  props,
841
942
  }: {
943
+ /**
944
+ * The name of the resource (used in error messages).
945
+ */
842
946
  name: string
947
+ /**
948
+ * The Zod schema used when validating props.
949
+ */
843
950
  schema: ZodSchema<Props>
951
+ /**
952
+ * The `props` to be validated.
953
+ */
844
954
  props: unknown
845
955
  }) => Props
846
956
  ```
@@ -860,7 +970,15 @@ Validates data against a Zod schema without throwing, returning a discriminated
860
970
  const safeParse: <T>(
861
971
  schema: ZodSchema<T>,
862
972
  data: unknown,
863
- ) => { success: true; data: T } | { success: false; error: ZodError }
973
+ ) =>
974
+ | {
975
+ success: true
976
+ data: T
977
+ }
978
+ | {
979
+ success: false
980
+ error: ZodError
981
+ }
864
982
  ```
865
983
 
866
984
  - `schema` — The Zod schema to validate against.
@@ -35,6 +35,45 @@ export interface Response {
35
35
  */
36
36
  body: any;
37
37
  }
38
+ /**
39
+ * Request overrides, or an explicit response that stops before the handler.
40
+ * The response form supports extra validation and unresolved external filters
41
+ * without writing data or inventing a placeholder identifier.
42
+ */
43
+ export type RequestMappingResult = Partial<MoleculeRequest> | {
44
+ response: Response;
45
+ };
46
+ /**
47
+ * Per-route wire-format adapters. Validation and authorization still belong to
48
+ * the original handler. Omit these options to retain its native contract.
49
+ */
50
+ export interface RequestHandlerOptions {
51
+ /**
52
+ * Return the fields to replace before the handler validates the request.
53
+ * The adapter preserves the original request's methods and getters, and does
54
+ * not mutate it. Returning `{ body }` or `{ query }` is sufficient, including
55
+ * frameworks whose request properties have read-only accessors.
56
+ * Return `{ response: { statusCode, headers?, body } }` to answer before the
57
+ * handler runs. That result still goes through mapResponse, with the original request.
58
+ */
59
+ mapRequest?: (req: MoleculeRequest, res: MoleculeResponse) => RequestMappingResult | Promise<RequestMappingResult>;
60
+ /**
61
+ * Adapt the returned status, headers or body. This sees error and partial
62
+ * responses too: narrow the success shape before changing it, and preserve
63
+ * headers with `{ ...response, body }`. It never runs for fall-through.
64
+ */
65
+ mapResponse?: (response: Response, req: MoleculeRequest, res: MoleculeResponse) => Response | Promise<Response>;
66
+ }
67
+ /**
68
+ * Compose wire-format adapters around a structured resource handler without
69
+ * sending its response. The response mapper receives the adapted request.
70
+ * Exceptions propagate to the caller's normal error boundary.
71
+ *
72
+ * @param handler - The existing resource handler, including its checks.
73
+ * @param options - Request and response mappings for this route only.
74
+ * @returns A structured handler that keeps null/undefined fall-through intact.
75
+ */
76
+ export declare const adaptHandler: (handler: Handler, options?: RequestHandlerOptions) => Handler;
38
77
  /**
39
78
  * Wraps a resource handler into Express-compatible middleware. Calls the handler,
40
79
  * then applies `statusCode`, `headers`, and `body` to the response. If the handler
@@ -42,7 +81,8 @@ export interface Response {
42
81
  * i18n-translated error messages.
43
82
  *
44
83
  * @param handler - The resource handler function to wrap.
84
+ * @param options - Optional wire-format mappings; neither mutates the shared handler.
45
85
  * @returns An Express-compatible middleware function `(req, res, next) => void`.
46
86
  */
47
- export declare const createRequestHandler: (handler: Handler) => (req: MoleculeRequest, res: MoleculeResponse, next: MoleculeNextFunction) => Promise<void>;
87
+ export declare const createRequestHandler: (handler: Handler, options?: RequestHandlerOptions) => (req: MoleculeRequest, res: MoleculeResponse, next: MoleculeNextFunction) => Promise<void>;
48
88
  //# sourceMappingURL=createRequestHandler.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"createRequestHandler.d.ts","sourceRoot":"","sources":["../src/createRequestHandler.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAKH,OAAO,KAAK,EAAE,oBAAoB,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAA;AAI9F;;;;;;;GAOG;AACH,MAAM,MAAM,OAAO,GAAG,CACpB,GAAG,EAAE,eAAe,EACpB,GAAG,EAAE,gBAAgB,KAClB,OAAO,CAAC,QAAQ,GAAG,IAAI,GAAG,SAAS,CAAC,GAAG,QAAQ,GAAG,IAAI,GAAG,SAAS,CAAA;AAEvE;;;GAGG;AACH,MAAM,WAAW,QAAQ;IACvB;;OAEG;IACH,UAAU,EAAE,MAAM,CAAA;IAClB;;OAEG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAChC;;OAEG;IACH,IAAI,EAAE,GAAG,CAAA;CACV;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,oBAAoB,GAC9B,SAAS,OAAO,MACV,KAAK,eAAe,EAAE,KAAK,gBAAgB,EAAE,MAAM,oBAAoB,kBAkB7E,CAAA"}
1
+ {"version":3,"file":"createRequestHandler.d.ts","sourceRoot":"","sources":["../src/createRequestHandler.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAKH,OAAO,KAAK,EAAE,oBAAoB,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAA;AAI9F;;;;;;;GAOG;AACH,MAAM,MAAM,OAAO,GAAG,CACpB,GAAG,EAAE,eAAe,EACpB,GAAG,EAAE,gBAAgB,KAClB,OAAO,CAAC,QAAQ,GAAG,IAAI,GAAG,SAAS,CAAC,GAAG,QAAQ,GAAG,IAAI,GAAG,SAAS,CAAA;AAEvE;;;GAGG;AACH,MAAM,WAAW,QAAQ;IACvB;;OAEG;IACH,UAAU,EAAE,MAAM,CAAA;IAClB;;OAEG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAChC;;OAEG;IACH,IAAI,EAAE,GAAG,CAAA;CACV;AAED;;;;GAIG;AACH,MAAM,MAAM,oBAAoB,GAAG,OAAO,CAAC,eAAe,CAAC,GAAG;IAAE,QAAQ,EAAE,QAAQ,CAAA;CAAE,CAAA;AAEpF;;;GAGG;AACH,MAAM,WAAW,qBAAqB;IACpC;;;;;;;OAOG;IACH,UAAU,CAAC,EAAE,CACX,GAAG,EAAE,eAAe,EACpB,GAAG,EAAE,gBAAgB,KAClB,oBAAoB,GAAG,OAAO,CAAC,oBAAoB,CAAC,CAAA;IACzD;;;;OAIG;IACH,WAAW,CAAC,EAAE,CACZ,QAAQ,EAAE,QAAQ,EAClB,GAAG,EAAE,eAAe,EACpB,GAAG,EAAE,gBAAgB,KAClB,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAA;CAClC;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,YAAY,GACtB,SAAS,OAAO,EAAE,UAAS,qBAA0B,KAAG,OAYxD,CAAA;AAEH;;;;;;;;;GASG;AACH,eAAO,MAAM,oBAAoB,GAAI,SAAS,OAAO,EAAE,UAAS,qBAA0B,MAE1E,KAAK,eAAe,EAAE,KAAK,gBAAgB,EAAE,MAAM,oBAAoB,kBAmBtF,CAAA"}
@@ -11,6 +11,26 @@
11
11
  import { getLogger } from '@molecule/api-bond';
12
12
  const logger = getLogger();
13
13
  import { t } from '@molecule/api-i18n';
14
+ /**
15
+ * Compose wire-format adapters around a structured resource handler without
16
+ * sending its response. The response mapper receives the adapted request.
17
+ * Exceptions propagate to the caller's normal error boundary.
18
+ *
19
+ * @param handler - The existing resource handler, including its checks.
20
+ * @param options - Request and response mappings for this route only.
21
+ * @returns A structured handler that keeps null/undefined fall-through intact.
22
+ */
23
+ export const adaptHandler = (handler, options = {}) => async (req, res) => {
24
+ const overrides = options.mapRequest ? await options.mapRequest(req, res) : undefined;
25
+ const earlyResponse = overrides && 'response' in overrides ? overrides.response : undefined;
26
+ const request = overrides && !earlyResponse
27
+ ? Object.create(req, Object.getOwnPropertyDescriptors(overrides))
28
+ : req;
29
+ const response = earlyResponse ?? (await handler(request, res));
30
+ return response && options.mapResponse
31
+ ? await options.mapResponse(response, request, res)
32
+ : response;
33
+ };
14
34
  /**
15
35
  * Wraps a resource handler into Express-compatible middleware. Calls the handler,
16
36
  * then applies `statusCode`, `headers`, and `body` to the response. If the handler
@@ -18,25 +38,29 @@ import { t } from '@molecule/api-i18n';
18
38
  * i18n-translated error messages.
19
39
  *
20
40
  * @param handler - The resource handler function to wrap.
41
+ * @param options - Optional wire-format mappings; neither mutates the shared handler.
21
42
  * @returns An Express-compatible middleware function `(req, res, next) => void`.
22
43
  */
23
- export const createRequestHandler = (handler) => async (req, res, next) => {
24
- try {
25
- const response = await handler(req, res);
26
- if (response) {
27
- if (response.headers) {
28
- res.set(response.headers);
44
+ export const createRequestHandler = (handler, options = {}) => {
45
+ const adapted = adaptHandler(handler, options);
46
+ return async (req, res, next) => {
47
+ try {
48
+ const response = await adapted(req, res);
49
+ if (response) {
50
+ if (response.headers) {
51
+ res.set(response.headers);
52
+ }
53
+ res.status(response.statusCode);
54
+ res.send(response.body);
55
+ }
56
+ else {
57
+ next();
29
58
  }
30
- res.status(response.statusCode);
31
- res.send(response.body);
32
59
  }
33
- else {
34
- next();
60
+ catch (error) {
61
+ logger.error(error);
62
+ next(t('resource.error.unknownError', undefined, { defaultValue: 'Something went wrong.' }));
35
63
  }
36
- }
37
- catch (error) {
38
- logger.error(error);
39
- next(t('resource.error.unknownError', undefined, { defaultValue: 'Something went wrong.' }));
40
- }
64
+ };
41
65
  };
42
66
  //# sourceMappingURL=createRequestHandler.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"createRequestHandler.js","sourceRoot":"","sources":["../src/createRequestHandler.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,uDAAuD;AACvD,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAA;AAG9C,MAAM,MAAM,GAAG,SAAS,EAAE,CAAA;AAC1B,OAAO,EAAE,CAAC,EAAE,MAAM,oBAAoB,CAAA;AAkCtC;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAC/B,CAAC,OAAgB,EAAE,EAAE,CACrB,KAAK,EAAE,GAAoB,EAAE,GAAqB,EAAE,IAA0B,EAAE,EAAE;IAChF,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,MAAM,OAAO,CAAC,GAAG,EAAE,GAAG,CAAC,CAAA;QAExC,IAAI,QAAQ,EAAE,CAAC;YACb,IAAI,QAAQ,CAAC,OAAO,EAAE,CAAC;gBACrB,GAAG,CAAC,GAAG,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAA;YAC3B,CAAC;YAED,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAA;YAC/B,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAA;QACzB,CAAC;aAAM,CAAC;YACN,IAAI,EAAE,CAAA;QACR,CAAC;IACH,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAA;QACnB,IAAI,CAAC,CAAC,CAAC,6BAA6B,EAAE,SAAS,EAAE,EAAE,YAAY,EAAE,uBAAuB,EAAE,CAAC,CAAC,CAAA;IAC9F,CAAC;AACH,CAAC,CAAA"}
1
+ {"version":3,"file":"createRequestHandler.js","sourceRoot":"","sources":["../src/createRequestHandler.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,uDAAuD;AACvD,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAA;AAG9C,MAAM,MAAM,GAAG,SAAS,EAAE,CAAA;AAC1B,OAAO,EAAE,CAAC,EAAE,MAAM,oBAAoB,CAAA;AAsEtC;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,YAAY,GACvB,CAAC,OAAgB,EAAE,UAAiC,EAAE,EAAW,EAAE,CACnE,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,EAAE;IACjB,MAAM,SAAS,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC,MAAM,OAAO,CAAC,UAAU,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,CAAA;IACrF,MAAM,aAAa,GAAG,SAAS,IAAI,UAAU,IAAI,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAA;IAC3F,MAAM,OAAO,GACX,SAAS,IAAI,CAAC,aAAa;QACzB,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,GAAG,EAAE,MAAM,CAAC,yBAAyB,CAAC,SAAS,CAAC,CAAC;QACjE,CAAC,CAAC,GAAG,CAAA;IACT,MAAM,QAAQ,GAAG,aAAa,IAAI,CAAC,MAAM,OAAO,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC,CAAA;IAC/D,OAAO,QAAQ,IAAI,OAAO,CAAC,WAAW;QACpC,CAAC,CAAC,MAAM,OAAO,CAAC,WAAW,CAAC,QAAQ,EAAE,OAAO,EAAE,GAAG,CAAC;QACnD,CAAC,CAAC,QAAQ,CAAA;AACd,CAAC,CAAA;AAEH;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAAC,OAAgB,EAAE,UAAiC,EAAE,EAAE,EAAE;IAC5F,MAAM,OAAO,GAAG,YAAY,CAAC,OAAO,EAAE,OAAO,CAAC,CAAA;IAC9C,OAAO,KAAK,EAAE,GAAoB,EAAE,GAAqB,EAAE,IAA0B,EAAE,EAAE;QACvF,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,MAAM,OAAO,CAAC,GAAG,EAAE,GAAG,CAAC,CAAA;YAExC,IAAI,QAAQ,EAAE,CAAC;gBACb,IAAI,QAAQ,CAAC,OAAO,EAAE,CAAC;oBACrB,GAAG,CAAC,GAAG,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAA;gBAC3B,CAAC;gBAED,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAA;gBAC/B,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAA;YACzB,CAAC;iBAAM,CAAC;gBACN,IAAI,EAAE,CAAA;YACR,CAAC;QACH,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAA;YACnB,IAAI,CAAC,CAAC,CAAC,6BAA6B,EAAE,SAAS,EAAE,EAAE,YAAY,EAAE,uBAAuB,EAAE,CAAC,CAAC,CAAA;QAC9F,CAAC;IACH,CAAC,CAAA;AACH,CAAC,CAAA"}
package/dist/index.d.ts CHANGED
@@ -21,6 +21,32 @@
21
21
  * Monkey-patching `res.json` (or `res.send`/`res.end`) to re-shape a handler's answer intercepts
22
22
  * nothing the handler produces, so the request it was meant to answer is left unanswered and hangs.
23
23
  *
24
+ * **A different request or response envelope is configuration.** Pass
25
+ * `mapRequest` and/or `mapResponse` as the second argument to `createRequestHandler`.
26
+ * Both may be asynchronous. The existing handler still validates and authorizes
27
+ * the adapted input. A response mapper sees the handler's errors and partial
28
+ * responses too: preserve them and its headers instead of treating every result
29
+ * as a success. `adaptHandler(handler, options)` offers the same mappings when
30
+ * another HTTP adapter will send the structured response.
31
+ * A request mapper can return `{ response: { statusCode, body } }` to answer
32
+ * before calling the handler, for example when extra validation fails or a
33
+ * filter lookup finds nothing. That response still passes through mapResponse.
34
+ *
35
+ * ```typescript
36
+ * import { createRequestHandler } from '@molecule/api-resource'
37
+ * import { handlers } from '@molecule/api-resource-user'
38
+ *
39
+ * const register = createRequestHandler(handlers.create({ name: 'User', tableName: 'users' }), {
40
+ * // Return overrides. The adapter retains framework methods and getters.
41
+ * mapRequest: (req) => ({ body: req.body?.account }),
42
+ * mapResponse: (result) => result.statusCode === 201
43
+ * ? { ...result, body: { account: result.body.props, accessToken: result.body.accessToken } }
44
+ * : result,
45
+ * })
46
+ * // Keep the route's existing rate limiter and authorization around register.
47
+ * // No mapping is inferred: a flat-body API keeps createRequestHandler(handler).
48
+ * ```
49
+ *
24
50
  * @example
25
51
  * ```typescript
26
52
  * import { z } from 'zod'
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqGG;AAEH,cAAc,oBAAoB,CAAA;AAClC,cAAc,aAAa,CAAA;AAC3B,cAAc,2BAA2B,CAAA;AACzC,cAAc,UAAU,CAAA;AACxB,cAAc,iBAAiB,CAAA;AAC/B,cAAc,WAAW,CAAA;AACzB,cAAc,YAAY,CAAA;AAC1B,cAAc,WAAW,CAAA;AACzB,cAAc,oBAAoB,CAAA;AAClC,OAAO,KAAK,MAAM,MAAM,aAAa,CAAA;AACrC,OAAO,KAAK,KAAK,MAAM,YAAY,CAAA;AACnC,cAAc,aAAa,CAAA;AAC3B,OAAO,KAAK,SAAS,MAAM,sBAAsB,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+HG;AAEH,cAAc,oBAAoB,CAAA;AAClC,cAAc,aAAa,CAAA;AAC3B,cAAc,2BAA2B,CAAA;AACzC,cAAc,UAAU,CAAA;AACxB,cAAc,iBAAiB,CAAA;AAC/B,cAAc,WAAW,CAAA;AACzB,cAAc,YAAY,CAAA;AAC1B,cAAc,WAAW,CAAA;AACzB,cAAc,oBAAoB,CAAA;AAClC,OAAO,KAAK,MAAM,MAAM,aAAa,CAAA;AACrC,OAAO,KAAK,KAAK,MAAM,YAAY,CAAA;AACnC,cAAc,aAAa,CAAA;AAC3B,OAAO,KAAK,SAAS,MAAM,sBAAsB,CAAA"}
package/dist/index.js CHANGED
@@ -21,6 +21,32 @@
21
21
  * Monkey-patching `res.json` (or `res.send`/`res.end`) to re-shape a handler's answer intercepts
22
22
  * nothing the handler produces, so the request it was meant to answer is left unanswered and hangs.
23
23
  *
24
+ * **A different request or response envelope is configuration.** Pass
25
+ * `mapRequest` and/or `mapResponse` as the second argument to `createRequestHandler`.
26
+ * Both may be asynchronous. The existing handler still validates and authorizes
27
+ * the adapted input. A response mapper sees the handler's errors and partial
28
+ * responses too: preserve them and its headers instead of treating every result
29
+ * as a success. `adaptHandler(handler, options)` offers the same mappings when
30
+ * another HTTP adapter will send the structured response.
31
+ * A request mapper can return `{ response: { statusCode, body } }` to answer
32
+ * before calling the handler, for example when extra validation fails or a
33
+ * filter lookup finds nothing. That response still passes through mapResponse.
34
+ *
35
+ * ```typescript
36
+ * import { createRequestHandler } from '@molecule/api-resource'
37
+ * import { handlers } from '@molecule/api-resource-user'
38
+ *
39
+ * const register = createRequestHandler(handlers.create({ name: 'User', tableName: 'users' }), {
40
+ * // Return overrides. The adapter retains framework methods and getters.
41
+ * mapRequest: (req) => ({ body: req.body?.account }),
42
+ * mapResponse: (result) => result.statusCode === 201
43
+ * ? { ...result, body: { account: result.body.props, accessToken: result.body.accessToken } }
44
+ * : result,
45
+ * })
46
+ * // Keep the route's existing rate limiter and authorization around register.
47
+ * // No mapping is inferred: a flat-body API keeps createRequestHandler(handler).
48
+ * ```
49
+ *
24
50
  * @example
25
51
  * ```typescript
26
52
  * import { z } from 'zod'
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqGG;AAEH,cAAc,oBAAoB,CAAA;AAClC,cAAc,aAAa,CAAA;AAC3B,cAAc,2BAA2B,CAAA;AACzC,cAAc,UAAU,CAAA;AACxB,cAAc,iBAAiB,CAAA;AAC/B,cAAc,WAAW,CAAA;AACzB,cAAc,YAAY,CAAA;AAC1B,cAAc,WAAW,CAAA;AACzB,cAAc,oBAAoB,CAAA;AAClC,OAAO,KAAK,MAAM,MAAM,aAAa,CAAA;AACrC,OAAO,KAAK,KAAK,MAAM,YAAY,CAAA;AACnC,cAAc,aAAa,CAAA;AAC3B,OAAO,KAAK,SAAS,MAAM,sBAAsB,CAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+HG;AAEH,cAAc,oBAAoB,CAAA;AAClC,cAAc,aAAa,CAAA;AAC3B,cAAc,2BAA2B,CAAA;AACzC,cAAc,UAAU,CAAA;AACxB,cAAc,iBAAiB,CAAA;AAC/B,cAAc,WAAW,CAAA;AACzB,cAAc,YAAY,CAAA;AAC1B,cAAc,WAAW,CAAA;AACzB,cAAc,oBAAoB,CAAA;AAClC,OAAO,KAAK,MAAM,MAAM,aAAa,CAAA;AACrC,OAAO,KAAK,KAAK,MAAM,YAAY,CAAA;AACnC,cAAc,aAAa,CAAA;AAC3B,OAAO,KAAK,SAAS,MAAM,sBAAsB,CAAA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@molecule/api-resource",
3
- "version": "1.0.6",
3
+ "version": "1.1.0",
4
4
  "description": "Resource CRUD operations and utilities for molecule.dev.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -39,10 +39,10 @@
39
39
  "zod": "4.6.7"
40
40
  },
41
41
  "devDependencies": {
42
- "@molecule/api-bond": "1.0.3",
43
- "@molecule/api-database": "1.1.5",
42
+ "@molecule/api-bond": "1.0.4",
43
+ "@molecule/api-database": "1.2.2",
44
44
  "@molecule/api-locales-resource": "1.0.2",
45
- "@molecule/api-utilities-validation": "1.0.3",
45
+ "@molecule/api-utilities-validation": "1.0.4",
46
46
  "@types/node": "26.6.5",
47
47
  "typescript": "6.0.3",
48
48
  "vitest": "4.1.12"