@refraction-ui/astro 0.5.1 → 0.6.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.
@@ -0,0 +1,273 @@
1
+ /**
2
+ * @refraction-ui/analytics — type contracts.
3
+ *
4
+ * The library is a neutral Segment-spec collector/router. The app instruments
5
+ * once via the canonical API; the router fans the canonical envelope out to
6
+ * N pluggable sinks. There is NO privileged engine — every vendor is a sink.
7
+ */
8
+
9
+ /** Canonical Segment event types. */
10
+ export type AnalyticsEventType =
11
+ | 'track'
12
+ | 'identify'
13
+ | 'page'
14
+ | 'screen'
15
+ | 'group'
16
+ | 'alias'
17
+
18
+ /** Schema version stamped onto every envelope and the wire contract path. */
19
+ export const SCHEMA_VERSION = 1
20
+
21
+ /** Arbitrary, JSON-serialisable bag of values. */
22
+ export type AnalyticsProperties = Record<string, unknown>
23
+
24
+ /**
25
+ * The context block attached to every event. `library` identifies the
26
+ * collector; `app`/`env` identify the product instance.
27
+ */
28
+ export interface AnalyticsContext {
29
+ app: string
30
+ env: string
31
+ page?: {
32
+ path?: string
33
+ url?: string
34
+ referrer?: string
35
+ title?: string
36
+ search?: string
37
+ }
38
+ library: {
39
+ name: string
40
+ version: string
41
+ }
42
+ /** Free-form additions contributed by `with(context)` children. */
43
+ [key: string]: unknown
44
+ }
45
+
46
+ /**
47
+ * The canonical Segment envelope. Every sink receives events in exactly this
48
+ * shape; the built-in HTTP sink ships it verbatim over the wire contract.
49
+ */
50
+ export interface AnalyticsEvent {
51
+ /** Segment call type. */
52
+ type: AnalyticsEventType
53
+ /** Event name (track/screen) or page name (page). */
54
+ event?: string
55
+ /** Idempotency key — backends MUST dedupe on this. */
56
+ messageId: string
57
+ /** Persistent, non-PII, resettable client id. */
58
+ anonymousId: string
59
+ /** Opaque app-supplied id (set after identify). */
60
+ userId?: string
61
+ /** Group id (group calls). */
62
+ groupId?: string
63
+ /** Previous id (alias calls). */
64
+ previousId?: string
65
+ /** Analytics session id (UUIDv4, minted at session start). */
66
+ sessionId: string
67
+ /** track/page/screen/group payload. */
68
+ properties?: AnalyticsProperties
69
+ /** identify/group trait payload. */
70
+ traits?: AnalyticsProperties
71
+ /** Collector + product context. */
72
+ context: AnalyticsContext
73
+ /** ISO-8601 client timestamp. */
74
+ timestamp: string
75
+ /** Envelope schema version. */
76
+ schemaVersion: number
77
+ }
78
+
79
+ /**
80
+ * Sink SPI — implemented by adapter packages (GA4, Azure, PostHog) and by the
81
+ * built-in HTTP sink. A sink declares the consent categories it requires; the
82
+ * router will not deliver to a sink whose categories are not all granted.
83
+ */
84
+ export interface AnalyticsSink {
85
+ /** Stable sink identifier. */
86
+ name: string
87
+ /** Consent categories this sink requires (e.g. ['analytics']). */
88
+ consentCategories?: string[]
89
+ /** Called once before the first delivery. */
90
+ init?(ctx: SinkInitContext): void | Promise<void>
91
+ /** Deliver a batch of canonical events. */
92
+ deliver(batch: AnalyticsEvent[], ctx: SinkDeliverContext): void | Promise<void>
93
+ /** Flush any buffered state (best-effort). */
94
+ flush?(): void | Promise<void>
95
+ /** Release resources on reset()/shutdown. */
96
+ shutdown?(): void | Promise<void>
97
+ }
98
+
99
+ /** Context handed to `sink.init`. */
100
+ export interface SinkInitContext {
101
+ app: string
102
+ env: string
103
+ endpoint?: string
104
+ }
105
+
106
+ /** Context handed to every `sink.deliver`. */
107
+ export interface SinkDeliverContext {
108
+ /** True on the unload path — sinks should prefer sendBeacon. */
109
+ unload: boolean
110
+ }
111
+
112
+ /** Cross-tab persistence SPI. Defaults pick localStorage/cookie/memory. */
113
+ export interface AnalyticsStorage {
114
+ get(key: string): string | null
115
+ set(key: string, value: string): void
116
+ remove(key: string): void
117
+ }
118
+
119
+ /** Session engine configuration. */
120
+ export interface SessionConfig {
121
+ /** Inactivity timeout in ms before a new session is minted. GA4 = 30min. */
122
+ timeoutMs?: number
123
+ /** Reset the session when a campaign/utm change is detected. */
124
+ resetOnCampaign?: boolean
125
+ /** Persistence backend; defaults to localStorage→cookie→memory. */
126
+ storage?: AnalyticsStorage
127
+ /** Storage key namespace. */
128
+ storageKey?: string
129
+ }
130
+
131
+ /** Identity engine configuration. */
132
+ export interface IdentityConfig {
133
+ /** Persistence backend; defaults to localStorage→cookie→memory. */
134
+ storage?: AnalyticsStorage
135
+ /** Storage key namespace. */
136
+ storageKey?: string
137
+ }
138
+
139
+ /** Consent gate configuration. */
140
+ export interface ConsentConfig {
141
+ /** Categories granted at boot. */
142
+ granted?: string[]
143
+ /**
144
+ * If true, an event is dropped entirely when NO sink can receive it.
145
+ * If false (default), events are still buffered until consent is granted.
146
+ */
147
+ strict?: boolean
148
+ }
149
+
150
+ /** Consent gate runtime API. */
151
+ export interface ConsentAPI {
152
+ grant(...categories: string[]): void
153
+ revoke(...categories: string[]): void
154
+ granted(): string[]
155
+ isGranted(category: string): boolean
156
+ }
157
+
158
+ /** Session runtime API. */
159
+ export interface SessionAPI {
160
+ /** Current session id (mints one lazily if none active). */
161
+ id(): string
162
+ /** Force-start a new session, returning the new id. */
163
+ start(): string
164
+ /** End the current session. */
165
+ end(): void
166
+ /** Attach arbitrary session-scoped properties. */
167
+ set(props: AnalyticsProperties): void
168
+ }
169
+
170
+ /** Built-in HTTP sink options (Segment HTTP Tracking API wire contract). */
171
+ export interface HttpSinkOptions {
172
+ /** Base endpoint; events POST to `{endpoint}/v{schemaVersion}/batch`. */
173
+ endpoint: string
174
+ /** Write key — sent as `Authorization: Basic base64(writeKey:)`. */
175
+ writeKey: string
176
+ /** Max retries for 429/5xx (exponential backoff). Default 3. */
177
+ maxRetries?: number
178
+ /** Base backoff delay in ms. Default 500. */
179
+ backoffBaseMs?: number
180
+ /** Injected fetch (defaults to global fetch). */
181
+ fetchImpl?: typeof fetch
182
+ /** Injected sendBeacon (defaults to navigator.sendBeacon). */
183
+ beaconImpl?: (url: string, body: string) => boolean
184
+ /** Consent categories this sink requires. Default ['analytics']. */
185
+ consentCategories?: string[]
186
+ /** Soft per-batch byte cap (Segment ≈ 500KB). Default 500_000. */
187
+ maxBatchBytes?: number
188
+ /** Soft per-event byte cap (Segment ≈ 32KB). Default 32_000. */
189
+ maxEventBytes?: number
190
+ }
191
+
192
+ /** `createAnalytics` configuration. */
193
+ export interface AnalyticsConfig {
194
+ /** Product/app identifier (stamped into context.app). */
195
+ app: string
196
+ /** Environment, e.g. 'development' | 'production'. Drives presets. */
197
+ env: string
198
+ /** When set, a built-in HTTP sink is auto-registered to this endpoint. */
199
+ endpoint?: string
200
+ /** Write key for the auto-registered HTTP sink. */
201
+ writeKey?: string
202
+ /** Kill switch — when false, a tree-shakeable noop is returned. Default true. */
203
+ enabled?: boolean
204
+ /** 0..1 sampling rate applied per top-level call. Default 1. */
205
+ sampleRate?: number
206
+ /** Extra property/trait keys to redact (in addition to the PII deny-list). */
207
+ redactKeys?: string[]
208
+ /** Explicit sinks (in addition to / instead of the auto HTTP sink). */
209
+ sinks?: AnalyticsSink[]
210
+ /** Session engine config. */
211
+ session?: SessionConfig
212
+ /** Identity engine config. */
213
+ identity?: IdentityConfig
214
+ /** Consent gate config. */
215
+ consent?: ConsentConfig
216
+ /**
217
+ * Force a preset. Defaults: env==='production' → 'prod', else 'dev'.
218
+ * dev = sync + console; prod = batch + sample + beacon flush on unload.
219
+ */
220
+ preset?: 'dev' | 'prod'
221
+ /** Batch size before an automatic flush (prod). Default 20. */
222
+ batchSize?: number
223
+ /** Auto-flush interval in ms (prod). Default 10_000. */
224
+ flushIntervalMs?: number
225
+ }
226
+
227
+ /** Options accepted by every top-level call. */
228
+ export interface CallOptions {
229
+ /** Per-call timestamp override (ISO-8601). */
230
+ timestamp?: string
231
+ /** Per-call context overrides (shallow-merged). */
232
+ context?: Partial<AnalyticsContext>
233
+ }
234
+
235
+ /** The public analytics surface returned by `createAnalytics`. */
236
+ export interface Analytics {
237
+ track(event: string, properties?: AnalyticsProperties, opts?: CallOptions): void
238
+ identify(userId: string, traits?: AnalyticsProperties, opts?: CallOptions): void
239
+ page(name?: string, properties?: AnalyticsProperties, opts?: CallOptions): void
240
+ screen(name?: string, properties?: AnalyticsProperties, opts?: CallOptions): void
241
+ group(groupId: string, traits?: AnalyticsProperties, opts?: CallOptions): void
242
+ alias(userId: string, previousId?: string, opts?: CallOptions): void
243
+
244
+ /** Analytics-session API (NOT replay). */
245
+ session: SessionAPI
246
+ /** Consent gate API. */
247
+ consent: ConsentAPI
248
+
249
+ /** Persistent, non-PII anonymous id. */
250
+ anonymousId(): string
251
+ /** Current opaque user id (if identified). */
252
+ userId(): string | undefined
253
+
254
+ /** Derive a child that merges extra context into every event. */
255
+ with(context: Partial<AnalyticsContext>): Analytics
256
+
257
+ /** Register an additional sink at runtime. */
258
+ addSink(sink: AnalyticsSink): void
259
+ /** Remove a sink by name. */
260
+ removeSink(name: string): void
261
+ /** Registered sink names. */
262
+ readonly sinks: string[]
263
+
264
+ /** Flush all sinks (and the internal batch). */
265
+ flush(): Promise<void>
266
+ /**
267
+ * Reset identity + session (privacy-safe logout). Mints a fresh
268
+ * anonymousId and ends the session.
269
+ */
270
+ reset(): void
271
+ /** Whether this is a live collector (false for the noop). */
272
+ readonly enabled: boolean
273
+ }
@@ -0,0 +1,63 @@
1
+ /**
2
+ * uuidv4 — RFC 4122 version 4 UUID.
3
+ *
4
+ * Prefers `crypto.randomUUID` / `crypto.getRandomValues` when available
5
+ * (browser, modern Node) and degrades to `Math.random` only as a last
6
+ * resort. No external dependency by design (this is the neutral router).
7
+ */
8
+ export function uuidv4(): string {
9
+ const c: Crypto | undefined =
10
+ typeof globalThis !== 'undefined'
11
+ ? (globalThis.crypto as Crypto | undefined)
12
+ : undefined
13
+
14
+ if (c && typeof c.randomUUID === 'function') {
15
+ return c.randomUUID()
16
+ }
17
+
18
+ const bytes = new Uint8Array(16)
19
+ if (c && typeof c.getRandomValues === 'function') {
20
+ c.getRandomValues(bytes)
21
+ } else {
22
+ for (let i = 0; i < 16; i++) bytes[i] = Math.floor(Math.random() * 256)
23
+ }
24
+
25
+ // Per RFC 4122 §4.4: set version (4) and variant (10xx) bits.
26
+ bytes[6] = (bytes[6] & 0x0f) | 0x40
27
+ bytes[8] = (bytes[8] & 0x3f) | 0x80
28
+
29
+ const hex: string[] = []
30
+ for (let i = 0; i < 256; i++) hex.push((i + 0x100).toString(16).slice(1))
31
+
32
+ return (
33
+ hex[bytes[0]] +
34
+ hex[bytes[1]] +
35
+ hex[bytes[2]] +
36
+ hex[bytes[3]] +
37
+ '-' +
38
+ hex[bytes[4]] +
39
+ hex[bytes[5]] +
40
+ '-' +
41
+ hex[bytes[6]] +
42
+ hex[bytes[7]] +
43
+ '-' +
44
+ hex[bytes[8]] +
45
+ hex[bytes[9]] +
46
+ '-' +
47
+ hex[bytes[10]] +
48
+ hex[bytes[11]] +
49
+ hex[bytes[12]] +
50
+ hex[bytes[13]] +
51
+ hex[bytes[14]] +
52
+ hex[bytes[15]]
53
+ )
54
+ }
55
+
56
+ /** RFC 4122 v4 shape matcher (case-insensitive). */
57
+ export const UUID_V4_RE =
58
+ /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i
59
+
60
+ /** True when `value` is a well-formed v4 UUID. */
61
+ export function isUuidV4(value: unknown): value is string {
62
+ return typeof value === 'string' && UUID_V4_RE.test(value)
63
+ }
@@ -0,0 +1,130 @@
1
+ ---
2
+ /**
3
+ * AnalyticsScript — injects a <script> that initializes the analytics
4
+ * router on the page. Exposes `window.__rfr_analytics` so other scripts
5
+ * can call track / identify / page / screen / group / alias.
6
+ *
7
+ * Props mirror the consumer-facing slice of `AnalyticsConfig` from
8
+ * `@refraction-ui/analytics`. The app instruments once and never names a
9
+ * vendor — `endpoint` points at your server relay (recommended prod
10
+ * topology: client → your endpoint → server-side fan-out), so the browser
11
+ * ships only our neutral router and stays ad-blocker-proof.
12
+ *
13
+ * Because inline scripts cannot import ES modules, this island stashes the
14
+ * config and a deferred `init(createAnalytics, sinks)` helper. The consumer
15
+ * calls it from their own module script with the real core:
16
+ *
17
+ * ```astro
18
+ * ---
19
+ * import { AnalyticsScript } from '../astro-analytics/index.ts'
20
+ * ---
21
+ * <AnalyticsScript app="my-app" env="production" endpoint="/relay" />
22
+ * <script>
23
+ * import { createAnalytics } from '../analytics/index.ts'
24
+ * window.__rfr_analytics.init(createAnalytics)
25
+ * </script>
26
+ * ```
27
+ */
28
+
29
+ interface Props extends astroHTML.JSX.HTMLAttributes {
30
+ /** Product/app identifier (stamped into context.app). */
31
+ app: string
32
+ /** Environment, e.g. 'development' | 'production'. Drives presets. */
33
+ env: string
34
+ /** Server-relay endpoint; an HTTP sink auto-registers here. */
35
+ endpoint?: string
36
+ /** Write key for the auto-registered HTTP sink. */
37
+ writeKey?: string
38
+ /** Kill switch — when false, a tree-shakeable noop is returned. */
39
+ enabled?: boolean
40
+ /** 0..1 sampling rate applied per top-level call. */
41
+ sampleRate?: number
42
+ /** Extra property/trait keys to redact (in addition to PII deny-list). */
43
+ redactKeys?: string[]
44
+ /** Force a preset (defaults from env). */
45
+ preset?: 'dev' | 'prod'
46
+ }
47
+
48
+ const {
49
+ app,
50
+ env,
51
+ endpoint,
52
+ writeKey,
53
+ enabled,
54
+ sampleRate,
55
+ redactKeys,
56
+ preset,
57
+ ...props
58
+ } = Astro.props
59
+
60
+ const configJSON = JSON.stringify({
61
+ app,
62
+ env,
63
+ endpoint,
64
+ writeKey,
65
+ enabled,
66
+ sampleRate,
67
+ redactKeys,
68
+ preset,
69
+ })
70
+ ---
71
+
72
+ <script
73
+ data-rfr-analytics-config={configJSON}
74
+ {...props}
75
+ is:inline
76
+ >
77
+ ;(function () {
78
+ // Only initialize once
79
+ if (window.__rfr_analytics) return
80
+
81
+ // We cannot import ES modules from an inline script, so we stash the
82
+ // config and let the consumer call init() from their own module script.
83
+ var configEl = document.querySelector('[data-rfr-analytics-config]')
84
+ var config = {}
85
+ if (configEl) {
86
+ try { config = JSON.parse(configEl.getAttribute('data-rfr-analytics-config') || '{}') } catch (_) { /* */ }
87
+ }
88
+
89
+ function notReady() {
90
+ throw new Error('Analytics not initialized. Call window.__rfr_analytics.init(createAnalytics, sinks) first.')
91
+ }
92
+
93
+ // Placeholder that will be replaced once createAnalytics is called
94
+ window.__rfr_analytics = {
95
+ _config: config,
96
+ _api: null,
97
+ _ready: false,
98
+ init: function (createAnalytics, sinks) {
99
+ var api = createAnalytics(
100
+ sinks && sinks.length
101
+ ? Object.assign({}, config, { sinks: sinks })
102
+ : config
103
+ )
104
+ window.__rfr_analytics._api = api
105
+ window.__rfr_analytics._ready = true
106
+ window.__rfr_analytics.track = api.track.bind(api)
107
+ window.__rfr_analytics.identify = api.identify.bind(api)
108
+ window.__rfr_analytics.page = api.page.bind(api)
109
+ window.__rfr_analytics.screen = api.screen.bind(api)
110
+ window.__rfr_analytics.group = api.group.bind(api)
111
+ window.__rfr_analytics.alias = api.alias.bind(api)
112
+ window.__rfr_analytics.flush = api.flush.bind(api)
113
+ window.__rfr_analytics.reset = api.reset.bind(api)
114
+ window.__rfr_analytics.session = api.session
115
+ window.__rfr_analytics.consent = api.consent
116
+ return api
117
+ },
118
+ track: notReady,
119
+ identify: notReady,
120
+ page: notReady,
121
+ screen: notReady,
122
+ group: notReady,
123
+ alias: notReady,
124
+ flush: notReady,
125
+ reset: notReady,
126
+ session: null,
127
+ consent: null,
128
+ }
129
+ })()
130
+ </script>
@@ -0,0 +1,49 @@
1
+ export { default as AnalyticsScript } from './AnalyticsScript.astro'
2
+
3
+ // Re-export core factories and types for convenience. The Astro adapter is a
4
+ // thin island over the neutral router — no privileged engine, every vendor is
5
+ // a sink, and the recommended prod topology routes through your server relay.
6
+ export {
7
+ createAnalytics,
8
+ createHttpSink,
9
+ createConsoleSink,
10
+ createMockSink,
11
+ createSession,
12
+ createIdentity,
13
+ createConsent,
14
+ createRedactor,
15
+ createMemoryStorage,
16
+ createLocalStorageAdapter,
17
+ createCookieAdapter,
18
+ resolveStorage,
19
+ campaignFingerprint,
20
+ uuidv4,
21
+ isUuidV4,
22
+ UUID_V4_RE,
23
+ PII_DENY_LIST,
24
+ PII_EXACT_KEYS,
25
+ REDACTED,
26
+ SCHEMA_VERSION,
27
+ DEFAULT_SESSION_TIMEOUT_MS,
28
+ type Analytics,
29
+ type AnalyticsConfig,
30
+ type AnalyticsEvent,
31
+ type AnalyticsEventType,
32
+ type AnalyticsProperties,
33
+ type AnalyticsContext,
34
+ type AnalyticsSink,
35
+ type AnalyticsStorage,
36
+ type SinkInitContext,
37
+ type SinkDeliverContext,
38
+ type SessionConfig,
39
+ type SessionAPI,
40
+ type IdentityConfig,
41
+ type ConsentConfig,
42
+ type ConsentAPI,
43
+ type HttpSinkOptions,
44
+ type CallOptions,
45
+ type ConsoleSinkOptions,
46
+ type MockSink,
47
+ type CreateMockSinkOptions,
48
+ type Redactor,
49
+ } from '../analytics/index.ts'
@@ -0,0 +1,108 @@
1
+ ---
2
+ /**
3
+ * TelemetryScript — injects a <script> that initializes the telemetry client
4
+ * on the page. Exposes `window.__rfr_telemetry` for other scripts to call
5
+ * debug / info / warn / error / fatal / child / startSpan / flush.
6
+ *
7
+ * Props mirror TelemetryConfig from @refraction-ui/logger. Faro stays fully
8
+ * hidden — the only wiring a consumer supplies is the backend `endpoint`.
9
+ */
10
+
11
+ interface Props extends astroHTML.JSX.HTMLAttributes {
12
+ /** Logical app/service name attached to every record. */
13
+ app: string
14
+ /** Environment preset selector. */
15
+ env: 'development' | 'production'
16
+ /** Remote collector endpoint. Omit for console-only (zero-config dev). */
17
+ endpoint?: string
18
+ /** Master kill switch. `false` -> tree-shakeable noop. Defaults to true. */
19
+ enabled?: boolean
20
+ /** Fraction of records kept, 0..1. Defaults to the preset value. */
21
+ sampleRate?: number
22
+ /** Context keys to strip (deep) before a record is emitted. */
23
+ redactKeys?: string[]
24
+ }
25
+
26
+ const {
27
+ app,
28
+ env,
29
+ endpoint,
30
+ enabled,
31
+ sampleRate,
32
+ redactKeys,
33
+ ...props
34
+ } = Astro.props
35
+
36
+ const configJSON = JSON.stringify({
37
+ app,
38
+ env,
39
+ endpoint,
40
+ enabled,
41
+ sampleRate,
42
+ redactKeys,
43
+ })
44
+ ---
45
+
46
+ <script
47
+ data-rfr-telemetry-config={configJSON}
48
+ {...props}
49
+ is:inline
50
+ >
51
+ ;(function () {
52
+ // Only initialize once
53
+ if (window.__rfr_telemetry) return
54
+
55
+ // We cannot import ES modules from an inline script, so we stash the config
56
+ // and let the consumer call the init function from their own module script.
57
+ // This inline script sets up the namespace and a deferred init helper.
58
+ var configEl = document.querySelector('[data-rfr-telemetry-config]')
59
+ var config = {}
60
+ if (configEl) {
61
+ try { config = JSON.parse(configEl.getAttribute('data-rfr-telemetry-config') || '{}') } catch (_) { /* */ }
62
+ }
63
+
64
+ function notReady() {
65
+ throw new Error('Telemetry not initialized. Call window.__rfr_telemetry.init(createTelemetry) first.')
66
+ }
67
+
68
+ // Placeholder that will be replaced once createTelemetry is called
69
+ window.__rfr_telemetry = {
70
+ _config: config,
71
+ _api: null,
72
+ _ready: false,
73
+ init: function (createTelemetry, sinks) {
74
+ var api = createTelemetry(config)
75
+ if (sinks) {
76
+ sinks.forEach(function (sink) {
77
+ api.addSink(sink)
78
+ })
79
+ }
80
+ window.__rfr_telemetry._api = api
81
+ window.__rfr_telemetry._ready = true
82
+ window.__rfr_telemetry.debug = api.debug.bind(api)
83
+ window.__rfr_telemetry.info = api.info.bind(api)
84
+ window.__rfr_telemetry.warn = api.warn.bind(api)
85
+ window.__rfr_telemetry.error = api.error.bind(api)
86
+ window.__rfr_telemetry.fatal = api.fatal.bind(api)
87
+ window.__rfr_telemetry.child = api.child.bind(api)
88
+ window.__rfr_telemetry.startSpan = api.startSpan.bind(api)
89
+ window.__rfr_telemetry.flush = api.flush.bind(api)
90
+ window.__rfr_telemetry.addSink = api.addSink.bind(api)
91
+ window.__rfr_telemetry.removeSink = api.removeSink.bind(api)
92
+ window.__rfr_telemetry.sinks = api.sinks
93
+ return api
94
+ },
95
+ debug: notReady,
96
+ info: notReady,
97
+ warn: notReady,
98
+ error: notReady,
99
+ fatal: notReady,
100
+ child: notReady,
101
+ startSpan: notReady,
102
+ flush: function () { return Promise.resolve() },
103
+ addSink: notReady,
104
+ removeSink: notReady,
105
+ sinks: [],
106
+ }
107
+ })()
108
+ </script>
@@ -0,0 +1,48 @@
1
+ export { default as TelemetryScript } from './TelemetryScript.astro'
2
+
3
+ // Astro-idiomatic server-side hook
4
+ export {
5
+ createTelemetryMiddleware,
6
+ type TelemetryMiddlewareContext,
7
+ type TelemetryMiddlewareNext,
8
+ type TelemetryMiddlewareOptions,
9
+ } from './telemetry-middleware.js'
10
+
11
+ // Library-origin error capture seam (epic #247 / issue #249)
12
+ export {
13
+ createLibraryErrorCapture,
14
+ captureAstroLibraryError,
15
+ type LibraryErrorCaptureContext,
16
+ type LibraryErrorCaptureNext,
17
+ type LibraryErrorCaptureOptions,
18
+ } from './library-error-capture.js'
19
+ export type {
20
+ LibraryOriginIdentity,
21
+ DevFeedbackRecord,
22
+ DevFeedbackSink,
23
+ } from '../shared/index.ts'
24
+
25
+ // Re-export core types and factories for convenience (Faro stays hidden)
26
+ export {
27
+ createTelemetry,
28
+ createConsoleSink,
29
+ createNoopTelemetry,
30
+ createMockSink,
31
+ redact,
32
+ PRESETS,
33
+ resolvePreset,
34
+ LEVEL_ORDER,
35
+ type Telemetry,
36
+ type TelemetryConfig,
37
+ type TelemetryEnv,
38
+ type TelemetryPreset,
39
+ type Logger,
40
+ type Span,
41
+ type LogLevel,
42
+ type LogContext,
43
+ type LogRecord,
44
+ type SpanRecord,
45
+ type TelemetrySink,
46
+ type ConsoleSinkOptions,
47
+ type MockSinkExtended,
48
+ } from '../logger/index.ts'