@beignet/next 0.0.27 → 0.0.29
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/CHANGELOG.md +15 -0
- package/README.md +59 -7
- package/dist/index.d.ts +67 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +336 -157
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/index.ts +570 -195
package/dist/index.js
CHANGED
|
@@ -19,6 +19,42 @@ function isServerLoader(source) {
|
|
|
19
19
|
async function resolveServerSource(source) {
|
|
20
20
|
return isServerLoader(source) ? source() : source;
|
|
21
21
|
}
|
|
22
|
+
function rawRoutePipeline(server) {
|
|
23
|
+
const candidate = server.rawRoute;
|
|
24
|
+
return typeof candidate === "function"
|
|
25
|
+
? candidate.bind(server)
|
|
26
|
+
: undefined;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Per-route-factory memo for the pipeline-built handler. The handler is
|
|
30
|
+
* rebuilt only when the resolved server instance changes (fresh server per
|
|
31
|
+
* test, hot reload), never per request.
|
|
32
|
+
*/
|
|
33
|
+
function createRawPipelineRunner(init, fn) {
|
|
34
|
+
let cached;
|
|
35
|
+
return (server) => {
|
|
36
|
+
const pipeline = rawRoutePipeline(server);
|
|
37
|
+
if (!pipeline)
|
|
38
|
+
return undefined;
|
|
39
|
+
if (!cached || cached.server !== server) {
|
|
40
|
+
const built = pipeline(init).handle(fn);
|
|
41
|
+
cached = {
|
|
42
|
+
server,
|
|
43
|
+
handler: async (req) => toWebResponse((await built(req))),
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
return cached.handler;
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Resolve the native `Request` behind a pipeline request. Requests routed
|
|
51
|
+
* through `createFetchHandler` carry it as `raw`; requests passed straight
|
|
52
|
+
* into a core handler are the native request itself.
|
|
53
|
+
*/
|
|
54
|
+
function nativeRequestOf(req) {
|
|
55
|
+
const raw = req.raw;
|
|
56
|
+
return (raw instanceof Request ? raw : req);
|
|
57
|
+
}
|
|
22
58
|
/**
|
|
23
59
|
* Create a memoized Next server loader for route modules.
|
|
24
60
|
*
|
|
@@ -478,8 +514,9 @@ async function isAuthorizedCronRequest(req, secret) {
|
|
|
478
514
|
* lifecycle hooks in serverless apps.
|
|
479
515
|
*/
|
|
480
516
|
export function createOutboxDrainRoute(options) {
|
|
481
|
-
const
|
|
482
|
-
const
|
|
517
|
+
const drainWithContext = async ({ req, ctx, }) => {
|
|
518
|
+
const nativeReq = nativeRequestOf(req);
|
|
519
|
+
const headers = resolveHeaders(options.headers, nativeReq);
|
|
483
520
|
const secret = options.secret;
|
|
484
521
|
if (!secret) {
|
|
485
522
|
return cronRouteJson({
|
|
@@ -487,13 +524,10 @@ export function createOutboxDrainRoute(options) {
|
|
|
487
524
|
error: "CRON_SECRET is not configured.",
|
|
488
525
|
}, 500, headers);
|
|
489
526
|
}
|
|
490
|
-
if (!(await isAuthorizedCronRequest(
|
|
527
|
+
if (!(await isAuthorizedCronRequest(nativeReq, secret))) {
|
|
491
528
|
return cronRouteJson({ ok: false, error: "Unauthorized" }, 401, headers);
|
|
492
529
|
}
|
|
493
|
-
let ctx;
|
|
494
530
|
try {
|
|
495
|
-
const server = await resolveServerSource(options.server);
|
|
496
|
-
ctx = await server.createRequestContext(toRequestLike(req));
|
|
497
531
|
const drainCtx = ctx;
|
|
498
532
|
const result = await drainOutbox({
|
|
499
533
|
outbox: drainCtx.ports.outbox,
|
|
@@ -541,13 +575,54 @@ export function createOutboxDrainRoute(options) {
|
|
|
541
575
|
}, 200, headers);
|
|
542
576
|
}
|
|
543
577
|
catch (error) {
|
|
544
|
-
ctx
|
|
578
|
+
ctx.ports.logger?.error("Outbox drain failed", { error });
|
|
545
579
|
return cronRouteJson({
|
|
546
580
|
ok: false,
|
|
547
581
|
error: "Outbox drain failed.",
|
|
548
582
|
}, 500, headers);
|
|
549
583
|
}
|
|
550
584
|
};
|
|
585
|
+
const pipelineInit = {
|
|
586
|
+
name: options.pipeline?.name ?? "outbox.drain",
|
|
587
|
+
path: options.pipeline?.path ?? "/outbox/drain",
|
|
588
|
+
metadata: options.pipeline?.metadata,
|
|
589
|
+
};
|
|
590
|
+
const pipelineRunners = {
|
|
591
|
+
GET: createRawPipelineRunner({ ...pipelineInit, method: "GET" }, drainWithContext),
|
|
592
|
+
POST: createRawPipelineRunner({ ...pipelineInit, method: "POST" }, drainWithContext),
|
|
593
|
+
};
|
|
594
|
+
const drainPendingOutbox = async (req) => {
|
|
595
|
+
const server = await resolveServerSource(options.server);
|
|
596
|
+
const runner = pipelineRunners[req.method.toUpperCase() === "GET" ? "GET" : "POST"];
|
|
597
|
+
const pipelined = runner(server);
|
|
598
|
+
if (pipelined)
|
|
599
|
+
return pipelined(req);
|
|
600
|
+
// Minimal servers without rawRoute(...) — usually test fakes — keep the
|
|
601
|
+
// original flow: authorize before touching context, then drain inline.
|
|
602
|
+
const headers = resolveHeaders(options.headers, req);
|
|
603
|
+
const secret = options.secret;
|
|
604
|
+
if (!secret) {
|
|
605
|
+
return cronRouteJson({
|
|
606
|
+
ok: false,
|
|
607
|
+
error: "CRON_SECRET is not configured.",
|
|
608
|
+
}, 500, headers);
|
|
609
|
+
}
|
|
610
|
+
if (!(await isAuthorizedCronRequest(req, secret))) {
|
|
611
|
+
return cronRouteJson({ ok: false, error: "Unauthorized" }, 401, headers);
|
|
612
|
+
}
|
|
613
|
+
let ctx;
|
|
614
|
+
try {
|
|
615
|
+
ctx = await server.createRequestContext(toRequestLike(req));
|
|
616
|
+
}
|
|
617
|
+
catch (error) {
|
|
618
|
+
console.error("Outbox drain context failed", { error });
|
|
619
|
+
return cronRouteJson({
|
|
620
|
+
ok: false,
|
|
621
|
+
error: "Outbox drain failed.",
|
|
622
|
+
}, 500, headers);
|
|
623
|
+
}
|
|
624
|
+
return drainWithContext({ req: toRequestLike(req), ctx });
|
|
625
|
+
};
|
|
551
626
|
return {
|
|
552
627
|
GET: drainPendingOutbox,
|
|
553
628
|
POST: drainPendingOutbox,
|
|
@@ -563,10 +638,117 @@ export function createOutboxDrainRoute(options) {
|
|
|
563
638
|
* payload schema, and delegates fulfillment to app code.
|
|
564
639
|
*/
|
|
565
640
|
export function createWebhookRoute(options) {
|
|
641
|
+
const handleWebhookRequest = async ({ req, ctx, }) => {
|
|
642
|
+
const nativeReq = nativeRequestOf(req);
|
|
643
|
+
const headers = resolveHeaders(options.headers, nativeReq);
|
|
644
|
+
const requestHeaders = headersToRecord(new Headers(req.headers));
|
|
645
|
+
let rawBody;
|
|
646
|
+
try {
|
|
647
|
+
rawBody = await req.text();
|
|
648
|
+
}
|
|
649
|
+
catch {
|
|
650
|
+
return webhookRouteJson({
|
|
651
|
+
ok: false,
|
|
652
|
+
error: "Webhook body read failed.",
|
|
653
|
+
}, 400, headers);
|
|
654
|
+
}
|
|
655
|
+
let event;
|
|
656
|
+
try {
|
|
657
|
+
const input = {
|
|
658
|
+
rawBody,
|
|
659
|
+
headers: requestHeaders,
|
|
660
|
+
receivedAt: new Date(),
|
|
661
|
+
};
|
|
662
|
+
const routeVerifier = options.verify;
|
|
663
|
+
const verifier = routeVerifier
|
|
664
|
+
? {
|
|
665
|
+
async verify() {
|
|
666
|
+
return routeVerifier({
|
|
667
|
+
req: nativeReq,
|
|
668
|
+
ctx,
|
|
669
|
+
rawBody,
|
|
670
|
+
headers: requestHeaders,
|
|
671
|
+
input,
|
|
672
|
+
});
|
|
673
|
+
},
|
|
674
|
+
}
|
|
675
|
+
: undefined;
|
|
676
|
+
event = (await verifyWebhook(options.webhook, input, {
|
|
677
|
+
verifier,
|
|
678
|
+
allowUnknownEvents: options.allowUnknownEvents,
|
|
679
|
+
}));
|
|
680
|
+
}
|
|
681
|
+
catch (error) {
|
|
682
|
+
ctx.ports.logger?.error("Webhook verification failed", {
|
|
683
|
+
error,
|
|
684
|
+
webhookName: options.webhook.name,
|
|
685
|
+
provider: options.webhook.provider,
|
|
686
|
+
requestId: ctx.requestId,
|
|
687
|
+
traceId: ctx.traceId,
|
|
688
|
+
});
|
|
689
|
+
return webhookRouteJson({
|
|
690
|
+
ok: false,
|
|
691
|
+
error: "Webhook verification failed.",
|
|
692
|
+
}, 400, headers);
|
|
693
|
+
}
|
|
694
|
+
try {
|
|
695
|
+
const result = await options.handle?.({
|
|
696
|
+
req: nativeReq,
|
|
697
|
+
ctx,
|
|
698
|
+
rawBody,
|
|
699
|
+
event,
|
|
700
|
+
});
|
|
701
|
+
if (result instanceof Response)
|
|
702
|
+
return result;
|
|
703
|
+
if (result) {
|
|
704
|
+
return toWebResponse({
|
|
705
|
+
status: result.status ?? 200,
|
|
706
|
+
headers: toHttpResponseHeaders(result.headers ?? headers),
|
|
707
|
+
body: result.body,
|
|
708
|
+
});
|
|
709
|
+
}
|
|
710
|
+
return toWebResponse({
|
|
711
|
+
status: 200,
|
|
712
|
+
headers: toHttpResponseHeaders(headers),
|
|
713
|
+
body: {
|
|
714
|
+
ok: true,
|
|
715
|
+
webhookName: options.webhook.name,
|
|
716
|
+
eventId: event.id,
|
|
717
|
+
eventType: event.type,
|
|
718
|
+
},
|
|
719
|
+
});
|
|
720
|
+
}
|
|
721
|
+
catch (error) {
|
|
722
|
+
ctx.ports.logger?.error("Webhook handler failed", {
|
|
723
|
+
error,
|
|
724
|
+
webhookName: options.webhook.name,
|
|
725
|
+
provider: options.webhook.provider,
|
|
726
|
+
requestId: ctx.requestId,
|
|
727
|
+
traceId: ctx.traceId,
|
|
728
|
+
eventId: event.id,
|
|
729
|
+
eventType: event.type,
|
|
730
|
+
});
|
|
731
|
+
return webhookRouteJson({
|
|
732
|
+
ok: false,
|
|
733
|
+
error: "Webhook handler failed.",
|
|
734
|
+
}, 500, headers);
|
|
735
|
+
}
|
|
736
|
+
};
|
|
737
|
+
const runPipeline = createRawPipelineRunner({
|
|
738
|
+
name: options.pipeline?.name ?? `webhook.${options.webhook.name}`,
|
|
739
|
+
method: "POST",
|
|
740
|
+
path: options.pipeline?.path ?? `/webhooks/${options.webhook.name}`,
|
|
741
|
+
metadata: options.pipeline?.metadata,
|
|
742
|
+
}, handleWebhookRequest);
|
|
566
743
|
return {
|
|
567
744
|
POST: async (req) => {
|
|
745
|
+
const server = await resolveServerSource(options.server);
|
|
746
|
+
const pipelined = runPipeline(server);
|
|
747
|
+
if (pipelined)
|
|
748
|
+
return pipelined(req);
|
|
749
|
+
// Minimal servers without rawRoute(...) — usually test fakes — build
|
|
750
|
+
// context directly and run the same handler outside the hooks pipeline.
|
|
568
751
|
const headers = resolveHeaders(options.headers, req);
|
|
569
|
-
const requestHeaders = headersToRecord(req.headers);
|
|
570
752
|
let rawBody;
|
|
571
753
|
try {
|
|
572
754
|
rawBody = await req.text();
|
|
@@ -577,10 +759,10 @@ export function createWebhookRoute(options) {
|
|
|
577
759
|
error: "Webhook body read failed.",
|
|
578
760
|
}, 400, headers);
|
|
579
761
|
}
|
|
762
|
+
const contextRequest = toContextRequest(req, rawBody);
|
|
580
763
|
let ctx;
|
|
581
764
|
try {
|
|
582
|
-
|
|
583
|
-
ctx = await server.createRequestContext(toContextRequest(req, rawBody));
|
|
765
|
+
ctx = await server.createRequestContext(contextRequest);
|
|
584
766
|
}
|
|
585
767
|
catch {
|
|
586
768
|
return webhookRouteJson({
|
|
@@ -588,87 +770,7 @@ export function createWebhookRoute(options) {
|
|
|
588
770
|
error: "Webhook context failed.",
|
|
589
771
|
}, 500, headers);
|
|
590
772
|
}
|
|
591
|
-
|
|
592
|
-
try {
|
|
593
|
-
const input = {
|
|
594
|
-
rawBody,
|
|
595
|
-
headers: requestHeaders,
|
|
596
|
-
receivedAt: new Date(),
|
|
597
|
-
};
|
|
598
|
-
const routeVerifier = options.verify;
|
|
599
|
-
const verifier = routeVerifier
|
|
600
|
-
? {
|
|
601
|
-
async verify() {
|
|
602
|
-
return routeVerifier({
|
|
603
|
-
req,
|
|
604
|
-
ctx,
|
|
605
|
-
rawBody,
|
|
606
|
-
headers: requestHeaders,
|
|
607
|
-
input,
|
|
608
|
-
});
|
|
609
|
-
},
|
|
610
|
-
}
|
|
611
|
-
: undefined;
|
|
612
|
-
event = (await verifyWebhook(options.webhook, input, {
|
|
613
|
-
verifier,
|
|
614
|
-
allowUnknownEvents: options.allowUnknownEvents,
|
|
615
|
-
}));
|
|
616
|
-
}
|
|
617
|
-
catch (error) {
|
|
618
|
-
ctx.ports.logger?.error("Webhook verification failed", {
|
|
619
|
-
error,
|
|
620
|
-
webhookName: options.webhook.name,
|
|
621
|
-
provider: options.webhook.provider,
|
|
622
|
-
requestId: ctx.requestId,
|
|
623
|
-
traceId: ctx.traceId,
|
|
624
|
-
});
|
|
625
|
-
return webhookRouteJson({
|
|
626
|
-
ok: false,
|
|
627
|
-
error: "Webhook verification failed.",
|
|
628
|
-
}, 400, headers);
|
|
629
|
-
}
|
|
630
|
-
try {
|
|
631
|
-
const result = await options.handle?.({
|
|
632
|
-
req,
|
|
633
|
-
ctx,
|
|
634
|
-
rawBody,
|
|
635
|
-
event,
|
|
636
|
-
});
|
|
637
|
-
if (result instanceof Response)
|
|
638
|
-
return result;
|
|
639
|
-
if (result) {
|
|
640
|
-
return toWebResponse({
|
|
641
|
-
status: result.status ?? 200,
|
|
642
|
-
headers: toHttpResponseHeaders(result.headers ?? headers),
|
|
643
|
-
body: result.body,
|
|
644
|
-
});
|
|
645
|
-
}
|
|
646
|
-
return toWebResponse({
|
|
647
|
-
status: 200,
|
|
648
|
-
headers: toHttpResponseHeaders(headers),
|
|
649
|
-
body: {
|
|
650
|
-
ok: true,
|
|
651
|
-
webhookName: options.webhook.name,
|
|
652
|
-
eventId: event.id,
|
|
653
|
-
eventType: event.type,
|
|
654
|
-
},
|
|
655
|
-
});
|
|
656
|
-
}
|
|
657
|
-
catch (error) {
|
|
658
|
-
ctx.ports.logger?.error("Webhook handler failed", {
|
|
659
|
-
error,
|
|
660
|
-
webhookName: options.webhook.name,
|
|
661
|
-
provider: options.webhook.provider,
|
|
662
|
-
requestId: ctx.requestId,
|
|
663
|
-
traceId: ctx.traceId,
|
|
664
|
-
eventId: event.id,
|
|
665
|
-
eventType: event.type,
|
|
666
|
-
});
|
|
667
|
-
return webhookRouteJson({
|
|
668
|
-
ok: false,
|
|
669
|
-
error: "Webhook handler failed.",
|
|
670
|
-
}, 500, headers);
|
|
671
|
-
}
|
|
773
|
+
return handleWebhookRequest({ req: toContextRequest(req, rawBody), ctx });
|
|
672
774
|
},
|
|
673
775
|
};
|
|
674
776
|
}
|
|
@@ -684,11 +786,102 @@ export function createWebhookRoute(options) {
|
|
|
684
786
|
*/
|
|
685
787
|
export function createPaymentWebhookRoute(options) {
|
|
686
788
|
const signatureHeader = options.signatureHeader ?? "stripe-signature";
|
|
789
|
+
const handlePaymentWebhook = async ({ req, ctx, }) => {
|
|
790
|
+
const nativeReq = nativeRequestOf(req);
|
|
791
|
+
const headers = resolveHeaders(options.headers, nativeReq);
|
|
792
|
+
const signature = new Headers(req.headers).get(signatureHeader);
|
|
793
|
+
if (!signature) {
|
|
794
|
+
return paymentWebhookJson({
|
|
795
|
+
ok: false,
|
|
796
|
+
error: `Missing ${signatureHeader} header.`,
|
|
797
|
+
}, 400, headers);
|
|
798
|
+
}
|
|
799
|
+
let rawBody;
|
|
800
|
+
try {
|
|
801
|
+
rawBody = await req.text();
|
|
802
|
+
}
|
|
803
|
+
catch {
|
|
804
|
+
return paymentWebhookJson({
|
|
805
|
+
ok: false,
|
|
806
|
+
error: "Payment webhook body read failed.",
|
|
807
|
+
}, 400, headers);
|
|
808
|
+
}
|
|
809
|
+
let event;
|
|
810
|
+
try {
|
|
811
|
+
event = await ctx.ports.payments.verifyWebhook({
|
|
812
|
+
rawBody,
|
|
813
|
+
signature,
|
|
814
|
+
});
|
|
815
|
+
}
|
|
816
|
+
catch (error) {
|
|
817
|
+
ctx.ports.logger?.error("Payment webhook verification failed", {
|
|
818
|
+
error,
|
|
819
|
+
requestId: ctx.requestId,
|
|
820
|
+
traceId: ctx.traceId,
|
|
821
|
+
});
|
|
822
|
+
return paymentWebhookJson({
|
|
823
|
+
ok: false,
|
|
824
|
+
error: "Payment webhook verification failed.",
|
|
825
|
+
}, 400, headers);
|
|
826
|
+
}
|
|
827
|
+
try {
|
|
828
|
+
const result = await options.handle?.({
|
|
829
|
+
req: nativeReq,
|
|
830
|
+
ctx,
|
|
831
|
+
rawBody,
|
|
832
|
+
signature,
|
|
833
|
+
event,
|
|
834
|
+
});
|
|
835
|
+
if (result instanceof Response)
|
|
836
|
+
return result;
|
|
837
|
+
if (result) {
|
|
838
|
+
return toWebResponse({
|
|
839
|
+
status: result.status ?? 200,
|
|
840
|
+
headers: toHttpResponseHeaders(result.headers ?? headers),
|
|
841
|
+
body: result.body,
|
|
842
|
+
});
|
|
843
|
+
}
|
|
844
|
+
return toWebResponse({
|
|
845
|
+
status: 200,
|
|
846
|
+
headers: toHttpResponseHeaders(headers),
|
|
847
|
+
body: {
|
|
848
|
+
ok: true,
|
|
849
|
+
eventId: event.id,
|
|
850
|
+
eventType: event.type,
|
|
851
|
+
},
|
|
852
|
+
});
|
|
853
|
+
}
|
|
854
|
+
catch (error) {
|
|
855
|
+
ctx.ports.logger?.error("Payment webhook handler failed", {
|
|
856
|
+
error,
|
|
857
|
+
requestId: ctx.requestId,
|
|
858
|
+
traceId: ctx.traceId,
|
|
859
|
+
eventId: event.id,
|
|
860
|
+
eventType: event.type,
|
|
861
|
+
});
|
|
862
|
+
return paymentWebhookJson({
|
|
863
|
+
ok: false,
|
|
864
|
+
error: "Payment webhook handler failed.",
|
|
865
|
+
}, 500, headers);
|
|
866
|
+
}
|
|
867
|
+
};
|
|
868
|
+
const runPipeline = createRawPipelineRunner({
|
|
869
|
+
name: options.pipeline?.name ?? "webhook.payments",
|
|
870
|
+
method: "POST",
|
|
871
|
+
path: options.pipeline?.path ?? "/webhooks/payments",
|
|
872
|
+
metadata: options.pipeline?.metadata,
|
|
873
|
+
}, handlePaymentWebhook);
|
|
687
874
|
return {
|
|
688
875
|
POST: async (req) => {
|
|
876
|
+
const server = await resolveServerSource(options.server);
|
|
877
|
+
const pipelined = runPipeline(server);
|
|
878
|
+
if (pipelined)
|
|
879
|
+
return pipelined(req);
|
|
880
|
+
// Minimal servers without rawRoute(...) — usually test fakes — build
|
|
881
|
+
// context directly and run the same handler outside the hooks pipeline.
|
|
882
|
+
// The signature gate runs first so no context is created without one.
|
|
689
883
|
const headers = resolveHeaders(options.headers, req);
|
|
690
|
-
|
|
691
|
-
if (!signature) {
|
|
884
|
+
if (!req.headers.get(signatureHeader)) {
|
|
692
885
|
return paymentWebhookJson({
|
|
693
886
|
ok: false,
|
|
694
887
|
error: `Missing ${signatureHeader} header.`,
|
|
@@ -706,7 +899,6 @@ export function createPaymentWebhookRoute(options) {
|
|
|
706
899
|
}
|
|
707
900
|
let ctx;
|
|
708
901
|
try {
|
|
709
|
-
const server = await resolveServerSource(options.server);
|
|
710
902
|
ctx = await server.createRequestContext(toContextRequest(req, rawBody));
|
|
711
903
|
}
|
|
712
904
|
catch {
|
|
@@ -715,64 +907,7 @@ export function createPaymentWebhookRoute(options) {
|
|
|
715
907
|
error: "Payment webhook context failed.",
|
|
716
908
|
}, 500, headers);
|
|
717
909
|
}
|
|
718
|
-
|
|
719
|
-
try {
|
|
720
|
-
event = await ctx.ports.payments.verifyWebhook({
|
|
721
|
-
rawBody,
|
|
722
|
-
signature,
|
|
723
|
-
});
|
|
724
|
-
}
|
|
725
|
-
catch (error) {
|
|
726
|
-
ctx.ports.logger?.error("Payment webhook verification failed", {
|
|
727
|
-
error,
|
|
728
|
-
requestId: ctx.requestId,
|
|
729
|
-
traceId: ctx.traceId,
|
|
730
|
-
});
|
|
731
|
-
return paymentWebhookJson({
|
|
732
|
-
ok: false,
|
|
733
|
-
error: "Payment webhook verification failed.",
|
|
734
|
-
}, 400, headers);
|
|
735
|
-
}
|
|
736
|
-
try {
|
|
737
|
-
const result = await options.handle?.({
|
|
738
|
-
req,
|
|
739
|
-
ctx,
|
|
740
|
-
rawBody,
|
|
741
|
-
signature,
|
|
742
|
-
event,
|
|
743
|
-
});
|
|
744
|
-
if (result instanceof Response)
|
|
745
|
-
return result;
|
|
746
|
-
if (result) {
|
|
747
|
-
return toWebResponse({
|
|
748
|
-
status: result.status ?? 200,
|
|
749
|
-
headers: toHttpResponseHeaders(result.headers ?? headers),
|
|
750
|
-
body: result.body,
|
|
751
|
-
});
|
|
752
|
-
}
|
|
753
|
-
return toWebResponse({
|
|
754
|
-
status: 200,
|
|
755
|
-
headers: toHttpResponseHeaders(headers),
|
|
756
|
-
body: {
|
|
757
|
-
ok: true,
|
|
758
|
-
eventId: event.id,
|
|
759
|
-
eventType: event.type,
|
|
760
|
-
},
|
|
761
|
-
});
|
|
762
|
-
}
|
|
763
|
-
catch (error) {
|
|
764
|
-
ctx.ports.logger?.error("Payment webhook handler failed", {
|
|
765
|
-
error,
|
|
766
|
-
requestId: ctx.requestId,
|
|
767
|
-
traceId: ctx.traceId,
|
|
768
|
-
eventId: event.id,
|
|
769
|
-
eventType: event.type,
|
|
770
|
-
});
|
|
771
|
-
return paymentWebhookJson({
|
|
772
|
-
ok: false,
|
|
773
|
-
error: "Payment webhook handler failed.",
|
|
774
|
-
}, 500, headers);
|
|
775
|
-
}
|
|
910
|
+
return handlePaymentWebhook({ req: toContextRequest(req, rawBody), ctx });
|
|
776
911
|
},
|
|
777
912
|
};
|
|
778
913
|
}
|
|
@@ -796,8 +931,9 @@ export function createScheduleRoute(options) {
|
|
|
796
931
|
? `Unknown schedule "${options.schedule}". Available schedules: ${available.join(", ")}.`
|
|
797
932
|
: `Unknown schedule "${options.schedule}". No schedules were passed to createScheduleRoute(...).`);
|
|
798
933
|
}
|
|
799
|
-
const
|
|
800
|
-
const
|
|
934
|
+
const runSchedule = async ({ req, ctx, }) => {
|
|
935
|
+
const nativeReq = nativeRequestOf(req);
|
|
936
|
+
const headers = resolveHeaders(options.headers, nativeReq);
|
|
801
937
|
const secret = options.secret;
|
|
802
938
|
if (!secret) {
|
|
803
939
|
return cronRouteJson({
|
|
@@ -806,12 +942,10 @@ export function createScheduleRoute(options) {
|
|
|
806
942
|
scheduleName: schedule.name,
|
|
807
943
|
}, 500, headers);
|
|
808
944
|
}
|
|
809
|
-
if (!(await isAuthorizedCronRequest(
|
|
945
|
+
if (!(await isAuthorizedCronRequest(nativeReq, secret))) {
|
|
810
946
|
return cronRouteJson({ ok: false, error: "Unauthorized" }, 401, headers);
|
|
811
947
|
}
|
|
812
948
|
try {
|
|
813
|
-
const server = await resolveServerSource(options.server);
|
|
814
|
-
const ctx = await server.createRequestContext(toRequestLike(req));
|
|
815
949
|
const runner = createInlineScheduleRunner({
|
|
816
950
|
ctx,
|
|
817
951
|
instrumentation: resolveProviderInstrumentationPort(ctx.ports),
|
|
@@ -842,6 +976,48 @@ export function createScheduleRoute(options) {
|
|
|
842
976
|
}, 500, headers);
|
|
843
977
|
}
|
|
844
978
|
};
|
|
979
|
+
const pipelineInit = {
|
|
980
|
+
name: options.pipeline?.name ?? `schedule.${schedule.name}`,
|
|
981
|
+
path: options.pipeline?.path ?? `/schedules/${schedule.name}`,
|
|
982
|
+
metadata: options.pipeline?.metadata,
|
|
983
|
+
};
|
|
984
|
+
const pipelineRunners = {
|
|
985
|
+
GET: createRawPipelineRunner({ ...pipelineInit, method: "GET" }, runSchedule),
|
|
986
|
+
POST: createRawPipelineRunner({ ...pipelineInit, method: "POST" }, runSchedule),
|
|
987
|
+
};
|
|
988
|
+
const runScheduleFromRequest = async (req) => {
|
|
989
|
+
const server = await resolveServerSource(options.server);
|
|
990
|
+
const runner = pipelineRunners[req.method.toUpperCase() === "GET" ? "GET" : "POST"];
|
|
991
|
+
const pipelined = runner(server);
|
|
992
|
+
if (pipelined)
|
|
993
|
+
return pipelined(req);
|
|
994
|
+
// Minimal servers without rawRoute(...) — usually test fakes — keep the
|
|
995
|
+
// original flow: authorize before touching context, then run inline.
|
|
996
|
+
const headers = resolveHeaders(options.headers, req);
|
|
997
|
+
const secret = options.secret;
|
|
998
|
+
if (!secret) {
|
|
999
|
+
return cronRouteJson({
|
|
1000
|
+
ok: false,
|
|
1001
|
+
error: "CRON_SECRET is not configured.",
|
|
1002
|
+
scheduleName: schedule.name,
|
|
1003
|
+
}, 500, headers);
|
|
1004
|
+
}
|
|
1005
|
+
if (!(await isAuthorizedCronRequest(req, secret))) {
|
|
1006
|
+
return cronRouteJson({ ok: false, error: "Unauthorized" }, 401, headers);
|
|
1007
|
+
}
|
|
1008
|
+
let ctx;
|
|
1009
|
+
try {
|
|
1010
|
+
ctx = await server.createRequestContext(toRequestLike(req));
|
|
1011
|
+
}
|
|
1012
|
+
catch {
|
|
1013
|
+
return cronRouteJson({
|
|
1014
|
+
ok: false,
|
|
1015
|
+
error: "Schedule failed.",
|
|
1016
|
+
scheduleName: schedule.name,
|
|
1017
|
+
}, 500, headers);
|
|
1018
|
+
}
|
|
1019
|
+
return runSchedule({ req: toRequestLike(req), ctx });
|
|
1020
|
+
};
|
|
845
1021
|
return {
|
|
846
1022
|
GET: runScheduleFromRequest,
|
|
847
1023
|
POST: runScheduleFromRequest,
|
|
@@ -861,6 +1037,9 @@ export async function createNextServer(options) {
|
|
|
861
1037
|
route: (contract) => ({
|
|
862
1038
|
handle: (fn) => createFetchHandler(runtime.route(contract).handle(fn)),
|
|
863
1039
|
}),
|
|
1040
|
+
rawRoute: (init) => ({
|
|
1041
|
+
handle: (fn) => createFetchHandler(runtime.rawRoute(init).handle(fn)),
|
|
1042
|
+
}),
|
|
864
1043
|
createContextFromNext: async () => {
|
|
865
1044
|
// "next" ships no package exports map, so Node-style ESM resolution
|
|
866
1045
|
// (NodeNext) needs the explicit .js subpath. Bundlers resolve it the same.
|