@apifuse/provider-sdk 2.2.0-beta.12 → 2.2.0-beta.14
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/AUTHORING.md +271 -6
- package/CHANGELOG.md +16 -0
- package/README.md +26 -2
- package/bin/apifuse-pack-types.ts +30 -1
- package/bin/apifuse-record.ts +622 -57
- package/bin/apifuse-submit-check.ts +43 -10
- package/dist/define.d.ts +2 -1
- package/dist/define.js +61 -3
- package/dist/errors.d.ts +1 -0
- package/dist/errors.js +6 -0
- package/dist/fixture-sanitization.d.ts +26 -0
- package/dist/fixture-sanitization.js +216 -0
- package/dist/index.d.ts +3 -2
- package/dist/index.js +2 -1
- package/dist/provider.d.ts +2 -1
- package/dist/provider.js +1 -0
- package/dist/runtime/executor.js +17 -2
- package/dist/runtime/http.js +86 -32
- package/dist/runtime/instrumentation.js +295 -9
- package/dist/runtime/native-network.d.ts +53 -0
- package/dist/runtime/native-network.js +477 -0
- package/dist/runtime/proxy-nodemaven.d.ts +14 -0
- package/dist/runtime/proxy-nodemaven.js +20 -2
- package/dist/runtime/request-options.d.ts +68 -1
- package/dist/runtime/request-options.js +548 -0
- package/dist/runtime/stealth.d.ts +3 -1
- package/dist/runtime/stealth.js +239 -39
- package/dist/server/index.d.ts +2 -2
- package/dist/server/index.js +2 -2
- package/dist/server/self-test-input-tokens.d.ts +2 -1
- package/dist/server/self-test-input-tokens.js +18 -14
- package/dist/server/serve.d.ts +9 -0
- package/dist/server/serve.js +153 -51
- package/dist/server/types.d.ts +3 -0
- package/dist/server/types.js +1 -0
- package/dist/stateful/stateful-provider-owner-forwarder.js +9 -1
- package/dist/stream-evidence.d.ts +74 -0
- package/dist/stream-evidence.js +785 -0
- package/dist/testing/index.d.ts +1 -1
- package/dist/testing/index.js +1 -1
- package/dist/testing/run.d.ts +32 -2
- package/dist/testing/run.js +451 -19
- package/dist/types.d.ts +162 -0
- package/package.json +2 -1
- package/src/define.ts +81 -3
- package/src/errors.ts +9 -0
- package/src/fixture-sanitization.ts +247 -0
- package/src/index.ts +43 -1
- package/src/provider.ts +37 -0
- package/src/runtime/executor.ts +22 -2
- package/src/runtime/http.ts +144 -38
- package/src/runtime/instrumentation.ts +424 -8
- package/src/runtime/native-network.ts +600 -0
- package/src/runtime/proxy-nodemaven.ts +37 -2
- package/src/runtime/request-options.ts +680 -1
- package/src/runtime/stealth.ts +293 -40
- package/src/server/index.ts +6 -1
- package/src/server/self-test-input-tokens.ts +29 -14
- package/src/server/serve.ts +190 -68
- package/src/server/types.ts +1 -0
- package/src/stateful/stateful-provider-owner-forwarder.ts +9 -1
- package/src/stream-evidence.ts +988 -0
- package/src/testing/index.ts +9 -1
- package/src/testing/run.ts +608 -12
- package/src/types.ts +194 -0
package/src/server/serve.ts
CHANGED
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
isProviderError,
|
|
10
10
|
isSessionExpiredError,
|
|
11
11
|
isTransportError,
|
|
12
|
+
isValidationError,
|
|
12
13
|
ProviderError,
|
|
13
14
|
} from "../errors.js";
|
|
14
15
|
import {
|
|
@@ -108,6 +109,14 @@ import {
|
|
|
108
109
|
|
|
109
110
|
const DEFAULT_HOST = "0.0.0.0";
|
|
110
111
|
const DEFAULT_PORT = 3000;
|
|
112
|
+
/** Compact SDK-owned error classification emitted separately from the public response body. */
|
|
113
|
+
export const ERROR_OBSERVABILITY_HEADER = "X-ApiFuse-Error-Observability";
|
|
114
|
+
export type ErrorObservabilityDetails = {
|
|
115
|
+
category: ProviderErrorCategory;
|
|
116
|
+
taxonomyVersion: string;
|
|
117
|
+
retryable: boolean;
|
|
118
|
+
upstreamStatus?: number;
|
|
119
|
+
};
|
|
111
120
|
const AUTH_FLOW_LOCALES = ["en", "ko", "ja"] as const;
|
|
112
121
|
const retryResponseMeta = new WeakMap<ProviderContext, HttpRetrySummary>();
|
|
113
122
|
const STATEFUL_INTERNAL_OPERATIONS_ROUTE = "/__apifuse/stateful/operations";
|
|
@@ -486,6 +495,7 @@ export type ProviderServerLogEvent =
|
|
|
486
495
|
errorCategory?: ProviderErrorCategory;
|
|
487
496
|
taxonomyVersion?: string;
|
|
488
497
|
retryable?: boolean;
|
|
498
|
+
signal?: "unregistered_provider_error_code";
|
|
489
499
|
issues?: Array<{ path: string; code: string; message: string }>;
|
|
490
500
|
})
|
|
491
501
|
| {
|
|
@@ -604,26 +614,28 @@ function zodDetails(error: z.ZodError): Array<{
|
|
|
604
614
|
}
|
|
605
615
|
|
|
606
616
|
function toErrorResponse(error: unknown, requestId?: string): OperationErrorResponse {
|
|
617
|
+
const observability = errorObservabilityDetails(error);
|
|
607
618
|
if (error instanceof StatefulRoutingDeadlineError) {
|
|
608
619
|
return {
|
|
609
620
|
error: {
|
|
610
621
|
code: "STATEFUL_FORWARDING_DEADLINE_EXPIRED",
|
|
611
622
|
message: "Stateful forwarding deadline expired.",
|
|
612
623
|
...(requestId ? { requestId } : {}),
|
|
613
|
-
|
|
624
|
+
retryable: observability.retryable,
|
|
614
625
|
},
|
|
615
626
|
};
|
|
616
627
|
}
|
|
617
628
|
|
|
618
629
|
if (isProviderError(error)) {
|
|
619
|
-
const details =
|
|
630
|
+
const details = error.details;
|
|
620
631
|
return {
|
|
621
632
|
error: {
|
|
622
633
|
code: error.code ?? "provider_error",
|
|
623
634
|
message: publicProviderErrorMessage(error),
|
|
624
635
|
...(requestId ? { requestId } : {}),
|
|
636
|
+
retryable: observability.retryable,
|
|
625
637
|
...(error.fix ? { fix: error.fix } : {}),
|
|
626
|
-
...(details ? { details } : {}),
|
|
638
|
+
...(details !== undefined ? { details } : {}),
|
|
627
639
|
},
|
|
628
640
|
};
|
|
629
641
|
}
|
|
@@ -634,6 +646,7 @@ function toErrorResponse(error: unknown, requestId?: string): OperationErrorResp
|
|
|
634
646
|
code: "invalid_request",
|
|
635
647
|
message: "Invalid request body",
|
|
636
648
|
...(requestId ? { requestId } : {}),
|
|
649
|
+
retryable: observability.retryable,
|
|
637
650
|
details: zodDetails(error),
|
|
638
651
|
},
|
|
639
652
|
};
|
|
@@ -650,6 +663,7 @@ function toErrorResponse(error: unknown, requestId?: string): OperationErrorResp
|
|
|
650
663
|
code: "internal_error",
|
|
651
664
|
message: "Internal error",
|
|
652
665
|
...(requestId ? { requestId } : {}),
|
|
666
|
+
retryable: observability.retryable,
|
|
653
667
|
details: {
|
|
654
668
|
retryable: false,
|
|
655
669
|
category: "internal_error",
|
|
@@ -659,42 +673,12 @@ function toErrorResponse(error: unknown, requestId?: string): OperationErrorResp
|
|
|
659
673
|
};
|
|
660
674
|
}
|
|
661
675
|
|
|
662
|
-
function publicProviderErrorDetails(error: ProviderError): unknown {
|
|
663
|
-
const providerDetails = error.details;
|
|
664
|
-
const observabilityDetails = providerObservabilityDetails(error);
|
|
665
|
-
|
|
666
|
-
if (providerDetails === undefined) {
|
|
667
|
-
return observabilityDetails;
|
|
668
|
-
}
|
|
669
|
-
if (observabilityDetails === undefined) {
|
|
670
|
-
return providerDetails;
|
|
671
|
-
}
|
|
672
|
-
if (isPlainRecord(providerDetails) && isPlainRecord(observabilityDetails)) {
|
|
673
|
-
return { ...providerDetails, ...observabilityDetails };
|
|
674
|
-
}
|
|
675
|
-
return {
|
|
676
|
-
provider: providerDetails,
|
|
677
|
-
observability: observabilityDetails,
|
|
678
|
-
};
|
|
679
|
-
}
|
|
680
|
-
|
|
681
|
-
function isPlainRecord(value: unknown): value is Record<string, unknown> {
|
|
682
|
-
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
683
|
-
}
|
|
684
|
-
|
|
685
676
|
// Accepts `unknown` so the branded guards narrow cleanly from the top: the
|
|
686
677
|
// subtype error classes are structurally compatible with ProviderError, so
|
|
687
678
|
// narrowing from a ProviderError-typed value would collapse the negative branch
|
|
688
679
|
// to `never`. Narrowing from unknown avoids that while still recognizing errors
|
|
689
680
|
// from a duplicate SDK module instance.
|
|
690
|
-
function providerObservabilityDetails(error: unknown):
|
|
691
|
-
| {
|
|
692
|
-
category: ProviderErrorCategory;
|
|
693
|
-
taxonomyVersion: string;
|
|
694
|
-
retryable: boolean;
|
|
695
|
-
upstreamStatus?: number;
|
|
696
|
-
}
|
|
697
|
-
| undefined {
|
|
681
|
+
function providerObservabilityDetails(error: unknown): ErrorObservabilityDetails | undefined {
|
|
698
682
|
// Session-expiry surfaces the credential_expired category + the opt-in
|
|
699
683
|
// retryable signal so Gateway/Credential Service can refresh and re-drive the
|
|
700
684
|
// operation (see design.md §4.3 D3). Without this branch the auth error would
|
|
@@ -751,6 +735,54 @@ function providerObservabilityDetails(error: unknown):
|
|
|
751
735
|
};
|
|
752
736
|
}
|
|
753
737
|
|
|
738
|
+
function errorObservabilityDetails(error: unknown): ErrorObservabilityDetails {
|
|
739
|
+
const providerDetails = providerObservabilityDetails(error);
|
|
740
|
+
if (providerDetails) return providerDetails;
|
|
741
|
+
|
|
742
|
+
if (error instanceof z.ZodError || isValidationError(error)) {
|
|
743
|
+
return {
|
|
744
|
+
category:
|
|
745
|
+
isProviderError(error) && error.options?.category
|
|
746
|
+
? error.options.category
|
|
747
|
+
: "input_validation",
|
|
748
|
+
taxonomyVersion: PROVIDER_OBSERVABILITY_TAXONOMY_VERSION,
|
|
749
|
+
retryable: isProviderError(error) ? (error.options?.retryable ?? false) : false,
|
|
750
|
+
};
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
if (error instanceof StatefulRoutingDeadlineError) {
|
|
754
|
+
return {
|
|
755
|
+
category: "timeout",
|
|
756
|
+
taxonomyVersion: PROVIDER_OBSERVABILITY_TAXONOMY_VERSION,
|
|
757
|
+
retryable: false,
|
|
758
|
+
};
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
if (isProviderError(error)) {
|
|
762
|
+
return {
|
|
763
|
+
category: error.options?.category ?? "provider_error",
|
|
764
|
+
taxonomyVersion: PROVIDER_OBSERVABILITY_TAXONOMY_VERSION,
|
|
765
|
+
retryable: error.options?.retryable ?? false,
|
|
766
|
+
};
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
return {
|
|
770
|
+
category: "internal_error",
|
|
771
|
+
taxonomyVersion: PROVIDER_OBSERVABILITY_TAXONOMY_VERSION,
|
|
772
|
+
retryable: false,
|
|
773
|
+
};
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
function responseWithErrorObservability(response: Response, error: unknown): Response {
|
|
777
|
+
const headers = new Headers(response.headers);
|
|
778
|
+
headers.set(ERROR_OBSERVABILITY_HEADER, JSON.stringify(errorObservabilityDetails(error)));
|
|
779
|
+
return new Response(response.body, {
|
|
780
|
+
status: response.status,
|
|
781
|
+
statusText: response.statusText,
|
|
782
|
+
headers,
|
|
783
|
+
});
|
|
784
|
+
}
|
|
785
|
+
|
|
754
786
|
function publicProviderErrorMessage(error: ProviderError): string {
|
|
755
787
|
if (isTransportError(error)) {
|
|
756
788
|
if (error.code === PROXY_AUTH_IP_DENIED_CODE) {
|
|
@@ -782,11 +814,6 @@ function toStatusCode(error: unknown): 400 | 401 | 404 | 429 | 500 | 502 | 503 |
|
|
|
782
814
|
if (error instanceof StatefulRoutingDeadlineError) {
|
|
783
815
|
return 504;
|
|
784
816
|
}
|
|
785
|
-
|
|
786
|
-
if (isTransportError(error)) {
|
|
787
|
-
return error.code === "transport_timeout" ? 504 : 502;
|
|
788
|
-
}
|
|
789
|
-
|
|
790
817
|
if (isProviderError(error)) {
|
|
791
818
|
switch (error.code) {
|
|
792
819
|
case "AUTH_REQUIRED":
|
|
@@ -812,13 +839,91 @@ function toStatusCode(error: unknown): 400 | 401 | 404 | 429 | 500 | 502 | 503 |
|
|
|
812
839
|
case "STATEFUL_FORWARDING_REPLAY_CACHE_FULL":
|
|
813
840
|
return 503;
|
|
814
841
|
}
|
|
842
|
+
if (isTransportError(error)) {
|
|
843
|
+
return error.code === "transport_timeout" ? 504 : 502;
|
|
844
|
+
}
|
|
845
|
+
if (isValidationError(error)) {
|
|
846
|
+
return error.options?.category === "output_validation" ? 500 : 400;
|
|
847
|
+
}
|
|
815
848
|
|
|
816
|
-
return
|
|
849
|
+
return 500;
|
|
817
850
|
}
|
|
818
851
|
|
|
819
852
|
return 500;
|
|
820
853
|
}
|
|
821
854
|
|
|
855
|
+
// Codes emitted by SDK-owned paths must never be attributed to provider
|
|
856
|
+
// authors by the unregistered-code signal, even when their intentional status
|
|
857
|
+
// is 500. Provider-authored codes not in this registry retain the signal.
|
|
858
|
+
const SDK_OWNED_PROVIDER_ERROR_CODES = new Set([
|
|
859
|
+
"AUTH_PROMPT_UNAVAILABLE",
|
|
860
|
+
"BROWSER_CDP_POOL_REQUIRED",
|
|
861
|
+
"BROWSER_RUNTIME_UNSUPPORTED",
|
|
862
|
+
"STEALTH_RUNTIME_UNSUPPORTED",
|
|
863
|
+
"SSE_EVENT_UNDECLARED",
|
|
864
|
+
"STREAM_EVENT_TOO_LARGE",
|
|
865
|
+
"STREAM_CHUNK_TOO_LARGE",
|
|
866
|
+
"SSE_RESULT_UNSUPPORTED",
|
|
867
|
+
"STREAM_RESULT_UNSUPPORTED",
|
|
868
|
+
"AUTH_FLOW_NOT_CONFIGURED",
|
|
869
|
+
"refresh_not_supported",
|
|
870
|
+
"RUNTIME_UNSUPPORTED",
|
|
871
|
+
"PROVIDER_STATE_UNSUPPORTED",
|
|
872
|
+
"CHOICE_TOKEN_MASTER_SECRET_NOT_CONFIGURED",
|
|
873
|
+
"CHOICE_STATE_PAYLOAD_TOO_LARGE",
|
|
874
|
+
"CHOICE_STATE_UNAVAILABLE",
|
|
875
|
+
"CHOICE_CONTEXT_REQUIRED",
|
|
876
|
+
"unsupported_stealth_cookie_store_version",
|
|
877
|
+
"provider_secret_error",
|
|
878
|
+
"credential_key_error",
|
|
879
|
+
"credential_mode_error",
|
|
880
|
+
"flow_expired",
|
|
881
|
+
"turn_validation_error",
|
|
882
|
+
"context_access_error",
|
|
883
|
+
"UNSUPPORTED_STT_OPTION",
|
|
884
|
+
"INVALID_STT_AUDIO",
|
|
885
|
+
"STT_AUDIO_TOO_LARGE",
|
|
886
|
+
"STT_UPSTREAM_FAILED",
|
|
887
|
+
"INVALID_STT_VERIFICATION_CODE_OPTIONS",
|
|
888
|
+
"NO_CODE_FOUND",
|
|
889
|
+
"AMBIGUOUS_CODE",
|
|
890
|
+
"retry_invalid_policy",
|
|
891
|
+
"retry_unsafe_method",
|
|
892
|
+
"stealth_cookie_store_serialize_failed",
|
|
893
|
+
"response_too_large",
|
|
894
|
+
"transport_stream_unavailable",
|
|
895
|
+
"transport_invalid_method",
|
|
896
|
+
"http_transport_override_unsupported",
|
|
897
|
+
"transport_invalid_url",
|
|
898
|
+
"retry_exhausted",
|
|
899
|
+
"auth_abort_unsafe_data",
|
|
900
|
+
"credentials_auth_missing_credential_keys",
|
|
901
|
+
"credentials_auth_missing_credential",
|
|
902
|
+
"credentials_auth_invalid_login_result",
|
|
903
|
+
"credentials_auth_unknown_challenge",
|
|
904
|
+
"credentials_auth_unknown_pending_challenge",
|
|
905
|
+
"STATEFUL_FORWARDING_NOT_CONFIGURED",
|
|
906
|
+
"STATEFUL_FORWARDING_SIGNATURE_MISSING",
|
|
907
|
+
"STATEFUL_FORWARDING_NONCE_INVALID",
|
|
908
|
+
"STATEFUL_FORWARDING_TIMESTAMP_INVALID",
|
|
909
|
+
"STATEFUL_FORWARDING_SIGNATURE_INVALID",
|
|
910
|
+
"STATEFUL_FORWARDING_REPLAY_DETECTED",
|
|
911
|
+
"STATEFUL_FORWARDING_REPLAY_CACHE_FULL",
|
|
912
|
+
"STATEFUL_FORWARDING_ENVELOPE_INVALID",
|
|
913
|
+
"STATEFUL_FORWARDING_PROVIDER_MISMATCH",
|
|
914
|
+
"STATEFUL_FORWARDING_SOURCE_POD_MISMATCH",
|
|
915
|
+
"STATEFUL_FORWARDING_OWNER_FENCE_INVALID",
|
|
916
|
+
"STATEFUL_FORWARDING_REQUEST_FAILED",
|
|
917
|
+
"STATEFUL_FORWARDING_CONTEXT_MISSING",
|
|
918
|
+
"STATEFUL_FORWARDING_BAD_RESPONSE",
|
|
919
|
+
"STATEFUL_INTERNAL_EXECUTOR_NOT_CONFIGURED",
|
|
920
|
+
"STATEFUL_FILE_FORWARDING_UNSUPPORTED",
|
|
921
|
+
"STATEFUL_CONTROL_PLANE_OPERATION_AMBIGUOUS",
|
|
922
|
+
"STATEFUL_CONTROL_PLANE_REQUEST_FAILED",
|
|
923
|
+
"STATEFUL_CONTROL_PLANE_HTTP_ERROR",
|
|
924
|
+
"STATEFUL_CONTROL_PLANE_INVALID_RESPONSE",
|
|
925
|
+
]);
|
|
926
|
+
|
|
822
927
|
function extractRequestId(raw: unknown): string | undefined {
|
|
823
928
|
if (!raw || typeof raw !== "object") {
|
|
824
929
|
return undefined;
|
|
@@ -847,7 +952,13 @@ function logProviderError(
|
|
|
847
952
|
: "internal_error";
|
|
848
953
|
const errorClass = error instanceof Error ? error.name : typeof error;
|
|
849
954
|
const message = error instanceof Error ? error.message : String(error);
|
|
850
|
-
const details =
|
|
955
|
+
const details = errorObservabilityDetails(error);
|
|
956
|
+
const isUnregisteredProviderErrorCode =
|
|
957
|
+
status === 500 &&
|
|
958
|
+
isProviderError(error) &&
|
|
959
|
+
!isValidationError(error) &&
|
|
960
|
+
typeof error.code === "string" &&
|
|
961
|
+
!SDK_OWNED_PROVIDER_ERROR_CODES.has(error.code);
|
|
851
962
|
const emit = typeof logger === "function" ? logger : defaultProviderServerLogger;
|
|
852
963
|
emit({
|
|
853
964
|
level: status >= 500 ? "error" : "warn",
|
|
@@ -861,15 +972,12 @@ function logProviderError(
|
|
|
861
972
|
code,
|
|
862
973
|
errorClass,
|
|
863
974
|
message,
|
|
864
|
-
...(
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
taxonomyVersion: details.taxonomyVersion,
|
|
871
|
-
retryable: details.retryable,
|
|
872
|
-
}
|
|
975
|
+
...(details.upstreamStatus ? { upstreamStatus: details.upstreamStatus } : {}),
|
|
976
|
+
errorCategory: details.category,
|
|
977
|
+
taxonomyVersion: details.taxonomyVersion,
|
|
978
|
+
retryable: details.retryable,
|
|
979
|
+
...(isUnregisteredProviderErrorCode
|
|
980
|
+
? { signal: "unregistered_provider_error_code" as const }
|
|
873
981
|
: {}),
|
|
874
982
|
...(error instanceof z.ZodError ? { issues: zodDetails(error) } : {}),
|
|
875
983
|
});
|
|
@@ -1614,17 +1722,10 @@ export function createServerApp(
|
|
|
1614
1722
|
});
|
|
1615
1723
|
}
|
|
1616
1724
|
|
|
1617
|
-
app.notFound((c) =>
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
code: "not_found",
|
|
1622
|
-
message: "Not found",
|
|
1623
|
-
},
|
|
1624
|
-
},
|
|
1625
|
-
404,
|
|
1626
|
-
),
|
|
1627
|
-
);
|
|
1725
|
+
app.notFound((c) => {
|
|
1726
|
+
const error = new ProviderError("Not found", { code: "not_found", retryable: false });
|
|
1727
|
+
return responseWithErrorObservability(c.json(toErrorResponse(error), 404), error);
|
|
1728
|
+
});
|
|
1628
1729
|
|
|
1629
1730
|
app.get("/health", (c) =>
|
|
1630
1731
|
c.json({
|
|
@@ -1786,7 +1887,10 @@ export function createServerApp(
|
|
|
1786
1887
|
status,
|
|
1787
1888
|
finishRequestCost(requestCost),
|
|
1788
1889
|
);
|
|
1789
|
-
return
|
|
1890
|
+
return responseWithErrorObservability(
|
|
1891
|
+
c.json(toErrorResponse(error, requestId), status),
|
|
1892
|
+
error,
|
|
1893
|
+
);
|
|
1790
1894
|
}
|
|
1791
1895
|
});
|
|
1792
1896
|
|
|
@@ -1850,7 +1954,10 @@ export function createServerApp(
|
|
|
1850
1954
|
);
|
|
1851
1955
|
const telemetryHeader = proxyTelemetry.toHeaderValue();
|
|
1852
1956
|
if (telemetryHeader) c.header(PROVIDER_TELEMETRY_HEADER, telemetryHeader);
|
|
1853
|
-
return
|
|
1957
|
+
return responseWithErrorObservability(
|
|
1958
|
+
c.json(toErrorResponse(error, requestId), status),
|
|
1959
|
+
error,
|
|
1960
|
+
);
|
|
1854
1961
|
}
|
|
1855
1962
|
});
|
|
1856
1963
|
|
|
@@ -1887,7 +1994,10 @@ export function createServerApp(
|
|
|
1887
1994
|
status,
|
|
1888
1995
|
finishRequestCost(requestCost),
|
|
1889
1996
|
);
|
|
1890
|
-
return
|
|
1997
|
+
return responseWithErrorObservability(
|
|
1998
|
+
c.json(toErrorResponse(error, requestId), status),
|
|
1999
|
+
error,
|
|
2000
|
+
);
|
|
1891
2001
|
}
|
|
1892
2002
|
});
|
|
1893
2003
|
|
|
@@ -1924,7 +2034,10 @@ export function createServerApp(
|
|
|
1924
2034
|
status,
|
|
1925
2035
|
finishRequestCost(requestCost),
|
|
1926
2036
|
);
|
|
1927
|
-
return
|
|
2037
|
+
return responseWithErrorObservability(
|
|
2038
|
+
c.json(toErrorResponse(error, requestId), status),
|
|
2039
|
+
error,
|
|
2040
|
+
);
|
|
1928
2041
|
}
|
|
1929
2042
|
});
|
|
1930
2043
|
|
|
@@ -1961,7 +2074,10 @@ export function createServerApp(
|
|
|
1961
2074
|
status,
|
|
1962
2075
|
finishRequestCost(requestCost),
|
|
1963
2076
|
);
|
|
1964
|
-
return
|
|
2077
|
+
return responseWithErrorObservability(
|
|
2078
|
+
c.json(toErrorResponse(error, requestId), status),
|
|
2079
|
+
error,
|
|
2080
|
+
);
|
|
1965
2081
|
}
|
|
1966
2082
|
});
|
|
1967
2083
|
|
|
@@ -1998,7 +2114,10 @@ export function createServerApp(
|
|
|
1998
2114
|
status,
|
|
1999
2115
|
finishRequestCost(requestCost),
|
|
2000
2116
|
);
|
|
2001
|
-
return
|
|
2117
|
+
return responseWithErrorObservability(
|
|
2118
|
+
c.json(toErrorResponse(error, requestId), status),
|
|
2119
|
+
error,
|
|
2120
|
+
);
|
|
2002
2121
|
}
|
|
2003
2122
|
});
|
|
2004
2123
|
|
|
@@ -2035,7 +2154,10 @@ export function createServerApp(
|
|
|
2035
2154
|
status,
|
|
2036
2155
|
finishRequestCost(requestCost),
|
|
2037
2156
|
);
|
|
2038
|
-
return
|
|
2157
|
+
return responseWithErrorObservability(
|
|
2158
|
+
c.json(toErrorResponse(error, requestId), status),
|
|
2159
|
+
error,
|
|
2160
|
+
);
|
|
2039
2161
|
}
|
|
2040
2162
|
});
|
|
2041
2163
|
|
package/src/server/types.ts
CHANGED
|
@@ -35,6 +35,14 @@ type FetchTransport = (url: string | URL | Request, init?: RequestInit) => Promi
|
|
|
35
35
|
|
|
36
36
|
const MAX_FORWARDED_HEADERS = 32;
|
|
37
37
|
const MAX_FORWARDED_HEADER_BYTES = 8 * 1024;
|
|
38
|
+
// Inbound compatibility boundary for rolling deploys: older owner pods omit
|
|
39
|
+
// top-level retryable. Emitted responses remain strict via
|
|
40
|
+
// OperationErrorResponseSchema.
|
|
41
|
+
const ForwardedOperationErrorResponseSchema = OperationErrorResponseSchema.extend({
|
|
42
|
+
error: OperationErrorResponseSchema.shape.error.extend({
|
|
43
|
+
retryable: z.boolean().optional().default(false),
|
|
44
|
+
}),
|
|
45
|
+
});
|
|
38
46
|
const SENSITIVE_HEADER_NAMES = new Set([
|
|
39
47
|
"authorization",
|
|
40
48
|
"cookie",
|
|
@@ -253,7 +261,7 @@ async function parseForwardedResponse(response: Response): Promise<StatefulOpera
|
|
|
253
261
|
return { output: success.data.data };
|
|
254
262
|
}
|
|
255
263
|
|
|
256
|
-
const error =
|
|
264
|
+
const error = ForwardedOperationErrorResponseSchema.safeParse(body);
|
|
257
265
|
if (error.success) {
|
|
258
266
|
throw new StatefulOwnerForwardingError({
|
|
259
267
|
code: error.data.error.code,
|