@od-oneapp/analytics 2026.1.1301

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 (184) hide show
  1. package/README.md +509 -0
  2. package/dist/ai-YMnynb-t.mjs +3347 -0
  3. package/dist/ai-YMnynb-t.mjs.map +1 -0
  4. package/dist/chunk-DQk6qfdC.mjs +18 -0
  5. package/dist/client-CTzJVFU5.mjs +9 -0
  6. package/dist/client-CTzJVFU5.mjs.map +1 -0
  7. package/dist/client-CcFTauAh.mjs +54 -0
  8. package/dist/client-CcFTauAh.mjs.map +1 -0
  9. package/dist/client-CeOLjbac.mjs +281 -0
  10. package/dist/client-CeOLjbac.mjs.map +1 -0
  11. package/dist/client-D339NFJS.mjs +267 -0
  12. package/dist/client-D339NFJS.mjs.map +1 -0
  13. package/dist/client-next.d.mts +62 -0
  14. package/dist/client-next.d.mts.map +1 -0
  15. package/dist/client-next.mjs +525 -0
  16. package/dist/client-next.mjs.map +1 -0
  17. package/dist/client.d.mts +30 -0
  18. package/dist/client.d.mts.map +1 -0
  19. package/dist/client.mjs +186 -0
  20. package/dist/client.mjs.map +1 -0
  21. package/dist/config-DPS6bSYo.d.mts +34 -0
  22. package/dist/config-DPS6bSYo.d.mts.map +1 -0
  23. package/dist/config-P6P5adJg.mjs +287 -0
  24. package/dist/config-P6P5adJg.mjs.map +1 -0
  25. package/dist/console-8bND3mMU.mjs +128 -0
  26. package/dist/console-8bND3mMU.mjs.map +1 -0
  27. package/dist/ecommerce-Cgu4wlux.mjs +993 -0
  28. package/dist/ecommerce-Cgu4wlux.mjs.map +1 -0
  29. package/dist/emitters-6-nKo8i-.mjs +208 -0
  30. package/dist/emitters-6-nKo8i-.mjs.map +1 -0
  31. package/dist/emitters-DldkVSPp.d.mts +12 -0
  32. package/dist/emitters-DldkVSPp.d.mts.map +1 -0
  33. package/dist/index-BfNWgfa5.d.mts +1494 -0
  34. package/dist/index-BfNWgfa5.d.mts.map +1 -0
  35. package/dist/index-BkIWe--N.d.mts +953 -0
  36. package/dist/index-BkIWe--N.d.mts.map +1 -0
  37. package/dist/index-jPzXRn52.d.mts +184 -0
  38. package/dist/index-jPzXRn52.d.mts.map +1 -0
  39. package/dist/manager-DvRRjza6.d.mts +76 -0
  40. package/dist/manager-DvRRjza6.d.mts.map +1 -0
  41. package/dist/posthog-bootstrap-CYfIy_WS.mjs +1769 -0
  42. package/dist/posthog-bootstrap-CYfIy_WS.mjs.map +1 -0
  43. package/dist/posthog-bootstrap-DWxFrxlt.d.mts +81 -0
  44. package/dist/posthog-bootstrap-DWxFrxlt.d.mts.map +1 -0
  45. package/dist/providers-http-client.d.mts +37 -0
  46. package/dist/providers-http-client.d.mts.map +1 -0
  47. package/dist/providers-http-client.mjs +320 -0
  48. package/dist/providers-http-client.mjs.map +1 -0
  49. package/dist/providers-http-server.d.mts +31 -0
  50. package/dist/providers-http-server.d.mts.map +1 -0
  51. package/dist/providers-http-server.mjs +297 -0
  52. package/dist/providers-http-server.mjs.map +1 -0
  53. package/dist/providers-http.d.mts +46 -0
  54. package/dist/providers-http.d.mts.map +1 -0
  55. package/dist/providers-http.mjs +4 -0
  56. package/dist/server-edge.d.mts +9 -0
  57. package/dist/server-edge.d.mts.map +1 -0
  58. package/dist/server-edge.mjs +373 -0
  59. package/dist/server-edge.mjs.map +1 -0
  60. package/dist/server-next.d.mts +67 -0
  61. package/dist/server-next.d.mts.map +1 -0
  62. package/dist/server-next.mjs +193 -0
  63. package/dist/server-next.mjs.map +1 -0
  64. package/dist/server.d.mts +10 -0
  65. package/dist/server.mjs +7 -0
  66. package/dist/service-cYtBBL8x.mjs +945 -0
  67. package/dist/service-cYtBBL8x.mjs.map +1 -0
  68. package/dist/shared.d.mts +16 -0
  69. package/dist/shared.d.mts.map +1 -0
  70. package/dist/shared.mjs +93 -0
  71. package/dist/shared.mjs.map +1 -0
  72. package/dist/types-BxBnNQ0V.d.mts +354 -0
  73. package/dist/types-BxBnNQ0V.d.mts.map +1 -0
  74. package/dist/types-CBvxUEaF.d.mts +216 -0
  75. package/dist/types-CBvxUEaF.d.mts.map +1 -0
  76. package/dist/types.d.mts +4 -0
  77. package/dist/types.mjs +0 -0
  78. package/dist/vercel-types-lwakUfoI.d.mts +102 -0
  79. package/dist/vercel-types-lwakUfoI.d.mts.map +1 -0
  80. package/package.json +129 -0
  81. package/src/client/index.ts +164 -0
  82. package/src/client/manager.ts +71 -0
  83. package/src/client/next/components.tsx +270 -0
  84. package/src/client/next/hooks.ts +217 -0
  85. package/src/client/next/manager.ts +141 -0
  86. package/src/client/next.ts +144 -0
  87. package/src/client-next.ts +101 -0
  88. package/src/client.ts +89 -0
  89. package/src/examples/ai-sdk-patterns.ts +583 -0
  90. package/src/examples/emitter-patterns.ts +476 -0
  91. package/src/examples/nextjs-emitter-patterns.tsx +403 -0
  92. package/src/next/app-router.tsx +564 -0
  93. package/src/next/client.ts +419 -0
  94. package/src/next/index.ts +84 -0
  95. package/src/next/middleware.ts +429 -0
  96. package/src/next/rsc.tsx +300 -0
  97. package/src/next/server.ts +253 -0
  98. package/src/next/types.d.ts +220 -0
  99. package/src/providers/base-provider.ts +419 -0
  100. package/src/providers/console/client.ts +10 -0
  101. package/src/providers/console/index.ts +152 -0
  102. package/src/providers/console/server.ts +6 -0
  103. package/src/providers/console/types.ts +15 -0
  104. package/src/providers/http/client.ts +464 -0
  105. package/src/providers/http/index.ts +30 -0
  106. package/src/providers/http/server.ts +396 -0
  107. package/src/providers/http/types.ts +135 -0
  108. package/src/providers/posthog/client.ts +518 -0
  109. package/src/providers/posthog/index.ts +11 -0
  110. package/src/providers/posthog/server.ts +329 -0
  111. package/src/providers/posthog/types.ts +104 -0
  112. package/src/providers/segment/client.ts +113 -0
  113. package/src/providers/segment/index.ts +11 -0
  114. package/src/providers/segment/server.ts +115 -0
  115. package/src/providers/segment/types.ts +51 -0
  116. package/src/providers/vercel/client.ts +102 -0
  117. package/src/providers/vercel/index.ts +11 -0
  118. package/src/providers/vercel/server.ts +89 -0
  119. package/src/providers/vercel/types.ts +27 -0
  120. package/src/server/index.ts +103 -0
  121. package/src/server/manager.ts +62 -0
  122. package/src/server/next.ts +210 -0
  123. package/src/server-edge.ts +442 -0
  124. package/src/server-next.ts +39 -0
  125. package/src/server.ts +106 -0
  126. package/src/shared/emitters/ai/README.md +981 -0
  127. package/src/shared/emitters/ai/events/agent.ts +130 -0
  128. package/src/shared/emitters/ai/events/artifacts.ts +167 -0
  129. package/src/shared/emitters/ai/events/chat.ts +126 -0
  130. package/src/shared/emitters/ai/events/chatbot-ecommerce.ts +133 -0
  131. package/src/shared/emitters/ai/events/completion.ts +103 -0
  132. package/src/shared/emitters/ai/events/content-generation.ts +347 -0
  133. package/src/shared/emitters/ai/events/conversation.ts +332 -0
  134. package/src/shared/emitters/ai/events/product-features.ts +1402 -0
  135. package/src/shared/emitters/ai/events/streaming.ts +114 -0
  136. package/src/shared/emitters/ai/events/tool.ts +93 -0
  137. package/src/shared/emitters/ai/index.ts +69 -0
  138. package/src/shared/emitters/ai/track-ai-sdk.ts +74 -0
  139. package/src/shared/emitters/ai/track-ai.ts +50 -0
  140. package/src/shared/emitters/ai/types.ts +1041 -0
  141. package/src/shared/emitters/ai/utils.ts +468 -0
  142. package/src/shared/emitters/ecommerce/events/cart-checkout.ts +106 -0
  143. package/src/shared/emitters/ecommerce/events/coupon.ts +49 -0
  144. package/src/shared/emitters/ecommerce/events/engagement.ts +61 -0
  145. package/src/shared/emitters/ecommerce/events/marketplace.ts +119 -0
  146. package/src/shared/emitters/ecommerce/events/order.ts +199 -0
  147. package/src/shared/emitters/ecommerce/events/product.ts +205 -0
  148. package/src/shared/emitters/ecommerce/events/registry.ts +123 -0
  149. package/src/shared/emitters/ecommerce/events/wishlist-sharing.ts +140 -0
  150. package/src/shared/emitters/ecommerce/index.ts +46 -0
  151. package/src/shared/emitters/ecommerce/track-ecommerce.ts +53 -0
  152. package/src/shared/emitters/ecommerce/types.ts +314 -0
  153. package/src/shared/emitters/ecommerce/utils.ts +216 -0
  154. package/src/shared/emitters/emitter-types.ts +974 -0
  155. package/src/shared/emitters/emitters.ts +292 -0
  156. package/src/shared/emitters/helpers.ts +419 -0
  157. package/src/shared/emitters/index.ts +66 -0
  158. package/src/shared/index.ts +142 -0
  159. package/src/shared/ingestion/index.ts +66 -0
  160. package/src/shared/ingestion/schemas.ts +386 -0
  161. package/src/shared/ingestion/service.ts +628 -0
  162. package/src/shared/node22-features.ts +848 -0
  163. package/src/shared/providers/console-provider.ts +160 -0
  164. package/src/shared/types/base-types.ts +54 -0
  165. package/src/shared/types/console-types.ts +19 -0
  166. package/src/shared/types/posthog-types.ts +131 -0
  167. package/src/shared/types/segment-types.ts +15 -0
  168. package/src/shared/types/types.ts +397 -0
  169. package/src/shared/types/vercel-types.ts +19 -0
  170. package/src/shared/utils/config-client.ts +19 -0
  171. package/src/shared/utils/config.ts +250 -0
  172. package/src/shared/utils/emitter-adapter.ts +212 -0
  173. package/src/shared/utils/manager.test.ts +36 -0
  174. package/src/shared/utils/manager.ts +1322 -0
  175. package/src/shared/utils/posthog-bootstrap.ts +136 -0
  176. package/src/shared/utils/posthog-client-utils.ts +48 -0
  177. package/src/shared/utils/posthog-next-utils.ts +282 -0
  178. package/src/shared/utils/posthog-server-utils.ts +210 -0
  179. package/src/shared/utils/rate-limit.ts +289 -0
  180. package/src/shared/utils/security.ts +545 -0
  181. package/src/shared/utils/validation-client.ts +161 -0
  182. package/src/shared/utils/validation.ts +399 -0
  183. package/src/shared.ts +155 -0
  184. package/src/types/index.ts +62 -0
