@byline/host-tanstack-start 4.15.0 → 4.16.0

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 (43) hide show
  1. package/dist/admin-shell/chrome/admin-menu-drawer.d.ts +2 -1
  2. package/dist/admin-shell/chrome/admin-menu-drawer.js +14 -2
  3. package/dist/integrations/analytics-agent.d.ts +20 -0
  4. package/dist/integrations/analytics-agent.js +57 -0
  5. package/dist/integrations/analytics-events.d.ts +51 -0
  6. package/dist/integrations/analytics-events.js +129 -0
  7. package/dist/integrations/serve-analytics-agent.d.ts +8 -0
  8. package/dist/integrations/serve-analytics-agent.js +11 -0
  9. package/dist/routes/analytics-range.d.ts +12 -0
  10. package/dist/routes/analytics-range.js +35 -0
  11. package/dist/routes/create-admin-analytics-route.d.ts +8 -0
  12. package/dist/routes/create-admin-analytics-route.js +108 -0
  13. package/dist/routes/create-admin-layout-route.js +10 -4
  14. package/dist/routes/index.d.ts +1 -0
  15. package/dist/routes/index.js +1 -0
  16. package/dist/server-fns/analytics/index.d.ts +9 -0
  17. package/dist/server-fns/analytics/index.js +2 -0
  18. package/dist/server-fns/analytics/maintenance.d.ts +22 -0
  19. package/dist/server-fns/analytics/maintenance.js +42 -0
  20. package/dist/server-fns/analytics/queries.d.ts +25 -0
  21. package/dist/server-fns/analytics/queries.js +54 -0
  22. package/dist/vite/analytics-dev.d.ts +22 -0
  23. package/dist/vite/analytics-dev.js +28 -0
  24. package/dist/vite/index.d.ts +1 -0
  25. package/dist/vite/index.js +1 -0
  26. package/package.json +14 -8
  27. package/src/admin-shell/chrome/admin-menu-drawer.tsx +14 -0
  28. package/src/integrations/analytics-agent.tsx +91 -0
  29. package/src/integrations/analytics-events.test.node.ts +245 -0
  30. package/src/integrations/analytics-events.ts +224 -0
  31. package/src/integrations/serve-analytics-agent.test.node.ts +20 -0
  32. package/src/integrations/serve-analytics-agent.ts +19 -0
  33. package/src/routes/analytics-range.ts +55 -0
  34. package/src/routes/create-admin-analytics-route.test.node.ts +46 -0
  35. package/src/routes/create-admin-analytics-route.tsx +109 -0
  36. package/src/routes/create-admin-layout-route.tsx +15 -8
  37. package/src/routes/index.ts +1 -0
  38. package/src/server-fns/analytics/index.ts +23 -0
  39. package/src/server-fns/analytics/maintenance.ts +54 -0
  40. package/src/server-fns/analytics/queries.ts +86 -0
  41. package/src/vite/analytics-dev.test.node.ts +126 -0
  42. package/src/vite/analytics-dev.ts +56 -0
  43. package/src/vite/index.ts +1 -0
