@karmaniverous/smoz 0.2.12 → 0.2.13
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.js +103 -25
- package/dist/index.d.ts +1 -1
- package/dist/mjs/index.js +103 -25
- package/package.json +1 -1
package/dist/cjs/index.js
CHANGED
|
@@ -375,6 +375,98 @@ const wrapSerializer = (fn, opts) => {
|
|
|
375
375
|
});
|
|
376
376
|
};
|
|
377
377
|
|
|
378
|
+
/**
|
|
379
|
+
* @module Response shaping helpers for HTTP middleware.
|
|
380
|
+
*
|
|
381
|
+
* Shared utilities for detecting and normalising Lambda Proxy response
|
|
382
|
+
* envelopes, used by both the `after` and `onError` middleware paths.
|
|
383
|
+
* Also exports dedicated `onError` steps that apply CORS headers and
|
|
384
|
+
* response shaping after `errorHandler` — necessary because middy v6+
|
|
385
|
+
* does not re-run the `after` chain on the error path.
|
|
386
|
+
*/
|
|
387
|
+
/**
|
|
388
|
+
* Detect a shaped Lambda Proxy response.
|
|
389
|
+
*
|
|
390
|
+
* A response is "shaped" when it has a numeric `statusCode` property —
|
|
391
|
+
* the definitive marker of an API-Gateway-style response envelope.
|
|
392
|
+
* Missing `headers` or `body` are defaulted by {@link shapeResponse}
|
|
393
|
+
* rather than treated as "unshaped", because `{ statusCode, headers }`
|
|
394
|
+
* (no body) is a valid Lambda Proxy return value.
|
|
395
|
+
*/
|
|
396
|
+
const isShapedResponse = (v) => typeof v === 'object' &&
|
|
397
|
+
v !== null &&
|
|
398
|
+
'statusCode' in v &&
|
|
399
|
+
typeof v.statusCode === 'number';
|
|
400
|
+
/**
|
|
401
|
+
* Shape the response into a well-formed Lambda Proxy result.
|
|
402
|
+
*
|
|
403
|
+
* Shaped responses (detected via {@link isShapedResponse}) are normalised
|
|
404
|
+
* in-place — missing `headers` and `body` are defaulted. Non-shaped
|
|
405
|
+
* values are wrapped in a `200` envelope.
|
|
406
|
+
*/
|
|
407
|
+
const shapeResponse = (current, contentType) => {
|
|
408
|
+
let res;
|
|
409
|
+
if (isShapedResponse(current)) {
|
|
410
|
+
res = current;
|
|
411
|
+
}
|
|
412
|
+
else {
|
|
413
|
+
res = { statusCode: 200, headers: {}, body: current };
|
|
414
|
+
}
|
|
415
|
+
// Default missing fields for shaped responses without body/headers.
|
|
416
|
+
if (res.body === undefined)
|
|
417
|
+
res.body = '';
|
|
418
|
+
if (res.body !== undefined && typeof res.body !== 'string') {
|
|
419
|
+
try {
|
|
420
|
+
res.body = JSON.stringify(res.body);
|
|
421
|
+
}
|
|
422
|
+
catch {
|
|
423
|
+
res.body = String(res.body);
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
const headers = res.headers ?? {};
|
|
427
|
+
headers['Content-Type'] = contentType;
|
|
428
|
+
res.headers = headers;
|
|
429
|
+
return res;
|
|
430
|
+
};
|
|
431
|
+
/**
|
|
432
|
+
* Apply CORS headers to error responses.
|
|
433
|
+
*
|
|
434
|
+
* middy v6+ does not re-run the `after` chain after `onError` handles an
|
|
435
|
+
* error, so the regular CORS `after` hook never fires for error responses.
|
|
436
|
+
* This dedicated `onError` step runs *after* the error handler has set
|
|
437
|
+
* `request.response`, ensuring CORS headers are present on error responses.
|
|
438
|
+
*/
|
|
439
|
+
const makeOnErrorCors = (opts) => {
|
|
440
|
+
const cors = httpCors({
|
|
441
|
+
credentials: true,
|
|
442
|
+
getOrigin: (o) => o,
|
|
443
|
+
...(opts?.cors ?? {}),
|
|
444
|
+
});
|
|
445
|
+
return tagStep({
|
|
446
|
+
onError: async (request) => {
|
|
447
|
+
if (request.response === undefined)
|
|
448
|
+
return;
|
|
449
|
+
if (cors.after)
|
|
450
|
+
await cors.after(request);
|
|
451
|
+
},
|
|
452
|
+
}, 'error-cors');
|
|
453
|
+
};
|
|
454
|
+
/**
|
|
455
|
+
* Shape error responses into well-formed Lambda Proxy results.
|
|
456
|
+
*
|
|
457
|
+
* Mirrors `makeShapeAndContentType` but runs on the `onError` path,
|
|
458
|
+
* ensuring error responses have proper `statusCode`, `headers`, and
|
|
459
|
+
* `body` structure even when the `after` chain is skipped.
|
|
460
|
+
*/
|
|
461
|
+
const makeOnErrorShape = (contentType) => tagStep({
|
|
462
|
+
onError: (request) => {
|
|
463
|
+
const container = request;
|
|
464
|
+
if (container.response === undefined)
|
|
465
|
+
return;
|
|
466
|
+
container.response = shapeResponse(container.response, contentType);
|
|
467
|
+
},
|
|
468
|
+
}, 'error-shape');
|
|
469
|
+
|
|
378
470
|
const makeHead = () => tagStep(shortCircuitHead, 'head');
|
|
379
471
|
const makeHeaderNormalizer = (opts) => tagStep(asApiMiddleware(httpHeaderNormalizer(opts?.headerNormalizer ?? { canonical: true })), 'header-normalizer');
|
|
380
472
|
const makeEventNormalizer = () => tagStep(asApiMiddleware(httpEventNormalizer()), 'event-normalizer');
|
|
@@ -529,31 +621,9 @@ const makeCors = (opts) => tagStep(asApiMiddleware(httpCors({
|
|
|
529
621
|
const makeShapeAndContentType = (contentType) => tagStep({
|
|
530
622
|
after: (request) => {
|
|
531
623
|
const container = request;
|
|
532
|
-
|
|
533
|
-
if (current === undefined)
|
|
624
|
+
if (container.response === undefined)
|
|
534
625
|
return;
|
|
535
|
-
|
|
536
|
-
current !== null &&
|
|
537
|
-
'statusCode' in current &&
|
|
538
|
-
'headers' in current &&
|
|
539
|
-
'body' in current;
|
|
540
|
-
let res;
|
|
541
|
-
if (looksShaped)
|
|
542
|
-
res = current;
|
|
543
|
-
else
|
|
544
|
-
res = { statusCode: 200, headers: {}, body: current };
|
|
545
|
-
if (res.body !== undefined && typeof res.body !== 'string') {
|
|
546
|
-
try {
|
|
547
|
-
res.body = JSON.stringify(res.body);
|
|
548
|
-
}
|
|
549
|
-
catch {
|
|
550
|
-
res.body = String(res.body);
|
|
551
|
-
}
|
|
552
|
-
}
|
|
553
|
-
const headers = res.headers ?? {};
|
|
554
|
-
headers['Content-Type'] = contentType;
|
|
555
|
-
res.headers = headers;
|
|
556
|
-
request.response = res;
|
|
626
|
+
container.response = shapeResponse(container.response, contentType);
|
|
557
627
|
},
|
|
558
628
|
}, 'shape');
|
|
559
629
|
const makeSerializer = (contentType, opts) => tagStep(asApiMiddleware(httpResponseSerializer({
|
|
@@ -589,7 +659,15 @@ const buildDefaultPhases = (args) => {
|
|
|
589
659
|
makeShapeAndContentType(contentType),
|
|
590
660
|
makeSerializer(contentType, opts),
|
|
591
661
|
];
|
|
592
|
-
const onError = [
|
|
662
|
+
const onError = [
|
|
663
|
+
makeErrorExpose(),
|
|
664
|
+
makeErrorHandler(opts),
|
|
665
|
+
// Post-error-handler processing: middy v6+ does not re-run the after
|
|
666
|
+
// chain after onError, so CORS and response shaping must happen here.
|
|
667
|
+
// Shape first so CORS can mutate a well-formed headers object.
|
|
668
|
+
makeOnErrorShape(contentType),
|
|
669
|
+
makeOnErrorCors(opts),
|
|
670
|
+
];
|
|
593
671
|
return { before, after, onError };
|
|
594
672
|
};
|
|
595
673
|
const buildSafeDefaults = (args) => buildDefaultPhases(args);
|
package/dist/index.d.ts
CHANGED
|
@@ -264,7 +264,7 @@ type ConsoleLogger = Pick<Console, 'debug' | 'error' | 'info' | 'log'>;
|
|
|
264
264
|
*/
|
|
265
265
|
|
|
266
266
|
type ApiMiddleware$1 = MiddlewareObj<APIGatewayProxyEvent, Context>;
|
|
267
|
-
type StepId = 'head' | 'header-normalizer' | 'event-normalizer' | 'content-negotiation' | 'json-body-parser' | 'zod-before' | 'head-finalize' | 'zod-after' | 'error-expose' | 'error-handler' | 'cors' | 'preferred-media' | 'shape' | 'serializer';
|
|
267
|
+
type StepId = 'head' | 'header-normalizer' | 'event-normalizer' | 'content-negotiation' | 'json-body-parser' | 'zod-before' | 'head-finalize' | 'zod-after' | 'error-expose' | 'error-handler' | 'error-cors' | 'error-shape' | 'cors' | 'preferred-media' | 'shape' | 'serializer';
|
|
268
268
|
/** Attach a non-enumerable __id to a middleware step. */
|
|
269
269
|
declare const tagStep: (mw: ApiMiddleware$1, id: StepId) => ApiMiddleware$1;
|
|
270
270
|
/** Retrieve a step's id, if present. */
|
package/dist/mjs/index.js
CHANGED
|
@@ -373,6 +373,98 @@ const wrapSerializer = (fn, opts) => {
|
|
|
373
373
|
});
|
|
374
374
|
};
|
|
375
375
|
|
|
376
|
+
/**
|
|
377
|
+
* @module Response shaping helpers for HTTP middleware.
|
|
378
|
+
*
|
|
379
|
+
* Shared utilities for detecting and normalising Lambda Proxy response
|
|
380
|
+
* envelopes, used by both the `after` and `onError` middleware paths.
|
|
381
|
+
* Also exports dedicated `onError` steps that apply CORS headers and
|
|
382
|
+
* response shaping after `errorHandler` — necessary because middy v6+
|
|
383
|
+
* does not re-run the `after` chain on the error path.
|
|
384
|
+
*/
|
|
385
|
+
/**
|
|
386
|
+
* Detect a shaped Lambda Proxy response.
|
|
387
|
+
*
|
|
388
|
+
* A response is "shaped" when it has a numeric `statusCode` property —
|
|
389
|
+
* the definitive marker of an API-Gateway-style response envelope.
|
|
390
|
+
* Missing `headers` or `body` are defaulted by {@link shapeResponse}
|
|
391
|
+
* rather than treated as "unshaped", because `{ statusCode, headers }`
|
|
392
|
+
* (no body) is a valid Lambda Proxy return value.
|
|
393
|
+
*/
|
|
394
|
+
const isShapedResponse = (v) => typeof v === 'object' &&
|
|
395
|
+
v !== null &&
|
|
396
|
+
'statusCode' in v &&
|
|
397
|
+
typeof v.statusCode === 'number';
|
|
398
|
+
/**
|
|
399
|
+
* Shape the response into a well-formed Lambda Proxy result.
|
|
400
|
+
*
|
|
401
|
+
* Shaped responses (detected via {@link isShapedResponse}) are normalised
|
|
402
|
+
* in-place — missing `headers` and `body` are defaulted. Non-shaped
|
|
403
|
+
* values are wrapped in a `200` envelope.
|
|
404
|
+
*/
|
|
405
|
+
const shapeResponse = (current, contentType) => {
|
|
406
|
+
let res;
|
|
407
|
+
if (isShapedResponse(current)) {
|
|
408
|
+
res = current;
|
|
409
|
+
}
|
|
410
|
+
else {
|
|
411
|
+
res = { statusCode: 200, headers: {}, body: current };
|
|
412
|
+
}
|
|
413
|
+
// Default missing fields for shaped responses without body/headers.
|
|
414
|
+
if (res.body === undefined)
|
|
415
|
+
res.body = '';
|
|
416
|
+
if (res.body !== undefined && typeof res.body !== 'string') {
|
|
417
|
+
try {
|
|
418
|
+
res.body = JSON.stringify(res.body);
|
|
419
|
+
}
|
|
420
|
+
catch {
|
|
421
|
+
res.body = String(res.body);
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
const headers = res.headers ?? {};
|
|
425
|
+
headers['Content-Type'] = contentType;
|
|
426
|
+
res.headers = headers;
|
|
427
|
+
return res;
|
|
428
|
+
};
|
|
429
|
+
/**
|
|
430
|
+
* Apply CORS headers to error responses.
|
|
431
|
+
*
|
|
432
|
+
* middy v6+ does not re-run the `after` chain after `onError` handles an
|
|
433
|
+
* error, so the regular CORS `after` hook never fires for error responses.
|
|
434
|
+
* This dedicated `onError` step runs *after* the error handler has set
|
|
435
|
+
* `request.response`, ensuring CORS headers are present on error responses.
|
|
436
|
+
*/
|
|
437
|
+
const makeOnErrorCors = (opts) => {
|
|
438
|
+
const cors = httpCors({
|
|
439
|
+
credentials: true,
|
|
440
|
+
getOrigin: (o) => o,
|
|
441
|
+
...(opts?.cors ?? {}),
|
|
442
|
+
});
|
|
443
|
+
return tagStep({
|
|
444
|
+
onError: async (request) => {
|
|
445
|
+
if (request.response === undefined)
|
|
446
|
+
return;
|
|
447
|
+
if (cors.after)
|
|
448
|
+
await cors.after(request);
|
|
449
|
+
},
|
|
450
|
+
}, 'error-cors');
|
|
451
|
+
};
|
|
452
|
+
/**
|
|
453
|
+
* Shape error responses into well-formed Lambda Proxy results.
|
|
454
|
+
*
|
|
455
|
+
* Mirrors `makeShapeAndContentType` but runs on the `onError` path,
|
|
456
|
+
* ensuring error responses have proper `statusCode`, `headers`, and
|
|
457
|
+
* `body` structure even when the `after` chain is skipped.
|
|
458
|
+
*/
|
|
459
|
+
const makeOnErrorShape = (contentType) => tagStep({
|
|
460
|
+
onError: (request) => {
|
|
461
|
+
const container = request;
|
|
462
|
+
if (container.response === undefined)
|
|
463
|
+
return;
|
|
464
|
+
container.response = shapeResponse(container.response, contentType);
|
|
465
|
+
},
|
|
466
|
+
}, 'error-shape');
|
|
467
|
+
|
|
376
468
|
const makeHead = () => tagStep(shortCircuitHead, 'head');
|
|
377
469
|
const makeHeaderNormalizer = (opts) => tagStep(asApiMiddleware(httpHeaderNormalizer(opts?.headerNormalizer ?? { canonical: true })), 'header-normalizer');
|
|
378
470
|
const makeEventNormalizer = () => tagStep(asApiMiddleware(httpEventNormalizer()), 'event-normalizer');
|
|
@@ -527,31 +619,9 @@ const makeCors = (opts) => tagStep(asApiMiddleware(httpCors({
|
|
|
527
619
|
const makeShapeAndContentType = (contentType) => tagStep({
|
|
528
620
|
after: (request) => {
|
|
529
621
|
const container = request;
|
|
530
|
-
|
|
531
|
-
if (current === undefined)
|
|
622
|
+
if (container.response === undefined)
|
|
532
623
|
return;
|
|
533
|
-
|
|
534
|
-
current !== null &&
|
|
535
|
-
'statusCode' in current &&
|
|
536
|
-
'headers' in current &&
|
|
537
|
-
'body' in current;
|
|
538
|
-
let res;
|
|
539
|
-
if (looksShaped)
|
|
540
|
-
res = current;
|
|
541
|
-
else
|
|
542
|
-
res = { statusCode: 200, headers: {}, body: current };
|
|
543
|
-
if (res.body !== undefined && typeof res.body !== 'string') {
|
|
544
|
-
try {
|
|
545
|
-
res.body = JSON.stringify(res.body);
|
|
546
|
-
}
|
|
547
|
-
catch {
|
|
548
|
-
res.body = String(res.body);
|
|
549
|
-
}
|
|
550
|
-
}
|
|
551
|
-
const headers = res.headers ?? {};
|
|
552
|
-
headers['Content-Type'] = contentType;
|
|
553
|
-
res.headers = headers;
|
|
554
|
-
request.response = res;
|
|
624
|
+
container.response = shapeResponse(container.response, contentType);
|
|
555
625
|
},
|
|
556
626
|
}, 'shape');
|
|
557
627
|
const makeSerializer = (contentType, opts) => tagStep(asApiMiddleware(httpResponseSerializer({
|
|
@@ -587,7 +657,15 @@ const buildDefaultPhases = (args) => {
|
|
|
587
657
|
makeShapeAndContentType(contentType),
|
|
588
658
|
makeSerializer(contentType, opts),
|
|
589
659
|
];
|
|
590
|
-
const onError = [
|
|
660
|
+
const onError = [
|
|
661
|
+
makeErrorExpose(),
|
|
662
|
+
makeErrorHandler(opts),
|
|
663
|
+
// Post-error-handler processing: middy v6+ does not re-run the after
|
|
664
|
+
// chain after onError, so CORS and response shaping must happen here.
|
|
665
|
+
// Shape first so CORS can mutate a well-formed headers object.
|
|
666
|
+
makeOnErrorShape(contentType),
|
|
667
|
+
makeOnErrorCors(opts),
|
|
668
|
+
];
|
|
591
669
|
return { before, after, onError };
|
|
592
670
|
};
|
|
593
671
|
const buildSafeDefaults = (args) => buildDefaultPhases(args);
|
package/package.json
CHANGED
|
@@ -184,7 +184,7 @@
|
|
|
184
184
|
"templates:lint": "eslint --fix -c templates/default/eslint.config.ts \"templates/default/**/*.{ts,tsx,js,jsx}\" \"templates/default/eslint.config.ts\""
|
|
185
185
|
},
|
|
186
186
|
"type": "module",
|
|
187
|
-
"version": "0.2.
|
|
187
|
+
"version": "0.2.13",
|
|
188
188
|
"volta": {
|
|
189
189
|
"node": "22.19.0"
|
|
190
190
|
}
|