@interfere/next 0.0.14 → 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.
Files changed (232) hide show
  1. package/README.md +1 -6
  2. package/dist/build/env-config.d.mts +7 -0
  3. package/dist/build/env-config.d.mts.map +1 -0
  4. package/dist/build/env-config.mjs +17 -0
  5. package/dist/build/env-config.mjs.map +1 -0
  6. package/dist/build/logger.d.mts +11 -0
  7. package/dist/build/logger.d.mts.map +1 -0
  8. package/dist/build/logger.mjs +155 -0
  9. package/dist/build/logger.mjs.map +1 -0
  10. package/dist/build/release-program.d.mts +19 -0
  11. package/dist/build/release-program.d.mts.map +1 -0
  12. package/dist/build/release-program.mjs +92 -0
  13. package/dist/build/release-program.mjs.map +1 -0
  14. package/dist/build/secret-key.d.mts +10 -0
  15. package/dist/build/secret-key.d.mts.map +1 -0
  16. package/dist/build/secret-key.mjs +16 -0
  17. package/dist/build/secret-key.mjs.map +1 -0
  18. package/dist/build/services/config.service.d.mts +9 -0
  19. package/dist/build/services/config.service.d.mts.map +1 -0
  20. package/dist/build/services/config.service.mjs +8 -0
  21. package/dist/build/services/config.service.mjs.map +1 -0
  22. package/dist/build/services/preflight.service.d.mts +19 -0
  23. package/dist/build/services/preflight.service.d.mts.map +1 -0
  24. package/dist/build/services/preflight.service.mjs +76 -0
  25. package/dist/build/services/preflight.service.mjs.map +1 -0
  26. package/dist/build/services/release-identity.service.d.mts +22 -0
  27. package/dist/build/services/release-identity.service.d.mts.map +1 -0
  28. package/dist/build/services/release-identity.service.mjs +48 -0
  29. package/dist/build/services/release-identity.service.mjs.map +1 -0
  30. package/dist/build/services/source-map.service.d.mts +24 -0
  31. package/dist/build/services/source-map.service.d.mts.map +1 -0
  32. package/dist/build/services/source-map.service.mjs +58 -0
  33. package/dist/build/services/source-map.service.mjs.map +1 -0
  34. package/dist/build/source-maps/api.d.mts +35 -0
  35. package/dist/build/source-maps/api.d.mts.map +1 -0
  36. package/dist/build/source-maps/api.mjs +61 -0
  37. package/dist/build/source-maps/api.mjs.map +1 -0
  38. package/dist/build/source-maps/client.d.mts +73 -0
  39. package/dist/build/source-maps/client.d.mts.map +1 -0
  40. package/dist/build/source-maps/client.mjs +228 -0
  41. package/dist/build/source-maps/client.mjs.map +1 -0
  42. package/dist/build/source-maps/errors.d.mts +109 -0
  43. package/dist/build/source-maps/errors.d.mts.map +1 -0
  44. package/dist/build/source-maps/errors.mjs +22 -0
  45. package/dist/build/source-maps/errors.mjs.map +1 -0
  46. package/dist/build/source-maps/files.d.mts +35 -0
  47. package/dist/build/source-maps/files.d.mts.map +1 -0
  48. package/dist/build/source-maps/files.mjs +222 -0
  49. package/dist/build/source-maps/files.mjs.map +1 -0
  50. package/dist/build/source-maps/providers/deployment/detector.d.mts +26 -0
  51. package/dist/build/source-maps/providers/deployment/detector.d.mts.map +1 -0
  52. package/dist/build/source-maps/providers/deployment/detector.mjs +22 -0
  53. package/dist/build/source-maps/providers/deployment/detector.mjs.map +1 -0
  54. package/dist/build/source-maps/providers/deployment/types.d.mts +12 -0
  55. package/dist/build/source-maps/providers/deployment/types.d.mts.map +1 -0
  56. package/dist/build/source-maps/providers/deployment/types.mjs +3 -0
  57. package/dist/build/source-maps/providers/deployment/vercel.d.mts +6 -0
  58. package/dist/build/source-maps/providers/deployment/vercel.d.mts.map +1 -0
  59. package/dist/build/source-maps/providers/deployment/vercel.mjs +44 -0
  60. package/dist/build/source-maps/providers/deployment/vercel.mjs.map +1 -0
  61. package/dist/build/source-maps/providers/source-control/detector.d.mts +15 -0
  62. package/dist/build/source-maps/providers/source-control/detector.d.mts.map +1 -0
  63. package/dist/build/source-maps/providers/source-control/detector.mjs +22 -0
  64. package/dist/build/source-maps/providers/source-control/detector.mjs.map +1 -0
  65. package/dist/build/source-maps/providers/source-control/git.d.mts +6 -0
  66. package/dist/build/source-maps/providers/source-control/git.d.mts.map +1 -0
  67. package/dist/build/source-maps/providers/source-control/git.mjs +50 -0
  68. package/dist/build/source-maps/providers/source-control/git.mjs.map +1 -0
  69. package/dist/build/source-maps/providers/source-control/types.d.mts +12 -0
  70. package/dist/build/source-maps/providers/source-control/types.d.mts.map +1 -0
  71. package/dist/build/source-maps/providers/source-control/types.mjs +3 -0
  72. package/dist/build/with-interfere.d.mts +48 -0
  73. package/dist/build/with-interfere.d.mts.map +1 -0
  74. package/dist/build/with-interfere.mjs +75 -0
  75. package/dist/build/with-interfere.mjs.map +1 -0
  76. package/dist/client/client.d.mts +3 -0
  77. package/dist/client/client.mjs +5 -0
  78. package/dist/client/provider.d.mts +22 -0
  79. package/dist/client/provider.d.mts.map +1 -0
  80. package/dist/client/provider.mjs +33 -0
  81. package/dist/client/provider.mjs.map +1 -0
  82. package/dist/lib/env.d.mts +12 -0
  83. package/dist/lib/env.d.mts.map +1 -0
  84. package/dist/lib/env.mjs +17 -0
  85. package/dist/lib/env.mjs.map +1 -0
  86. package/dist/lib/test-utils/make-next-request.d.mts +6 -0
  87. package/dist/lib/test-utils/make-next-request.d.mts.map +1 -0
  88. package/dist/lib/test-utils/make-next-request.mjs +12 -0
  89. package/dist/lib/test-utils/make-next-request.mjs.map +1 -0
  90. package/dist/lib/types.d.mts +22 -0
  91. package/dist/lib/types.d.mts.map +1 -0
  92. package/dist/lib/types.mjs +7 -0
  93. package/dist/lib/types.mjs.map +1 -0
  94. package/dist/server/middleware.d.mts +11 -0
  95. package/dist/server/middleware.d.mts.map +1 -0
  96. package/dist/server/middleware.mjs +84 -0
  97. package/dist/server/middleware.mjs.map +1 -0
  98. package/dist/server/proxy.d.mts +6 -0
  99. package/dist/server/proxy.d.mts.map +1 -0
  100. package/dist/server/proxy.mjs +29 -0
  101. package/dist/server/proxy.mjs.map +1 -0
  102. package/dist/server/route-handler.d.mts +9 -0
  103. package/dist/server/route-handler.d.mts.map +1 -0
  104. package/dist/server/route-handler.mjs +172 -0
  105. package/dist/server/route-handler.mjs.map +1 -0
  106. package/dist/server/services/config.service.d.mts +21 -0
  107. package/dist/server/services/config.service.d.mts.map +1 -0
  108. package/dist/server/services/config.service.mjs +43 -0
  109. package/dist/server/services/config.service.mjs.map +1 -0
  110. package/dist/server/services/error-tracking.service.d.mts +19 -0
  111. package/dist/server/services/error-tracking.service.d.mts.map +1 -0
  112. package/dist/server/services/error-tracking.service.mjs +31 -0
  113. package/dist/server/services/error-tracking.service.mjs.map +1 -0
  114. package/package.json +73 -36
  115. package/dist/__tests__/build/with-interfere-coverage.test.d.ts +0 -2
  116. package/dist/__tests__/build/with-interfere-coverage.test.d.ts.map +0 -1
  117. package/dist/__tests__/build/with-interfere-coverage.test.js +0 -295
  118. package/dist/__tests__/build/with-interfere-coverage.test.js.map +0 -1
  119. package/dist/__tests__/build/with-interfere.test.d.ts +0 -2
  120. package/dist/__tests__/build/with-interfere.test.d.ts.map +0 -1
  121. package/dist/__tests__/build/with-interfere.test.js +0 -363
  122. package/dist/__tests__/build/with-interfere.test.js.map +0 -1
  123. package/dist/__tests__/core/client.test.d.ts +0 -2
  124. package/dist/__tests__/core/client.test.d.ts.map +0 -1
  125. package/dist/__tests__/core/client.test.js +0 -373
  126. package/dist/__tests__/core/client.test.js.map +0 -1
  127. package/dist/__tests__/core/encoders.test.d.ts +0 -2
  128. package/dist/__tests__/core/encoders.test.d.ts.map +0 -1
  129. package/dist/__tests__/core/encoders.test.js +0 -56
  130. package/dist/__tests__/core/encoders.test.js.map +0 -1
  131. package/dist/__tests__/core/rage-click.test.d.ts +0 -2
  132. package/dist/__tests__/core/rage-click.test.d.ts.map +0 -1
  133. package/dist/__tests__/core/rage-click.test.js +0 -121
  134. package/dist/__tests__/core/rage-click.test.js.map +0 -1
  135. package/dist/__tests__/core/session-manager.test.d.ts +0 -2
  136. package/dist/__tests__/core/session-manager.test.d.ts.map +0 -1
  137. package/dist/__tests__/core/session-manager.test.js +0 -1168
  138. package/dist/__tests__/core/session-manager.test.js.map +0 -1
  139. package/dist/__tests__/integration/release-upload.test.d.ts +0 -2
  140. package/dist/__tests__/integration/release-upload.test.d.ts.map +0 -1
  141. package/dist/__tests__/integration/release-upload.test.js +0 -153
  142. package/dist/__tests__/integration/release-upload.test.js.map +0 -1
  143. package/dist/__tests__/provider.test.d.ts +0 -2
  144. package/dist/__tests__/provider.test.d.ts.map +0 -1
  145. package/dist/__tests__/provider.test.js +0 -84
  146. package/dist/__tests__/provider.test.js.map +0 -1
  147. package/dist/__tests__/session/persistence.test.d.ts +0 -2
  148. package/dist/__tests__/session/persistence.test.d.ts.map +0 -1
  149. package/dist/__tests__/session/persistence.test.js +0 -129
  150. package/dist/__tests__/session/persistence.test.js.map +0 -1
  151. package/dist/__tests__/session/session-summary.test.d.ts +0 -2
  152. package/dist/__tests__/session/session-summary.test.d.ts.map +0 -1
  153. package/dist/__tests__/session/session-summary.test.js +0 -763
  154. package/dist/__tests__/session/session-summary.test.js.map +0 -1
  155. package/dist/client.d.ts +0 -75
  156. package/dist/client.d.ts.map +0 -1
  157. package/dist/client.js +0 -123
  158. package/dist/client.js.map +0 -1
  159. package/dist/config.d.ts +0 -40
  160. package/dist/config.d.ts.map +0 -1
  161. package/dist/config.js +0 -340
  162. package/dist/config.js.map +0 -1
  163. package/dist/index.d.ts +0 -37
  164. package/dist/index.d.ts.map +0 -1
  165. package/dist/index.js +0 -49
  166. package/dist/index.js.map +0 -1
  167. package/dist/index.jsx +0 -87
  168. package/dist/index.jsx.map +0 -1
  169. package/dist/lib/core/client-core.d.ts +0 -27
  170. package/dist/lib/core/client-core.d.ts.map +0 -1
  171. package/dist/lib/core/client-core.js +0 -152
  172. package/dist/lib/core/client-core.js.map +0 -1
  173. package/dist/lib/core/constants.d.ts +0 -12
  174. package/dist/lib/core/constants.d.ts.map +0 -1
  175. package/dist/lib/core/constants.js +0 -17
  176. package/dist/lib/core/constants.js.map +0 -1
  177. package/dist/lib/core/debug.d.ts +0 -47
  178. package/dist/lib/core/debug.d.ts.map +0 -1
  179. package/dist/lib/core/debug.js +0 -79
  180. package/dist/lib/core/debug.js.map +0 -1
  181. package/dist/lib/core/encoders.d.ts +0 -3
  182. package/dist/lib/core/encoders.d.ts.map +0 -1
  183. package/dist/lib/core/encoders.js +0 -5
  184. package/dist/lib/core/encoders.js.map +0 -1
  185. package/dist/lib/core/error-handlers.d.ts +0 -14
  186. package/dist/lib/core/error-handlers.d.ts.map +0 -1
  187. package/dist/lib/core/error-handlers.js +0 -191
  188. package/dist/lib/core/error-handlers.js.map +0 -1
  189. package/dist/lib/core/runtime.d.ts +0 -7
  190. package/dist/lib/core/runtime.d.ts.map +0 -1
  191. package/dist/lib/core/runtime.js +0 -16
  192. package/dist/lib/core/runtime.js.map +0 -1
  193. package/dist/lib/persistence/storage.d.ts +0 -5
  194. package/dist/lib/persistence/storage.d.ts.map +0 -1
  195. package/dist/lib/persistence/storage.js +0 -67
  196. package/dist/lib/persistence/storage.js.map +0 -1
  197. package/dist/lib/session/constants.d.ts +0 -19
  198. package/dist/lib/session/constants.d.ts.map +0 -1
  199. package/dist/lib/session/constants.js +0 -34
  200. package/dist/lib/session/constants.js.map +0 -1
  201. package/dist/lib/session/persistence.d.ts +0 -58
  202. package/dist/lib/session/persistence.d.ts.map +0 -1
  203. package/dist/lib/session/persistence.js +0 -179
  204. package/dist/lib/session/persistence.js.map +0 -1
  205. package/dist/lib/session/rage-click.d.ts +0 -17
  206. package/dist/lib/session/rage-click.d.ts.map +0 -1
  207. package/dist/lib/session/rage-click.js +0 -104
  208. package/dist/lib/session/rage-click.js.map +0 -1
  209. package/dist/lib/session/replay.d.ts +0 -3
  210. package/dist/lib/session/replay.d.ts.map +0 -1
  211. package/dist/lib/session/replay.js +0 -109
  212. package/dist/lib/session/replay.js.map +0 -1
  213. package/dist/lib/session/session-manager.d.ts +0 -126
  214. package/dist/lib/session/session-manager.d.ts.map +0 -1
  215. package/dist/lib/session/session-manager.js +0 -635
  216. package/dist/lib/session/session-manager.js.map +0 -1
  217. package/dist/lib/session/session-summary.d.ts +0 -3
  218. package/dist/lib/session/session-summary.d.ts.map +0 -1
  219. package/dist/lib/session/session-summary.js +0 -214
  220. package/dist/lib/session/session-summary.js.map +0 -1
  221. package/dist/middleware.d.ts +0 -8
  222. package/dist/middleware.d.ts.map +0 -1
  223. package/dist/middleware.js +0 -139
  224. package/dist/middleware.js.map +0 -1
  225. package/dist/types/storage.d.ts +0 -7
  226. package/dist/types/storage.d.ts.map +0 -1
  227. package/dist/types/storage.js +0 -2
  228. package/dist/types/storage.js.map +0 -1
  229. package/dist/types.d.ts +0 -6
  230. package/dist/types.d.ts.map +0 -1
  231. package/dist/types.js +0 -4
  232. package/dist/types.js.map +0 -1