@@ -0,0 +1,429 @@
1
+ /**
2
+ * @fileoverview Analytics middleware factory - framework agnostic
3
+ *
4
+ * Provides automatic tracking and context extraction for analytics.
5
+ * Framework-agnostic implementation that can be used with Next.js or other frameworks.
6
+ *
7
+ * @remarks
8
+ * This middleware:
9
+ * - Automatically tracks page views and API routes
10
+ * - Extracts analytics context from request headers
11
+ * - Manages PostHog distinct IDs via cookies
12
+ * - Supports route exclusion patterns
13
+ * - Provides framework-agnostic interfaces
14
+ *
15
+ * @module @od-oneapp/analytics/next/middleware
16
+ */
17
+
18
+ import { generateDistinctId, getDistinctIdFromCookies } from '../shared/utils/posthog-bootstrap';
19
+
20
+ import type { AnalyticsConfig } from '../shared/types/types';
21
+
22
+ /**
23
+ * Request interface for middleware (framework-agnostic)
24
+ */
25
+ export interface MiddlewareRequest {
26
+ cookies: {
27
+ get: (name: string) => { value: string } | undefined;
28
+ set: (name: string, value: string, options?: any) => void;
29
+ };
30
+ headers: Headers;
31
+ method: string;
32
+ nextUrl: {
33
+ pathname: string;
34
+ searchParams: URLSearchParams;
35
+ };
36
+ }
37
+
38
+ /**
39
+ * Response interface for middleware (framework-agnostic)
40
+ */
41
+ export interface MiddlewareResponse {
42
+ headers: Headers;
43
+ status: number;
44
+ cookies: {
45
+ set: (name: string, value: string, options?: any) => void;
46
+ };
47
+ }
48
+
49
+ export interface AnalyticsMiddlewareConfig {
50
+ /**
51
+ * Analytics configuration
52
+ */
53
+ analytics: AnalyticsConfig;
54
+
55
+ /**
56
+ * PostHog API key for distinct ID management
57
+ */
58
+ posthogApiKey?: string;
59
+
60
+ /**
61
+ * Routes to exclude from tracking
62
+ */
63
+ excludeRoutes?: string[] | RegExp[];
64
+
65
+ /**
66
+ * Custom route matcher function
67
+ */
68
+ shouldTrack?: (pathname: string) => boolean;
69
+
70
+ /**
71
+ * Enable debug logging
72
+ */
73
+ debug?: boolean;
74
+
75
+ /**
76
+ * Custom event name generator
77
+ */
78
+ getEventName?: (pathname: string, method: string) => string;
79
+
80
+ /**
81
+ * Custom properties extractor
82
+ */
83
+ getProperties?: (request: MiddlewareRequest) => Record<string, any>;
84
+
85
+ /**
86
+ * Track page views automatically
87
+ */
88
+ trackPageViews?: boolean;
89
+
90
+ /**
91
+ * Track API routes automatically
92
+ */
93
+ trackApiRoutes?: boolean;
94
+
95
+ /**
96
+ * Factory function to create response (framework-specific)
97
+ */
98
+ createResponse: () => MiddlewareResponse;
99
+ }
100
+
101
+ /**
102
+ * Create analytics middleware (framework-agnostic)
103
+ * @param config - Middleware configuration
104
+ * @returns Middleware function that accepts a request and returns a response
105
+ */
106
+ export function createAnalyticsMiddleware(config: AnalyticsMiddlewareConfig) {
107
+ return async function analyticsMiddleware(
108
+ request: MiddlewareRequest,
109
+ ): Promise<MiddlewareResponse> {
110
+ const { pathname, searchParams } = request.nextUrl;
111
+ const { method } = request;
112
+
113
+ // Check if we should track this route
114
+ if (!shouldTrackRoute(pathname, config)) {
115
+ return config.createResponse();
116
+ }
117
+
118
+ // Create response object to modify headers/cookies
119
+ const response = config.createResponse();
120
+
121
+ try {
122
+ // Handle distinct ID for PostHog
123
+ if (config.posthogApiKey) {
124
+ const distinctId = await ensureDistinctId(request, response, config.posthogApiKey);
125
+
126
+ // Add distinct ID to response headers for downstream use
127
+ response.headers.set('x-analytics-distinct-id', distinctId);
128
+ }
129
+
130
+ // Extract analytics context
131
+ const context = extractAnalyticsContext(request);
132
+
133
+ // Add context to response headers for server components
134
+ response.headers.set('x-analytics-context', JSON.stringify(context));
135
+
136
+ // Track the request if edge runtime analytics is available
137
+ if (globalThis.analytics && typeof globalThis.analytics.track === 'function') {
138
+ const eventName = config.getEventName
139
+ ? config.getEventName(pathname, method)
140
+ : getDefaultEventName(pathname, method, config);
141
+
142
+ const properties = {
143
+ ...context,
144
+ ...(config.getProperties ? config.getProperties(request) : {}),
145
+ pathname,
146
+ method,
147
+ search_params: Object.fromEntries(searchParams.entries()),
148
+ timestamp: new Date().toISOString(),
149
+ };
150
+
151
+ // Track asynchronously without blocking response
152
+ // Use async IIFE to properly handle the promise
153
+ void (async () => {
154
+ try {
155
+ // Re-check analytics exists inside async context for TypeScript
156
+ if (globalThis.analytics && typeof globalThis.analytics.track === 'function') {
157
+ await globalThis.analytics.track(eventName, properties);
158
+ }
159
+ } catch {
160
+ // Silently fail to avoid disrupting request
161
+ }
162
+ })();
163
+ }
164
+ } catch {
165
+ // Don't let analytics errors break the app
166
+ if (config.debug) {
167
+ // Log error in debug mode only
168
+ }
169
+ }
170
+
171
+ return response;
172
+ };
173
+ }
174
+
175
+ /**
176
+ * Ensure distinct ID exists for PostHog tracking
177
+ */
178
+ async function ensureDistinctId(
179
+ request: MiddlewareRequest,
180
+ response: MiddlewareResponse,
181
+ apiKey: string,
182
+ ): Promise<string> {
183
+ // Try to get existing distinct ID from cookies
184
+ const existingId = getDistinctIdFromCookies(request.cookies as any, apiKey);
185
+
186
+ if (existingId) {
187
+ return existingId;
188
+ }
189
+
190
+ // Generate new distinct ID
191
+ const newId = generateDistinctId();
192
+
193
+ // Set cookie with appropriate options
194
+ const cookieName = `ph_${apiKey}_posthog`;
195
+ response.cookies.set(cookieName, JSON.stringify({ distinct_id: newId }), {
196
+ httpOnly: false, // PostHog JS needs to read this
197
+ maxAge: 365 * 24 * 60 * 60, // 1 year
198
+ path: '/',
199
+ sameSite: 'lax',
200
+ secure: process.env.NODE_ENV === 'production',
201
+ });
202
+
203
+ return newId;
204
+ }
205
+
206
+ /**
207
+ * Check if route should be tracked
208
+ */
209
+ function shouldTrackRoute(pathname: string, config: AnalyticsMiddlewareConfig): boolean {
210
+ // Use custom matcher if provided
211
+ if (config.shouldTrack) {
212
+ return config.shouldTrack(pathname);
213
+ }
214
+
215
+ // Check exclude routes
216
+ if (config.excludeRoutes) {
217
+ for (const exclude of config.excludeRoutes) {
218
+ if (typeof exclude === 'string') {
219
+ if (pathname.startsWith(exclude)) {
220
+ return false;
221
+ }
222
+ } else if (exclude instanceof RegExp && exclude.test(pathname)) {
223
+ return false;
224
+ }
225
+ }
226
+ }
227
+
228
+ // Default exclusions
229
+ const defaultExcludes = [
230
+ '/_next/',
231
+ '/favicon.ico',
232
+ '/robots.txt',
233
+ '/sitemap.xml',
234
+ '/.well-known/',
235
+ ];
236
+
237
+ for (const exclude of defaultExcludes) {
238
+ if (pathname.startsWith(exclude)) {
239
+ return false;
240
+ }
241
+ }
242
+
243
+ // Check if we should track this type of route
244
+ const isApiRoute = pathname.startsWith('/api/');
245
+
246
+ if (isApiRoute && config.trackApiRoutes === false) {
247
+ return false;
248
+ }
249
+
250
+ if (!isApiRoute && config.trackPageViews === false) {
251
+ return false;
252
+ }
253
+
254
+ return true;
255
+ }
256
+
257
+ /**
258
+ * Extract analytics context from request
259
+ */
260
+ function extractAnalyticsContext(request: MiddlewareRequest): Record<string, any> {
261
+ const { headers } = request;
262
+
263
+ return {
264
+ // User agent info
265
+ user_agent: headers.get('user-agent') ?? undefined,
266
+
267
+ // Referrer info
268
+ referer: headers.get('referer') ?? undefined,
269
+ referrer: headers.get('referrer') ?? undefined,
270
+
271
+ // IP and location info (if available)
272
+ ip:
273
+ headers.get('x-forwarded-for')?.split(',')[0]?.trim() ??
274
+ headers.get('x-real-ip') ??
275
+ undefined,
276
+
277
+ city: headers.get('x-vercel-ip-city') ?? undefined,
278
+ country: headers.get('x-vercel-ip-country') ?? undefined,
279
+ region: headers.get('x-vercel-ip-country-region') ?? undefined,
280
+
281
+ // Request info
282
+ host: headers.get('host') ?? undefined,
283
+ protocol: headers.get('x-forwarded-proto') ?? 'https',
284
+
285
+ accept_encoding: headers.get('accept-encoding') ?? undefined,
286
+ // Accept headers
287
+ accept_language: headers.get('accept-language') ?? undefined,
288
+
289
+ deployment_id: headers.get('x-vercel-deployment-id') ?? undefined,
290
+ // Edge runtime info
291
+ edge_region: headers.get('x-vercel-edge-region') ?? undefined,
292
+ };
293
+ }
294
+
295
+ /**
296
+ * Get default event name based on route type
297
+ */
298
+ function getDefaultEventName(
299
+ pathname: string,
300
+ method: string,
301
+ _config: AnalyticsMiddlewareConfig,
302
+ ): string {
303
+ const isApiRoute = pathname.startsWith('/api/');
304
+
305
+ if (isApiRoute) {
306
+ return `API ${method} Request`;
307
+ }
308
+
309
+ return 'Page View';
310
+ }
311
+
312
+ /**
313
+ * Middleware configuration helper for common patterns (framework-agnostic)
314
+ * Note: createResponse must be provided by the framework-specific wrapper
315
+ */
316
+ export function createAnalyticsMiddlewareConfig(options: {
317
+ createResponse: () => MiddlewareResponse;
318
+ posthogApiKey?: string;
319
+ segmentWriteKey?: string;
320
+ excludeRoutes?: string[] | RegExp[];
321
+ debug?: boolean;
322
+ }): AnalyticsMiddlewareConfig {
323
+ const config: AnalyticsMiddlewareConfig = {
324
+ analytics: {
325
+ providers: {},
326
+ },
327
+ createResponse: options.createResponse,
328
+ ...(options.debug !== undefined && { debug: options.debug }),
329
+ ...(options.excludeRoutes !== undefined && { excludeRoutes: options.excludeRoutes }),
330
+ ...(options.posthogApiKey !== undefined && { posthogApiKey: options.posthogApiKey }),
331
+ trackApiRoutes: true,
332
+ trackPageViews: true,
333
+ };
334
+
335
+ // Add providers based on configuration
336
+ if (options.posthogApiKey) {
337
+ config.analytics.providers.posthog = {
338
+ apiKey: options.posthogApiKey,
339
+ };
340
+ }
341
+
342
+ if (options.segmentWriteKey) {
343
+ config.analytics.providers.segment = {
344
+ writeKey: options.segmentWriteKey,
345
+ };
346
+ }
347
+
348
+ return config;
349
+ }
350
+
351
+ /**
352
+ * Extract analytics context from headers (framework-agnostic)
353
+ */
354
+ export function getAnalyticsContextFromHeaders(headers: Headers): Record<string, any> {
355
+ const contextHeader = headers.get('x-analytics-context');
356
+
357
+ if (!contextHeader) {
358
+ return {};
359
+ }
360
+
361
+ try {
362
+ return JSON.parse(contextHeader);
363
+ } catch {
364
+ return {};
365
+ }
366
+ }
367
+
368
+ /**
369
+ * Get distinct ID from headers (for server components)
370
+ */
371
+ export function getDistinctIdFromHeaders(headers: Headers): string | null {
372
+ return headers.get('x-analytics-distinct-id');
373
+ }
374
+
375
+ /**
376
+ * Conditional middleware helper
377
+ */
378
+ export function conditionalAnalyticsMiddleware(
379
+ config: AnalyticsMiddlewareConfig,
380
+ condition: (request: MiddlewareRequest) => boolean,
381
+ ) {
382
+ const middleware = createAnalyticsMiddleware(config);
383
+
384
+ return async function (request: MiddlewareRequest): Promise<MiddlewareResponse> {
385
+ if (condition(request)) {
386
+ return middleware(request);
387
+ }
388
+ return config.createResponse();
389
+ };
390
+ }
391
+
392
+ /**
393
+ * Compose multiple middleware functions
394
+ */
395
+ export function composeMiddleware(
396
+ ...middlewares: ((
397
+ request: MiddlewareRequest,
398
+ createResponse?: () => MiddlewareResponse,
399
+ ) => Promise<MiddlewareResponse>)[]
400
+ ) {
401
+ return async function composedMiddleware(
402
+ request: MiddlewareRequest,
403
+ createResponse: () => MiddlewareResponse,
404
+ ): Promise<MiddlewareResponse> {
405
+ let response = createResponse();
406
+
407
+ for (const middleware of middlewares) {
408
+ response = await middleware(request, createResponse);
409
+
410
+ // If middleware returns a redirect or error, stop processing
411
+ if (response.status !== 200 || response.headers.get('location')) {
412
+ return response;
413
+ }
414
+ }
415
+
416
+ return response;
417
+ };
418
+ }
419
+
420
+ // Type declaration for edge runtime global analytics
421
+ declare global {
422
+ var analytics:
423
+ | {
424
+ track: (event: string, properties?: any) => Promise<void>;
425
+ identify: (userId: string, traits?: any) => Promise<void>;
426
+ page: (name?: string, properties?: any) => Promise<void>;
427
+ }
428
+ | undefined;
429
+ }