@traffical/svelte 0.1.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.
- package/README.md +365 -0
- package/dist/TrafficalProvider.svelte +34 -0
- package/dist/TrafficalProvider.svelte.d.ts +12 -0
- package/dist/TrafficalProvider.svelte.d.ts.map +1 -0
- package/dist/context.svelte.d.ts +44 -0
- package/dist/context.svelte.d.ts.map +1 -0
- package/dist/context.svelte.js +192 -0
- package/dist/hooks.svelte.d.ts +155 -0
- package/dist/hooks.svelte.d.ts.map +1 -0
- package/dist/hooks.svelte.js +371 -0
- package/dist/index.d.ts +61 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +83 -0
- package/dist/index.test.d.ts +7 -0
- package/dist/index.test.d.ts.map +1 -0
- package/dist/index.test.js +184 -0
- package/dist/sveltekit.d.ts +73 -0
- package/dist/sveltekit.d.ts.map +1 -0
- package/dist/sveltekit.js +111 -0
- package/dist/sveltekit.test.d.ts +5 -0
- package/dist/sveltekit.test.d.ts.map +1 -0
- package/dist/sveltekit.test.js +170 -0
- package/dist/types.d.ts +206 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +6 -0
- package/package.json +67 -0
- package/src/TrafficalProvider.svelte +34 -0
- package/src/context.svelte.ts +232 -0
- package/src/hooks.svelte.ts +445 -0
- package/src/index.test.ts +221 -0
- package/src/index.ts +144 -0
- package/src/sveltekit.test.ts +218 -0
- package/src/sveltekit.ts +139 -0
- package/src/types.ts +296 -0
package/README.md
ADDED
|
@@ -0,0 +1,365 @@
|
|
|
1
|
+
# @traffical/svelte
|
|
2
|
+
|
|
3
|
+
Traffical SDK for Svelte 5 applications. Provides reactive hooks and components for parameter resolution, A/B testing, and feature flags with full SSR/hydration support.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Svelte 5 Runes** - Uses `$state`, `$derived`, and `$effect` for reactive, fine-grained updates
|
|
8
|
+
- **Full SSR Support** - Pre-fetch config bundles in SvelteKit load functions
|
|
9
|
+
- **Hydration-Safe** - No FOOC (Flash of Original Content) with proper initialization
|
|
10
|
+
- **Feature Parity** - Same capabilities as the React SDK
|
|
11
|
+
- **Type-Safe** - Full TypeScript support with generic parameter types
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
bun add @traffical/svelte
|
|
17
|
+
# or
|
|
18
|
+
npm install @traffical/svelte
|
|
19
|
+
# or
|
|
20
|
+
pnpm add @traffical/svelte
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Quick Start
|
|
24
|
+
|
|
25
|
+
### 1. Set up the Provider
|
|
26
|
+
|
|
27
|
+
In your root layout, initialize Traffical:
|
|
28
|
+
|
|
29
|
+
```svelte
|
|
30
|
+
<!-- src/routes/+layout.svelte -->
|
|
31
|
+
<script lang="ts">
|
|
32
|
+
import { TrafficalProvider } from '@traffical/svelte';
|
|
33
|
+
import { PUBLIC_TRAFFICAL_API_KEY } from '$env/static/public';
|
|
34
|
+
|
|
35
|
+
let { data, children } = $props();
|
|
36
|
+
</script>
|
|
37
|
+
|
|
38
|
+
<TrafficalProvider
|
|
39
|
+
config={{
|
|
40
|
+
orgId: 'org_123',
|
|
41
|
+
projectId: 'proj_456',
|
|
42
|
+
env: 'production',
|
|
43
|
+
apiKey: PUBLIC_TRAFFICAL_API_KEY,
|
|
44
|
+
initialBundle: data.traffical?.bundle,
|
|
45
|
+
}}
|
|
46
|
+
>
|
|
47
|
+
{@render children()}
|
|
48
|
+
</TrafficalProvider>
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### 2. Use Parameters in Components
|
|
52
|
+
|
|
53
|
+
```svelte
|
|
54
|
+
<!-- src/routes/checkout/+page.svelte -->
|
|
55
|
+
<script lang="ts">
|
|
56
|
+
import { useTraffical } from '@traffical/svelte';
|
|
57
|
+
|
|
58
|
+
const { params, ready, track } = useTraffical({
|
|
59
|
+
defaults: {
|
|
60
|
+
'checkout.ctaText': 'Buy Now',
|
|
61
|
+
'checkout.ctaColor': '#000000',
|
|
62
|
+
},
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
function handlePurchase(amount: number) {
|
|
66
|
+
// track has the decisionId already bound!
|
|
67
|
+
track('purchase', { value: amount, orderId: 'ord_123' });
|
|
68
|
+
}
|
|
69
|
+
</script>
|
|
70
|
+
|
|
71
|
+
{#if ready}
|
|
72
|
+
<button
|
|
73
|
+
style="background: {params['checkout.ctaColor']}"
|
|
74
|
+
onclick={() => handlePurchase(99.99)}
|
|
75
|
+
>
|
|
76
|
+
{params['checkout.ctaText']}
|
|
77
|
+
</button>
|
|
78
|
+
{:else}
|
|
79
|
+
<button disabled>Loading...</button>
|
|
80
|
+
{/if}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## SSR with SvelteKit
|
|
84
|
+
|
|
85
|
+
For optimal performance and to prevent content flashing, fetch the config bundle on the server:
|
|
86
|
+
|
|
87
|
+
### Server Load Function
|
|
88
|
+
|
|
89
|
+
```typescript
|
|
90
|
+
// src/routes/+layout.server.ts
|
|
91
|
+
import { loadTrafficalBundle } from '@traffical/svelte/sveltekit';
|
|
92
|
+
import { TRAFFICAL_API_KEY } from '$env/static/private';
|
|
93
|
+
|
|
94
|
+
export async function load({ fetch }) {
|
|
95
|
+
const { bundle, error } = await loadTrafficalBundle({
|
|
96
|
+
orgId: 'org_123',
|
|
97
|
+
projectId: 'proj_456',
|
|
98
|
+
env: 'production',
|
|
99
|
+
apiKey: TRAFFICAL_API_KEY,
|
|
100
|
+
fetch,
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
if (error) {
|
|
104
|
+
console.warn('[Traffical] Failed to load config:', error);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return {
|
|
108
|
+
traffical: { bundle },
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### Pre-resolve Parameters (Optional)
|
|
114
|
+
|
|
115
|
+
For pages where you need resolved values during SSR:
|
|
116
|
+
|
|
117
|
+
```typescript
|
|
118
|
+
// src/routes/checkout/+page.server.ts
|
|
119
|
+
import { loadTrafficalBundle, resolveParamsSSR } from '@traffical/svelte/sveltekit';
|
|
120
|
+
import { TRAFFICAL_API_KEY } from '$env/static/private';
|
|
121
|
+
|
|
122
|
+
export async function load({ fetch, cookies }) {
|
|
123
|
+
const { bundle } = await loadTrafficalBundle({
|
|
124
|
+
orgId: 'org_123',
|
|
125
|
+
projectId: 'proj_456',
|
|
126
|
+
env: 'production',
|
|
127
|
+
apiKey: TRAFFICAL_API_KEY,
|
|
128
|
+
fetch,
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
// Get user context from cookies/session
|
|
132
|
+
const userId = cookies.get('userId') || 'anonymous';
|
|
133
|
+
|
|
134
|
+
// Pre-resolve params for this page
|
|
135
|
+
const checkoutParams = resolveParamsSSR(
|
|
136
|
+
bundle,
|
|
137
|
+
{ userId },
|
|
138
|
+
{
|
|
139
|
+
'checkout.ctaText': 'Buy Now',
|
|
140
|
+
'checkout.ctaColor': '#000000',
|
|
141
|
+
}
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
return {
|
|
145
|
+
traffical: { bundle },
|
|
146
|
+
checkoutParams,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
## API Reference
|
|
152
|
+
|
|
153
|
+
### Provider
|
|
154
|
+
|
|
155
|
+
#### `<TrafficalProvider>`
|
|
156
|
+
|
|
157
|
+
Wrapper component that initializes Traffical context.
|
|
158
|
+
|
|
159
|
+
```svelte
|
|
160
|
+
<TrafficalProvider config={...}>
|
|
161
|
+
<slot />
|
|
162
|
+
</TrafficalProvider>
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
#### `initTraffical(config)`
|
|
166
|
+
|
|
167
|
+
Function-based alternative to the Provider component.
|
|
168
|
+
|
|
169
|
+
```svelte
|
|
170
|
+
<script>
|
|
171
|
+
import { initTraffical } from '@traffical/svelte';
|
|
172
|
+
|
|
173
|
+
initTraffical({
|
|
174
|
+
orgId: 'org_123',
|
|
175
|
+
projectId: 'proj_456',
|
|
176
|
+
env: 'production',
|
|
177
|
+
apiKey: 'pk_...',
|
|
178
|
+
});
|
|
179
|
+
</script>
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
### Hooks
|
|
183
|
+
|
|
184
|
+
#### `useTraffical(options)`
|
|
185
|
+
|
|
186
|
+
Primary hook for parameter resolution and decision tracking.
|
|
187
|
+
|
|
188
|
+
```typescript
|
|
189
|
+
const { params, ready, decision, error, trackExposure, track } = useTraffical({
|
|
190
|
+
defaults: { 'feature.name': 'default-value' },
|
|
191
|
+
context: { customField: 'value' }, // Optional
|
|
192
|
+
tracking: 'full', // 'full' | 'decision' | 'none'
|
|
193
|
+
});
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
**Tracking Modes:**
|
|
197
|
+
- `'full'` (default) - Track decision + automatic exposure
|
|
198
|
+
- `'decision'` - Track decision only, manual exposure control
|
|
199
|
+
- `'none'` - No tracking (for SSR, tests, or internal logic)
|
|
200
|
+
|
|
201
|
+
**Return Value:**
|
|
202
|
+
- `params` - Resolved parameter values (reactive)
|
|
203
|
+
- `decision` - Decision metadata (null when `tracking="none"`)
|
|
204
|
+
- `ready` - Whether the client is ready
|
|
205
|
+
- `error` - Any initialization error
|
|
206
|
+
- `trackExposure` - Manually track exposure (no-op when `tracking="none"`)
|
|
207
|
+
- `track` - Track event with bound decisionId (no-op when `tracking="none"`)
|
|
208
|
+
|
|
209
|
+
#### `useTrafficalTrack()`
|
|
210
|
+
|
|
211
|
+
Returns a function to track user events.
|
|
212
|
+
|
|
213
|
+
> **Tip:** For most use cases, use the bound `track` from `useTraffical()` instead. It automatically includes the `decisionId`. Use this standalone hook for advanced scenarios like cross-component event tracking.
|
|
214
|
+
|
|
215
|
+
```typescript
|
|
216
|
+
// Recommended: use bound track from useTraffical
|
|
217
|
+
const { params, track } = useTraffical({
|
|
218
|
+
defaults: { 'checkout.ctaText': 'Buy Now' },
|
|
219
|
+
});
|
|
220
|
+
track('purchase', { value: 99.99, orderId: 'ord_123' });
|
|
221
|
+
|
|
222
|
+
// Advanced: standalone hook when you need to attribute to a specific decision
|
|
223
|
+
const standaloneTrack = useTrafficalTrack();
|
|
224
|
+
standaloneTrack({
|
|
225
|
+
event: 'purchase',
|
|
226
|
+
properties: { value: 99.99 },
|
|
227
|
+
decisionId: someOtherDecision.decisionId,
|
|
228
|
+
});
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
#### `useTrafficalReward()` (deprecated)
|
|
232
|
+
|
|
233
|
+
> **Deprecated:** Use `useTrafficalTrack()` instead.
|
|
234
|
+
|
|
235
|
+
#### `useTrafficalClient()`
|
|
236
|
+
|
|
237
|
+
Access the underlying Traffical client directly.
|
|
238
|
+
|
|
239
|
+
```typescript
|
|
240
|
+
const { client, ready, error } = useTrafficalClient();
|
|
241
|
+
|
|
242
|
+
if (client) {
|
|
243
|
+
const version = client.getConfigVersion();
|
|
244
|
+
const stableId = client.getStableId();
|
|
245
|
+
}
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
#### `useTrafficalPlugin(name)`
|
|
249
|
+
|
|
250
|
+
Access a registered plugin by name.
|
|
251
|
+
|
|
252
|
+
```typescript
|
|
253
|
+
import type { DOMBindingPlugin } from '@traffical/js-client';
|
|
254
|
+
|
|
255
|
+
const domPlugin = useTrafficalPlugin<DOMBindingPlugin>('dom-binding');
|
|
256
|
+
domPlugin?.applyBindings();
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
### SvelteKit Helpers
|
|
260
|
+
|
|
261
|
+
#### `loadTrafficalBundle(options)`
|
|
262
|
+
|
|
263
|
+
Fetch the config bundle in a SvelteKit load function.
|
|
264
|
+
|
|
265
|
+
```typescript
|
|
266
|
+
import { loadTrafficalBundle } from '@traffical/svelte/sveltekit';
|
|
267
|
+
|
|
268
|
+
const { bundle, error } = await loadTrafficalBundle({
|
|
269
|
+
orgId: 'org_123',
|
|
270
|
+
projectId: 'proj_456',
|
|
271
|
+
env: 'production',
|
|
272
|
+
apiKey: 'pk_...',
|
|
273
|
+
fetch, // SvelteKit's fetch
|
|
274
|
+
});
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
#### `resolveParamsSSR(bundle, context, defaults)`
|
|
278
|
+
|
|
279
|
+
Resolve parameters on the server for SSR.
|
|
280
|
+
|
|
281
|
+
```typescript
|
|
282
|
+
import { resolveParamsSSR } from '@traffical/svelte/sveltekit';
|
|
283
|
+
|
|
284
|
+
const params = resolveParamsSSR(bundle, { userId: 'user_123' }, {
|
|
285
|
+
'feature.name': 'default',
|
|
286
|
+
});
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
## Configuration Options
|
|
290
|
+
|
|
291
|
+
```typescript
|
|
292
|
+
interface TrafficalProviderConfig {
|
|
293
|
+
// Required
|
|
294
|
+
orgId: string;
|
|
295
|
+
projectId: string;
|
|
296
|
+
env: string;
|
|
297
|
+
apiKey: string;
|
|
298
|
+
|
|
299
|
+
// Optional - Connection
|
|
300
|
+
baseUrl?: string;
|
|
301
|
+
localConfig?: ConfigBundle;
|
|
302
|
+
refreshIntervalMs?: number; // Default: 60000
|
|
303
|
+
|
|
304
|
+
// Optional - Identity
|
|
305
|
+
unitKeyFn?: () => string;
|
|
306
|
+
contextFn?: () => Context;
|
|
307
|
+
|
|
308
|
+
// Optional - Tracking
|
|
309
|
+
trackDecisions?: boolean; // Default: true
|
|
310
|
+
decisionDeduplicationTtlMs?: number; // Default: 1 hour
|
|
311
|
+
exposureSessionTtlMs?: number; // Default: 30 minutes
|
|
312
|
+
|
|
313
|
+
// Optional - Plugins
|
|
314
|
+
plugins?: TrafficalPlugin[];
|
|
315
|
+
|
|
316
|
+
// Optional - Event Batching
|
|
317
|
+
eventBatchSize?: number; // Default: 10
|
|
318
|
+
eventFlushIntervalMs?: number; // Default: 30000
|
|
319
|
+
|
|
320
|
+
// Optional - SSR
|
|
321
|
+
initialBundle?: ConfigBundle | null;
|
|
322
|
+
initialParams?: Record<string, unknown>;
|
|
323
|
+
}
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
## TypeScript
|
|
327
|
+
|
|
328
|
+
The SDK is fully typed. Use generics for type-safe parameter access:
|
|
329
|
+
|
|
330
|
+
```typescript
|
|
331
|
+
interface CheckoutParams {
|
|
332
|
+
'checkout.ctaText': string;
|
|
333
|
+
'checkout.ctaColor': string;
|
|
334
|
+
'checkout.showDiscount': boolean;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
const { params } = useTraffical<CheckoutParams>({
|
|
338
|
+
defaults: {
|
|
339
|
+
'checkout.ctaText': 'Buy Now',
|
|
340
|
+
'checkout.ctaColor': '#000000',
|
|
341
|
+
'checkout.showDiscount': false,
|
|
342
|
+
},
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
// params['checkout.ctaText'] is typed as string
|
|
346
|
+
// params['checkout.showDiscount'] is typed as boolean
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
## Comparison with React SDK
|
|
350
|
+
|
|
351
|
+
| Feature | React | Svelte |
|
|
352
|
+
|---------|-------|--------|
|
|
353
|
+
| Provider | `<TrafficalProvider>` | `<TrafficalProvider>` |
|
|
354
|
+
| Main hook | `useTraffical()` | `useTraffical()` |
|
|
355
|
+
| Bound event tracking | `track` from `useTraffical()` | `track` from `useTraffical()` |
|
|
356
|
+
| Standalone track hook | `useTrafficalTrack()` | `useTrafficalTrack()` |
|
|
357
|
+
| Client access | `useTrafficalClient()` | `useTrafficalClient()` |
|
|
358
|
+
| Plugin access | `useTrafficalPlugin()` | `useTrafficalPlugin()` |
|
|
359
|
+
| Reactivity | `useState`/`useEffect` | `$state`/`$derived` |
|
|
360
|
+
| SSR | `initialParams` prop | `loadTrafficalBundle()` helper |
|
|
361
|
+
|
|
362
|
+
## License
|
|
363
|
+
|
|
364
|
+
MIT
|
|
365
|
+
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
@traffical/svelte - TrafficalProvider Component
|
|
3
|
+
|
|
4
|
+
Wrapper component that initializes the Traffical context.
|
|
5
|
+
Alternative to calling initTraffical() directly in your layout.
|
|
6
|
+
-->
|
|
7
|
+
<script lang="ts">
|
|
8
|
+
import type { Snippet } from "svelte";
|
|
9
|
+
import { initTraffical } from "./context.svelte.js";
|
|
10
|
+
import type { TrafficalProviderConfig } from "./types.js";
|
|
11
|
+
|
|
12
|
+
interface Props {
|
|
13
|
+
/** Configuration for the Traffical client */
|
|
14
|
+
config: TrafficalProviderConfig;
|
|
15
|
+
/** Child content */
|
|
16
|
+
children: Snippet;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
let { config, children }: Props = $props();
|
|
20
|
+
|
|
21
|
+
// Initialize context - this sets up the client and makes it available to children
|
|
22
|
+
const context = initTraffical(config);
|
|
23
|
+
|
|
24
|
+
// Cleanup on component destroy
|
|
25
|
+
$effect(() => {
|
|
26
|
+
return () => {
|
|
27
|
+
// Destroy client when provider unmounts
|
|
28
|
+
context.client?.destroy();
|
|
29
|
+
};
|
|
30
|
+
});
|
|
31
|
+
</script>
|
|
32
|
+
|
|
33
|
+
{@render children()}
|
|
34
|
+
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Snippet } from "svelte";
|
|
2
|
+
import type { TrafficalProviderConfig } from "./types.js";
|
|
3
|
+
interface Props {
|
|
4
|
+
/** Configuration for the Traffical client */
|
|
5
|
+
config: TrafficalProviderConfig;
|
|
6
|
+
/** Child content */
|
|
7
|
+
children: Snippet;
|
|
8
|
+
}
|
|
9
|
+
declare const TrafficalProvider: import("svelte").Component<Props, {}, "">;
|
|
10
|
+
type TrafficalProvider = ReturnType<typeof TrafficalProvider>;
|
|
11
|
+
export default TrafficalProvider;
|
|
12
|
+
//# sourceMappingURL=TrafficalProvider.svelte.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TrafficalProvider.svelte.d.ts","sourceRoot":"","sources":["../src/TrafficalProvider.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AAEtC,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,YAAY,CAAC;AAGxD,UAAU,KAAK;IACb,6CAA6C;IAC7C,MAAM,EAAE,uBAAuB,CAAC;IAChC,oBAAoB;IACpB,QAAQ,EAAE,OAAO,CAAC;CACnB;AA2BH,QAAA,MAAM,iBAAiB,2CAAwC,CAAC;AAChE,KAAK,iBAAiB,GAAG,UAAU,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAC9D,eAAe,iBAAiB,CAAC"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @traffical/svelte - Context Layer
|
|
3
|
+
*
|
|
4
|
+
* SSR-safe context management using Svelte 5 runes and Svelte's context API.
|
|
5
|
+
* Initializes the TrafficalClient with environment-appropriate providers.
|
|
6
|
+
*/
|
|
7
|
+
import type { TrafficalProviderConfig, TrafficalContextValue } from "./types.js";
|
|
8
|
+
/**
|
|
9
|
+
* Initializes the Traffical context.
|
|
10
|
+
* Must be called at the root of your application (e.g., in +layout.svelte).
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```svelte
|
|
14
|
+
* <script>
|
|
15
|
+
* import { initTraffical } from '@traffical/svelte';
|
|
16
|
+
*
|
|
17
|
+
* let { data, children } = $props();
|
|
18
|
+
*
|
|
19
|
+
* initTraffical({
|
|
20
|
+
* orgId: 'org_123',
|
|
21
|
+
* projectId: 'proj_456',
|
|
22
|
+
* env: 'production',
|
|
23
|
+
* apiKey: 'pk_...',
|
|
24
|
+
* initialBundle: data.traffical?.bundle,
|
|
25
|
+
* });
|
|
26
|
+
* </script>
|
|
27
|
+
*
|
|
28
|
+
* {@render children()}
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
export declare function initTraffical(config: TrafficalProviderConfig): TrafficalContextValue;
|
|
32
|
+
/**
|
|
33
|
+
* Gets the Traffical context value.
|
|
34
|
+
* Must be called within a component tree where initTraffical() has been called.
|
|
35
|
+
*
|
|
36
|
+
* @throws Error if called outside of Traffical context
|
|
37
|
+
*/
|
|
38
|
+
export declare function getTrafficalContext(): TrafficalContextValue;
|
|
39
|
+
/**
|
|
40
|
+
* Checks if Traffical context is available.
|
|
41
|
+
* Useful for conditional rendering or optional Traffical integration.
|
|
42
|
+
*/
|
|
43
|
+
export declare function hasTrafficalContext(): boolean;
|
|
44
|
+
//# sourceMappingURL=context.svelte.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context.svelte.d.ts","sourceRoot":"","sources":["../src/context.svelte.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAUH,OAAO,KAAK,EACV,uBAAuB,EACvB,qBAAqB,EACtB,MAAM,YAAY,CAAC;AAmJpB;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,aAAa,CAC3B,MAAM,EAAE,uBAAuB,GAC9B,qBAAqB,CAIvB;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,IAAI,qBAAqB,CAa3D;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,IAAI,OAAO,CAS7C"}
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @traffical/svelte - Context Layer
|
|
3
|
+
*
|
|
4
|
+
* SSR-safe context management using Svelte 5 runes and Svelte's context API.
|
|
5
|
+
* Initializes the TrafficalClient with environment-appropriate providers.
|
|
6
|
+
*/
|
|
7
|
+
import { getContext, setContext } from "svelte";
|
|
8
|
+
import { TrafficalClient, createTrafficalClientSync, MemoryStorageProvider, LocalStorageProvider, } from "@traffical/js-client";
|
|
9
|
+
// =============================================================================
|
|
10
|
+
// Constants
|
|
11
|
+
// =============================================================================
|
|
12
|
+
const TRAFFICAL_CONTEXT_KEY = Symbol("traffical");
|
|
13
|
+
// =============================================================================
|
|
14
|
+
// Browser Detection
|
|
15
|
+
// =============================================================================
|
|
16
|
+
/**
|
|
17
|
+
* Check if we're running in a browser environment.
|
|
18
|
+
* SSR-safe - returns false during server-side rendering.
|
|
19
|
+
*/
|
|
20
|
+
function isBrowser() {
|
|
21
|
+
return typeof window !== "undefined" && typeof document !== "undefined";
|
|
22
|
+
}
|
|
23
|
+
// =============================================================================
|
|
24
|
+
// Context State Factory
|
|
25
|
+
// =============================================================================
|
|
26
|
+
/**
|
|
27
|
+
* Creates the reactive Traffical context state.
|
|
28
|
+
* Uses $state for reactive properties that work with SSR.
|
|
29
|
+
*/
|
|
30
|
+
function createTrafficalContextState(config) {
|
|
31
|
+
// Reactive state using Svelte 5 runes
|
|
32
|
+
let client = $state(null);
|
|
33
|
+
let ready = $state(!!config.initialBundle); // Ready immediately if we have initial bundle
|
|
34
|
+
let error = $state(null);
|
|
35
|
+
let bundle = $state(config.initialBundle ?? null);
|
|
36
|
+
// Initialize client only in browser
|
|
37
|
+
if (isBrowser()) {
|
|
38
|
+
// Use localStorage in browser, memory storage would lose data
|
|
39
|
+
const storage = new LocalStorageProvider();
|
|
40
|
+
const clientInstance = createTrafficalClientSync({
|
|
41
|
+
orgId: config.orgId,
|
|
42
|
+
projectId: config.projectId,
|
|
43
|
+
env: config.env,
|
|
44
|
+
apiKey: config.apiKey,
|
|
45
|
+
baseUrl: config.baseUrl,
|
|
46
|
+
localConfig: config.initialBundle ?? config.localConfig,
|
|
47
|
+
refreshIntervalMs: config.refreshIntervalMs,
|
|
48
|
+
storage,
|
|
49
|
+
// Decision tracking options
|
|
50
|
+
trackDecisions: config.trackDecisions,
|
|
51
|
+
decisionDeduplicationTtlMs: config.decisionDeduplicationTtlMs,
|
|
52
|
+
// Exposure options
|
|
53
|
+
exposureSessionTtlMs: config.exposureSessionTtlMs,
|
|
54
|
+
// Event batching options
|
|
55
|
+
eventBatchSize: config.eventBatchSize,
|
|
56
|
+
eventFlushIntervalMs: config.eventFlushIntervalMs,
|
|
57
|
+
// Plugins
|
|
58
|
+
plugins: config.plugins,
|
|
59
|
+
});
|
|
60
|
+
client = clientInstance;
|
|
61
|
+
// Initialize asynchronously (fetches fresh config if needed)
|
|
62
|
+
clientInstance
|
|
63
|
+
.initialize()
|
|
64
|
+
.then(() => {
|
|
65
|
+
ready = true;
|
|
66
|
+
// Update bundle if client fetched a newer one
|
|
67
|
+
const configVersion = clientInstance.getConfigVersion();
|
|
68
|
+
if (configVersion) {
|
|
69
|
+
// Bundle is internal, but we track ready state
|
|
70
|
+
}
|
|
71
|
+
})
|
|
72
|
+
.catch((err) => {
|
|
73
|
+
error = err instanceof Error ? err : new Error(String(err));
|
|
74
|
+
// Still mark as ready - we'll use defaults/initial bundle
|
|
75
|
+
ready = true;
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
// On server, use memory storage and mark as ready if we have initial data
|
|
80
|
+
// The client won't actually be used for tracking on server
|
|
81
|
+
if (config.initialBundle) {
|
|
82
|
+
const storage = new MemoryStorageProvider();
|
|
83
|
+
const clientInstance = createTrafficalClientSync({
|
|
84
|
+
orgId: config.orgId,
|
|
85
|
+
projectId: config.projectId,
|
|
86
|
+
env: config.env,
|
|
87
|
+
apiKey: config.apiKey,
|
|
88
|
+
localConfig: config.initialBundle,
|
|
89
|
+
storage,
|
|
90
|
+
// Disable background operations on server
|
|
91
|
+
refreshIntervalMs: 0,
|
|
92
|
+
});
|
|
93
|
+
client = clientInstance;
|
|
94
|
+
ready = true;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
// Unit key getter - uses config function or client's stable ID
|
|
98
|
+
function getUnitKey() {
|
|
99
|
+
if (config.unitKeyFn) {
|
|
100
|
+
return config.unitKeyFn();
|
|
101
|
+
}
|
|
102
|
+
// Fall back to client's auto-generated stable ID
|
|
103
|
+
return client?.getStableId() ?? "";
|
|
104
|
+
}
|
|
105
|
+
// Context getter - merges unit key with additional context
|
|
106
|
+
function getContext() {
|
|
107
|
+
const unitKey = getUnitKey();
|
|
108
|
+
const additionalContext = config.contextFn?.() ?? {};
|
|
109
|
+
return {
|
|
110
|
+
...additionalContext,
|
|
111
|
+
// Include common unit key field names for compatibility
|
|
112
|
+
userId: unitKey,
|
|
113
|
+
deviceId: unitKey,
|
|
114
|
+
anonymousId: unitKey,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
return {
|
|
118
|
+
get client() {
|
|
119
|
+
return client;
|
|
120
|
+
},
|
|
121
|
+
get ready() {
|
|
122
|
+
return ready;
|
|
123
|
+
},
|
|
124
|
+
get error() {
|
|
125
|
+
return error;
|
|
126
|
+
},
|
|
127
|
+
get bundle() {
|
|
128
|
+
return bundle;
|
|
129
|
+
},
|
|
130
|
+
getUnitKey,
|
|
131
|
+
getContext,
|
|
132
|
+
initialParams: config.initialParams,
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
// =============================================================================
|
|
136
|
+
// Public API
|
|
137
|
+
// =============================================================================
|
|
138
|
+
/**
|
|
139
|
+
* Initializes the Traffical context.
|
|
140
|
+
* Must be called at the root of your application (e.g., in +layout.svelte).
|
|
141
|
+
*
|
|
142
|
+
* @example
|
|
143
|
+
* ```svelte
|
|
144
|
+
* <script>
|
|
145
|
+
* import { initTraffical } from '@traffical/svelte';
|
|
146
|
+
*
|
|
147
|
+
* let { data, children } = $props();
|
|
148
|
+
*
|
|
149
|
+
* initTraffical({
|
|
150
|
+
* orgId: 'org_123',
|
|
151
|
+
* projectId: 'proj_456',
|
|
152
|
+
* env: 'production',
|
|
153
|
+
* apiKey: 'pk_...',
|
|
154
|
+
* initialBundle: data.traffical?.bundle,
|
|
155
|
+
* });
|
|
156
|
+
* </script>
|
|
157
|
+
*
|
|
158
|
+
* {@render children()}
|
|
159
|
+
* ```
|
|
160
|
+
*/
|
|
161
|
+
export function initTraffical(config) {
|
|
162
|
+
const contextValue = createTrafficalContextState(config);
|
|
163
|
+
setContext(TRAFFICAL_CONTEXT_KEY, contextValue);
|
|
164
|
+
return contextValue;
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Gets the Traffical context value.
|
|
168
|
+
* Must be called within a component tree where initTraffical() has been called.
|
|
169
|
+
*
|
|
170
|
+
* @throws Error if called outside of Traffical context
|
|
171
|
+
*/
|
|
172
|
+
export function getTrafficalContext() {
|
|
173
|
+
const context = getContext(TRAFFICAL_CONTEXT_KEY);
|
|
174
|
+
if (!context) {
|
|
175
|
+
throw new Error("getTrafficalContext() must be called within a component tree where initTraffical() has been called. " +
|
|
176
|
+
"Make sure to call initTraffical() in your root layout or wrap your app with <TrafficalProvider>.");
|
|
177
|
+
}
|
|
178
|
+
return context;
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Checks if Traffical context is available.
|
|
182
|
+
* Useful for conditional rendering or optional Traffical integration.
|
|
183
|
+
*/
|
|
184
|
+
export function hasTrafficalContext() {
|
|
185
|
+
try {
|
|
186
|
+
const context = getContext(TRAFFICAL_CONTEXT_KEY);
|
|
187
|
+
return context !== undefined;
|
|
188
|
+
}
|
|
189
|
+
catch {
|
|
190
|
+
return false;
|
|
191
|
+
}
|
|
192
|
+
}
|