@interfere/next 0.0.15-alpha.8 → 0.1.0-alpha.11
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/LICENSE +1 -1
- package/README.md +36 -8
- package/dist/_virtual/_rolldown/runtime.mjs +7 -0
- package/dist/build/env-config.d.mts +3 -1
- package/dist/build/env-config.d.mts.map +1 -1
- package/dist/build/env-config.mjs +9 -1
- package/dist/build/env-config.mjs.map +1 -1
- package/dist/build/exchange-surface.d.mts +9 -0
- package/dist/build/exchange-surface.d.mts.map +1 -0
- package/dist/build/exchange-surface.mjs +36 -0
- package/dist/build/exchange-surface.mjs.map +1 -0
- package/dist/build/loaders/value-injection-loader.d.mts +22 -0
- package/dist/build/loaders/value-injection-loader.d.mts.map +1 -0
- package/dist/build/loaders/value-injection-loader.mjs +35 -0
- package/dist/build/loaders/value-injection-loader.mjs.map +1 -0
- package/dist/build/logger.d.mts +2 -2
- package/dist/build/logger.d.mts.map +1 -1
- package/dist/build/logger.mjs +10 -12
- package/dist/build/logger.mjs.map +1 -1
- package/dist/build/nextjs-version.d.mts +40 -0
- package/dist/build/nextjs-version.d.mts.map +1 -0
- package/dist/build/nextjs-version.mjs +66 -0
- package/dist/build/nextjs-version.mjs.map +1 -0
- package/dist/build/release-program.d.mts +5 -3
- package/dist/build/release-program.d.mts.map +1 -1
- package/dist/build/release-program.mjs +11 -12
- package/dist/build/release-program.mjs.map +1 -1
- package/dist/build/services/config.service.d.mts.map +1 -1
- package/dist/build/services/config.service.mjs.map +1 -1
- package/dist/build/services/instrumentation-detection.service.d.mts +19 -0
- package/dist/build/services/instrumentation-detection.service.d.mts.map +1 -0
- package/dist/build/services/instrumentation-detection.service.mjs +77 -0
- package/dist/build/services/instrumentation-detection.service.mjs.map +1 -0
- package/dist/build/services/preflight.service.d.mts +2 -3
- package/dist/build/services/preflight.service.d.mts.map +1 -1
- package/dist/build/services/preflight.service.mjs +28 -37
- package/dist/build/services/preflight.service.mjs.map +1 -1
- package/dist/build/services/release-api.service.d.mts +11 -0
- package/dist/build/services/release-api.service.d.mts.map +1 -0
- package/dist/build/services/release-api.service.mjs +9 -0
- package/dist/build/services/release-api.service.mjs.map +1 -0
- package/dist/build/services/release-identity.service.d.mts +4 -3
- package/dist/build/services/release-identity.service.d.mts.map +1 -1
- package/dist/build/services/release-identity.service.mjs +28 -15
- package/dist/build/services/release-identity.service.mjs.map +1 -1
- package/dist/build/services/source-map-failure-cleanup.service.d.mts +11 -0
- package/dist/build/services/source-map-failure-cleanup.service.d.mts.map +1 -0
- package/dist/build/services/source-map-failure-cleanup.service.mjs +9 -0
- package/dist/build/services/source-map-failure-cleanup.service.mjs.map +1 -0
- package/dist/build/services/source-map.service.d.mts +4 -8
- package/dist/build/services/source-map.service.d.mts.map +1 -1
- package/dist/build/services/source-map.service.mjs +2 -4
- package/dist/build/services/source-map.service.mjs.map +1 -1
- package/dist/build/source-maps/api.d.mts +25 -16
- package/dist/build/source-maps/api.d.mts.map +1 -1
- package/dist/build/source-maps/api.mjs +11 -8
- package/dist/build/source-maps/api.mjs.map +1 -1
- package/dist/build/source-maps/client.d.mts +11 -2
- package/dist/build/source-maps/client.d.mts.map +1 -1
- package/dist/build/source-maps/client.mjs +25 -14
- package/dist/build/source-maps/client.mjs.map +1 -1
- package/dist/build/source-maps/errors.d.mts +118 -106
- package/dist/build/source-maps/errors.d.mts.map +1 -1
- package/dist/build/source-maps/errors.mjs +42 -18
- package/dist/build/source-maps/errors.mjs.map +1 -1
- package/dist/build/source-maps/files.d.mts +1 -1
- package/dist/build/source-maps/files.d.mts.map +1 -1
- package/dist/build/source-maps/files.mjs +13 -15
- package/dist/build/source-maps/files.mjs.map +1 -1
- package/dist/build/source-maps/providers/deployment/detector.d.mts +8 -17
- package/dist/build/source-maps/providers/deployment/detector.d.mts.map +1 -1
- package/dist/build/source-maps/providers/deployment/detector.mjs +11 -13
- package/dist/build/source-maps/providers/deployment/detector.mjs.map +1 -1
- package/dist/build/source-maps/providers/deployment/types.d.mts +2 -2
- package/dist/build/source-maps/providers/deployment/types.d.mts.map +1 -1
- package/dist/build/source-maps/providers/deployment/vercel.d.mts.map +1 -1
- package/dist/build/source-maps/providers/deployment/vercel.mjs +8 -19
- package/dist/build/source-maps/providers/deployment/vercel.mjs.map +1 -1
- package/dist/build/source-maps/providers/source-control/detector.d.mts +6 -5
- package/dist/build/source-maps/providers/source-control/detector.d.mts.map +1 -1
- package/dist/build/source-maps/providers/source-control/detector.mjs +11 -13
- package/dist/build/source-maps/providers/source-control/detector.mjs.map +1 -1
- package/dist/build/source-maps/providers/source-control/git.d.mts.map +1 -1
- package/dist/build/source-maps/providers/source-control/git.mjs +5 -8
- package/dist/build/source-maps/providers/source-control/git.mjs.map +1 -1
- package/dist/build/source-maps/providers/source-control/types.d.mts +5 -3
- package/dist/build/source-maps/providers/source-control/types.d.mts.map +1 -1
- package/dist/build/with-interfere.d.mts +25 -3
- package/dist/build/with-interfere.d.mts.map +1 -1
- package/dist/build/with-interfere.mjs +131 -24
- package/dist/build/with-interfere.mjs.map +1 -1
- package/dist/client/auto-init.d.mts +91 -0
- package/dist/client/auto-init.d.mts.map +1 -0
- package/dist/client/auto-init.mjs +121 -0
- package/dist/client/auto-init.mjs.map +1 -0
- package/dist/client/provider.d.mts +3 -3
- package/dist/client/provider.d.mts.map +1 -1
- package/dist/client/provider.mjs +21 -8
- package/dist/client/provider.mjs.map +1 -1
- package/dist/lib/env.d.mts.map +1 -1
- package/dist/lib/types.d.mts +6 -6
- package/dist/lib/types.d.mts.map +1 -1
- package/dist/lib/types.mjs.map +1 -1
- package/dist/server/auto-init.d.mts +88 -0
- package/dist/server/auto-init.d.mts.map +1 -0
- package/dist/server/auto-init.mjs +101 -0
- package/dist/server/auto-init.mjs.map +1 -0
- package/dist/server/middleware.d.mts.map +1 -1
- package/dist/server/middleware.mjs +20 -13
- package/dist/server/middleware.mjs.map +1 -1
- package/dist/server/on-request-error.d.mts +27 -0
- package/dist/server/on-request-error.d.mts.map +1 -0
- package/dist/server/on-request-error.mjs +74 -0
- package/dist/server/on-request-error.mjs.map +1 -0
- package/dist/server/proxy.d.mts.map +1 -1
- package/dist/server/proxy.mjs +4 -5
- package/dist/server/proxy.mjs.map +1 -1
- package/dist/server/route-handler.d.mts +31 -1
- package/dist/server/route-handler.d.mts.map +1 -1
- package/dist/server/route-handler.mjs +78 -79
- package/dist/server/route-handler.mjs.map +1 -1
- package/dist/server/sdk.d.mts +96 -0
- package/dist/server/sdk.d.mts.map +1 -0
- package/dist/server/sdk.mjs +152 -0
- package/dist/server/sdk.mjs.map +1 -0
- package/dist/server/services/config.service.d.mts +33 -6
- package/dist/server/services/config.service.d.mts.map +1 -1
- package/dist/server/services/config.service.mjs +54 -30
- package/dist/server/services/config.service.mjs.map +1 -1
- package/dist/server/services/error-tracking.service.d.mts +3 -3
- package/dist/server/services/error-tracking.service.d.mts.map +1 -1
- package/dist/server/services/error-tracking.service.mjs +5 -3
- package/dist/server/services/error-tracking.service.mjs.map +1 -1
- package/dist/server/session-context.d.mts +60 -0
- package/dist/server/session-context.d.mts.map +1 -0
- package/dist/server/session-context.mjs +62 -0
- package/dist/server/session-context.mjs.map +1 -0
- package/package.json +58 -34
- package/dist/build/secret-key.d.mts +0 -10
- package/dist/build/secret-key.d.mts.map +0 -1
- package/dist/build/secret-key.mjs +0 -16
- package/dist/build/secret-key.mjs.map +0 -1
- package/dist/lib/test-utils/make-next-request.d.mts +0 -6
- package/dist/lib/test-utils/make-next-request.d.mts.map +0 -1
- package/dist/lib/test-utils/make-next-request.mjs +0 -12
- package/dist/lib/test-utils/make-next-request.mjs.map +0 -1
|
@@ -1,22 +1,21 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { NextConfigLive } from "./services/config.service.mjs";
|
|
2
2
|
import { ErrorTrackingService, ErrorTrackingServiceLive } from "./services/error-tracking.service.mjs";
|
|
3
3
|
import { withInterfereLogger } from "@interfere/effect-utils/observability";
|
|
4
4
|
import { Data, Effect, Layer } from "effect";
|
|
5
|
-
import { withSpan } from "@interfere/react/effect/layers/tracer.layer";
|
|
6
5
|
|
|
7
6
|
//#region src/server/middleware.ts
|
|
8
7
|
var MiddlewareError = class extends Data.TaggedError("MiddlewareError") {};
|
|
9
8
|
function withInterfereMiddleware(middleware) {
|
|
10
|
-
const layer = Layer.mergeAll(
|
|
9
|
+
const layer = Layer.mergeAll(NextConfigLive, ErrorTrackingServiceLive);
|
|
11
10
|
return async (request) => {
|
|
12
11
|
if (request.nextUrl.pathname.startsWith("/api/interfere")) return middleware(request);
|
|
13
12
|
const startTime = Date.now();
|
|
14
13
|
const program = Effect.gen(function* () {
|
|
15
14
|
const errorTracking = yield* ErrorTrackingService;
|
|
16
|
-
return yield*
|
|
15
|
+
return yield* Effect.tryPromise({
|
|
17
16
|
try: () => Promise.resolve(middleware(request)),
|
|
18
17
|
catch: (cause) => new MiddlewareError({ cause })
|
|
19
|
-
}), { attributes: { path: request.nextUrl.pathname } })
|
|
18
|
+
}).pipe(Effect.annotateLogs({ path: request.nextUrl.pathname }), Effect.withSpan("middleware.request", { attributes: { path: request.nextUrl.pathname } }), Effect.tapError((error) => errorTracking.captureError(error.cause instanceof Error ? error.cause : new Error(String(error.cause)), request, {
|
|
20
19
|
pathname: request.nextUrl.pathname,
|
|
21
20
|
middleware: true,
|
|
22
21
|
duration: Date.now() - startTime
|
|
@@ -26,16 +25,16 @@ function withInterfereMiddleware(middleware) {
|
|
|
26
25
|
};
|
|
27
26
|
}
|
|
28
27
|
function withInterfereApiRoute(handler) {
|
|
29
|
-
const layer = Layer.mergeAll(
|
|
28
|
+
const layer = Layer.mergeAll(NextConfigLive, ErrorTrackingServiceLive);
|
|
30
29
|
return async (req) => {
|
|
31
30
|
if (req.nextUrl.pathname.startsWith("/api/interfere")) return handler(req);
|
|
32
31
|
const startTime = Date.now();
|
|
33
32
|
const program = Effect.gen(function* () {
|
|
34
33
|
const errorTracking = yield* ErrorTrackingService;
|
|
35
|
-
return yield*
|
|
34
|
+
return yield* Effect.tryPromise({
|
|
36
35
|
try: () => handler(req),
|
|
37
36
|
catch: (cause) => new MiddlewareError({ cause })
|
|
38
|
-
}), { attributes: { path: req.nextUrl.pathname } })
|
|
37
|
+
}).pipe(Effect.annotateLogs({ path: req.nextUrl.pathname }), Effect.withSpan("api.request", { attributes: { path: req.nextUrl.pathname } }), Effect.tapError((error) => errorTracking.captureError(error.cause instanceof Error ? error.cause : new Error(String(error.cause)), req, {
|
|
39
38
|
pathname: req.nextUrl.pathname,
|
|
40
39
|
type: "api_route",
|
|
41
40
|
duration: Date.now() - startTime
|
|
@@ -45,20 +44,28 @@ function withInterfereApiRoute(handler) {
|
|
|
45
44
|
};
|
|
46
45
|
}
|
|
47
46
|
function withInterfereServerComponent(component, componentName) {
|
|
48
|
-
const layer = Layer.mergeAll(
|
|
47
|
+
const layer = Layer.mergeAll(NextConfigLive, ErrorTrackingServiceLive);
|
|
49
48
|
async function wrapped(...args) {
|
|
50
49
|
const self = this;
|
|
50
|
+
let originalError = null;
|
|
51
51
|
const program = Effect.gen(function* () {
|
|
52
52
|
const errorTracking = yield* ErrorTrackingService;
|
|
53
53
|
return yield* Effect.tryPromise({
|
|
54
54
|
try: () => component.apply(self, args),
|
|
55
|
-
catch: (cause) =>
|
|
55
|
+
catch: (cause) => {
|
|
56
|
+
originalError = cause;
|
|
57
|
+
return new MiddlewareError({ cause });
|
|
58
|
+
}
|
|
56
59
|
}).pipe(Effect.tapError((error) => errorTracking.captureError(error.cause instanceof Error ? error.cause : new Error(String(error.cause)), void 0, {
|
|
57
60
|
type: "server_component",
|
|
58
61
|
componentName: componentName || component.name
|
|
59
62
|
})));
|
|
60
63
|
}).pipe(Effect.provide(layer));
|
|
61
|
-
|
|
64
|
+
try {
|
|
65
|
+
return await Effect.runPromise(withInterfereLogger("next", program));
|
|
66
|
+
} catch (error) {
|
|
67
|
+
throw originalError ?? error;
|
|
68
|
+
}
|
|
62
69
|
}
|
|
63
70
|
try {
|
|
64
71
|
Object.defineProperty(wrapped, "name", {
|
|
@@ -69,14 +76,14 @@ function withInterfereServerComponent(component, componentName) {
|
|
|
69
76
|
return wrapped;
|
|
70
77
|
}
|
|
71
78
|
function createInterfereErrorHandler() {
|
|
72
|
-
const layer = Layer.mergeAll(
|
|
79
|
+
const layer = Layer.mergeAll(NextConfigLive, ErrorTrackingServiceLive);
|
|
73
80
|
return async (error, errorInfo) => {
|
|
74
81
|
const program = Effect.gen(function* () {
|
|
75
82
|
yield* (yield* ErrorTrackingService).captureError(error, void 0, {
|
|
76
83
|
digest: errorInfo.digest,
|
|
77
84
|
type: "app_directory_error"
|
|
78
85
|
});
|
|
79
|
-
}).pipe(Effect.provide(layer)
|
|
86
|
+
}).pipe(Effect.provide(layer));
|
|
80
87
|
await Effect.runPromise(withInterfereLogger("next", program));
|
|
81
88
|
};
|
|
82
89
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"middleware.mjs","names":[],"sources":["../../src/server/middleware.ts"],"sourcesContent":["import { withInterfereLogger } from \"@interfere/effect-utils/observability\";\
|
|
1
|
+
{"version":3,"file":"middleware.mjs","names":[],"sources":["../../src/server/middleware.ts"],"sourcesContent":["import { withInterfereLogger } from \"@interfere/effect-utils/observability\";\n\nimport { Data, Effect, Layer } from \"effect\";\nimport type { NextRequest, NextResponse } from \"next/server\";\n\nclass MiddlewareError extends Data.TaggedError(\"MiddlewareError\")<{\n cause: unknown;\n}> {}\n\nimport { NextConfigLive } from \"./services/config.service\";\nimport {\n ErrorTrackingService,\n ErrorTrackingServiceLive,\n} from \"./services/error-tracking.service\";\n\n// Middleware wrapper to capture errors with proper Effect handling\nexport function withInterfereMiddleware(\n middleware: (request: NextRequest) => Promise<NextResponse> | NextResponse\n) {\n const layer = Layer.mergeAll(NextConfigLive, ErrorTrackingServiceLive);\n\n return async (request: NextRequest) => {\n // Skip capturing for Interfere's own endpoint to avoid recursion\n if (request.nextUrl.pathname.startsWith(\"/api/interfere\")) {\n return middleware(request);\n }\n\n const startTime = Date.now();\n\n const program = Effect.gen(function* () {\n const errorTracking = yield* ErrorTrackingService;\n\n const response = yield* Effect.tryPromise({\n try: () => Promise.resolve(middleware(request)),\n catch: (cause) => new MiddlewareError({ cause }),\n }).pipe(\n Effect.annotateLogs({ path: request.nextUrl.pathname }),\n Effect.withSpan(\"middleware.request\", {\n attributes: { path: request.nextUrl.pathname },\n }),\n Effect.tapError((error) =>\n errorTracking.captureError(\n error.cause instanceof Error\n ? error.cause\n : new Error(String(error.cause)),\n request,\n {\n pathname: request.nextUrl.pathname,\n middleware: true,\n duration: Date.now() - startTime,\n }\n )\n )\n );\n\n return response;\n }).pipe(Effect.provide(layer));\n\n return await Effect.runPromise(withInterfereLogger(\"next\", program));\n };\n}\n\n// Helper for API route error handling with Effect\nexport function withInterfereApiRoute(\n handler: (req: NextRequest) => Promise<Response>\n) {\n const layer = Layer.mergeAll(NextConfigLive, ErrorTrackingServiceLive);\n\n return async (req: NextRequest) => {\n // Skip capturing for Interfere's own endpoint to avoid recursion\n if (req.nextUrl.pathname.startsWith(\"/api/interfere\")) {\n return handler(req);\n }\n\n const startTime = Date.now();\n\n const program = Effect.gen(function* () {\n const errorTracking = yield* ErrorTrackingService;\n\n const response = yield* Effect.tryPromise({\n try: () => handler(req),\n catch: (cause) => new MiddlewareError({ cause }),\n }).pipe(\n Effect.annotateLogs({ path: req.nextUrl.pathname }),\n Effect.withSpan(\"api.request\", {\n attributes: { path: req.nextUrl.pathname },\n }),\n Effect.tapError((error) =>\n errorTracking.captureError(\n error.cause instanceof Error\n ? error.cause\n : new Error(String(error.cause)),\n req,\n {\n pathname: req.nextUrl.pathname,\n type: \"api_route\",\n duration: Date.now() - startTime,\n }\n )\n )\n );\n\n return response;\n }).pipe(Effect.provide(layer));\n\n return await Effect.runPromise(withInterfereLogger(\"next\", program));\n };\n}\n\n// Helper for server component error handling with Effect\nexport function withInterfereServerComponent<\n T extends (...args: unknown[]) => Promise<unknown>,\n>(component: T, componentName?: string): T {\n const layer = Layer.mergeAll(NextConfigLive, ErrorTrackingServiceLive);\n\n async function wrapped(this: unknown, ...args: Parameters<T>) {\n const self = this;\n\n // Track the original error so we can re-throw it cleanly\n let originalError: unknown = null;\n\n const program = Effect.gen(function* () {\n const errorTracking = yield* ErrorTrackingService;\n\n const result = yield* Effect.tryPromise({\n try: () => component.apply(self as never, args as never),\n catch: (cause) => {\n // Store the original error before wrapping\n originalError = cause;\n return new MiddlewareError({ cause });\n },\n }).pipe(\n Effect.tapError((error) =>\n errorTracking.captureError(\n error.cause instanceof Error\n ? error.cause\n : new Error(String(error.cause)),\n undefined,\n {\n type: \"server_component\",\n componentName: componentName || component.name,\n }\n )\n )\n );\n\n return result;\n }).pipe(Effect.provide(layer));\n\n try {\n return await Effect.runPromise(withInterfereLogger(\"next\", program));\n } catch (error) {\n // Re-throw the original error so Next.js sees it cleanly\n // instead of the FiberFailure/MiddlewareError wrapper.\n // If originalError is null, it means the error occurred before the component\n // execution (e.g., during service initialization), so re-throw the actual error.\n throw originalError ?? error;\n }\n }\n\n // Preserve the original function name for diagnostics and tests\n try {\n Object.defineProperty(wrapped, \"name\", {\n value: component.name,\n configurable: true,\n });\n } catch {\n // Non-critical if we cannot redefine the name\n }\n\n return wrapped as unknown as T;\n}\n\n// App directory error handling with Effect\nexport function createInterfereErrorHandler() {\n const layer = Layer.mergeAll(NextConfigLive, ErrorTrackingServiceLive);\n\n return async (error: Error, errorInfo: { digest?: string }) => {\n const program = Effect.gen(function* () {\n const errorTracking = yield* ErrorTrackingService;\n\n yield* errorTracking.captureError(error, undefined, {\n digest: errorInfo.digest,\n type: \"app_directory_error\",\n });\n }).pipe(Effect.provide(layer));\n\n await Effect.runPromise(withInterfereLogger(\"next\", program));\n };\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
//#region src/server/on-request-error.d.ts
|
|
2
|
+
interface NextRequestErrorContext {
|
|
3
|
+
renderSource?: "react-server-components" | "react-server-components-payload" | "server-rendering";
|
|
4
|
+
renderType?: "dynamic" | "dynamic-resume";
|
|
5
|
+
revalidateReason?: "on-demand" | "stale";
|
|
6
|
+
routePath: string;
|
|
7
|
+
routerKind: "Pages Router" | "App Router";
|
|
8
|
+
routeType: "render" | "route" | "action" | "middleware" | "proxy";
|
|
9
|
+
}
|
|
10
|
+
interface NextRequestErrorRequest {
|
|
11
|
+
headers: Record<string, string | string[] | undefined>;
|
|
12
|
+
method: string;
|
|
13
|
+
path: string;
|
|
14
|
+
}
|
|
15
|
+
type NextRequestError = Error & {
|
|
16
|
+
digest?: string;
|
|
17
|
+
};
|
|
18
|
+
interface CaptureRequestErrorParams {
|
|
19
|
+
context: NextRequestErrorContext;
|
|
20
|
+
error: NextRequestError;
|
|
21
|
+
request: NextRequestErrorRequest;
|
|
22
|
+
}
|
|
23
|
+
declare function captureRequestError(params: CaptureRequestErrorParams): Promise<void>;
|
|
24
|
+
declare function isErrorAlreadyCaptured(error: unknown): boolean;
|
|
25
|
+
declare function markErrorAsCaptured(error: unknown): void;
|
|
26
|
+
//#endregion
|
|
27
|
+
export { CaptureRequestErrorParams, NextRequestError, NextRequestErrorContext, NextRequestErrorRequest, captureRequestError, isErrorAlreadyCaptured, markErrorAsCaptured };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"on-request-error.d.mts","names":[],"sources":["../../src/server/on-request-error.ts"],"mappings":";UAgBiB,uBAAA;EACf,YAAA;EAIA,UAAA;EACA,gBAAA;EACA,SAAA;EACA,UAAA;EACA,SAAA;AAAA;AAAA,UAGe,uBAAA;EACf,OAAA,EAAS,MAAA;EACT,MAAA;EACA,IAAA;AAAA;AAAA,KAGU,gBAAA,GAAmB,KAAA;EAAU,MAAA;AAAA;AAAA,UAExB,yBAAA;EACf,OAAA,EAAS,uBAAA;EACT,KAAA,EAAO,gBAAA;EACP,OAAA,EAAS,uBAAA;AAAA;AAAA,iBAgDW,mBAAA,CACpB,MAAA,EAAQ,yBAAA,GACP,OAAA;AAAA,iBAmDa,sBAAA,CAAuB,KAAA;AAAA,iBAIvB,mBAAA,CAAoB,KAAA"}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { NextConfigLive } from "./services/config.service.mjs";
|
|
2
|
+
import { createSessionContextFromRequest, runWithSessionContext } from "./session-context.mjs";
|
|
3
|
+
import { ErrorTrackingService, ErrorTrackingServiceLive } from "./services/error-tracking.service.mjs";
|
|
4
|
+
import { withInterfereLogger } from "@interfere/effect-utils/observability";
|
|
5
|
+
import { Effect, Layer } from "effect";
|
|
6
|
+
|
|
7
|
+
//#region src/server/on-request-error.ts
|
|
8
|
+
const CAPTURED_SYMBOL = Symbol.for("interfere.captured");
|
|
9
|
+
const errorTrackingLayer = Layer.mergeAll(NextConfigLive, ErrorTrackingServiceLive);
|
|
10
|
+
const CAPTURE_TIMEOUT_MS = 5e3;
|
|
11
|
+
function normalizeHeaders(headers) {
|
|
12
|
+
const normalized = {};
|
|
13
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
14
|
+
if (value === void 0) continue;
|
|
15
|
+
normalized[key] = Array.isArray(value) ? value.join(", ") : value;
|
|
16
|
+
}
|
|
17
|
+
return normalized;
|
|
18
|
+
}
|
|
19
|
+
function isErrorCaptured(error) {
|
|
20
|
+
if (typeof error !== "object" || error === null) return false;
|
|
21
|
+
return CAPTURED_SYMBOL in error;
|
|
22
|
+
}
|
|
23
|
+
function markErrorCaptured(error) {
|
|
24
|
+
if (typeof error !== "object" || error === null) return;
|
|
25
|
+
try {
|
|
26
|
+
Object.defineProperty(error, CAPTURED_SYMBOL, {
|
|
27
|
+
value: true,
|
|
28
|
+
enumerable: false,
|
|
29
|
+
writable: false,
|
|
30
|
+
configurable: false
|
|
31
|
+
});
|
|
32
|
+
} catch {}
|
|
33
|
+
}
|
|
34
|
+
async function captureRequestError(params) {
|
|
35
|
+
const { error, request, context } = params;
|
|
36
|
+
if (isErrorCaptured(error)) return;
|
|
37
|
+
markErrorCaptured(error);
|
|
38
|
+
const pseudoRequest = new Request(new URL(request.path, "http://localhost"), {
|
|
39
|
+
method: request.method,
|
|
40
|
+
headers: normalizeHeaders(request.headers)
|
|
41
|
+
});
|
|
42
|
+
const sessionContext = createSessionContextFromRequest(pseudoRequest);
|
|
43
|
+
const program = Effect.gen(function* () {
|
|
44
|
+
const errorTracking = yield* ErrorTrackingService;
|
|
45
|
+
const enrichedContext = {
|
|
46
|
+
routerKind: context.routerKind,
|
|
47
|
+
routePath: context.routePath,
|
|
48
|
+
routeType: context.routeType,
|
|
49
|
+
...context.renderSource && { renderSource: context.renderSource },
|
|
50
|
+
...context.revalidateReason && { revalidateReason: context.revalidateReason },
|
|
51
|
+
...context.renderType && { renderType: context.renderType },
|
|
52
|
+
requestPath: request.path,
|
|
53
|
+
requestMethod: request.method,
|
|
54
|
+
...error.digest && { errorDigest: error.digest },
|
|
55
|
+
mechanism: {
|
|
56
|
+
type: "onRequestError",
|
|
57
|
+
handled: false,
|
|
58
|
+
synthetic: false
|
|
59
|
+
},
|
|
60
|
+
captureSource: "on_request_error"
|
|
61
|
+
};
|
|
62
|
+
yield* errorTracking.captureError(error, pseudoRequest, enrichedContext);
|
|
63
|
+
}).pipe(Effect.provide(errorTrackingLayer), Effect.timeout(CAPTURE_TIMEOUT_MS), Effect.catchAll(() => Effect.void));
|
|
64
|
+
await runWithSessionContext(sessionContext, () => Effect.runPromise(withInterfereLogger("next-server", program)).catch(() => {}));
|
|
65
|
+
}
|
|
66
|
+
function isErrorAlreadyCaptured(error) {
|
|
67
|
+
return isErrorCaptured(error);
|
|
68
|
+
}
|
|
69
|
+
function markErrorAsCaptured(error) {
|
|
70
|
+
markErrorCaptured(error);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
//#endregion
|
|
74
|
+
export { captureRequestError, isErrorAlreadyCaptured, markErrorAsCaptured };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"on-request-error.mjs","names":[],"sources":["../../src/server/on-request-error.ts"],"sourcesContent":["import { withInterfereLogger } from \"@interfere/effect-utils/observability\";\n\nimport { Effect, Layer } from \"effect\";\n\nimport { NextConfigLive } from \"./services/config.service.js\";\nimport {\n ErrorTrackingService,\n ErrorTrackingServiceLive,\n} from \"./services/error-tracking.service.js\";\nimport {\n createSessionContextFromRequest,\n runWithSessionContext,\n} from \"./session-context.js\";\n\nconst CAPTURED_SYMBOL = Symbol.for(\"interfere.captured\");\n\nexport interface NextRequestErrorContext {\n renderSource?:\n | \"react-server-components\"\n | \"react-server-components-payload\"\n | \"server-rendering\";\n renderType?: \"dynamic\" | \"dynamic-resume\";\n revalidateReason?: \"on-demand\" | \"stale\";\n routePath: string;\n routerKind: \"Pages Router\" | \"App Router\";\n routeType: \"render\" | \"route\" | \"action\" | \"middleware\" | \"proxy\";\n}\n\nexport interface NextRequestErrorRequest {\n headers: Record<string, string | string[] | undefined>;\n method: string;\n path: string;\n}\n\nexport type NextRequestError = Error & { digest?: string };\n\nexport interface CaptureRequestErrorParams {\n context: NextRequestErrorContext;\n error: NextRequestError;\n request: NextRequestErrorRequest;\n}\n\nconst errorTrackingLayer = Layer.mergeAll(\n NextConfigLive,\n ErrorTrackingServiceLive\n);\n\nconst CAPTURE_TIMEOUT_MS = 5000;\n\nfunction normalizeHeaders(\n headers: Record<string, string | string[] | undefined>\n): Record<string, string> {\n const normalized: Record<string, string> = {};\n\n for (const [key, value] of Object.entries(headers)) {\n if (value === undefined) {\n continue;\n }\n normalized[key] = Array.isArray(value) ? value.join(\", \") : value;\n }\n\n return normalized;\n}\n\nfunction isErrorCaptured(error: unknown): boolean {\n if (typeof error !== \"object\" || error === null) {\n return false;\n }\n return CAPTURED_SYMBOL in error;\n}\n\nfunction markErrorCaptured(error: unknown): void {\n if (typeof error !== \"object\" || error === null) {\n return;\n }\n try {\n Object.defineProperty(error, CAPTURED_SYMBOL, {\n value: true,\n enumerable: false,\n writable: false,\n configurable: false,\n });\n } catch {\n /* frozen objects */\n }\n}\n\nexport async function captureRequestError(\n params: CaptureRequestErrorParams\n): Promise<void> {\n const { error, request, context } = params;\n\n if (isErrorCaptured(error)) {\n return;\n }\n markErrorCaptured(error);\n\n // Create pseudo Request object for session context parsing\n const pseudoRequest = new Request(new URL(request.path, \"http://localhost\"), {\n method: request.method,\n headers: normalizeHeaders(request.headers),\n });\n\n // Extract session context from request headers\n const sessionContext = createSessionContextFromRequest(pseudoRequest);\n\n const program = Effect.gen(function* () {\n const errorTracking = yield* ErrorTrackingService;\n\n const enrichedContext: Record<string, unknown> = {\n routerKind: context.routerKind,\n routePath: context.routePath,\n routeType: context.routeType,\n ...(context.renderSource && { renderSource: context.renderSource }),\n ...(context.revalidateReason && {\n revalidateReason: context.revalidateReason,\n }),\n ...(context.renderType && { renderType: context.renderType }),\n requestPath: request.path,\n requestMethod: request.method,\n ...(error.digest && { errorDigest: error.digest }),\n mechanism: { type: \"onRequestError\", handled: false, synthetic: false },\n captureSource: \"on_request_error\",\n };\n\n yield* errorTracking.captureError(error, pseudoRequest, enrichedContext);\n }).pipe(\n Effect.provide(errorTrackingLayer),\n Effect.timeout(CAPTURE_TIMEOUT_MS),\n Effect.catchAll(() => Effect.void)\n );\n\n // Run the program within session context for AsyncLocalStorage propagation\n await runWithSessionContext(sessionContext, () =>\n Effect.runPromise(withInterfereLogger(\"next-server\", program)).catch(() => {\n /* best-effort */\n })\n );\n}\n\nexport function isErrorAlreadyCaptured(error: unknown): boolean {\n return isErrorCaptured(error);\n}\n\nexport function markErrorAsCaptured(error: unknown): void {\n markErrorCaptured(error);\n}\n"],"mappings":";;;;;;;AAcA,MAAM,kBAAkB,OAAO,IAAI,qBAAqB;AA4BxD,MAAM,qBAAqB,MAAM,SAC/B,gBACA,yBACD;AAED,MAAM,qBAAqB;AAE3B,SAAS,iBACP,SACwB;CACxB,MAAM,aAAqC,EAAE;AAE7C,MAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,QAAQ,EAAE;AAClD,MAAI,UAAU,OACZ;AAEF,aAAW,OAAO,MAAM,QAAQ,MAAM,GAAG,MAAM,KAAK,KAAK,GAAG;;AAG9D,QAAO;;AAGT,SAAS,gBAAgB,OAAyB;AAChD,KAAI,OAAO,UAAU,YAAY,UAAU,KACzC,QAAO;AAET,QAAO,mBAAmB;;AAG5B,SAAS,kBAAkB,OAAsB;AAC/C,KAAI,OAAO,UAAU,YAAY,UAAU,KACzC;AAEF,KAAI;AACF,SAAO,eAAe,OAAO,iBAAiB;GAC5C,OAAO;GACP,YAAY;GACZ,UAAU;GACV,cAAc;GACf,CAAC;SACI;;AAKV,eAAsB,oBACpB,QACe;CACf,MAAM,EAAE,OAAO,SAAS,YAAY;AAEpC,KAAI,gBAAgB,MAAM,CACxB;AAEF,mBAAkB,MAAM;CAGxB,MAAM,gBAAgB,IAAI,QAAQ,IAAI,IAAI,QAAQ,MAAM,mBAAmB,EAAE;EAC3E,QAAQ,QAAQ;EAChB,SAAS,iBAAiB,QAAQ,QAAQ;EAC3C,CAAC;CAGF,MAAM,iBAAiB,gCAAgC,cAAc;CAErE,MAAM,UAAU,OAAO,IAAI,aAAa;EACtC,MAAM,gBAAgB,OAAO;EAE7B,MAAM,kBAA2C;GAC/C,YAAY,QAAQ;GACpB,WAAW,QAAQ;GACnB,WAAW,QAAQ;GACnB,GAAI,QAAQ,gBAAgB,EAAE,cAAc,QAAQ,cAAc;GAClE,GAAI,QAAQ,oBAAoB,EAC9B,kBAAkB,QAAQ,kBAC3B;GACD,GAAI,QAAQ,cAAc,EAAE,YAAY,QAAQ,YAAY;GAC5D,aAAa,QAAQ;GACrB,eAAe,QAAQ;GACvB,GAAI,MAAM,UAAU,EAAE,aAAa,MAAM,QAAQ;GACjD,WAAW;IAAE,MAAM;IAAkB,SAAS;IAAO,WAAW;IAAO;GACvE,eAAe;GAChB;AAED,SAAO,cAAc,aAAa,OAAO,eAAe,gBAAgB;GACxE,CAAC,KACD,OAAO,QAAQ,mBAAmB,EAClC,OAAO,QAAQ,mBAAmB,EAClC,OAAO,eAAe,OAAO,KAAK,CACnC;AAGD,OAAM,sBAAsB,sBAC1B,OAAO,WAAW,oBAAoB,eAAe,QAAQ,CAAC,CAAC,YAAY,GAEzE,CACH;;AAGH,SAAgB,uBAAuB,OAAyB;AAC9D,QAAO,gBAAgB,MAAM;;AAG/B,SAAgB,oBAAoB,OAAsB;AACxD,mBAAkB,MAAM"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"proxy.d.mts","names":[],"sources":["../../src/server/proxy.ts"],"
|
|
1
|
+
{"version":3,"file":"proxy.d.mts","names":[],"sources":["../../src/server/proxy.ts"],"mappings":";;;iBAgBgB,kBAAA,CACd,OAAA,GACE,GAAA,EAAK,WAAA,EACL,KAAA,EAAO,cAAA,KACJ,OAAA,CAAQ,QAAA,IAAY,QAAA,IAIX,GAAA,EAAK,WAAA,EAAa,KAAA,EAAO,cAAA,KAAc,OAAA,CAAA,QAAA"}
|
package/dist/server/proxy.mjs
CHANGED
|
@@ -1,22 +1,21 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { NextConfigLive } from "./services/config.service.mjs";
|
|
2
2
|
import { ErrorTrackingService, ErrorTrackingServiceLive } from "./services/error-tracking.service.mjs";
|
|
3
3
|
import { withInterfereLogger } from "@interfere/effect-utils/observability";
|
|
4
4
|
import { Data, Effect, Layer } from "effect";
|
|
5
|
-
import { withSpan } from "@interfere/react/effect/layers/tracer.layer";
|
|
6
5
|
|
|
7
6
|
//#region src/server/proxy.ts
|
|
8
7
|
var ProxyError = class extends Data.TaggedError("ProxyError") {};
|
|
9
8
|
function withInterfereProxy(handler) {
|
|
10
|
-
const layer = Layer.mergeAll(
|
|
9
|
+
const layer = Layer.mergeAll(NextConfigLive, ErrorTrackingServiceLive);
|
|
11
10
|
return async (req, event) => {
|
|
12
11
|
if (req.nextUrl.pathname.startsWith("/api/interfere")) return handler(req, event);
|
|
13
12
|
const started = Date.now();
|
|
14
13
|
const program = Effect.gen(function* () {
|
|
15
14
|
const errorTracking = yield* ErrorTrackingService;
|
|
16
|
-
return yield*
|
|
15
|
+
return yield* Effect.tryPromise({
|
|
17
16
|
try: () => Promise.resolve(handler(req, event)),
|
|
18
17
|
catch: (cause) => new ProxyError({ cause })
|
|
19
|
-
}), { attributes: { path: req.nextUrl.pathname } })
|
|
18
|
+
}).pipe(Effect.annotateLogs({ path: req.nextUrl.pathname }), Effect.withSpan("proxy.request", { attributes: { path: req.nextUrl.pathname } }), Effect.tapError((error) => errorTracking.captureError(error.cause instanceof Error ? error.cause : new Error(String(error.cause)), req, {
|
|
20
19
|
pathname: req.nextUrl.pathname,
|
|
21
20
|
proxy: true,
|
|
22
21
|
duration: Date.now() - started
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"proxy.mjs","names":[],"sources":["../../src/server/proxy.ts"],"sourcesContent":["import { withInterfereLogger } from \"@interfere/effect-utils/observability\";\
|
|
1
|
+
{"version":3,"file":"proxy.mjs","names":[],"sources":["../../src/server/proxy.ts"],"sourcesContent":["import { withInterfereLogger } from \"@interfere/effect-utils/observability\";\n\nimport { Data, Effect, Layer } from \"effect\";\nimport type { NextFetchEvent, NextRequest } from \"next/server\";\n\nclass ProxyError extends Data.TaggedError(\"ProxyError\")<{\n cause: unknown;\n}> {}\n\nimport { NextConfigLive } from \"./services/config.service\";\nimport {\n ErrorTrackingService,\n ErrorTrackingServiceLive,\n} from \"./services/error-tracking.service\";\n\n// Proxy wrapper compatible with Next 16 `proxy.ts` with Effect\nexport function withInterfereProxy(\n handler: (\n req: NextRequest,\n event: NextFetchEvent\n ) => Promise<Response> | Response\n) {\n const layer = Layer.mergeAll(NextConfigLive, ErrorTrackingServiceLive);\n\n return async (req: NextRequest, event: NextFetchEvent) => {\n if (req.nextUrl.pathname.startsWith(\"/api/interfere\")) {\n return handler(req, event);\n }\n\n const started = Date.now();\n\n const program = Effect.gen(function* () {\n const errorTracking = yield* ErrorTrackingService;\n\n const response = yield* Effect.tryPromise({\n try: () => Promise.resolve(handler(req, event)),\n catch: (cause) => new ProxyError({ cause }),\n }).pipe(\n Effect.annotateLogs({ path: req.nextUrl.pathname }),\n Effect.withSpan(\"proxy.request\", {\n attributes: { path: req.nextUrl.pathname },\n }),\n Effect.tapError((error) =>\n errorTracking.captureError(\n error.cause instanceof Error\n ? error.cause\n : new Error(String(error.cause)),\n req,\n {\n pathname: req.nextUrl.pathname,\n proxy: true,\n duration: Date.now() - started,\n }\n )\n )\n );\n\n return response;\n }).pipe(Effect.provide(layer));\n\n return await Effect.runPromise(withInterfereLogger(\"next\", program));\n };\n}\n"],"mappings":";;;;;;AAKA,IAAM,aAAN,cAAyB,KAAK,YAAY,aAAa,CAEpD;AASH,SAAgB,mBACd,SAIA;CACA,MAAM,QAAQ,MAAM,SAAS,gBAAgB,yBAAyB;AAEtE,QAAO,OAAO,KAAkB,UAA0B;AACxD,MAAI,IAAI,QAAQ,SAAS,WAAW,iBAAiB,CACnD,QAAO,QAAQ,KAAK,MAAM;EAG5B,MAAM,UAAU,KAAK,KAAK;EAE1B,MAAM,UAAU,OAAO,IAAI,aAAa;GACtC,MAAM,gBAAgB,OAAO;AAyB7B,UAvBiB,OAAO,OAAO,WAAW;IACxC,WAAW,QAAQ,QAAQ,QAAQ,KAAK,MAAM,CAAC;IAC/C,QAAQ,UAAU,IAAI,WAAW,EAAE,OAAO,CAAC;IAC5C,CAAC,CAAC,KACD,OAAO,aAAa,EAAE,MAAM,IAAI,QAAQ,UAAU,CAAC,EACnD,OAAO,SAAS,iBAAiB,EAC/B,YAAY,EAAE,MAAM,IAAI,QAAQ,UAAU,EAC3C,CAAC,EACF,OAAO,UAAU,UACf,cAAc,aACZ,MAAM,iBAAiB,QACnB,MAAM,QACN,IAAI,MAAM,OAAO,MAAM,MAAM,CAAC,EAClC,KACA;IACE,UAAU,IAAI,QAAQ;IACtB,OAAO;IACP,UAAU,KAAK,KAAK,GAAG;IACxB,CACF,CACF,CACF;IAGD,CAAC,KAAK,OAAO,QAAQ,MAAM,CAAC;AAE9B,SAAO,MAAM,OAAO,WAAW,oBAAoB,QAAQ,QAAQ,CAAC"}
|
|
@@ -1,9 +1,39 @@
|
|
|
1
|
+
import { ConfigService } from "./services/config.service.mjs";
|
|
2
|
+
import { ErrorTrackingService } from "./services/error-tracking.service.mjs";
|
|
3
|
+
import { Context, Effect, Layer } from "effect";
|
|
1
4
|
import { NextRequest } from "next/server";
|
|
5
|
+
import * as effect_Types0 from "effect/Types";
|
|
6
|
+
import * as effect_Cause0 from "effect/Cause";
|
|
7
|
+
import * as effect_ParseResult0 from "effect/ParseResult";
|
|
2
8
|
|
|
3
9
|
//#region src/server/route-handler.d.ts
|
|
4
10
|
type RouteHandler = (request: NextRequest) => Promise<Response>;
|
|
11
|
+
declare const ProxyError_base: new <A extends Record<string, any> = {}>(args: effect_Types0.Equals<A, {}> extends true ? void : { readonly [P in keyof A as P extends "_tag" ? never : P]: A[P] }) => effect_Cause0.YieldableError & {
|
|
12
|
+
readonly _tag: "ProxyError";
|
|
13
|
+
} & Readonly<A>;
|
|
14
|
+
declare class ProxyError extends ProxyError_base<{
|
|
15
|
+
readonly status: number;
|
|
16
|
+
readonly message: string;
|
|
17
|
+
readonly body?: string;
|
|
18
|
+
}> {}
|
|
19
|
+
interface ProxyUpstreamClient {
|
|
20
|
+
readonly postIngest: (args: {
|
|
21
|
+
readonly apiUrl: string;
|
|
22
|
+
readonly publicToken: string;
|
|
23
|
+
readonly traceparent: string;
|
|
24
|
+
readonly body: string;
|
|
25
|
+
}) => Effect.Effect<Response, ProxyError>;
|
|
26
|
+
}
|
|
27
|
+
declare const ProxyUpstreamClient: Context.Tag<ProxyUpstreamClient, ProxyUpstreamClient>;
|
|
28
|
+
declare const ProxyUpstreamClientLive: Layer.Layer<ProxyUpstreamClient, never, never>;
|
|
5
29
|
declare const GET: RouteHandler;
|
|
30
|
+
declare const getStatusEffect: Effect.Effect<Response, never, ConfigService>;
|
|
31
|
+
declare const proxyIngestRequest: (request: Request, params: {
|
|
32
|
+
apiUrl: string;
|
|
33
|
+
publicToken: string;
|
|
34
|
+
}) => Effect.Effect<Response, string | effect_ParseResult0.ParseError | ProxyError, ProxyUpstreamClient>;
|
|
6
35
|
declare const POST: RouteHandler;
|
|
36
|
+
declare const postProxyEffect: (request: NextRequest) => Effect.Effect<Response, never, ConfigService | ErrorTrackingService | ProxyUpstreamClient>;
|
|
7
37
|
declare const OPTIONS: RouteHandler;
|
|
8
38
|
//#endregion
|
|
9
|
-
export { GET, OPTIONS, POST };
|
|
39
|
+
export { GET, OPTIONS, POST, ProxyUpstreamClient, ProxyUpstreamClientLive, getStatusEffect, postProxyEffect, proxyIngestRequest };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"route-handler.d.mts","names":[],"sources":["../../src/server/route-handler.ts"],"
|
|
1
|
+
{"version":3,"file":"route-handler.d.mts","names":[],"sources":["../../src/server/route-handler.ts"],"mappings":";;;;;;;;;KAuBK,YAAA,IAAgB,OAAA,EAAS,WAAA,KAAgB,OAAA,CAAQ,QAAA;AAAA,cAAQ,eAAA;;;cAGxD,UAAA,SAAmB,eAAA;EAAA,SACd,MAAA;EAAA,SACA,OAAA;EAAA,SACA,IAAA;AAAA;AAAA,UAGM,mBAAA;EAAA,SACN,UAAA,GAAa,IAAA;IAAA,SACX,MAAA;IAAA,SACA,WAAA;IAAA,SACA,WAAA;IAAA,SACA,IAAA;EAAA,MACL,MAAA,CAAO,MAAA,CAAO,QAAA,EAAU,UAAA;AAAA;AAAA,cAGnB,mBAAA,EAAmB,OAAA,CAAA,GAAA,CAAA,mBAAA,EAAA,mBAAA;AAAA,cAInB,uBAAA,EAAuB,KAAA,CAAA,KAAA,CAAA,mBAAA;AAAA,cAuCvB,GAAA,EAAK,YAAA;AAAA,cAKL,eAAA,EAAe,MAAA,CAAA,MAAA,CAAA,QAAA,SAAA,aAAA;AAAA,cAUf,kBAAA,GAAkB,OAAA,EAAA,OAAA,EAAA,MAAA;;;;cAkHlB,IAAA,EAAM,YAAA;AAAA,cAKN,eAAA,GAAmB,OAAA,EAAS,WAAA,KAAW,MAAA,CAAA,MAAA,CAAA,QAAA,SAAA,aAAA,GAAA,oBAAA,GAAA,mBAAA;AAAA,cA0FvC,OAAA,EAAS,YAAA"}
|
|
@@ -1,57 +1,66 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
import { ConfigService, ConfigServiceLive } from "./services/config.service.mjs";
|
|
1
|
+
import { ConfigService, NextConfigLive } from "./services/config.service.mjs";
|
|
4
2
|
import { ErrorTrackingService, ErrorTrackingServiceLive } from "./services/error-tracking.service.mjs";
|
|
5
3
|
import { API_PATHS } from "@interfere/constants/api";
|
|
6
4
|
import { withInterfereLogger } from "@interfere/effect-utils/observability";
|
|
7
|
-
import { Data, Effect, Layer, Schedule } from "effect";
|
|
8
|
-
import { withSpan } from "@interfere/react/effect/layers/tracer.layer";
|
|
5
|
+
import { Context, Data, Effect, Layer, Schedule, Schema } from "effect";
|
|
9
6
|
import { createBackoffSchedule, shouldRetryHttp, withTimeoutFail } from "@interfere/effect-utils/retry";
|
|
7
|
+
import { Status } from "@interfere/constants/http";
|
|
10
8
|
import { envelopeSchema } from "@interfere/types/sdk/envelope";
|
|
11
9
|
import { v7 } from "uuid";
|
|
12
10
|
import { z } from "zod";
|
|
13
11
|
|
|
14
12
|
//#region src/server/route-handler.ts
|
|
15
13
|
/** biome-ignore-all lint/suspicious/useAwait: route handlers must be async */
|
|
16
|
-
const layer = Layer.mergeAll(ConfigServiceLive, ErrorTrackingServiceLive);
|
|
17
14
|
var ProxyError = class extends Data.TaggedError("ProxyError") {};
|
|
15
|
+
const ProxyUpstreamClient = Context.GenericTag("@interfere/next/ProxyUpstreamClient");
|
|
16
|
+
const ProxyUpstreamClientLive = Layer.succeed(ProxyUpstreamClient, ProxyUpstreamClient.of({ postIngest: ({ apiUrl, publicToken, traceparent, body }) => Effect.tryPromise({
|
|
17
|
+
try: () => fetch(`${apiUrl}${API_PATHS.INGEST_V1}`, {
|
|
18
|
+
method: "POST",
|
|
19
|
+
headers: {
|
|
20
|
+
"Content-Type": "application/json",
|
|
21
|
+
"x-interfere-pub-token": publicToken,
|
|
22
|
+
traceparent
|
|
23
|
+
},
|
|
24
|
+
body
|
|
25
|
+
}),
|
|
26
|
+
catch: (error) => new ProxyError({
|
|
27
|
+
status: 0,
|
|
28
|
+
message: error instanceof Error ? error.message : String(error)
|
|
29
|
+
})
|
|
30
|
+
}) }));
|
|
31
|
+
const configLayer = NextConfigLive;
|
|
32
|
+
const layer = Layer.mergeAll(configLayer, ErrorTrackingServiceLive, ProxyUpstreamClientLive);
|
|
18
33
|
const TIMEOUT_MS = 1e4;
|
|
19
34
|
const RETRY_COUNT = 2;
|
|
20
|
-
const HTTP_BAD_GATEWAY = 502;
|
|
21
35
|
const RETRY_INITIAL_MS = 100;
|
|
22
36
|
const RETRY_MAX_MS = 2e3;
|
|
23
37
|
const envelopesArraySchema = z.array(envelopeSchema);
|
|
24
38
|
const GET = async () => {
|
|
25
|
-
const program = Effect.
|
|
26
|
-
const configured = yield* (yield* ConfigService).getSecretKey.pipe(Effect.map(() => true), Effect.catchAll(() => Effect.succeed(false)));
|
|
27
|
-
return Response.json({
|
|
28
|
-
status: "ok",
|
|
29
|
-
configured,
|
|
30
|
-
timestamp: Date.now()
|
|
31
|
-
});
|
|
32
|
-
}).pipe(Effect.provide(layer));
|
|
39
|
+
const program = getStatusEffect.pipe(Effect.provide(layer));
|
|
33
40
|
return await Effect.runPromise(withInterfereLogger("next", program));
|
|
34
41
|
};
|
|
35
|
-
const
|
|
42
|
+
const getStatusEffect = Effect.gen(function* () {
|
|
43
|
+
const configured = yield* (yield* ConfigService).getApiKey.pipe(Effect.map(() => true), Effect.catchAll(() => Effect.succeed(false)));
|
|
44
|
+
return Response.json({
|
|
45
|
+
status: "ok",
|
|
46
|
+
configured,
|
|
47
|
+
timestamp: Date.now()
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
const proxyIngestRequest = Effect.fn("proxyIngestRequest")(function* (request, params) {
|
|
36
51
|
const traceparent = (request.headers.get("traceparent") || void 0) ?? generateTraceparent();
|
|
37
52
|
const body = yield* Effect.tryPromise({
|
|
38
53
|
try: () => request.clone().text(),
|
|
39
54
|
catch: () => new ProxyError({
|
|
40
|
-
status:
|
|
55
|
+
status: Status.BAD_REQUEST,
|
|
41
56
|
message: "Invalid request body"
|
|
42
57
|
})
|
|
43
58
|
});
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
return yield* Effect.fail(new ProxyError({
|
|
50
|
-
status: 400,
|
|
51
|
-
message: "Invalid JSON in request body",
|
|
52
|
-
body: String(error)
|
|
53
|
-
}));
|
|
54
|
-
}
|
|
59
|
+
const parsed = yield* Schema.decode(Schema.parseJson())(body).pipe(Effect.mapError((error) => new ProxyError({
|
|
60
|
+
status: Status.BAD_REQUEST,
|
|
61
|
+
message: "Invalid JSON in request body",
|
|
62
|
+
body: String(error)
|
|
63
|
+
})), Effect.tapError(() => Effect.logWarning("Invalid JSON in request body").pipe(Effect.annotateLogs({ body }))));
|
|
55
64
|
const validation = envelopesArraySchema.safeParse(parsed);
|
|
56
65
|
if (!validation.success) {
|
|
57
66
|
const issues = validation.error.issues.map((issue) => ({
|
|
@@ -60,13 +69,13 @@ const proxyIngestRequest = (request, params) => Effect.gen(function* () {
|
|
|
60
69
|
code: issue.code
|
|
61
70
|
}));
|
|
62
71
|
yield* Effect.logWarning("Invalid envelope schema").pipe(Effect.annotateLogs({ validationErrors: issues }));
|
|
63
|
-
const b =
|
|
72
|
+
const b = yield* Schema.encode(Schema.parseJson())({
|
|
64
73
|
ok: false,
|
|
65
74
|
code: "INVALID_ENVELOPE_SCHEMA",
|
|
66
75
|
errors: issues
|
|
67
76
|
});
|
|
68
77
|
return new Response(b, {
|
|
69
|
-
status:
|
|
78
|
+
status: Status.UNPROCESSABLE_ENTITY,
|
|
70
79
|
headers: { "content-type": "application/json" }
|
|
71
80
|
});
|
|
72
81
|
}
|
|
@@ -82,20 +91,13 @@ const proxyIngestRequest = (request, params) => Effect.gen(function* () {
|
|
|
82
91
|
dedupedCount: deduped.length,
|
|
83
92
|
droppedDuplicates: dropped
|
|
84
93
|
}));
|
|
85
|
-
const
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
},
|
|
93
|
-
body: JSON.stringify(deduped)
|
|
94
|
-
}),
|
|
95
|
-
catch: (error) => new ProxyError({
|
|
96
|
-
status: 0,
|
|
97
|
-
message: error instanceof Error ? error.message : String(error)
|
|
98
|
-
})
|
|
94
|
+
const upstreamClient = yield* ProxyUpstreamClient;
|
|
95
|
+
const upstreamRequestBody = yield* Schema.encode(Schema.parseJson())(deduped);
|
|
96
|
+
const upstream = yield* withTimeoutFail(upstreamClient.postIngest({
|
|
97
|
+
apiUrl: params.apiUrl,
|
|
98
|
+
publicToken: params.publicToken,
|
|
99
|
+
traceparent,
|
|
100
|
+
body: upstreamRequestBody
|
|
99
101
|
}), TIMEOUT_MS, () => new ProxyError({
|
|
100
102
|
status: 0,
|
|
101
103
|
message: "Network timeout"
|
|
@@ -110,43 +112,40 @@ const proxyIngestRequest = (request, params) => Effect.gen(function* () {
|
|
|
110
112
|
});
|
|
111
113
|
});
|
|
112
114
|
const POST = async (request) => {
|
|
113
|
-
const program = Effect.
|
|
114
|
-
yield* Effect.logDebug("Handling ingest proxy request").pipe(Effect.annotateLogs({ path: request.nextUrl.pathname }));
|
|
115
|
-
const config = yield* ConfigService;
|
|
116
|
-
const errorTracking = yield* ErrorTrackingService;
|
|
117
|
-
yield* config.getSecretKey.pipe(Effect.tapError(() => Effect.logError("Service not configured - missing INTERFERE_SECRET_KEY")), Effect.catchTag("ConfigError", () => Effect.fail(new ProxyError({
|
|
118
|
-
status: 503,
|
|
119
|
-
message: "Service not configured. INTERFERE_SECRET_KEY environment variable is required."
|
|
120
|
-
}))));
|
|
121
|
-
const surfaceToken = yield* config.getPublicToken.pipe(Effect.tapError((error) => Effect.logError("Failed to get public token").pipe(Effect.annotateLogs({ reason: error.message }))), Effect.catchTag("ConfigError", (error) => Effect.fail(new ProxyError({
|
|
122
|
-
status: 503,
|
|
123
|
-
message: `Auth not available: ${error.message}`
|
|
124
|
-
}))));
|
|
125
|
-
const apiUrl = yield* config.getApiUrl;
|
|
126
|
-
const schedule = createBackoffSchedule(RETRY_INITIAL_MS, RETRY_MAX_MS, RETRY_COUNT).pipe(Schedule.whileInput((e) => e instanceof ProxyError ? shouldRetryHttp(e.status) : false));
|
|
127
|
-
const response = yield* withSpan("proxy.forward", proxyIngestRequest(request, {
|
|
128
|
-
apiUrl,
|
|
129
|
-
surfaceToken
|
|
130
|
-
}), { attributes: {} }).pipe(Effect.retry(schedule), Effect.tapError((error) => Effect.gen(function* () {
|
|
131
|
-
yield* errorTracking.captureError(error, request, {
|
|
132
|
-
pathname: request.nextUrl.pathname,
|
|
133
|
-
type: "api_route"
|
|
134
|
-
});
|
|
135
|
-
yield* Effect.logError("Proxy request failed").pipe(Effect.annotateLogs({
|
|
136
|
-
error: String(error),
|
|
137
|
-
status: error instanceof ProxyError ? error.status : 0
|
|
138
|
-
}));
|
|
139
|
-
})));
|
|
140
|
-
yield* Effect.logDebug("Proxy request completed").pipe(Effect.annotateLogs({ status: response.status }));
|
|
141
|
-
return response;
|
|
142
|
-
}).pipe(Effect.provide(layer), Effect.catchTag("ProxyError", (error) => Effect.succeed(Response.json({ error: error.message }, { status: error.status || HTTP_BAD_GATEWAY }))), Effect.catchAll((error) => {
|
|
143
|
-
Effect.runFork(Effect.logError("Unexpected error in route handler").pipe(Effect.annotateLogs({ error: String(error) })));
|
|
144
|
-
return Effect.succeed(Response.json({ error: "Internal server error" }, { status: 500 }));
|
|
145
|
-
}));
|
|
115
|
+
const program = postProxyEffect(request).pipe(Effect.provide(layer));
|
|
146
116
|
return await Effect.runPromise(withInterfereLogger("next", program));
|
|
147
117
|
};
|
|
118
|
+
const postProxyEffect = (request) => Effect.gen(function* () {
|
|
119
|
+
yield* Effect.logDebug("Handling ingest proxy request").pipe(Effect.annotateLogs({ path: request.nextUrl.pathname }));
|
|
120
|
+
const config = yield* ConfigService;
|
|
121
|
+
const errorTracking = yield* ErrorTrackingService;
|
|
122
|
+
const publicToken = yield* config.getPublicToken.pipe(Effect.tapError(() => Effect.logError("Service not configured - missing public token configuration")), Effect.catchTag("ConfigError", () => Effect.fail(new ProxyError({
|
|
123
|
+
status: Status.SERVICE_UNAVAILABLE,
|
|
124
|
+
message: "Service not configured. Public token configuration is required."
|
|
125
|
+
}))));
|
|
126
|
+
const apiUrl = yield* config.getApiUrl;
|
|
127
|
+
const schedule = createBackoffSchedule(RETRY_INITIAL_MS, RETRY_MAX_MS, RETRY_COUNT).pipe(Schedule.whileInput((e) => e instanceof ProxyError ? shouldRetryHttp(e.status) : false));
|
|
128
|
+
const response = yield* proxyIngestRequest(request, {
|
|
129
|
+
apiUrl,
|
|
130
|
+
publicToken
|
|
131
|
+
}).pipe(Effect.withSpan("proxy.forward"), Effect.retry(schedule), Effect.tapError(Effect.fn("handleProxyError")(function* (error) {
|
|
132
|
+
yield* errorTracking.captureError(error, request, {
|
|
133
|
+
pathname: request.nextUrl.pathname,
|
|
134
|
+
type: "api_route"
|
|
135
|
+
});
|
|
136
|
+
yield* Effect.logError("Proxy request failed").pipe(Effect.annotateLogs({
|
|
137
|
+
error: String(error),
|
|
138
|
+
status: error instanceof ProxyError ? error.status : 0
|
|
139
|
+
}));
|
|
140
|
+
})));
|
|
141
|
+
yield* Effect.logDebug("Proxy request completed").pipe(Effect.annotateLogs({ status: response.status }));
|
|
142
|
+
return response;
|
|
143
|
+
}).pipe(Effect.catchTag("ProxyError", (error) => Effect.succeed(Response.json({ error: error.message }, { status: error.status || Status.BAD_GATEWAY }))), Effect.catchAll((error) => {
|
|
144
|
+
Effect.runFork(Effect.logError("Unexpected error in route handler").pipe(Effect.annotateLogs({ error: String(error) })));
|
|
145
|
+
return Effect.succeed(Response.json({ error: "Internal server error" }, { status: Status.INTERNAL_SERVER_ERROR }));
|
|
146
|
+
}));
|
|
148
147
|
const OPTIONS = async () => Effect.runPromise(withInterfereLogger("next", Effect.succeed(new Response(null, {
|
|
149
|
-
status:
|
|
148
|
+
status: Status.OK,
|
|
150
149
|
headers: {
|
|
151
150
|
"Access-Control-Allow-Origin": "*",
|
|
152
151
|
"Access-Control-Allow-Methods": "GET, POST, OPTIONS",
|
|
@@ -169,4 +168,4 @@ function randomHex(bytes) {
|
|
|
169
168
|
}
|
|
170
169
|
|
|
171
170
|
//#endregion
|
|
172
|
-
export { GET, OPTIONS, POST };
|
|
171
|
+
export { GET, OPTIONS, POST, ProxyUpstreamClient, ProxyUpstreamClientLive, getStatusEffect, postProxyEffect, proxyIngestRequest };
|