@@ -0,0 +1,172 @@
1
+ "use server";
2
+
3
+ import { ConfigService, ConfigServiceLive } from "./services/config.service.mjs";
4
+ import { ErrorTrackingService, ErrorTrackingServiceLive } from "./services/error-tracking.service.mjs";
5
+ import { API_PATHS } from "@interfere/constants/api";
6
+ 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";
9
+ import { createBackoffSchedule, shouldRetryHttp, withTimeoutFail } from "@interfere/effect-utils/retry";
10
+ import { envelopeSchema } from "@interfere/types/sdk/envelope";
11
+ import { v7 } from "uuid";
12
+ import { z } from "zod";
13
+
14
+ //#region src/server/route-handler.ts
15
+ /** biome-ignore-all lint/suspicious/useAwait: route handlers must be async */
16
+ const layer = Layer.mergeAll(ConfigServiceLive, ErrorTrackingServiceLive);
17
+ var ProxyError = class extends Data.TaggedError("ProxyError") {};
18
+ const TIMEOUT_MS = 1e4;
19
+ const RETRY_COUNT = 2;
20
+ const HTTP_BAD_GATEWAY = 502;
21
+ const RETRY_INITIAL_MS = 100;
22
+ const RETRY_MAX_MS = 2e3;
23
+ const envelopesArraySchema = z.array(envelopeSchema);
24
+ const GET = async () => {
25
+ const program = Effect.gen(function* () {
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));
33
+ return await Effect.runPromise(withInterfereLogger("next", program));
34
+ };
35
+ const proxyIngestRequest = (request, params) => Effect.gen(function* () {
36
+ const traceparent = (request.headers.get("traceparent") || void 0) ?? generateTraceparent();
37
+ const body = yield* Effect.tryPromise({
38
+ try: () => request.clone().text(),
39
+ catch: () => new ProxyError({
40
+ status: 400,
41
+ message: "Invalid request body"
42
+ })
43
+ });
44
+ let parsed;
45
+ try {
46
+ parsed = JSON.parse(body);
47
+ } catch (error) {
48
+ yield* Effect.logWarning("Invalid JSON in request body").pipe(Effect.annotateLogs({ error: String(error) }));
49
+ return yield* Effect.fail(new ProxyError({
50
+ status: 400,
51
+ message: "Invalid JSON in request body",
52
+ body: String(error)
53
+ }));
54
+ }
55
+ const validation = envelopesArraySchema.safeParse(parsed);
56
+ if (!validation.success) {
57
+ const issues = validation.error.issues.map((issue) => ({
58
+ path: issue.path.join("."),
59
+ message: issue.message,
60
+ code: issue.code
61
+ }));
62
+ yield* Effect.logWarning("Invalid envelope schema").pipe(Effect.annotateLogs({ validationErrors: issues }));
63
+ const b = JSON.stringify({
64
+ ok: false,
65
+ code: "INVALID_ENVELOPE_SCHEMA",
66
+ errors: issues
67
+ });
68
+ return new Response(b, {
69
+ status: 422,
70
+ headers: { "content-type": "application/json" }
71
+ });
72
+ }
73
+ const seen = /* @__PURE__ */ new Set();
74
+ const deduped = validation.data.filter((env) => {
75
+ if (seen.has(env.uuid)) return false;
76
+ seen.add(env.uuid);
77
+ return true;
78
+ });
79
+ const dropped = validation.data.length - deduped.length;
80
+ yield* Effect.logDebug("Envelope validation successful").pipe(Effect.annotateLogs({
81
+ envelopeCount: validation.data.length,
82
+ dedupedCount: deduped.length,
83
+ droppedDuplicates: dropped
84
+ }));
85
+ const upstream = yield* withTimeoutFail(Effect.tryPromise({
86
+ try: () => fetch(`${params.apiUrl}${API_PATHS.INGEST_V0}`, {
87
+ method: "POST",
88
+ headers: {
89
+ "Content-Type": "application/json",
90
+ "x-interfere-surface": params.surfaceToken,
91
+ traceparent
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
+ })
99
+ }), TIMEOUT_MS, () => new ProxyError({
100
+ status: 0,
101
+ message: "Network timeout"
102
+ }));
103
+ const responseBody = yield* Effect.tryPromise({
104
+ try: () => upstream.text(),
105
+ catch: () => ""
106
+ });
107
+ return new Response(responseBody, {
108
+ status: upstream.status,
109
+ headers: { "content-type": upstream.headers.get("content-type") || "application/json" }
110
+ });
111
+ });
112
+ const POST = async (request) => {
113
+ const program = Effect.gen(function* () {
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(() => Effect.logError("Auth not available - missing public token")), Effect.catchTag("ConfigError", () => Effect.fail(new ProxyError({
122
+ status: 503,
123
+ message: "Auth not available"
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
+ }));
146
+ return await Effect.runPromise(withInterfereLogger("next", program));
147
+ };
148
+ const OPTIONS = async () => Effect.runPromise(withInterfereLogger("next", Effect.succeed(new Response(null, {
149
+ status: 200,
150
+ headers: {
151
+ "Access-Control-Allow-Origin": "*",
152
+ "Access-Control-Allow-Methods": "GET, POST, OPTIONS",
153
+ "Access-Control-Allow-Headers": "Content-Type",
154
+ "Access-Control-Max-Age": "86400"
155
+ }
156
+ }))));
157
+ const VERSION = "00";
158
+ const TRACE_ID_BYTES = 16;
159
+ const SPAN_ID_BYTES = 8;
160
+ const FLAGS = "01";
161
+ function generateTraceparent() {
162
+ return `${VERSION}-${randomHex(TRACE_ID_BYTES)}-${randomHex(SPAN_ID_BYTES)}-${FLAGS}`;
163
+ }
164
+ function randomHex(bytes) {
165
+ const raw = v7().replace(/-/g, "");
166
+ const needed = bytes * 2;
167
+ if (raw.length >= needed) return raw.substring(0, needed);
168
+ return (raw + raw).substring(0, needed);
169
+ }
170
+
171
+ //#endregion
172
+ export { GET, OPTIONS, POST };
@@ -0,0 +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\";\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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"}
@@ -0,0 +1,21 @@
1
+ import { NonEmptyString } from "../../lib/types.mjs";
2
+ import { Context, Effect, Layer } from "effect";
3
+ import * as effect_Types15 from "effect/Types";
4
+ import * as effect_Cause15 from "effect/Cause";
5
+
6
+ //#region src/server/services/config.service.d.ts
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
+ readonly _tag: "ConfigError";
9
+ } & Readonly<A>;
10
+ declare class ConfigError extends ConfigError_base<{
11
+ readonly message: string;
12
+ }> {}
13
+ interface ConfigService {
14
+ readonly getApiUrl: Effect.Effect<string>;
15
+ readonly getSecretKey: Effect.Effect<NonEmptyString, ConfigError>;
16
+ readonly getPublicToken: Effect.Effect<string, ConfigError>;
17
+ }
18
+ declare const ConfigService: Context.Tag<ConfigService, ConfigService>;
19
+ declare const ConfigServiceLive: Layer.Layer<ConfigService, never, never>;
20
+ //#endregion
21
+ export { ConfigError, ConfigService, ConfigServiceLive };
@@ -0,0 +1 @@
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"}
@@ -0,0 +1,43 @@
1
+ import { normalizeSecretKey, resolveApiUrl } from "../../build/env-config.mjs";
2
+ import { Context, Data, Effect, Layer } from "effect";
3
+ import { getPublicToken } from "@interfere/react/server/auth";
4
+
5
+ //#region src/server/services/config.service.ts
6
+ const ONE_HOUR_MS = 36e5;
7
+ var ConfigError = class extends Data.TaggedError("ConfigError") {};
8
+ const ConfigService = Context.GenericTag("@interfere/next/ConfigService");
9
+ let cachedPublicToken = null;
10
+ const getSecretKey = () => Effect.gen(function* () {
11
+ const normalized = normalizeSecretKey(process.env.INTERFERE_SECRET_KEY);
12
+ if (!normalized) return yield* Effect.fail(new ConfigError({ message: "INTERFERE_SECRET_KEY not set or empty" }));
13
+ return normalized;
14
+ });
15
+ const getPublicTokenImpl = () => Effect.gen(function* () {
16
+ const override = process.env.INTERFERE_PUBLIC_TOKEN;
17
+ if (override) return override;
18
+ const secret = yield* getSecretKey();
19
+ const apiUrl = resolveApiUrl();
20
+ const now = Date.now();
21
+ if (cachedPublicToken && cachedPublicToken.expiresAt > now) return cachedPublicToken.token;
22
+ const token = yield* Effect.tryPromise({
23
+ try: () => getPublicToken({
24
+ apiUrl,
25
+ secret
26
+ }),
27
+ catch: (error) => new ConfigError({ message: `Failed to get public token: ${error}` })
28
+ });
29
+ if (!token) return yield* Effect.fail(new ConfigError({ message: "No token returned" }));
30
+ cachedPublicToken = {
31
+ token,
32
+ expiresAt: now + ONE_HOUR_MS
33
+ };
34
+ return token;
35
+ });
36
+ const ConfigServiceLive = Layer.succeed(ConfigService, ConfigService.of({
37
+ getApiUrl: Effect.succeed(resolveApiUrl()),
38
+ getSecretKey: getSecretKey(),
39
+ getPublicToken: getPublicTokenImpl()
40
+ }));
41
+
42
+ //#endregion
43
+ export { ConfigError, ConfigService, ConfigServiceLive };
@@ -0,0 +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\";\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"}
@@ -0,0 +1,19 @@
1
+ import { ConfigService } from "./config.service.mjs";
2
+ import { Context, Effect, Layer } from "effect";
3
+ import * as effect_Types16 from "effect/Types";
4
+ import * as effect_Cause16 from "effect/Cause";
5
+
6
+ //#region src/server/services/error-tracking.service.d.ts
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
+ readonly _tag: "ErrorTrackingError";
9
+ } & Readonly<A>;
10
+ declare class ErrorTrackingError extends ErrorTrackingError_base<{
11
+ readonly message: string;
12
+ }> {}
13
+ interface ErrorTrackingService {
14
+ readonly captureError: (error: unknown, request?: Request, context?: Record<string, unknown>) => Effect.Effect<void, never, ConfigService>;
15
+ }
16
+ declare const ErrorTrackingService: Context.Tag<ErrorTrackingService, ErrorTrackingService>;
17
+ declare const ErrorTrackingServiceLive: Layer.Layer<ErrorTrackingService, never, never>;
18
+ //#endregion
19
+ export { ErrorTrackingError, ErrorTrackingService, ErrorTrackingServiceLive };
@@ -0,0 +1 @@
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"}
@@ -0,0 +1,31 @@
1
+ import { ConfigService } from "./config.service.mjs";
2
+ import { API_PATHS } from "@interfere/constants/api";
3
+ import { Context, Data, Effect, Layer } from "effect";
4
+ import { getRuntime } from "@interfere/react/core/runtime/config";
5
+ import { buildServerErrorEnvelope, sendEnvelopesToIngest } from "@interfere/react/server/capture";
6
+
7
+ //#region src/server/services/error-tracking.service.ts
8
+ var ErrorTrackingError = class extends Data.TaggedError("ErrorTrackingError") {};
9
+ const ErrorTrackingService = Context.GenericTag("@interfere/next/ErrorTrackingService");
10
+ const ErrorTrackingServiceLive = Layer.succeed(ErrorTrackingService, ErrorTrackingService.of({ captureError: (error, request, context) => Effect.gen(function* () {
11
+ const config = yield* ConfigService;
12
+ const token = yield* config.getPublicToken.pipe(Effect.catchAll(() => Effect.succeed(null)));
13
+ if (!token) return;
14
+ const apiUrl = yield* config.getApiUrl;
15
+ const envelope = buildServerErrorEnvelope({
16
+ buildId: process.env.NEXT_PUBLIC_INTERFERE_BUILD_ID ?? null,
17
+ releaseId: process.env.NEXT_PUBLIC_INTERFERE_RELEASE_ID ?? null,
18
+ environment: process.env.NODE_ENV || null,
19
+ runtime: getRuntime(),
20
+ error,
21
+ request,
22
+ context: { ...context }
23
+ });
24
+ yield* Effect.tryPromise({
25
+ try: () => sendEnvelopesToIngest([envelope], `${apiUrl}${API_PATHS.INGEST_V0}`, token),
26
+ catch: () => new ErrorTrackingError({ message: "Failed to send error to ingest" })
27
+ }).pipe(Effect.catchAll(() => Effect.void));
28
+ }) }));
29
+
30
+ //#endregion
31
+ export { ErrorTrackingError, ErrorTrackingService, ErrorTrackingServiceLive };
@@ -0,0 +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\";\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,13 +1,15 @@
1
1
  {
2
2
  "name": "@interfere/next",
3
- "version": "0.0.14",
3
+ "version": "0.0.15-alpha.1",
4
4
  "license": "MIT",
5
5
  "description": "Build apps that never break.",
6
6
  "keywords": [
7
7
  "observability",
8
8
  "typescript",
9
- "nextjs",
10
- "logging"
9
+ "react",
10
+ "logging",
11
+ "error-tracking",
12
+ "session-replay"
11
13
  ],
12
14
  "homepage": "https://interfere.com",
13
15
  "bugs": {
@@ -18,56 +20,91 @@
18
20
  "dist"
19
21
  ],
20
22
  "type": "module",
21
- "main": "./dist/index.jsx",
22
- "types": "./dist/index.d.ts",
23
23
  "exports": {
24
- ".": {
25
- "import": "./dist/index.jsx",
26
- "require": "./dist/index.jsx",
27
- "types": "./dist/index.d.ts"
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
28
  },
29
- "./*": {
30
- "import": "./dist/*.js",
31
- "require": "./dist/*.js",
32
- "types": "./dist/*.d.ts"
29
+ "./build": {
30
+ "@source": "./src/build/with-interfere.ts",
31
+ "types": "./dist/build/with-interfere.d.mts",
32
+ "default": "./dist/build/with-interfere.mjs"
33
+ },
34
+ "./provider": {
35
+ "@source": "./src/client/provider.tsx",
36
+ "types": "./dist/client/provider.d.mts",
37
+ "default": "./dist/client/provider.mjs"
38
+ },
39
+ "./client": {
40
+ "@source": "./src/client/client.ts",
41
+ "types": "./dist/client/client.d.mts",
42
+ "default": "./dist/client/client.mjs"
43
+ },
44
+ "./route-handler": {
45
+ "@source": "./src/server/route-handler.ts",
46
+ "types": "./dist/server/route-handler.d.mts",
47
+ "default": "./dist/server/route-handler.mjs"
48
+ },
49
+ "./server/route-handler": {
50
+ "@source": "./src/server/route-handler.ts",
51
+ "types": "./dist/server/route-handler.d.mts",
52
+ "default": "./dist/server/route-handler.mjs"
53
+ },
54
+ "./middleware": {
55
+ "@source": "./src/server/middleware.ts",
56
+ "types": "./dist/server/middleware.d.mts",
57
+ "default": "./dist/server/middleware.mjs"
58
+ },
59
+ "./proxy": {
60
+ "@source": "./src/server/proxy.ts",
61
+ "types": "./dist/server/proxy.d.mts",
62
+ "default": "./dist/server/proxy.mjs"
33
63
  }
34
64
  },
35
65
  "publishConfig": {
36
66
  "access": "public"
37
67
  },
38
68
  "dependencies": {
39
- "@rrweb/packer": "2.0.0-alpha.18",
40
- "@rrweb/types": "2.0.0-alpha.18",
41
- "form-data": "^4.0.4",
42
- "glob": "^11.0.3",
43
- "nanoid": "^5.1.6",
44
- "node-fetch": "^3.3.2",
45
- "rrweb": "2.0.0-alpha.18",
69
+ "@effect/platform": "^0.94.0",
70
+ "chalk": "^5.6.2",
71
+ "effect": "^3.19.13",
72
+ "glob": "^13.0.0",
46
73
  "uuid": "^13.0.0",
47
- "@interfere/schemas": "0.0.7"
74
+ "zod": "^4.2.1",
75
+ "@interfere/constants": "0.0.2-alpha.0",
76
+ "@interfere/effect-utils": "0.0.2-alpha.0",
77
+ "@interfere/react": "0.0.2-alpha.0",
78
+ "@interfere/types": "0.0.2-alpha.0"
48
79
  },
49
80
  "peerDependencies": {
50
- "next": ">=15",
81
+ "@vercel/blob": "^2",
82
+ "next": ">=15 || >=16",
51
83
  "react": ">=19",
52
84
  "react-dom": ">=19"
53
85
  },
54
86
  "devDependencies": {
55
- "@types/node": "22.15.29",
56
- "@types/react": "19.1.13",
57
- "@types/react-dom": "19.1.9",
58
- "@vitest/coverage-v8": "^3.2.4",
59
- "next": "^15.5.3",
60
- "react": "^19.1.1",
61
- "react-dom": "^19.1.1",
62
- "typescript": "5.9.2",
63
- "vitest": "^3.2.4",
64
- "webpack": "^5.101.3",
65
- "@interfere/typescript-config": "1.0.2"
87
+ "@types/node": "^22.19.3",
88
+ "@types/react": "19.2.7",
89
+ "@types/react-dom": "19.2.3",
90
+ "@vitest/coverage-v8": "^4.0.16",
91
+ "jsdom": "^27.3.0",
92
+ "msw": "^2.12.4",
93
+ "next": "^16.1.1",
94
+ "react": "^19.2.3",
95
+ "react-dom": "^19.2.3",
96
+ "tsdown": "^0.18.3",
97
+ "typescript": "5.9.3",
98
+ "vitest": "^4.0.16",
99
+ "webpack": "^5.104.1",
100
+ "@interfere/test-utils": "0.0.0",
101
+ "@interfere/typescript-config": "1.0.3-alpha.0",
102
+ "@interfere/vitest-config": "1.0.1-alpha.0"
66
103
  },
67
104
  "scripts": {
68
- "build": "tsc",
69
- "dev": "tsc --watch",
70
- "check-types": "tsc --noEmit --incremental",
105
+ "build": "tsdown",
106
+ "dev": "tsdown --watch",
107
+ "typecheck": "tsc --noEmit --incremental",
71
108
  "test": "vitest run --coverage"
72
109
  }
73
110
  }
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=with-interfere-coverage.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"with-interfere-coverage.test.d.ts","sourceRoot":"","sources":["../../../src/__tests__/build/with-interfere-coverage.test.ts"],"names":[],"mappings":""}