@interfere/next 0.0.15-alpha.0 → 0.0.15-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/build/logger.d.mts.map +1 -1
- package/dist/build/release-program.d.mts +1 -1
- package/dist/build/release-program.d.mts.map +1 -1
- package/dist/build/services/preflight.service.mjs +2 -2
- package/dist/build/services/preflight.service.mjs.map +1 -1
- package/dist/build/with-interfere.d.mts +2 -3
- package/dist/build/with-interfere.d.mts.map +1 -1
- package/dist/build/with-interfere.mjs +4 -4
- package/dist/build/with-interfere.mjs.map +1 -1
- package/dist/client/provider.d.mts +2 -2
- package/dist/client/provider.d.mts.map +1 -1
- package/dist/client/provider.mjs.map +1 -1
- package/dist/lib/types.d.mts +4 -4
- package/dist/lib/types.d.mts.map +1 -1
- package/dist/lib/types.mjs.map +1 -1
- package/dist/server/middleware.d.mts.map +1 -1
- package/dist/server/middleware.mjs.map +1 -1
- package/dist/server/proxy.d.mts.map +1 -1
- package/dist/server/proxy.mjs.map +1 -1
- package/dist/server/route-handler.d.mts.map +1 -1
- package/dist/server/route-handler.mjs.map +1 -1
- package/dist/server/services/config.service.d.mts +5 -5
- package/dist/server/services/config.service.d.mts.map +1 -1
- package/dist/server/services/config.service.mjs.map +1 -1
- package/dist/server/services/error-tracking.service.d.mts +5 -5
- package/dist/server/services/error-tracking.service.d.mts.map +1 -1
- package/dist/server/services/error-tracking.service.mjs.map +1 -1
- package/package.json +8 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"logger.d.mts","names":[],"sources":["../../src/build/logger.ts"],"sourcesContent":[],"mappings":";;;cA+CM,iBAAe,MAAA,CAAA;AAAf,iBA0HU,cAAA,CA1HK,KAAA,EA0HiB,QAAA,CAAS,OA1H1B,EAAA,KAAA,EAAA,MAAA,EAAA,KAAA,EAAA,MAAA,EAAA,CAAA,EA0HiE,MAAA,CAAA,MA1HjE,CAAA,IAAA,EAAA,KAAA,EAAA,KAAA,CAAA;AA0HL,iBAyCA,gBAAA,
|
|
1
|
+
{"version":3,"file":"logger.d.mts","names":[],"sources":["../../src/build/logger.ts"],"sourcesContent":[],"mappings":";;;cA+CM,iBAAe,MAAA,CAAA;AAAf,iBA0HU,cAAA,CA1HK,KAAA,EA0HiB,QAAA,CAAS,OA1H1B,EAAA,KAAA,EAAA,MAAA,EAAA,KAAA,EAAA,MAAA,EAAA,CAAA,EA0HiE,MAAA,CAAA,MA1HjE,CAAA,IAAA,EAAA,KAAA,EAAA,KAAA,CAAA;AA0HL,iBAyCA,gBAAA,CAzC+B,KAAA,EA0CtC,QAAA,CAAS,OA1CoE,EAAA,KAAA,EAAA,MAAA,CAAA,EA2CvE,MAAA,CAAA,MA3CuE,CAAA,IAAA,EAAA,KAAA,EAAA,KAAA,CAAA;AAyCtE,iBAUA,kBAAA,CATE,IAAA,EAAA,MACH,CAAA,EAQgC,MAAA,CAAA,MARhC,CAAA,IAAA,EAAA,KAAA,EAAA,KAAA,CAAA;AAQC,iBAWA,aAAA,CAAA,CAX+B,EAWlB,MAAA,CAAA,MAXkB,CAAA,IAAA,EAAA,KAAA,EAAA,KAAA,CAAA;AAW/B,iBAmBA,oBAAA,CAnBa,KAAA,EAoBpB,QAAA,CAAS,OApBW,EAAA,KAAA,EAAA,MAAA,EAAA,GAAA,EAAA,CAAA,GAAA,EAAA,CAAA,IAAA,EAAA,MAAA,EAAA,GAuBF,UAvBE,CAAA,OAuBgB,kBAvBhB,CAAA,EAAA,GAwBtB,MAAA,CAAO,MAxBe,CAAA,OAAA,EAAA,OAAA,EAAA,OAAA,CAAA,CAAA,EAwBkB,MAAA,CAAA,MAxBlB,CAAA,OAAA,EAAA,OAAA,EAAA,OAAA,CAAA"}
|
|
@@ -14,6 +14,6 @@ declare const releaseProgram: Effect.Effect<unknown, unknown, unknown>;
|
|
|
14
14
|
/**
|
|
15
15
|
* Create the layer stack for the release program
|
|
16
16
|
*/
|
|
17
|
-
declare const createReleaseLayers: (config: PreflightConfig) => Layer.Layer<
|
|
17
|
+
declare const createReleaseLayers: (config: PreflightConfig) => Layer.Layer<SourceMapService | ConfigService | ReleaseIdentityService, never, never>;
|
|
18
18
|
//#endregion
|
|
19
19
|
export { createReleaseLayers, releaseProgram };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"release-program.d.mts","names":[],"sources":["../../src/build/release-program.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;AAuBA;AA6IA;AAA4C,cA7I/B,cA6I+B,EA7IjB,MAAA,CAAA,MA6IiB,CAAA,OAAA,EAAA,OAAA,EAAA,OAAA,CAAA;;;;AAAe,cAA9C,mBAA8C,EAAA,CAAA,MAAA,EAAf,eAAe,EAAA,GAAA,KAAA,CAAA,KAAA,CAAA,
|
|
1
|
+
{"version":3,"file":"release-program.d.mts","names":[],"sources":["../../src/build/release-program.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;AAuBA;AA6IA;AAA4C,cA7I/B,cA6I+B,EA7IjB,MAAA,CAAA,MA6IiB,CAAA,OAAA,EAAA,OAAA,EAAA,OAAA,CAAA;;;;AAAe,cAA9C,mBAA8C,EAAA,CAAA,MAAA,EAAf,eAAe,EAAA,GAAA,KAAA,CAAA,KAAA,CAAA,gBAAA,GAAA,aAAA,GAAA,sBAAA,EAAA,KAAA,EAAA,KAAA,CAAA"}
|
|
@@ -20,7 +20,7 @@ const validateSecretKey = (raw) => Effect.gen(function* () {
|
|
|
20
20
|
const extractSurfaceSlug = (secretKey) => Effect.gen(function* () {
|
|
21
21
|
const parsed = parseSurfaceSlugFromSecretKey(secretKey);
|
|
22
22
|
const result = parsed ? toNonEmptyString(parsed) : void 0;
|
|
23
|
-
if (!result) return yield* Effect.fail(new InvalidSecretKeyError({ message: "Invalid secret key format.
|
|
23
|
+
if (!result) return yield* Effect.fail(new InvalidSecretKeyError({ message: "Invalid secret key format. Please use the secret key provided in your Interfere dashboard." }));
|
|
24
24
|
return result;
|
|
25
25
|
});
|
|
26
26
|
const PreflightServiceLive = Layer.succeed(PreflightService, {
|
|
@@ -52,7 +52,7 @@ const PreflightServiceLive = Layer.succeed(PreflightService, {
|
|
|
52
52
|
yield* logBuildResult("Warning", "Interfere disabled (invalid secret key format)", [
|
|
53
53
|
message,
|
|
54
54
|
"Source maps will not be uploaded for this build.",
|
|
55
|
-
"
|
|
55
|
+
"Please use the secret key provided in your Interfere dashboard."
|
|
56
56
|
]);
|
|
57
57
|
return {
|
|
58
58
|
enabled: false,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"preflight.service.mjs","names":[],"sources":["../../../src/build/services/preflight.service.ts"],"sourcesContent":["import { Context, Effect, Layer } from \"effect\";\nimport {\n type NonEmptyString,\n type PreflightConfig,\n toNonEmptyString,\n} from \"../../lib/types.js\";\nimport { logBuildResult } from \"../logger.js\";\nimport { parseSurfaceSlugFromSecretKey } from \"../secret-key.js\";\nimport {\n InvalidSecretKeyError,\n WithInterfereUsageError,\n} from \"../source-maps/errors.js\";\n\nexport class PreflightService extends Context.Tag(\"PreflightService\")<\n PreflightService,\n {\n readonly validateEnvironment: () => Effect.Effect<void, WithInterfereUsageError>;\n readonly validateSecretKey: (key: string | null) => Effect.Effect<NonEmptyString, InvalidSecretKeyError>;\n readonly extractSurfaceSlug: (key: NonEmptyString) => Effect.Effect<NonEmptyString, InvalidSecretKeyError>;\n readonly buildConfig: (options: {\n environment?: string;\n debug?: boolean;\n cleanupSourceMaps?: boolean;\n }) => Effect.Effect<PreflightConfig, WithInterfereUsageError>;\n }\n>() {}\n\nconst validateEnvironment = () =>\n Effect.gen(function* () {\n if (typeof window !== \"undefined\") {\n return yield* Effect.fail(\n new WithInterfereUsageError({\n message: \"Security Error: withInterfere must only be used in next.config.js (server-side). It should never be imported in client-side code.\"\n })\n );\n }\n\n if (typeof process === \"undefined\" || !process.versions?.node) {\n return yield* Effect.fail(\n new WithInterfereUsageError({\n message: \"Security Error: withInterfere requires Node.js environment. It should only run during build time, not in the browser.\"\n })\n );\n }\n\n if (\n typeof process.env.INTERFERE_SECRET_KEY === \"string\" &&\n process.env.INTERFERE_SECRET_KEY.startsWith(\"NEXT_PUBLIC_\")\n ) {\n return yield* Effect.fail(\n new WithInterfereUsageError({\n message: \"Security Error: Secret key should NOT use NEXT_PUBLIC_ prefix. Use INTERFERE_SECRET_KEY instead to keep it server-side only.\"\n })\n );\n }\n });\n\nconst validateSecretKey = (raw: string | null) =>\n Effect.gen(function* () {\n if (typeof raw !== \"string\") {\n return yield* Effect.fail(\n new InvalidSecretKeyError(\n { message: \"Missing secret key. Set the INTERFERE_SECRET_KEY environment variable\" }\n )\n );\n }\n\n const trimmed = raw.trim();\n const cleaned = trimmed.replace(/^['\"]|['\"]$/g, \"\");\n const result = toNonEmptyString(cleaned);\n\n if (!result) {\n return yield* Effect.fail(\n new InvalidSecretKeyError(\n { message: \"Invalid secret key. Set the INTERFERE_SECRET_KEY environment variable\" }\n )\n );\n }\n\n return result;\n });\n\nconst extractSurfaceSlug = (secretKey: NonEmptyString) =>\n Effect.gen(function* () {\n const parsed = parseSurfaceSlugFromSecretKey(secretKey);\n const result = parsed ? (toNonEmptyString(parsed) as NonEmptyString) : undefined;\n\n if (!result) {\n return yield* Effect.fail(\n new InvalidSecretKeyError(\n { message: \"Invalid secret key format.
|
|
1
|
+
{"version":3,"file":"preflight.service.mjs","names":[],"sources":["../../../src/build/services/preflight.service.ts"],"sourcesContent":["import { Context, Effect, Layer } from \"effect\";\nimport {\n type NonEmptyString,\n type PreflightConfig,\n toNonEmptyString,\n} from \"../../lib/types.js\";\nimport { logBuildResult } from \"../logger.js\";\nimport { parseSurfaceSlugFromSecretKey } from \"../secret-key.js\";\nimport {\n InvalidSecretKeyError,\n WithInterfereUsageError,\n} from \"../source-maps/errors.js\";\n\nexport class PreflightService extends Context.Tag(\"PreflightService\")<\n PreflightService,\n {\n readonly validateEnvironment: () => Effect.Effect<void, WithInterfereUsageError>;\n readonly validateSecretKey: (key: string | null) => Effect.Effect<NonEmptyString, InvalidSecretKeyError>;\n readonly extractSurfaceSlug: (key: NonEmptyString) => Effect.Effect<NonEmptyString, InvalidSecretKeyError>;\n readonly buildConfig: (options: {\n environment?: string;\n debug?: boolean;\n cleanupSourceMaps?: boolean;\n }) => Effect.Effect<PreflightConfig, WithInterfereUsageError>;\n }\n>() {}\n\nconst validateEnvironment = () =>\n Effect.gen(function* () {\n if (typeof window !== \"undefined\") {\n return yield* Effect.fail(\n new WithInterfereUsageError({\n message: \"Security Error: withInterfere must only be used in next.config.js (server-side). It should never be imported in client-side code.\"\n })\n );\n }\n\n if (typeof process === \"undefined\" || !process.versions?.node) {\n return yield* Effect.fail(\n new WithInterfereUsageError({\n message: \"Security Error: withInterfere requires Node.js environment. It should only run during build time, not in the browser.\"\n })\n );\n }\n\n if (\n typeof process.env.INTERFERE_SECRET_KEY === \"string\" &&\n process.env.INTERFERE_SECRET_KEY.startsWith(\"NEXT_PUBLIC_\")\n ) {\n return yield* Effect.fail(\n new WithInterfereUsageError({\n message: \"Security Error: Secret key should NOT use NEXT_PUBLIC_ prefix. Use INTERFERE_SECRET_KEY instead to keep it server-side only.\"\n })\n );\n }\n });\n\nconst validateSecretKey = (raw: string | null) =>\n Effect.gen(function* () {\n if (typeof raw !== \"string\") {\n return yield* Effect.fail(\n new InvalidSecretKeyError(\n { message: \"Missing secret key. Set the INTERFERE_SECRET_KEY environment variable\" }\n )\n );\n }\n\n const trimmed = raw.trim();\n const cleaned = trimmed.replace(/^['\"]|['\"]$/g, \"\");\n const result = toNonEmptyString(cleaned);\n\n if (!result) {\n return yield* Effect.fail(\n new InvalidSecretKeyError(\n { message: \"Invalid secret key. Set the INTERFERE_SECRET_KEY environment variable\" }\n )\n );\n }\n\n return result;\n });\n\nconst extractSurfaceSlug = (secretKey: NonEmptyString) =>\n Effect.gen(function* () {\n const parsed = parseSurfaceSlugFromSecretKey(secretKey);\n const result = parsed ? (toNonEmptyString(parsed) as NonEmptyString) : undefined;\n\n if (!result) {\n return yield* Effect.fail(\n new InvalidSecretKeyError(\n { message: \"Invalid secret key format. Please use the secret key provided in your Interfere dashboard.\" }\n )\n );\n }\n\n return result;\n });\n\nexport const PreflightServiceLive = Layer.succeed(\n PreflightService,\n {\n validateEnvironment,\n validateSecretKey,\n extractSurfaceSlug,\n buildConfig: (options) =>\n Effect.gen(function* () {\n yield* validateEnvironment();\n \n const secretKeyRaw = process.env.INTERFERE_SECRET_KEY;\n \n // Try to get secret key and surface slug\n const secretKeyResult = yield* validateSecretKey(\n secretKeyRaw ?? null\n ).pipe(Effect.either);\n\n if (secretKeyResult._tag === \"Left\") {\n const message = secretKeyResult.left.message;\n\n yield* logBuildResult(\n \"Warning\",\n \"Interfere disabled (invalid secret key)\",\n [\n message,\n \"Source maps will not be uploaded for this build.\",\n \"Set a valid INTERFERE_SECRET_KEY in your environment to enable Interfere.\",\n ]\n );\n\n // Return disabled config if no valid secret key\n return {\n enabled: false as const,\n environment: options.environment || process.env.NODE_ENV,\n debug: options.debug ?? false,\n cleanupSourceMaps: options.cleanupSourceMaps ?? true,\n };\n }\n \n const secretKey = secretKeyResult.right;\n const surfaceSlugResult = yield* extractSurfaceSlug(secretKey).pipe(\n Effect.either\n );\n\n if (surfaceSlugResult._tag === \"Left\") {\n const message = surfaceSlugResult.left.message;\n\n yield* logBuildResult(\n \"Warning\",\n \"Interfere disabled (invalid secret key format)\",\n [\n message,\n \"Source maps will not be uploaded for this build.\",\n \"Please use the secret key provided in your Interfere dashboard.\",\n ]\n );\n\n // Return disabled config if no valid surface slug\n return {\n enabled: false as const,\n environment: options.environment || process.env.NODE_ENV,\n debug: options.debug ?? false,\n cleanupSourceMaps: options.cleanupSourceMaps ?? true,\n };\n }\n\n return {\n enabled: true as const,\n surface: surfaceSlugResult.right,\n secretKey,\n environment: options.environment || process.env.NODE_ENV,\n debug: options.debug ?? false,\n cleanupSourceMaps: options.cleanupSourceMaps ?? true,\n };\n }),\n }\n);\n"],"mappings":";;;;;;;AAaA,IAAa,mBAAb,cAAsC,QAAQ,IAAI,mBAAmB,EAYlE,CAAC;AAEJ,MAAM,4BACJ,OAAO,IAAI,aAAa;AACtB,KAAI,OAAO,WAAW,YACpB,QAAO,OAAO,OAAO,KACnB,IAAI,wBAAwB,EAC1B,SAAS,qIACV,CAAC,CACH;AAGH,KAAI,OAAO,YAAY,eAAe,CAAC,QAAQ,UAAU,KACvD,QAAO,OAAO,OAAO,KACnB,IAAI,wBAAwB,EAC1B,SAAS,yHACV,CAAC,CACH;AAGH,KACE,OAAO,QAAQ,IAAI,yBAAyB,YAC5C,QAAQ,IAAI,qBAAqB,WAAW,eAAe,CAE3D,QAAO,OAAO,OAAO,KACnB,IAAI,wBAAwB,EAC1B,SAAS,gIACV,CAAC,CACH;EAEH;AAEJ,MAAM,qBAAqB,QACzB,OAAO,IAAI,aAAa;AACtB,KAAI,OAAO,QAAQ,SACjB,QAAO,OAAO,OAAO,KACnB,IAAI,sBACF,EAAE,SAAS,yEAAyE,CACrF,CACF;CAKH,MAAM,SAAS,iBAFC,IAAI,MAAM,CACF,QAAQ,gBAAgB,GAAG,CACX;AAExC,KAAI,CAAC,OACH,QAAO,OAAO,OAAO,KACnB,IAAI,sBACF,EAAE,SAAS,yEAAyE,CACrF,CACF;AAGH,QAAO;EACP;AAEJ,MAAM,sBAAsB,cAC1B,OAAO,IAAI,aAAa;CACtB,MAAM,SAAS,8BAA8B,UAAU;CACvD,MAAM,SAAS,SAAU,iBAAiB,OAAO,GAAsB;AAEvE,KAAI,CAAC,OACH,QAAO,OAAO,OAAO,KACnB,IAAI,sBACF,EAAE,SAAS,8FAA8F,CAC1G,CACF;AAGH,QAAO;EACP;AAEJ,MAAa,uBAAuB,MAAM,QACxC,kBACA;CACE;CACA;CACA;CACA,cAAc,YACZ,OAAO,IAAI,aAAa;AACtB,SAAO,qBAAqB;EAE5B,MAAM,eAAe,QAAQ,IAAI;EAGjC,MAAM,kBAAkB,OAAO,kBAC7B,gBAAgB,KACjB,CAAC,KAAK,OAAO,OAAO;AAErB,MAAI,gBAAgB,SAAS,QAAQ;GACnC,MAAM,UAAU,gBAAgB,KAAK;AAErC,UAAO,eACL,WACA,2CACA;IACE;IACA;IACA;IACD,CACF;AAGD,UAAO;IACL,SAAS;IACT,aAAa,QAAQ,eAAe,QAAQ,IAAI;IAChD,OAAO,QAAQ,SAAS;IACxB,mBAAmB,QAAQ,qBAAqB;IACjD;;EAGH,MAAM,YAAY,gBAAgB;EAClC,MAAM,oBAAoB,OAAO,mBAAmB,UAAU,CAAC,KAC7D,OAAO,OACR;AAED,MAAI,kBAAkB,SAAS,QAAQ;GACrC,MAAM,UAAU,kBAAkB,KAAK;AAEvC,UAAO,eACL,WACA,kDACA;IACE;IACA;IACA;IACD,CACF;AAGD,UAAO;IACL,SAAS;IACT,aAAa,QAAQ,eAAe,QAAQ,IAAI;IAChD,OAAO,QAAQ,SAAS;IACxB,mBAAmB,QAAQ,qBAAqB;IACjD;;AAGH,SAAO;GACL,SAAS;GACT,SAAS,kBAAkB;GAC3B;GACA,aAAa,QAAQ,eAAe,QAAQ,IAAI;GAChD,OAAO,QAAQ,SAAS;GACxB,mBAAmB,QAAQ,qBAAqB;GACjD;GACD;CACL,CACF"}
|
|
@@ -25,14 +25,13 @@ type NextConfigWithInterfere = NextConfig & {
|
|
|
25
25
|
* Next.js configuration wrapper that automatically uploads source maps in production builds
|
|
26
26
|
*
|
|
27
27
|
* SECURITY: Secret key must be set via INTERFERE_SECRET_KEY env var (without NEXT_PUBLIC_ prefix)
|
|
28
|
-
* Format: int_sk_{surfaceId}_{random}
|
|
29
28
|
*
|
|
30
29
|
* @example
|
|
31
30
|
* ```js
|
|
32
31
|
* // next.config.js
|
|
33
|
-
*
|
|
32
|
+
* import { withInterfere } from '@interfere/next/config';
|
|
34
33
|
*
|
|
35
|
-
*
|
|
34
|
+
* export default withInterfere({
|
|
36
35
|
* environment: 'staging',
|
|
37
36
|
* debug: true,
|
|
38
37
|
* cleanupSourceMaps: true
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"with-interfere.d.mts","names":[],"sources":["../../src/build/with-interfere.ts"],"sourcesContent":[],"mappings":";;;KAQY,gBAAA;;AAAZ;AAkBE;
|
|
1
|
+
{"version":3,"file":"with-interfere.d.mts","names":[],"sources":["../../src/build/with-interfere.ts"],"sourcesContent":[],"mappings":";;;KAQY,gBAAA;;AAAZ;AAkBE;AAyBF;EACE,WAAA,CAAA,EAAA,MAAA;EAEC;;;;;;;;;;;KA1BE,uBAAA,GAA0B;cACjB;;;;;;;;;;;;;;;;;;;;;iBAsBE,aAAA;;;IAGb,gCAAqC"}
|
|
@@ -10,14 +10,13 @@ import { Effect, Layer, LogLevel, Logger, Option } from "effect";
|
|
|
10
10
|
* Next.js configuration wrapper that automatically uploads source maps in production builds
|
|
11
11
|
*
|
|
12
12
|
* SECURITY: Secret key must be set via INTERFERE_SECRET_KEY env var (without NEXT_PUBLIC_ prefix)
|
|
13
|
-
* Format: int_sk_{surfaceId}_{random}
|
|
14
13
|
*
|
|
15
14
|
* @example
|
|
16
15
|
* ```js
|
|
17
16
|
* // next.config.js
|
|
18
|
-
*
|
|
17
|
+
* import { withInterfere } from '@interfere/next/config';
|
|
19
18
|
*
|
|
20
|
-
*
|
|
19
|
+
* export default withInterfere({
|
|
21
20
|
* environment: 'staging',
|
|
22
21
|
* debug: true,
|
|
23
22
|
* cleanupSourceMaps: true
|
|
@@ -66,7 +65,8 @@ function withInterfere({ interfere = {}, ...nextConfig } = {}) {
|
|
|
66
65
|
...nextConfig,
|
|
67
66
|
compiler,
|
|
68
67
|
env,
|
|
69
|
-
productionBrowserSourceMaps: interfereConfig.enabled ? true : nextConfig.productionBrowserSourceMaps
|
|
68
|
+
productionBrowserSourceMaps: interfereConfig.enabled ? true : nextConfig.productionBrowserSourceMaps,
|
|
69
|
+
turbopack: nextConfig.turbopack ?? {}
|
|
70
70
|
};
|
|
71
71
|
};
|
|
72
72
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"with-interfere.mjs","names":[],"sources":["../../src/build/with-interfere.ts"],"sourcesContent":["import { withInterfereLogger } from \"@interfere/effect-utils/observability\";\nimport { Effect, Layer, Logger, LogLevel, Option } from \"effect\";\nimport type { NextConfig } from \"next\";\nimport { nextBuildLogger } from \"./logger.js\";\nimport { createReleaseLayers, releaseProgram } from \"./release-program.js\";\nimport { PreflightService, PreflightServiceLive } from \"./services/preflight.service.js\";\nimport { ReleaseIdentityService, ReleaseIdentityServiceLive } from \"./services/release-identity.service.js\";\n\nexport type InterfereOptions = {\n /**\n * Environment name\n * @default \"production\"\n */\n environment?: string;\n\n /**\n * Enable debug logging\n * @default false\n */\n debug?: boolean;\n\n /**\n * Whether to delete source maps after upload to prevent leakage\n * @default true\n */\n cleanupSourceMaps?: boolean;\n};\n\ntype NextConfigWithInterfere = NextConfig & {\n interfere?: InterfereOptions;\n};\n\n/**\n * Next.js configuration wrapper that automatically uploads source maps in production builds\n *\n * SECURITY: Secret key must be set via INTERFERE_SECRET_KEY env var (without NEXT_PUBLIC_ prefix)\n
|
|
1
|
+
{"version":3,"file":"with-interfere.mjs","names":[],"sources":["../../src/build/with-interfere.ts"],"sourcesContent":["import { withInterfereLogger } from \"@interfere/effect-utils/observability\";\nimport { Effect, Layer, Logger, LogLevel, Option } from \"effect\";\nimport type { NextConfig } from \"next\";\nimport { nextBuildLogger } from \"./logger.js\";\nimport { createReleaseLayers, releaseProgram } from \"./release-program.js\";\nimport { PreflightService, PreflightServiceLive } from \"./services/preflight.service.js\";\nimport { ReleaseIdentityService, ReleaseIdentityServiceLive } from \"./services/release-identity.service.js\";\n\nexport type InterfereOptions = {\n /**\n * Environment name\n * @default \"production\"\n */\n environment?: string;\n\n /**\n * Enable debug logging\n * @default false\n */\n debug?: boolean;\n\n /**\n * Whether to delete source maps after upload to prevent leakage\n * @default true\n */\n cleanupSourceMaps?: boolean;\n};\n\ntype NextConfigWithInterfere = NextConfig & {\n interfere?: InterfereOptions;\n};\n\n/**\n * Next.js configuration wrapper that automatically uploads source maps in production builds\n *\n * SECURITY: Secret key must be set via INTERFERE_SECRET_KEY env var (without NEXT_PUBLIC_ prefix)\n *\n * @example\n * ```js\n * // next.config.js\n * import { withInterfere } from '@interfere/next/config';\n *\n * export default withInterfere({\n * environment: 'staging',\n * debug: true,\n * cleanupSourceMaps: true\n * })({\n * // Your Next.js config\n * });\n * ```\n */\nexport function withInterfere({\n interfere = {},\n ...nextConfig\n}: NextConfigWithInterfere = {}): () => NextConfig {\n // Build the preflight config using the service\n const configProgram = Effect.gen(function* () {\n const preflight = yield* PreflightService;\n return yield* preflight.buildConfig(interfere);\n }).pipe(\n Effect.provide(PreflightServiceLive),\n Effect.runSync\n );\n\n const interfereConfig = configProgram;\n \n // Use the ReleaseIdentityService to infer build/release IDs\n // This runs the same logic that the release program uses\n const identityProgram = Effect.gen(function* () {\n const identityEffect = yield* ReleaseIdentityService;\n return yield* identityEffect.pipe(Effect.option);\n }).pipe(\n Effect.provide(ReleaseIdentityServiceLive),\n Effect.runSync\n );\n \n const identity = Option.getOrUndefined(identityProgram);\n\n return function buildConfig() {\n // Use the same release identity logic that the release program uses\n // This ensures consistency between what gets uploaded and what the client SDK sends\n const env = {\n ...nextConfig.env,\n // Pass the deduced/configured environment to the client SDK\n // (it defaults to NODE_ENV in preflight service)\n NEXT_PUBLIC_INTERFERE_ENVIRONMENT: interfereConfig.environment,\n // Pass the inferred build/release IDs to the client SDK\n // These come from the same ReleaseIdentityService that the release program uses\n ...(identity?.buildId\n ? { NEXT_PUBLIC_INTERFERE_BUILD_ID: identity.buildId }\n : {}),\n ...(identity?.releaseId\n ? { NEXT_PUBLIC_INTERFERE_RELEASE_ID: identity.releaseId }\n : {}),\n };\n\n // Set up compiler hook if enabled\n const compiler = !interfereConfig.enabled\n ? nextConfig.compiler\n : {\n ...nextConfig.compiler,\n runAfterProductionCompile: async (ctx: {\n projectDir: string;\n distDir: string;\n }) => {\n const debugEnabled = process.env.INTERFERE_PLUGIN_DEBUG === \"1\";\n const startTime = Date.now();\n\n if (debugEnabled) {\n console.log(\"[Interfere Plugin] Starting runAfterProductionCompile\");\n }\n\n // Run existing hook first\n const existingHook = nextConfig.compiler?.runAfterProductionCompile;\n if (existingHook) {\n const hookStart = Date.now();\n await existingHook(ctx);\n if (debugEnabled) {\n console.log(`[Interfere Plugin] Existing hook completed in ${Date.now() - hookStart}ms`);\n }\n }\n\n // Run the unified release program with all layers\n const scopedProgram = releaseProgram.pipe(\n Effect.provide(createReleaseLayers(interfereConfig)),\n Effect.scoped,\n debugEnabled\n ? Logger.withMinimumLogLevel(LogLevel.Debug)\n : (x) => x\n ) as Effect.Effect<unknown, unknown, never>;\n\n const programStart = Date.now();\n await Effect.runPromise(\n withInterfereLogger(\"next\", scopedProgram, { logger: nextBuildLogger })\n );\n if (debugEnabled) {\n console.log(`[Interfere Plugin] Release program completed in ${Date.now() - programStart}ms`);\n console.log(`[Interfere Plugin] Total runAfterProductionCompile time: ${Date.now() - startTime}ms`);\n }\n },\n };\n\n return {\n ...nextConfig,\n compiler,\n env,\n // Automatically enable browser source maps when Interfere is enabled\n productionBrowserSourceMaps: interfereConfig.enabled\n ? true\n : nextConfig.productionBrowserSourceMaps,\n // Silence Next.js 16+ Turbopack warning when using compiler hooks\n // The runAfterProductionCompile hook is webpack-specific, but works fine\n // during production builds which still use webpack\n turbopack: nextConfig.turbopack ?? {},\n };\n };\n}"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;AAmDA,SAAgB,cAAc,EAC5B,YAAY,EAAE,EACd,GAAG,eACwB,EAAE,EAAoB;CAUjD,MAAM,kBARgB,OAAO,IAAI,aAAa;AAE5C,SAAO,QADW,OAAO,kBACD,YAAY,UAAU;GAC9C,CAAC,KACD,OAAO,QAAQ,qBAAqB,EACpC,OAAO,QACR;CAMD,MAAM,kBAAkB,OAAO,IAAI,aAAa;AAE9C,SAAO,QADgB,OAAO,wBACD,KAAK,OAAO,OAAO;GAChD,CAAC,KACD,OAAO,QAAQ,2BAA2B,EAC1C,OAAO,QACR;CAED,MAAM,WAAW,OAAO,eAAe,gBAAgB;AAEvD,QAAO,SAAS,cAAc;EAG5B,MAAM,MAAM;GACV,GAAG,WAAW;GAGd,mCAAmC,gBAAgB;GAGnD,GAAI,UAAU,UACV,EAAE,gCAAgC,SAAS,SAAS,GACpD,EAAE;GACN,GAAI,UAAU,YACV,EAAE,kCAAkC,SAAS,WAAW,GACxD,EAAE;GACP;EAGD,MAAM,WAAW,CAAC,gBAAgB,UAC9B,WAAW,WACX;GACE,GAAG,WAAW;GACd,2BAA2B,OAAO,QAG5B;IACJ,MAAM,eAAe,QAAQ,IAAI,2BAA2B;IAC5D,MAAM,YAAY,KAAK,KAAK;AAE5B,QAAI,aACF,SAAQ,IAAI,wDAAwD;IAItE,MAAM,eAAe,WAAW,UAAU;AAC1C,QAAI,cAAc;KAChB,MAAM,YAAY,KAAK,KAAK;AAC5B,WAAM,aAAa,IAAI;AACvB,SAAI,aACF,SAAQ,IAAI,iDAAiD,KAAK,KAAK,GAAG,UAAU,IAAI;;IAK5F,MAAM,gBAAgB,eAAe,KACnC,OAAO,QAAQ,oBAAoB,gBAAgB,CAAC,EACpD,OAAO,QACP,eACI,OAAO,oBAAoB,SAAS,MAAM,IACzC,MAAM,EACZ;IAED,MAAM,eAAe,KAAK,KAAK;AAC/B,UAAM,OAAO,WACX,oBAAoB,QAAQ,eAAe,EAAE,QAAQ,iBAAiB,CAAC,CACxE;AACD,QAAI,cAAc;AAChB,aAAQ,IAAI,mDAAmD,KAAK,KAAK,GAAG,aAAa,IAAI;AAC7F,aAAQ,IAAI,4DAA4D,KAAK,KAAK,GAAG,UAAU,IAAI;;;GAGxG;AAEL,SAAO;GACL,GAAG;GACH;GACA;GAEA,6BAA6B,gBAAgB,UACzC,OACA,WAAW;GAIf,WAAW,WAAW,aAAa,EAAE;GACtC"}
|
|
@@ -3,14 +3,14 @@ import { ConfigInput } from "@interfere/types/sdk/config";
|
|
|
3
3
|
import * as react0 from "react";
|
|
4
4
|
|
|
5
5
|
//#region src/client/provider.d.ts
|
|
6
|
-
|
|
6
|
+
interface NextConfigInput {
|
|
7
7
|
features?: ConfigInput["features"];
|
|
8
8
|
metadata?: ConfigInput["metadata"];
|
|
9
9
|
batch?: ConfigInput["batch"];
|
|
10
10
|
proxyUrl?: string;
|
|
11
11
|
ingestUrl?: string;
|
|
12
12
|
surfaceToken?: string;
|
|
13
|
-
}
|
|
13
|
+
}
|
|
14
14
|
declare function InterfereProvider({
|
|
15
15
|
children,
|
|
16
16
|
config,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"provider.d.mts","names":[],"sources":["../../src/client/provider.tsx"],"sourcesContent":[],"mappings":";;;;;
|
|
1
|
+
{"version":3,"file":"provider.d.mts","names":[],"sources":["../../src/client/provider.tsx"],"sourcesContent":[],"mappings":";;;;;UAuBU,eAAA;aACG;aACA;EAFH,KAAA,CAAA,EAGA,WAHe,CAAA,OAAA,CAAA;EACZ,QAAA,CAAA,EAAA,MAAA;EACA,SAAA,CAAA,EAAA,MAAA;EACH,YAAA,CAAA,EAAA,MAAA;;AAgCM,iBAAA,iBAAA,CAAiB;EAAA,QAAA;EAAA,MAAA;EAAA,GAAA;CAAA,EAI9B,IAJ8B,CAIzB,sBAJyB,EAAA,QAAA,CAAA,GAAA;EAC/B,MAAA,CAAA,EAGqD,eAHrD;CACA,CAAA,EAEsE,MAAA,CAAA,GAAA,CAAA,OAFtE"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"provider.mjs","names":["defaults: ConfigInput[\"metadata\"]","InterfereProviderReact"],"sources":["../../src/client/provider.tsx"],"sourcesContent":["import { getRuntime } from \"@interfere/react/core/runtime/config\";\
|
|
1
|
+
{"version":3,"file":"provider.mjs","names":["defaults: ConfigInput[\"metadata\"]","InterfereProviderReact"],"sources":["../../src/client/provider.tsx"],"sourcesContent":["import { getRuntime } from \"@interfere/react/core/runtime/config\";\nimport {\n type InterfereProviderProps,\n InterfereProvider as InterfereProviderReact,\n} from \"@interfere/react/provider\";\nimport {\n type Config,\n type ConfigInput,\n configSchema,\n} from \"@interfere/types/sdk/config\";\nimport { envSchema } from \"@interfere/types/sdk/runtime\";\n\nconst defaults: ConfigInput[\"metadata\"] = {\n buildId: process.env.NEXT_PUBLIC_INTERFERE_BUILD_ID ?? null,\n releaseId: process.env.NEXT_PUBLIC_INTERFERE_RELEASE_ID ?? null,\n environment: process.env.NEXT_PUBLIC_INTERFERE_ENVIRONMENT\n ? envSchema.parse(process.env.NEXT_PUBLIC_INTERFERE_ENVIRONMENT)\n : \"production\",\n runtime: getRuntime(),\n};\n\n// Next.js-specific config type that allows omitting transport\n// because Next.js can provide its own proxy endpoint\ninterface NextConfigInput {\n features?: ConfigInput[\"features\"];\n metadata?: ConfigInput[\"metadata\"];\n batch?: ConfigInput[\"batch\"];\n proxyUrl?: string;\n ingestUrl?: string;\n surfaceToken?: string;\n}\n\nfunction mergeConfig(config: NextConfigInput): Config {\n const incomingMeta = config.metadata ?? {};\n\n // Determine transport config:\n // - If they explicitly set proxyUrl, use proxy mode\n // - If they set ingestUrl or surfaceToken, use direct mode (let schema validate)\n // - Otherwise, default to Next.js proxy\n const hasTransportConfig =\n typeof config.proxyUrl !== \"undefined\" ||\n typeof config.ingestUrl !== \"undefined\" ||\n typeof config.surfaceToken !== \"undefined\";\n\n const transportConfig = hasTransportConfig\n ? config\n : { proxyUrl: \"/api/interfere\" }; // Default Next.js proxy endpoint\n\n return configSchema.parse({\n ...transportConfig,\n ...config,\n metadata: {\n ...defaults,\n ...incomingMeta,\n },\n });\n}\n\nexport function InterfereProvider({\n children,\n config = {},\n ...rest\n}: Omit<InterfereProviderProps, \"config\"> & { config?: NextConfigInput }) {\n const mergedConfig = mergeConfig(config);\n\n return (\n <InterfereProviderReact config={mergedConfig} {...rest}>\n {children}\n </InterfereProviderReact>\n );\n}\n"],"mappings":";;;;;;AAYA,MAAMA,WAAoC;CACxC,SAAS,QAAQ,IAAI,kCAAkC;CACvD,WAAW,QAAQ,IAAI,oCAAoC;CAC3D,aAAa,QAAQ,IAAI,oCACrB,UAAU,MAAM,QAAQ,IAAI,kCAAkC,GAC9D;CACJ,SAAS,YAAY;CACtB;AAaD,SAAS,YAAY,QAAiC;CACpD,MAAM,eAAe,OAAO,YAAY,EAAE;CAW1C,MAAM,kBAJJ,OAAO,OAAO,aAAa,eAC3B,OAAO,OAAO,cAAc,eAC5B,OAAO,OAAO,iBAAiB,cAG7B,SACA,EAAE,UAAU,kBAAkB;AAElC,QAAO,aAAa,MAAM;EACxB,GAAG;EACH,GAAG;EACH,UAAU;GACR,GAAG;GACH,GAAG;GACJ;EACF,CAAC;;AAGJ,SAAgB,kBAAkB,EAChC,UACA,SAAS,EAAE,EACX,GAAG,QACqE;CACxE,MAAM,eAAe,YAAY,OAAO;AAExC,QACE,CAACC,oBAAuB,QAAQ,kBAAkB,MAAM;OACrD,SAAS;IACZ,EAAEA"}
|
package/dist/lib/types.d.mts
CHANGED
|
@@ -3,20 +3,20 @@ type NonEmptyString = string & {
|
|
|
3
3
|
readonly __brand: unique symbol;
|
|
4
4
|
};
|
|
5
5
|
declare function toNonEmptyString(value: string): NonEmptyString | null;
|
|
6
|
-
|
|
6
|
+
interface PreflightEnabled {
|
|
7
7
|
enabled: true;
|
|
8
8
|
surface: NonEmptyString;
|
|
9
9
|
secretKey: NonEmptyString;
|
|
10
10
|
environment: string;
|
|
11
11
|
debug: boolean;
|
|
12
12
|
cleanupSourceMaps: boolean;
|
|
13
|
-
}
|
|
14
|
-
|
|
13
|
+
}
|
|
14
|
+
interface PreflightDisabled {
|
|
15
15
|
enabled: false;
|
|
16
16
|
environment: string | undefined;
|
|
17
17
|
debug: boolean;
|
|
18
18
|
cleanupSourceMaps: boolean;
|
|
19
|
-
}
|
|
19
|
+
}
|
|
20
20
|
type PreflightConfig = PreflightEnabled | PreflightDisabled;
|
|
21
21
|
//#endregion
|
|
22
22
|
export { NonEmptyString, PreflightConfig, PreflightDisabled, PreflightEnabled, toNonEmptyString };
|
package/dist/lib/types.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.mts","names":[],"sources":["../../src/lib/types.ts"],"sourcesContent":[],"mappings":";KACY,cAAA;EAAA,SAAA,OAAA,EAAc,OAAA,MAAA;AAE1B,CAAA;
|
|
1
|
+
{"version":3,"file":"types.d.mts","names":[],"sources":["../../src/lib/types.ts"],"sourcesContent":[],"mappings":";KACY,cAAA;EAAA,SAAA,OAAA,EAAc,OAAA,MAAA;AAE1B,CAAA;AAIiB,iBAJD,gBAAA,CAML,KAAA,EAAA,MACE,CAAA,EAPoC,cAOtB,GAAA,IAAA;AAMV,UATA,gBAAA,CASiB;EAOtB,OAAA,EAAA,IAAA;WAdD;aACE;;;;;UAMI,iBAAA;;;;;;KAOL,eAAA,GAAkB,mBAAmB"}
|
package/dist/lib/types.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.mjs","names":[],"sources":["../../src/lib/types.ts"],"sourcesContent":["// Branded non-empty string type to prevent passing empty strings accidentally\nexport type NonEmptyString = string & { readonly __brand: unique symbol };\n\nexport function toNonEmptyString(value: string): NonEmptyString | null {\n return value.length > 0 ? (value as NonEmptyString) : null;\n}\n\nexport
|
|
1
|
+
{"version":3,"file":"types.mjs","names":[],"sources":["../../src/lib/types.ts"],"sourcesContent":["// Branded non-empty string type to prevent passing empty strings accidentally\nexport type NonEmptyString = string & { readonly __brand: unique symbol };\n\nexport function toNonEmptyString(value: string): NonEmptyString | null {\n return value.length > 0 ? (value as NonEmptyString) : null;\n}\n\nexport interface PreflightEnabled {\n enabled: true;\n surface: NonEmptyString;\n secretKey: NonEmptyString;\n environment: string;\n debug: boolean;\n cleanupSourceMaps: boolean;\n}\n\nexport interface PreflightDisabled {\n enabled: false;\n environment: string | undefined;\n debug: boolean;\n cleanupSourceMaps: boolean;\n}\n\nexport type PreflightConfig = PreflightEnabled | PreflightDisabled;\n"],"mappings":";AAGA,SAAgB,iBAAiB,OAAsC;AACrE,QAAO,MAAM,SAAS,IAAK,QAA2B"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"middleware.d.mts","names":[],"sources":["../../src/server/middleware.ts"],"sourcesContent":[],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"middleware.d.mts","names":[],"sources":["../../src/server/middleware.ts"],"sourcesContent":[],"mappings":";;;iBAagB,uBAAA,uBACQ,gBAAgB,QAAQ,gBAAgB,yBAIvC,gBAAW,QAAA;iBAoCpB,qBAAA,gBACC,gBAAgB,QAAQ,kBAIpB,gBAAW,QAAA;AA9ChB,iBAkFA,4BAlFuB,CAAA,UAAA,CAAA,GAAA,IAAA,EAAA,OAAA,EAAA,EAAA,GAmFH,OAnFG,CAAA,OAAA,CAAA,CAAA,CAAA,SAAA,EAoF1B,CApF0B,EAAA,aAAA,CAAA,EAAA,MAAA,CAAA,EAoFE,CApFF;AACf,iBA4HR,2BAAA,CAAA,CA5HQ,EAAA,CAAA,KAAA,EA+HD,KA/HC,EAAA,SAAA,EAAA;EAAwB,MAAA,CAAA,EAAA,MAAA;CAAR,EAAA,GA+HoB,OA/HpB,CAAA,IAAA,CAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"middleware.mjs","names":[],"sources":["../../src/server/middleware.ts"],"sourcesContent":["import { withInterfereLogger } from \"@interfere/effect-utils/observability\";\nimport { withSpan } from \"@interfere/react/effect/layers/tracer.layer\";\nimport { Effect, Layer } from \"effect\";\nimport type { NextRequest, NextResponse } from \"next/server\";\nimport { ConfigServiceLive } 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(ConfigServiceLive, 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* withSpan(\n \"middleware.request\",\n Effect.tryPromise({\n try: () => Promise.resolve(middleware(request)),\n catch: (error) => error as Error,\n }),\n { attributes: { path: request.nextUrl.pathname } }\n ).pipe(\n Effect.tapError((error) =>\n errorTracking.captureError(error, request, {\n pathname: request.nextUrl.pathname,\n middleware: true,\n duration: Date.now() - startTime,\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(ConfigServiceLive, 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* withSpan(\n \"api.request\",\n Effect.tryPromise({\n try: () => handler(req),\n catch: (error) => error as Error,\n }),\n { attributes: { path: req.nextUrl.pathname } }\n ).pipe(\n Effect.tapError((error) =>\n errorTracking.captureError(error, req, {\n pathname: req.nextUrl.pathname,\n type: \"api_route\",\n duration: Date.now() - startTime,\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(ConfigServiceLive, ErrorTrackingServiceLive);\n\n async function wrapped(this: unknown, ...args: Parameters<T>) {\n const self = this;\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: (error) => error as Error,\n }).pipe(\n Effect.tapError((error) =>\n errorTracking.captureError(error, undefined, {\n type: \"server_component\",\n componentName: componentName || component.name,\n })\n )\n );\n\n return result;\n }).pipe(Effect.provide(layer));\n\n return await Effect.runPromise(withInterfereLogger(\"next\", program));\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(ConfigServiceLive, 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(\n Effect.provide(layer),\n Effect.catchAll(() => Effect.void) // Best-effort\n );\n\n await Effect.runPromise(withInterfereLogger(\"next\", program));\n };\n}\n"],"mappings":";;;;;;;
|
|
1
|
+
{"version":3,"file":"middleware.mjs","names":[],"sources":["../../src/server/middleware.ts"],"sourcesContent":["import { withInterfereLogger } from \"@interfere/effect-utils/observability\";\nimport { withSpan } from \"@interfere/react/effect/layers/tracer.layer\";\n\nimport { Effect, Layer } from \"effect\";\nimport type { NextRequest, NextResponse } from \"next/server\";\n\nimport { ConfigServiceLive } 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(ConfigServiceLive, 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* withSpan(\n \"middleware.request\",\n Effect.tryPromise({\n try: () => Promise.resolve(middleware(request)),\n catch: (error) => error as Error,\n }),\n { attributes: { path: request.nextUrl.pathname } }\n ).pipe(\n Effect.tapError((error) =>\n errorTracking.captureError(error, request, {\n pathname: request.nextUrl.pathname,\n middleware: true,\n duration: Date.now() - startTime,\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(ConfigServiceLive, 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* withSpan(\n \"api.request\",\n Effect.tryPromise({\n try: () => handler(req),\n catch: (error) => error as Error,\n }),\n { attributes: { path: req.nextUrl.pathname } }\n ).pipe(\n Effect.tapError((error) =>\n errorTracking.captureError(error, req, {\n pathname: req.nextUrl.pathname,\n type: \"api_route\",\n duration: Date.now() - startTime,\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(ConfigServiceLive, ErrorTrackingServiceLive);\n\n async function wrapped(this: unknown, ...args: Parameters<T>) {\n const self = this;\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: (error) => error as Error,\n }).pipe(\n Effect.tapError((error) =>\n errorTracking.captureError(error, undefined, {\n type: \"server_component\",\n componentName: componentName || component.name,\n })\n )\n );\n\n return result;\n }).pipe(Effect.provide(layer));\n\n return await Effect.runPromise(withInterfereLogger(\"next\", program));\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(ConfigServiceLive, 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(\n Effect.provide(layer),\n Effect.catchAll(() => Effect.void) // Best-effort\n );\n\n await Effect.runPromise(withInterfereLogger(\"next\", program));\n };\n}\n"],"mappings":";;;;;;;AAaA,SAAgB,wBACd,YACA;CACA,MAAM,QAAQ,MAAM,SAAS,mBAAmB,yBAAyB;AAEzE,QAAO,OAAO,YAAyB;AAErC,MAAI,QAAQ,QAAQ,SAAS,WAAW,iBAAiB,CACvD,QAAO,WAAW,QAAQ;EAG5B,MAAM,YAAY,KAAK,KAAK;EAE5B,MAAM,UAAU,OAAO,IAAI,aAAa;GACtC,MAAM,gBAAgB,OAAO;AAmB7B,UAjBiB,OAAO,SACtB,sBACA,OAAO,WAAW;IAChB,WAAW,QAAQ,QAAQ,WAAW,QAAQ,CAAC;IAC/C,QAAQ,UAAU;IACnB,CAAC,EACF,EAAE,YAAY,EAAE,MAAM,QAAQ,QAAQ,UAAU,EAAE,CACnD,CAAC,KACA,OAAO,UAAU,UACf,cAAc,aAAa,OAAO,SAAS;IACzC,UAAU,QAAQ,QAAQ;IAC1B,YAAY;IACZ,UAAU,KAAK,KAAK,GAAG;IACxB,CAAC,CACH,CACF;IAGD,CAAC,KAAK,OAAO,QAAQ,MAAM,CAAC;AAE9B,SAAO,MAAM,OAAO,WAAW,oBAAoB,QAAQ,QAAQ,CAAC;;;AAKxE,SAAgB,sBACd,SACA;CACA,MAAM,QAAQ,MAAM,SAAS,mBAAmB,yBAAyB;AAEzE,QAAO,OAAO,QAAqB;AAEjC,MAAI,IAAI,QAAQ,SAAS,WAAW,iBAAiB,CACnD,QAAO,QAAQ,IAAI;EAGrB,MAAM,YAAY,KAAK,KAAK;EAE5B,MAAM,UAAU,OAAO,IAAI,aAAa;GACtC,MAAM,gBAAgB,OAAO;AAmB7B,UAjBiB,OAAO,SACtB,eACA,OAAO,WAAW;IAChB,WAAW,QAAQ,IAAI;IACvB,QAAQ,UAAU;IACnB,CAAC,EACF,EAAE,YAAY,EAAE,MAAM,IAAI,QAAQ,UAAU,EAAE,CAC/C,CAAC,KACA,OAAO,UAAU,UACf,cAAc,aAAa,OAAO,KAAK;IACrC,UAAU,IAAI,QAAQ;IACtB,MAAM;IACN,UAAU,KAAK,KAAK,GAAG;IACxB,CAAC,CACH,CACF;IAGD,CAAC,KAAK,OAAO,QAAQ,MAAM,CAAC;AAE9B,SAAO,MAAM,OAAO,WAAW,oBAAoB,QAAQ,QAAQ,CAAC;;;AAKxE,SAAgB,6BAEd,WAAc,eAA2B;CACzC,MAAM,QAAQ,MAAM,SAAS,mBAAmB,yBAAyB;CAEzE,eAAe,QAAuB,GAAG,MAAqB;EAC5D,MAAM,OAAO;EAEb,MAAM,UAAU,OAAO,IAAI,aAAa;GACtC,MAAM,gBAAgB,OAAO;AAc7B,UAZe,OAAO,OAAO,WAAW;IACtC,WAAW,UAAU,MAAM,MAAe,KAAc;IACxD,QAAQ,UAAU;IACnB,CAAC,CAAC,KACD,OAAO,UAAU,UACf,cAAc,aAAa,OAAO,QAAW;IAC3C,MAAM;IACN,eAAe,iBAAiB,UAAU;IAC3C,CAAC,CACH,CACF;IAGD,CAAC,KAAK,OAAO,QAAQ,MAAM,CAAC;AAE9B,SAAO,MAAM,OAAO,WAAW,oBAAoB,QAAQ,QAAQ,CAAC;;AAItE,KAAI;AACF,SAAO,eAAe,SAAS,QAAQ;GACrC,OAAO,UAAU;GACjB,cAAc;GACf,CAAC;SACI;AAIR,QAAO;;AAIT,SAAgB,8BAA8B;CAC5C,MAAM,QAAQ,MAAM,SAAS,mBAAmB,yBAAyB;AAEzE,QAAO,OAAO,OAAc,cAAmC;EAC7D,MAAM,UAAU,OAAO,IAAI,aAAa;AAGtC,WAFsB,OAAO,sBAER,aAAa,OAAO,QAAW;IAClD,QAAQ,UAAU;IAClB,MAAM;IACP,CAAC;IACF,CAAC,KACD,OAAO,QAAQ,MAAM,EACrB,OAAO,eAAe,OAAO,KAAK,CACnC;AAED,QAAM,OAAO,WAAW,oBAAoB,QAAQ,QAAQ,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"proxy.d.mts","names":[],"sources":["../../src/server/proxy.ts"],"sourcesContent":[],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"proxy.d.mts","names":[],"sources":["../../src/server/proxy.ts"],"sourcesContent":[],"mappings":";;;iBAagB,kBAAA,gBAEP,oBACE,mBACJ,QAAQ,YAAY,iBAIN,oBAAoB,mBAAc,QAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"proxy.mjs","names":[],"sources":["../../src/server/proxy.ts"],"sourcesContent":["import { withInterfereLogger } from \"@interfere/effect-utils/observability\";\nimport { withSpan } from \"@interfere/react/effect/layers/tracer.layer\";\nimport { Effect, Layer } from \"effect\";\nimport type { NextFetchEvent, NextRequest } from \"next/server\";\nimport { ConfigServiceLive } 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(ConfigServiceLive, 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* withSpan(\n \"proxy.request\",\n Effect.tryPromise({\n try: () => Promise.resolve(handler(req, event)),\n catch: (error) => error as Error,\n }),\n { attributes: { path: req.nextUrl.pathname } }\n ).pipe(\n Effect.tapError((error) =>\n errorTracking.captureError(error, req, {\n pathname: req.nextUrl.pathname,\n proxy: true,\n duration: Date.now() - started,\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":";;;;;;;
|
|
1
|
+
{"version":3,"file":"proxy.mjs","names":[],"sources":["../../src/server/proxy.ts"],"sourcesContent":["import { withInterfereLogger } from \"@interfere/effect-utils/observability\";\nimport { withSpan } from \"@interfere/react/effect/layers/tracer.layer\";\n\nimport { Effect, Layer } from \"effect\";\nimport type { NextFetchEvent, NextRequest } from \"next/server\";\n\nimport { ConfigServiceLive } 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(ConfigServiceLive, 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* withSpan(\n \"proxy.request\",\n Effect.tryPromise({\n try: () => Promise.resolve(handler(req, event)),\n catch: (error) => error as Error,\n }),\n { attributes: { path: req.nextUrl.pathname } }\n ).pipe(\n Effect.tapError((error) =>\n errorTracking.captureError(error, req, {\n pathname: req.nextUrl.pathname,\n proxy: true,\n duration: Date.now() - started,\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":";;;;;;;AAaA,SAAgB,mBACd,SAIA;CACA,MAAM,QAAQ,MAAM,SAAS,mBAAmB,yBAAyB;AAEzE,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;AAmB7B,UAjBiB,OAAO,SACtB,iBACA,OAAO,WAAW;IAChB,WAAW,QAAQ,QAAQ,QAAQ,KAAK,MAAM,CAAC;IAC/C,QAAQ,UAAU;IACnB,CAAC,EACF,EAAE,YAAY,EAAE,MAAM,IAAI,QAAQ,UAAU,EAAE,CAC/C,CAAC,KACA,OAAO,UAAU,UACf,cAAc,aAAa,OAAO,KAAK;IACrC,UAAU,IAAI,QAAQ;IACtB,OAAO;IACP,UAAU,KAAK,KAAK,GAAG;IACxB,CAAC,CACH,CACF;IAGD,CAAC,KAAK,OAAO,QAAQ,MAAM,CAAC;AAE9B,SAAO,MAAM,OAAO,WAAW,oBAAoB,QAAQ,QAAQ,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"route-handler.d.mts","names":[],"sources":["../../src/server/route-handler.ts"],"sourcesContent":[],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"route-handler.d.mts","names":[],"sources":["../../src/server/route-handler.ts"],"sourcesContent":[],"mappings":";;;KAwBK,YAAA,aAAyB,gBAAgB,QAAQ;cAmBzC,KAAK;AAnBb,cA2JQ,IA3JI,EA2JE,YA3JF;AAAa,cAoQjB,OApQiB,EAoQR,YApQQ"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"route-handler.mjs","names":["GET: RouteHandler","parsed: unknown","POST: RouteHandler","OPTIONS: RouteHandler","uuidv7"],"sources":["../../src/server/route-handler.ts"],"sourcesContent":["/** biome-ignore-all lint/suspicious/useAwait: route handlers must be async */\n\"use server\";\n\nimport { API_PATHS } from \"@interfere/constants/api\";\nimport { withInterfereLogger } from \"@interfere/effect-utils/observability\";\nimport {\n createBackoffSchedule,\n shouldRetryHttp,\n withTimeoutFail,\n} from \"@interfere/effect-utils/retry\";\nimport { withSpan } from \"@interfere/react/effect/layers/tracer.layer\";\nimport { envelopeSchema } from \"@interfere/types/sdk/envelope\";\nimport { Data, Effect, Layer, Schedule } from \"effect\";\nimport type { NextRequest } from \"next/server\";\nimport { v7 as uuidv7 } from \"uuid\";\nimport { z } from \"zod\";\nimport { ConfigService, ConfigServiceLive } from \"./services/config.service\";\nimport {\n ErrorTrackingService,\n ErrorTrackingServiceLive,\n} from \"./services/error-tracking.service\";\n\ntype RouteHandler = (request: NextRequest) => Promise<Response>;\n\nconst layer = Layer.mergeAll(ConfigServiceLive, ErrorTrackingServiceLive);\n\n// Error types for the proxy\nclass ProxyError extends Data.TaggedError(\"ProxyError\")<{\n readonly status: number;\n readonly message: string;\n readonly body?: string;\n}> {}\n\nconst TIMEOUT_MS = 10_000;\nconst RETRY_COUNT = 2;\nconst HTTP_BAD_GATEWAY = 502;\nconst RETRY_INITIAL_MS = 100;\nconst RETRY_MAX_MS = 2000;\n\nconst envelopesArraySchema = z.array(envelopeSchema);\n\nexport const GET: RouteHandler = async () => {\n const program = Effect.gen(function* () {\n const config = yield* ConfigService;\n const configured = yield* config.getSecretKey.pipe(\n Effect.map(() => true),\n Effect.catchAll(() => Effect.succeed(false))\n );\n\n return Response.json({ status: \"ok\", configured, timestamp: Date.now() });\n }).pipe(Effect.provide(layer));\n\n return await Effect.runPromise(withInterfereLogger(\"next\", program));\n};\n\nconst proxyIngestRequest = (\n request: Request,\n params: { apiUrl: string; surfaceToken: string }\n) =>\n Effect.gen(function* () {\n const incomingTrace = request.headers.get(\"traceparent\") || undefined;\n const traceparent = incomingTrace ?? generateTraceparent();\n\n // Read request body once\n const body = yield* Effect.tryPromise({\n try: () => request.clone().text(),\n catch: () =>\n new ProxyError({ status: 400, message: \"Invalid request body\" }),\n });\n\n // Validate the envelopes before proxying\n let parsed: unknown;\n\n try {\n parsed = JSON.parse(body);\n } catch (error) {\n yield* Effect.logWarning(\"Invalid JSON in request body\").pipe(\n Effect.annotateLogs({ error: String(error) })\n );\n\n return yield* Effect.fail(\n new ProxyError({\n status: 400,\n message: \"Invalid JSON in request body\",\n body: String(error),\n })\n );\n }\n\n const validation = envelopesArraySchema.safeParse(parsed);\n\n if (!validation.success) {\n const issues = validation.error.issues.map((issue) => ({\n path: issue.path.join(\".\"),\n message: issue.message,\n code: issue.code,\n }));\n\n yield* Effect.logWarning(\"Invalid envelope schema\").pipe(\n Effect.annotateLogs({ validationErrors: issues })\n );\n\n // For malformed envelopes, short‑circuit and return a 422 directly.\n // This lets SDKs drop bad envelopes instead of retrying or feeding them\n // back into the error tracking pipeline.\n const b = JSON.stringify({\n ok: false,\n code: \"INVALID_ENVELOPE_SCHEMA\",\n errors: issues,\n });\n\n return new Response(b, {\n status: 422,\n headers: {\n \"content-type\": \"application/json\",\n },\n });\n }\n\n // De-duplicate by envelope.uuid to avoid reprocessing on client retries\n const seen = new Set<string>();\n\n const deduped = validation.data.filter((env) => {\n if (seen.has(env.uuid)) {\n return false;\n }\n\n seen.add(env.uuid);\n return true;\n });\n\n const dropped = validation.data.length - deduped.length;\n\n yield* Effect.logDebug(\"Envelope validation successful\").pipe(\n Effect.annotateLogs({\n envelopeCount: validation.data.length,\n dedupedCount: deduped.length,\n droppedDuplicates: dropped,\n })\n );\n\n const upstream = yield* withTimeoutFail(\n Effect.tryPromise({\n try: () =>\n fetch(`${params.apiUrl}${API_PATHS.INGEST_V0}`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"x-interfere-surface\": params.surfaceToken,\n traceparent,\n },\n body: JSON.stringify(deduped),\n }),\n catch: (error) =>\n new ProxyError({\n status: 0,\n message: error instanceof Error ? error.message : String(error),\n }),\n }),\n TIMEOUT_MS,\n () => new ProxyError({ status: 0, message: \"Network timeout\" })\n );\n\n const responseBody = yield* Effect.tryPromise({\n try: () => upstream.text(),\n catch: () => \"\",\n });\n\n return new Response(responseBody, {\n status: upstream.status,\n headers: {\n \"content-type\":\n upstream.headers.get(\"content-type\") || \"application/json\",\n },\n });\n });\n\nexport const POST: RouteHandler = async (request): Promise<Response> => {\n const program = Effect.gen(function* () {\n yield* Effect.logDebug(\"Handling ingest proxy request\").pipe(\n Effect.annotateLogs({ path: request.nextUrl.pathname })\n );\n\n const config = yield* ConfigService;\n const errorTracking = yield* ErrorTrackingService;\n\n // Check secret key\n yield* config.getSecretKey.pipe(\n Effect.tapError(() =>\n Effect.logError(\"Service not configured - missing INTERFERE_SECRET_KEY\")\n ),\n Effect.catchTag(\"ConfigError\", () =>\n Effect.fail(\n new ProxyError({\n status: 503,\n message:\n \"Service not configured. INTERFERE_SECRET_KEY environment variable is required.\",\n })\n )\n )\n );\n\n // Get public token\n const surfaceToken = yield* config.getPublicToken.pipe(\n Effect.tapError(() =>\n Effect.logError(\"Auth not available - missing public token\")\n ),\n Effect.catchTag(\"ConfigError\", () =>\n Effect.fail(\n new ProxyError({\n status: 503,\n message: \"Auth not available\",\n })\n )\n )\n );\n\n const apiUrl = yield* config.getApiUrl;\n\n // Proxy the request with retry logic\n const schedule = createBackoffSchedule(\n RETRY_INITIAL_MS,\n RETRY_MAX_MS,\n RETRY_COUNT\n ).pipe(\n Schedule.whileInput((e: unknown) =>\n e instanceof ProxyError ? shouldRetryHttp(e.status) : false\n )\n );\n\n const response = yield* withSpan(\n \"proxy.forward\",\n proxyIngestRequest(request, { apiUrl, surfaceToken }),\n { attributes: {} }\n ).pipe(\n Effect.retry(schedule),\n Effect.tapError((error) =>\n Effect.gen(function* () {\n yield* errorTracking.captureError(error, request, {\n pathname: request.nextUrl.pathname,\n type: \"api_route\",\n });\n yield* Effect.logError(\"Proxy request failed\").pipe(\n Effect.annotateLogs({\n error: String(error),\n status: error instanceof ProxyError ? error.status : 0,\n })\n );\n })\n )\n );\n\n yield* Effect.logDebug(\"Proxy request completed\").pipe(\n Effect.annotateLogs({ status: response.status })\n );\n\n return response;\n }).pipe(\n Effect.provide(layer),\n Effect.catchTag(\"ProxyError\", (error) =>\n Effect.succeed(\n Response.json(\n { error: error.message },\n { status: error.status || HTTP_BAD_GATEWAY }\n )\n )\n ),\n Effect.catchAll((error) => {\n Effect.runFork(\n Effect.logError(\"Unexpected error in route handler\").pipe(\n Effect.annotateLogs({ error: String(error) })\n )\n );\n return Effect.succeed(\n Response.json({ error: \"Internal server error\" }, { status: 500 })\n );\n })\n );\n\n return await Effect.runPromise(withInterfereLogger(\"next\", program));\n};\n\nexport const OPTIONS: RouteHandler = async () =>\n Effect.runPromise(\n withInterfereLogger(\n \"next\",\n Effect.succeed(\n new Response(null, {\n status: 200,\n headers: {\n \"Access-Control-Allow-Origin\": \"*\",\n \"Access-Control-Allow-Methods\": \"GET, POST, OPTIONS\",\n \"Access-Control-Allow-Headers\": \"Content-Type\",\n \"Access-Control-Max-Age\": \"86400\",\n },\n })\n )\n )\n );\n\n// Helper functions\nconst VERSION = \"00\";\nconst TRACE_ID_BYTES = 16;\nconst SPAN_ID_BYTES = 8;\nconst FLAGS = \"01\";\n\nfunction generateTraceparent() {\n const traceId = randomHex(TRACE_ID_BYTES);\n const spanId = randomHex(SPAN_ID_BYTES);\n return `${VERSION}-${traceId}-${spanId}-${FLAGS}`;\n}\n\nfunction randomHex(bytes: number): string {\n const raw = uuidv7().replace(/-/g, \"\");\n const needed = bytes * 2;\n if (raw.length >= needed) {\n return raw.substring(0, needed);\n }\n return (raw + raw).substring(0, needed);\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"route-handler.mjs","names":["GET: RouteHandler","parsed: unknown","POST: RouteHandler","OPTIONS: RouteHandler","uuidv7"],"sources":["../../src/server/route-handler.ts"],"sourcesContent":["/** biome-ignore-all lint/suspicious/useAwait: route handlers must be async */\n\"use server\";\n\nimport { API_PATHS } from \"@interfere/constants/api\";\nimport { withInterfereLogger } from \"@interfere/effect-utils/observability\";\nimport {\n createBackoffSchedule,\n shouldRetryHttp,\n withTimeoutFail,\n} from \"@interfere/effect-utils/retry\";\nimport { withSpan } from \"@interfere/react/effect/layers/tracer.layer\";\nimport { envelopeSchema } from \"@interfere/types/sdk/envelope\";\n\nimport { Data, Effect, Layer, Schedule } from \"effect\";\nimport type { NextRequest } from \"next/server\";\nimport { v7 as uuidv7 } from \"uuid\";\nimport { z } from \"zod\";\n\nimport { ConfigService, ConfigServiceLive } from \"./services/config.service\";\nimport {\n ErrorTrackingService,\n ErrorTrackingServiceLive,\n} from \"./services/error-tracking.service\";\n\ntype RouteHandler = (request: NextRequest) => Promise<Response>;\n\nconst layer = Layer.mergeAll(ConfigServiceLive, ErrorTrackingServiceLive);\n\n// Error types for the proxy\nclass ProxyError extends Data.TaggedError(\"ProxyError\")<{\n readonly status: number;\n readonly message: string;\n readonly body?: string;\n}> {}\n\nconst TIMEOUT_MS = 10_000;\nconst RETRY_COUNT = 2;\nconst HTTP_BAD_GATEWAY = 502;\nconst RETRY_INITIAL_MS = 100;\nconst RETRY_MAX_MS = 2000;\n\nconst envelopesArraySchema = z.array(envelopeSchema);\n\nexport const GET: RouteHandler = async () => {\n const program = Effect.gen(function* () {\n const config = yield* ConfigService;\n const configured = yield* config.getSecretKey.pipe(\n Effect.map(() => true),\n Effect.catchAll(() => Effect.succeed(false))\n );\n\n return Response.json({ status: \"ok\", configured, timestamp: Date.now() });\n }).pipe(Effect.provide(layer));\n\n return await Effect.runPromise(withInterfereLogger(\"next\", program));\n};\n\nconst proxyIngestRequest = (\n request: Request,\n params: { apiUrl: string; surfaceToken: string }\n) =>\n Effect.gen(function* () {\n const incomingTrace = request.headers.get(\"traceparent\") || undefined;\n const traceparent = incomingTrace ?? generateTraceparent();\n\n // Read request body once\n const body = yield* Effect.tryPromise({\n try: () => request.clone().text(),\n catch: () =>\n new ProxyError({ status: 400, message: \"Invalid request body\" }),\n });\n\n // Validate the envelopes before proxying\n let parsed: unknown;\n\n try {\n parsed = JSON.parse(body);\n } catch (error) {\n yield* Effect.logWarning(\"Invalid JSON in request body\").pipe(\n Effect.annotateLogs({ error: String(error) })\n );\n\n return yield* Effect.fail(\n new ProxyError({\n status: 400,\n message: \"Invalid JSON in request body\",\n body: String(error),\n })\n );\n }\n\n const validation = envelopesArraySchema.safeParse(parsed);\n\n if (!validation.success) {\n const issues = validation.error.issues.map((issue) => ({\n path: issue.path.join(\".\"),\n message: issue.message,\n code: issue.code,\n }));\n\n yield* Effect.logWarning(\"Invalid envelope schema\").pipe(\n Effect.annotateLogs({ validationErrors: issues })\n );\n\n // For malformed envelopes, short‑circuit and return a 422 directly.\n // This lets SDKs drop bad envelopes instead of retrying or feeding them\n // back into the error tracking pipeline.\n const b = JSON.stringify({\n ok: false,\n code: \"INVALID_ENVELOPE_SCHEMA\",\n errors: issues,\n });\n\n return new Response(b, {\n status: 422,\n headers: {\n \"content-type\": \"application/json\",\n },\n });\n }\n\n // De-duplicate by envelope.uuid to avoid reprocessing on client retries\n const seen = new Set<string>();\n\n const deduped = validation.data.filter((env) => {\n if (seen.has(env.uuid)) {\n return false;\n }\n\n seen.add(env.uuid);\n return true;\n });\n\n const dropped = validation.data.length - deduped.length;\n\n yield* Effect.logDebug(\"Envelope validation successful\").pipe(\n Effect.annotateLogs({\n envelopeCount: validation.data.length,\n dedupedCount: deduped.length,\n droppedDuplicates: dropped,\n })\n );\n\n const upstream = yield* withTimeoutFail(\n Effect.tryPromise({\n try: () =>\n fetch(`${params.apiUrl}${API_PATHS.INGEST_V0}`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"x-interfere-surface\": params.surfaceToken,\n traceparent,\n },\n body: JSON.stringify(deduped),\n }),\n catch: (error) =>\n new ProxyError({\n status: 0,\n message: error instanceof Error ? error.message : String(error),\n }),\n }),\n TIMEOUT_MS,\n () => new ProxyError({ status: 0, message: \"Network timeout\" })\n );\n\n const responseBody = yield* Effect.tryPromise({\n try: () => upstream.text(),\n catch: () => \"\",\n });\n\n return new Response(responseBody, {\n status: upstream.status,\n headers: {\n \"content-type\":\n upstream.headers.get(\"content-type\") || \"application/json\",\n },\n });\n });\n\nexport const POST: RouteHandler = async (request): Promise<Response> => {\n const program = Effect.gen(function* () {\n yield* Effect.logDebug(\"Handling ingest proxy request\").pipe(\n Effect.annotateLogs({ path: request.nextUrl.pathname })\n );\n\n const config = yield* ConfigService;\n const errorTracking = yield* ErrorTrackingService;\n\n // Check secret key\n yield* config.getSecretKey.pipe(\n Effect.tapError(() =>\n Effect.logError(\"Service not configured - missing INTERFERE_SECRET_KEY\")\n ),\n Effect.catchTag(\"ConfigError\", () =>\n Effect.fail(\n new ProxyError({\n status: 503,\n message:\n \"Service not configured. INTERFERE_SECRET_KEY environment variable is required.\",\n })\n )\n )\n );\n\n // Get public token\n const surfaceToken = yield* config.getPublicToken.pipe(\n Effect.tapError(() =>\n Effect.logError(\"Auth not available - missing public token\")\n ),\n Effect.catchTag(\"ConfigError\", () =>\n Effect.fail(\n new ProxyError({\n status: 503,\n message: \"Auth not available\",\n })\n )\n )\n );\n\n const apiUrl = yield* config.getApiUrl;\n\n // Proxy the request with retry logic\n const schedule = createBackoffSchedule(\n RETRY_INITIAL_MS,\n RETRY_MAX_MS,\n RETRY_COUNT\n ).pipe(\n Schedule.whileInput((e: unknown) =>\n e instanceof ProxyError ? shouldRetryHttp(e.status) : false\n )\n );\n\n const response = yield* withSpan(\n \"proxy.forward\",\n proxyIngestRequest(request, { apiUrl, surfaceToken }),\n { attributes: {} }\n ).pipe(\n Effect.retry(schedule),\n Effect.tapError((error) =>\n Effect.gen(function* () {\n yield* errorTracking.captureError(error, request, {\n pathname: request.nextUrl.pathname,\n type: \"api_route\",\n });\n yield* Effect.logError(\"Proxy request failed\").pipe(\n Effect.annotateLogs({\n error: String(error),\n status: error instanceof ProxyError ? error.status : 0,\n })\n );\n })\n )\n );\n\n yield* Effect.logDebug(\"Proxy request completed\").pipe(\n Effect.annotateLogs({ status: response.status })\n );\n\n return response;\n }).pipe(\n Effect.provide(layer),\n Effect.catchTag(\"ProxyError\", (error) =>\n Effect.succeed(\n Response.json(\n { error: error.message },\n { status: error.status || HTTP_BAD_GATEWAY }\n )\n )\n ),\n Effect.catchAll((error) => {\n Effect.runFork(\n Effect.logError(\"Unexpected error in route handler\").pipe(\n Effect.annotateLogs({ error: String(error) })\n )\n );\n return Effect.succeed(\n Response.json({ error: \"Internal server error\" }, { status: 500 })\n );\n })\n );\n\n return await Effect.runPromise(withInterfereLogger(\"next\", program));\n};\n\nexport const OPTIONS: RouteHandler = async () =>\n Effect.runPromise(\n withInterfereLogger(\n \"next\",\n Effect.succeed(\n new Response(null, {\n status: 200,\n headers: {\n \"Access-Control-Allow-Origin\": \"*\",\n \"Access-Control-Allow-Methods\": \"GET, POST, OPTIONS\",\n \"Access-Control-Allow-Headers\": \"Content-Type\",\n \"Access-Control-Max-Age\": \"86400\",\n },\n })\n )\n )\n );\n\n// Helper functions\nconst VERSION = \"00\";\nconst TRACE_ID_BYTES = 16;\nconst SPAN_ID_BYTES = 8;\nconst FLAGS = \"01\";\n\nfunction generateTraceparent() {\n const traceId = randomHex(TRACE_ID_BYTES);\n const spanId = randomHex(SPAN_ID_BYTES);\n return `${VERSION}-${traceId}-${spanId}-${FLAGS}`;\n}\n\nfunction randomHex(bytes: number): string {\n const raw = uuidv7().replace(/-/g, \"\");\n const needed = bytes * 2;\n if (raw.length >= needed) {\n return raw.substring(0, needed);\n }\n return (raw + raw).substring(0, needed);\n}\n"],"mappings":"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"}
|
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
import { NonEmptyString } from "../../lib/types.mjs";
|
|
2
2
|
import { Context, Effect, Layer } from "effect";
|
|
3
|
-
import * as
|
|
4
|
-
import * as
|
|
3
|
+
import * as effect_Types15 from "effect/Types";
|
|
4
|
+
import * as effect_Cause15 from "effect/Cause";
|
|
5
5
|
|
|
6
6
|
//#region src/server/services/config.service.d.ts
|
|
7
|
-
declare const ConfigError_base: new <A extends Record<string, any> = {}>(args:
|
|
7
|
+
declare const ConfigError_base: new <A extends Record<string, any> = {}>(args: effect_Types15.Equals<A, {}> extends true ? void : { readonly [P in keyof A as P extends "_tag" ? never : P]: A[P] }) => effect_Cause15.YieldableError & {
|
|
8
8
|
readonly _tag: "ConfigError";
|
|
9
9
|
} & Readonly<A>;
|
|
10
10
|
declare class ConfigError extends ConfigError_base<{
|
|
11
11
|
readonly message: string;
|
|
12
12
|
}> {}
|
|
13
|
-
|
|
13
|
+
interface ConfigService {
|
|
14
14
|
readonly getApiUrl: Effect.Effect<string>;
|
|
15
15
|
readonly getSecretKey: Effect.Effect<NonEmptyString, ConfigError>;
|
|
16
16
|
readonly getPublicToken: Effect.Effect<string, ConfigError>;
|
|
17
|
-
}
|
|
17
|
+
}
|
|
18
18
|
declare const ConfigService: Context.Tag<ConfigService, ConfigService>;
|
|
19
19
|
declare const ConfigServiceLive: Layer.Layer<ConfigService, never, never>;
|
|
20
20
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.service.d.mts","names":[],"sources":["../../../src/server/services/config.service.ts"],"sourcesContent":[],"mappings":";;;;;;
|
|
1
|
+
{"version":3,"file":"config.service.d.mts","names":[],"sources":["../../../src/server/services/config.service.ts"],"sourcesContent":[],"mappings":";;;;;;cAKyD;;;cAI5C,WAAA,SAAoB;EAJwB,SAAA,OAAA,EAAA,MAAA;;UAQxC,aAAA;sBACK,MAAA,CAAO;yBACJ,MAAA,CAAO,OAAO,gBAAgB;2BAC5B,MAAA,CAAO,eAAe;;cAGpC,eAAa,OAAA,CAAA,IAAA,eAAA;cAsDb,mBAAiB,KAAA,CAAA,MAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.service.mjs","names":["cachedPublicToken: { token: string; expiresAt: number } | null","getPublicTokenShared"],"sources":["../../../src/server/services/config.service.ts"],"sourcesContent":["import { getPublicToken as getPublicTokenShared } from \"@interfere/react/server/auth\";\nimport { Context, Data, Effect, Layer } from \"effect\";\n\nimport { normalizeSecretKey, resolveApiUrl } from \"../../build/env-config.js\";\nimport type { NonEmptyString } from \"../../lib/types.js\";\n\nconst ONE_HOUR_MS = 3_600_000;\n\nexport class ConfigError extends Data.TaggedError(\"ConfigError\")<{\n readonly message: string;\n}> {}\n\nexport
|
|
1
|
+
{"version":3,"file":"config.service.mjs","names":["cachedPublicToken: { token: string; expiresAt: number } | null","getPublicTokenShared"],"sources":["../../../src/server/services/config.service.ts"],"sourcesContent":["import { getPublicToken as getPublicTokenShared } from \"@interfere/react/server/auth\";\n\nimport { Context, Data, Effect, Layer } from \"effect\";\n\nimport { normalizeSecretKey, resolveApiUrl } from \"../../build/env-config.js\";\nimport type { NonEmptyString } from \"../../lib/types.js\";\n\nconst ONE_HOUR_MS = 3_600_000;\n\nexport class ConfigError extends Data.TaggedError(\"ConfigError\")<{\n readonly message: string;\n}> {}\n\nexport interface ConfigService {\n readonly getApiUrl: Effect.Effect<string>;\n readonly getSecretKey: Effect.Effect<NonEmptyString, ConfigError>;\n readonly getPublicToken: Effect.Effect<string, ConfigError>;\n}\n\nexport const ConfigService = Context.GenericTag<ConfigService>(\n \"@interfere/next/ConfigService\"\n);\n\nlet cachedPublicToken: { token: string; expiresAt: number } | null = null;\n\nconst getSecretKey = (): Effect.Effect<NonEmptyString, ConfigError> =>\n Effect.gen(function* () {\n const normalized = normalizeSecretKey(process.env.INTERFERE_SECRET_KEY);\n\n if (!normalized) {\n return yield* Effect.fail(\n new ConfigError({ message: \"INTERFERE_SECRET_KEY not set or empty\" })\n );\n }\n\n return normalized;\n });\n\nconst getPublicTokenImpl = (): Effect.Effect<string, ConfigError> =>\n Effect.gen(function* () {\n const override = process.env.INTERFERE_PUBLIC_TOKEN;\n if (override) {\n return override;\n }\n\n const secret = yield* getSecretKey();\n const apiUrl = resolveApiUrl();\n\n const now = Date.now();\n if (cachedPublicToken && cachedPublicToken.expiresAt > now) {\n return cachedPublicToken.token;\n }\n\n const token = yield* Effect.tryPromise({\n try: () => getPublicTokenShared({ apiUrl, secret }),\n catch: (error) =>\n new ConfigError({ message: `Failed to get public token: ${error}` }),\n });\n\n if (!token) {\n return yield* Effect.fail(\n new ConfigError({ message: \"No token returned\" })\n );\n }\n\n cachedPublicToken = {\n token,\n expiresAt: now + ONE_HOUR_MS,\n };\n\n return token;\n });\n\nexport const ConfigServiceLive = Layer.succeed(\n ConfigService,\n ConfigService.of({\n getApiUrl: Effect.succeed(resolveApiUrl()),\n getSecretKey: getSecretKey(),\n getPublicToken: getPublicTokenImpl(),\n })\n);\n"],"mappings":";;;;;AAOA,MAAM,cAAc;AAEpB,IAAa,cAAb,cAAiC,KAAK,YAAY,cAAc,CAE7D;AAQH,MAAa,gBAAgB,QAAQ,WACnC,gCACD;AAED,IAAIA,oBAAiE;AAErE,MAAM,qBACJ,OAAO,IAAI,aAAa;CACtB,MAAM,aAAa,mBAAmB,QAAQ,IAAI,qBAAqB;AAEvE,KAAI,CAAC,WACH,QAAO,OAAO,OAAO,KACnB,IAAI,YAAY,EAAE,SAAS,yCAAyC,CAAC,CACtE;AAGH,QAAO;EACP;AAEJ,MAAM,2BACJ,OAAO,IAAI,aAAa;CACtB,MAAM,WAAW,QAAQ,IAAI;AAC7B,KAAI,SACF,QAAO;CAGT,MAAM,SAAS,OAAO,cAAc;CACpC,MAAM,SAAS,eAAe;CAE9B,MAAM,MAAM,KAAK,KAAK;AACtB,KAAI,qBAAqB,kBAAkB,YAAY,IACrD,QAAO,kBAAkB;CAG3B,MAAM,QAAQ,OAAO,OAAO,WAAW;EACrC,WAAWC,eAAqB;GAAE;GAAQ;GAAQ,CAAC;EACnD,QAAQ,UACN,IAAI,YAAY,EAAE,SAAS,+BAA+B,SAAS,CAAC;EACvE,CAAC;AAEF,KAAI,CAAC,MACH,QAAO,OAAO,OAAO,KACnB,IAAI,YAAY,EAAE,SAAS,qBAAqB,CAAC,CAClD;AAGH,qBAAoB;EAClB;EACA,WAAW,MAAM;EAClB;AAED,QAAO;EACP;AAEJ,MAAa,oBAAoB,MAAM,QACrC,eACA,cAAc,GAAG;CACf,WAAW,OAAO,QAAQ,eAAe,CAAC;CAC1C,cAAc,cAAc;CAC5B,gBAAgB,oBAAoB;CACrC,CAAC,CACH"}
|
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
import { ConfigService } from "./config.service.mjs";
|
|
2
2
|
import { Context, Effect, Layer } from "effect";
|
|
3
|
-
import * as
|
|
4
|
-
import * as
|
|
3
|
+
import * as effect_Types16 from "effect/Types";
|
|
4
|
+
import * as effect_Cause16 from "effect/Cause";
|
|
5
5
|
|
|
6
6
|
//#region src/server/services/error-tracking.service.d.ts
|
|
7
|
-
declare const ErrorTrackingError_base: new <A extends Record<string, any> = {}>(args:
|
|
7
|
+
declare const ErrorTrackingError_base: new <A extends Record<string, any> = {}>(args: effect_Types16.Equals<A, {}> extends true ? void : { readonly [P in keyof A as P extends "_tag" ? never : P]: A[P] }) => effect_Cause16.YieldableError & {
|
|
8
8
|
readonly _tag: "ErrorTrackingError";
|
|
9
9
|
} & Readonly<A>;
|
|
10
10
|
declare class ErrorTrackingError extends ErrorTrackingError_base<{
|
|
11
11
|
readonly message: string;
|
|
12
12
|
}> {}
|
|
13
|
-
|
|
13
|
+
interface ErrorTrackingService {
|
|
14
14
|
readonly captureError: (error: unknown, request?: Request, context?: Record<string, unknown>) => Effect.Effect<void, never, ConfigService>;
|
|
15
|
-
}
|
|
15
|
+
}
|
|
16
16
|
declare const ErrorTrackingService: Context.Tag<ErrorTrackingService, ErrorTrackingService>;
|
|
17
17
|
declare const ErrorTrackingServiceLive: Layer.Layer<ErrorTrackingService, never, never>;
|
|
18
18
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"error-tracking.service.d.mts","names":[],"sources":["../../../src/server/services/error-tracking.service.ts"],"sourcesContent":[],"mappings":";;;;;;
|
|
1
|
+
{"version":3,"file":"error-tracking.service.d.mts","names":[],"sources":["../../../src/server/services/error-tracking.service.ts"],"sourcesContent":[],"mappings":";;;;;;cASiD;;;cAEpC,kBAAA,SAA2B;EAFS,SAAA,OAAA,EAAA,MAAA;;UAMhC,oBAAA;oDAGH,mBACA,4BACP,MAAA,CAAO,oBAAoB;;cAGrB,sBAAoB,OAAA,CAAA,IAAA,sBAAA;cAIpB,0BAAwB,KAAA,CAAA,MAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"error-tracking.service.mjs","names":[],"sources":["../../../src/server/services/error-tracking.service.ts"],"sourcesContent":["import { API_PATHS } from \"@interfere/constants/api\";\nimport { getRuntime } from \"@interfere/react/core/runtime/config\";\nimport {\n buildServerErrorEnvelope,\n sendEnvelopesToIngest,\n} from \"@interfere/react/server/capture\";\nimport { Context, Data, Effect, Layer } from \"effect\";\nimport { ConfigService } from \"./config.service\";\n\nexport class ErrorTrackingError extends Data.TaggedError(\"ErrorTrackingError\")<{\n readonly message: string;\n}> {}\n\nexport
|
|
1
|
+
{"version":3,"file":"error-tracking.service.mjs","names":[],"sources":["../../../src/server/services/error-tracking.service.ts"],"sourcesContent":["import { API_PATHS } from \"@interfere/constants/api\";\nimport { getRuntime } from \"@interfere/react/core/runtime/config\";\nimport {\n buildServerErrorEnvelope,\n sendEnvelopesToIngest,\n} from \"@interfere/react/server/capture\";\n\nimport { Context, Data, Effect, Layer } from \"effect\";\n\nimport { ConfigService } from \"./config.service\";\n\nexport class ErrorTrackingError extends Data.TaggedError(\"ErrorTrackingError\")<{\n readonly message: string;\n}> {}\n\nexport interface ErrorTrackingService {\n readonly captureError: (\n error: unknown,\n request?: Request,\n context?: Record<string, unknown>\n ) => Effect.Effect<void, never, ConfigService>;\n}\n\nexport const ErrorTrackingService = Context.GenericTag<ErrorTrackingService>(\n \"@interfere/next/ErrorTrackingService\"\n);\n\nexport const ErrorTrackingServiceLive = Layer.succeed(\n ErrorTrackingService,\n ErrorTrackingService.of({\n captureError: (error, request, context) =>\n Effect.gen(function* () {\n const config = yield* ConfigService;\n const token = yield* config.getPublicToken.pipe(\n Effect.catchAll(() => Effect.succeed(null))\n );\n\n if (!token) {\n return;\n }\n\n const apiUrl = yield* config.getApiUrl;\n const inferredValues = {\n buildId: process.env.NEXT_PUBLIC_INTERFERE_BUILD_ID ?? null,\n releaseId: process.env.NEXT_PUBLIC_INTERFERE_RELEASE_ID ?? null,\n environment: process.env.NODE_ENV || null,\n runtime: getRuntime(),\n };\n\n const envelope = buildServerErrorEnvelope({\n ...inferredValues,\n error,\n request,\n context: {\n ...context,\n },\n });\n\n yield* Effect.tryPromise({\n try: () =>\n sendEnvelopesToIngest(\n [envelope],\n `${apiUrl}${API_PATHS.INGEST_V0}`,\n token\n ),\n catch: () =>\n new ErrorTrackingError({\n message: \"Failed to send error to ingest\",\n }),\n }).pipe(\n Effect.catchAll(() => Effect.void) // Best-effort\n );\n }),\n })\n);\n"],"mappings":";;;;;;;AAWA,IAAa,qBAAb,cAAwC,KAAK,YAAY,qBAAqB,CAE3E;AAUH,MAAa,uBAAuB,QAAQ,WAC1C,uCACD;AAED,MAAa,2BAA2B,MAAM,QAC5C,sBACA,qBAAqB,GAAG,EACtB,eAAe,OAAO,SAAS,YAC7B,OAAO,IAAI,aAAa;CACtB,MAAM,SAAS,OAAO;CACtB,MAAM,QAAQ,OAAO,OAAO,eAAe,KACzC,OAAO,eAAe,OAAO,QAAQ,KAAK,CAAC,CAC5C;AAED,KAAI,CAAC,MACH;CAGF,MAAM,SAAS,OAAO,OAAO;CAQ7B,MAAM,WAAW,yBAAyB;EANxC,SAAS,QAAQ,IAAI,kCAAkC;EACvD,WAAW,QAAQ,IAAI,oCAAoC;EAC3D,aAAa,QAAQ,IAAI,YAAY;EACrC,SAAS,YAAY;EAKrB;EACA;EACA,SAAS,EACP,GAAG,SACJ;EACF,CAAC;AAEF,QAAO,OAAO,WAAW;EACvB,WACE,sBACE,CAAC,SAAS,EACV,GAAG,SAAS,UAAU,aACtB,MACD;EACH,aACE,IAAI,mBAAmB,EACrB,SAAS,kCACV,CAAC;EACL,CAAC,CAAC,KACD,OAAO,eAAe,OAAO,KAAK,CACnC;EACD,EACL,CAAC,CACH"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@interfere/next",
|
|
3
|
-
"version": "0.0.15-alpha.
|
|
3
|
+
"version": "0.0.15-alpha.1",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "Build apps that never break.",
|
|
6
6
|
"keywords": [
|
|
@@ -21,6 +21,11 @@
|
|
|
21
21
|
],
|
|
22
22
|
"type": "module",
|
|
23
23
|
"exports": {
|
|
24
|
+
"./config": {
|
|
25
|
+
"@source": "./src/build/with-interfere.ts",
|
|
26
|
+
"types": "./dist/build/with-interfere.d.mts",
|
|
27
|
+
"default": "./dist/build/with-interfere.mjs"
|
|
28
|
+
},
|
|
24
29
|
"./build": {
|
|
25
30
|
"@source": "./src/build/with-interfere.ts",
|
|
26
31
|
"types": "./dist/build/with-interfere.d.mts",
|
|
@@ -85,9 +90,10 @@
|
|
|
85
90
|
"@vitest/coverage-v8": "^4.0.16",
|
|
86
91
|
"jsdom": "^27.3.0",
|
|
87
92
|
"msw": "^2.12.4",
|
|
88
|
-
"next": "^16.1.
|
|
93
|
+
"next": "^16.1.1",
|
|
89
94
|
"react": "^19.2.3",
|
|
90
95
|
"react-dom": "^19.2.3",
|
|
96
|
+
"tsdown": "^0.18.3",
|
|
91
97
|
"typescript": "5.9.3",
|
|
92
98
|
"vitest": "^4.0.16",
|
|
93
99
|
"webpack": "^5.104.1",
|