@@ -0,0 +1,86 @@
1
+ /**
2
+ * This Source Code is subject to the terms of the Mozilla Public
3
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
4
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5
+ *
6
+ * Copyright (c) Infonomic Company Limited
7
+ */
8
+
9
+ import { createServerFn } from '@tanstack/react-start'
10
+
11
+ import { assertAdminActor, requireAdminActor } from '@byline/admin'
12
+ import { ANALYTICS_ABILITIES } from '@byline/admin/analytics'
13
+ import type {
14
+ AnalyticsCountryTotal,
15
+ AnalyticsDateRange,
16
+ AnalyticsEventKind,
17
+ AnalyticsPathTotal,
18
+ AnalyticsRankedTotals,
19
+ AnalyticsReferrerTotal,
20
+ AnalyticsReportCoverage,
21
+ AnalyticsSummary,
22
+ } from '@byline/analytics'
23
+ import { getAdminRequestContext } from '@byline/client/server'
24
+
25
+ export interface AnalyticsTopInput extends AnalyticsDateRange {
26
+ kind: AnalyticsEventKind
27
+ limit?: number
28
+ }
29
+
30
+ export interface AnalyticsRuntimeState {
31
+ enabled: boolean
32
+ }
33
+
34
+ export const getAnalyticsRuntime = createServerFn({ method: 'GET' }).handler(
35
+ async (): Promise<AnalyticsRuntimeState> => {
36
+ const context = await getAdminRequestContext()
37
+ requireAdminActor(context, 'reading analytics runtime state')
38
+ const { isAnalyticsRegistered } = await import('@byline/analytics')
39
+ return { enabled: isAnalyticsRegistered() }
40
+ }
41
+ )
42
+
43
+ export const getAnalyticsSummary = createServerFn({ method: 'GET' })
44
+ .validator((input: AnalyticsDateRange) => input)
45
+ .handler(async ({ data }): Promise<AnalyticsSummary> => {
46
+ await assertAnalyticsRead()
47
+ const { getAnalytics } = await import('@byline/analytics')
48
+ return getAnalytics().getSummary(data)
49
+ })
50
+
51
+ export const getAnalyticsReportCoverage = createServerFn({ method: 'GET' }).handler(
52
+ async (): Promise<AnalyticsReportCoverage> => {
53
+ await assertAnalyticsRead()
54
+ const { getAnalytics } = await import('@byline/analytics')
55
+ return getAnalytics().getReportCoverage()
56
+ }
57
+ )
58
+
59
+ export const getAnalyticsTop = createServerFn({ method: 'GET' })
60
+ .validator((input: AnalyticsTopInput) => input)
61
+ .handler(async ({ data }): Promise<AnalyticsRankedTotals<AnalyticsPathTotal>> => {
62
+ await assertAnalyticsRead()
63
+ const { getAnalytics } = await import('@byline/analytics')
64
+ return getAnalytics().getTopPaths(data)
65
+ })
66
+
67
+ export const getAnalyticsReferrers = createServerFn({ method: 'GET' })
68
+ .validator((input: AnalyticsDateRange & { limit?: number }) => input)
69
+ .handler(async ({ data }): Promise<AnalyticsRankedTotals<AnalyticsReferrerTotal>> => {
70
+ await assertAnalyticsRead()
71
+ const { getAnalytics } = await import('@byline/analytics')
72
+ return getAnalytics().getReferrers(data)
73
+ })
74
+
75
+ export const getAnalyticsCountries = createServerFn({ method: 'GET' })
76
+ .validator((input: AnalyticsDateRange) => input)
77
+ .handler(async ({ data }): Promise<AnalyticsCountryTotal[]> => {
78
+ await assertAnalyticsRead()
79
+ const { getAnalytics } = await import('@byline/analytics')
80
+ return getAnalytics().getCountries(data)
81
+ })
82
+
83
+ async function assertAnalyticsRead(): Promise<void> {
84
+ const context = await getAdminRequestContext()
85
+ assertAdminActor(context, ANALYTICS_ABILITIES.read)
86
+ }
@@ -0,0 +1,126 @@
1
+ /**
2
+ * This Source Code is subject to the terms of the Mozilla Public
3
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
4
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5
+ *
6
+ * Copyright (c) Infonomic Company Limited
7
+ */
8
+
9
+ import type { IncomingMessage, ServerResponse } from 'node:http'
10
+
11
+ import { ANALYTICS_AGENT_SOURCE } from '@byline/analytics-agent/source'
12
+ import type { Plugin } from 'vite'
13
+ import { describe, expect, it, vi } from 'vitest'
14
+
15
+ import { bylineAnalyticsDev } from './analytics-dev.js'
16
+
17
+ type Middleware = (
18
+ request: IncomingMessage,
19
+ response: ServerResponse,
20
+ next: (error?: unknown) => void
21
+ ) => void | Promise<void>
22
+
23
+ describe('bylineAnalyticsDev', () => {
24
+ it('directly serves browser script requests before Vite can transform them', async () => {
25
+ const handler = captureMiddleware(bylineAnalyticsDev())
26
+ const request = requestStub('GET', '/b.js?version=current', {
27
+ accept: '*/*',
28
+ 'sec-fetch-dest': 'script',
29
+ })
30
+ const { response, headers, end } = responseStub()
31
+ const next = vi.fn()
32
+
33
+ await handler(request, response, next)
34
+
35
+ expect(response.statusCode).toBe(200)
36
+ expect(headers).toEqual({
37
+ 'cache-control': 'public, max-age=3600, must-revalidate',
38
+ 'content-type': 'text/javascript; charset=utf-8',
39
+ 'x-content-type-options': 'nosniff',
40
+ })
41
+ expect(end).toHaveBeenCalledWith(ANALYTICS_AGENT_SOURCE)
42
+ expect(next).not.toHaveBeenCalled()
43
+ expect(request.headers).toEqual({
44
+ accept: '*/*',
45
+ 'sec-fetch-dest': 'script',
46
+ })
47
+ })
48
+
49
+ it('continues unrelated requests through the Vite middleware chain', async () => {
50
+ const handler = captureMiddleware(bylineAnalyticsDev())
51
+ const { response, end } = responseStub()
52
+ const next = vi.fn()
53
+
54
+ await handler(requestStub('GET', '/about'), response, next)
55
+ await handler(requestStub('POST', '/api/events'), response, next)
56
+
57
+ expect(next).toHaveBeenCalledTimes(2)
58
+ expect(end).not.toHaveBeenCalled()
59
+ })
60
+
61
+ it('supports a custom public agent path', async () => {
62
+ const handler = captureMiddleware(bylineAnalyticsDev({ agentPath: '/first-party.js' }))
63
+ const defaultResponse = responseStub()
64
+ const customResponse = responseStub()
65
+ const next = vi.fn()
66
+
67
+ await handler(requestStub('GET', '/b.js'), defaultResponse.response, next)
68
+ await handler(requestStub('GET', '/first-party.js'), customResponse.response, next)
69
+
70
+ expect(next).toHaveBeenCalledOnce()
71
+ expect(defaultResponse.end).not.toHaveBeenCalled()
72
+ expect(customResponse.end).toHaveBeenCalledWith(ANALYTICS_AGENT_SOURCE)
73
+ })
74
+ })
75
+
76
+ function captureMiddleware(plugin: Plugin): Middleware {
77
+ const handlers: Middleware[] = []
78
+ const configureServer = plugin.configureServer
79
+
80
+ expect(typeof configureServer).toBe('function')
81
+ if (typeof configureServer !== 'function') {
82
+ throw new Error('Expected a Vite configureServer hook')
83
+ }
84
+
85
+ configureServer.call(
86
+ {} as never,
87
+ {
88
+ middlewares: {
89
+ use(handler: Middleware) {
90
+ handlers.push(handler)
91
+ },
92
+ },
93
+ } as never
94
+ )
95
+
96
+ const handler = handlers[0]
97
+ expect(handler).toBeDefined()
98
+ if (handler == null) throw new Error('Expected a Vite middleware')
99
+ return handler
100
+ }
101
+
102
+ function requestStub(
103
+ method: string,
104
+ url: string,
105
+ headers: IncomingMessage['headers'] = {}
106
+ ): IncomingMessage {
107
+ return { method, url, headers } as IncomingMessage
108
+ }
109
+
110
+ function responseStub(): {
111
+ response: ServerResponse
112
+ headers: Record<string, string | number | readonly string[]>
113
+ end: ReturnType<typeof vi.fn>
114
+ } {
115
+ const headers: Record<string, string | number | readonly string[]> = {}
116
+ const end = vi.fn()
117
+ const response = {
118
+ statusCode: 0,
119
+ setHeader(name: string, value: string | number | readonly string[]) {
120
+ headers[name] = value
121
+ return response
122
+ },
123
+ end,
124
+ } as unknown as ServerResponse
125
+ return { response, headers, end }
126
+ }
@@ -0,0 +1,56 @@
1
+ /**
2
+ * This Source Code is subject to the terms of the Mozilla Public
3
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
4
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5
+ *
6
+ * Copyright (c) Infonomic Company Limited
7
+ */
8
+
9
+ import type { Plugin } from 'vite'
10
+
11
+ import { serveAnalyticsAgent } from '../integrations/serve-analytics-agent.js'
12
+
13
+ /**
14
+ * Complete the first-party analytics transport during Vite development.
15
+ *
16
+ * Browser script requests carry `Sec-Fetch-Dest: script`, which Vite routes
17
+ * into its module pipeline before TanStack Start can serve the agent route.
18
+ * This plugin serves the agent directly. The application event route remains
19
+ * responsible for its development identity because POST server routes can
20
+ * bypass Vite's ordinary middleware chain. Production continues to use the
21
+ * application-owned agent route.
22
+ */
23
+ export function bylineAnalyticsDev(
24
+ options: {
25
+ /** Public path that serves the standalone agent. */
26
+ agentPath?: string
27
+ } = {}
28
+ ): Plugin {
29
+ const agentPath = options.agentPath ?? '/b.js'
30
+
31
+ return {
32
+ name: 'byline:analytics-dev',
33
+ apply: 'serve',
34
+ configureServer(server) {
35
+ server.middlewares.use(async (request, response, next) => {
36
+ const pathname = request.url?.split('?', 1)[0]
37
+
38
+ if (request.method === 'GET' && pathname === agentPath) {
39
+ try {
40
+ const agentResponse = serveAnalyticsAgent()
41
+ response.statusCode = agentResponse.status
42
+ agentResponse.headers.forEach((value, name) => {
43
+ response.setHeader(name, value)
44
+ })
45
+ response.end(await agentResponse.text())
46
+ } catch (error) {
47
+ next(error)
48
+ }
49
+ return
50
+ }
51
+
52
+ next()
53
+ })
54
+ },
55
+ }
56
+ }
package/src/vite/index.ts CHANGED
@@ -6,4 +6,5 @@
6
6
  * Copyright (c) Infonomic Company Limited
7
7
  */
8
8
 
9
+ export { bylineAnalyticsDev } from './analytics-dev.js'
9
10
  export { bylineClientHookBoundary } from './client-hook-boundary.js'