@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/src/index.ts
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @traffical/svelte
|
|
3
|
+
*
|
|
4
|
+
* Traffical SDK for Svelte 5 applications.
|
|
5
|
+
* Provides Provider component and hooks for parameter resolution and decision tracking.
|
|
6
|
+
*
|
|
7
|
+
* Features:
|
|
8
|
+
* - Full SSR/hydration support for SvelteKit
|
|
9
|
+
* - Svelte 5 runes for reactive, fine-grained updates
|
|
10
|
+
* - Browser-optimized with sendBeacon, localStorage persistence
|
|
11
|
+
* - Automatic stable ID for anonymous users
|
|
12
|
+
* - Plugin system support (DecisionTrackingPlugin enabled by default)
|
|
13
|
+
* - Decision and exposure deduplication
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```svelte
|
|
17
|
+
* <!-- +layout.svelte -->
|
|
18
|
+
* <script>
|
|
19
|
+
* import { TrafficalProvider } from '@traffical/svelte';
|
|
20
|
+
*
|
|
21
|
+
* let { data, children } = $props();
|
|
22
|
+
* </script>
|
|
23
|
+
*
|
|
24
|
+
* <TrafficalProvider
|
|
25
|
+
* config={{
|
|
26
|
+
* orgId: 'org_123',
|
|
27
|
+
* projectId: 'proj_456',
|
|
28
|
+
* env: 'production',
|
|
29
|
+
* apiKey: 'pk_...',
|
|
30
|
+
* initialBundle: data.traffical?.bundle,
|
|
31
|
+
* }}
|
|
32
|
+
* >
|
|
33
|
+
* {@render children()}
|
|
34
|
+
* </TrafficalProvider>
|
|
35
|
+
* ```
|
|
36
|
+
*
|
|
37
|
+
* @example
|
|
38
|
+
* ```svelte
|
|
39
|
+
* <!-- MyComponent.svelte -->
|
|
40
|
+
* <script>
|
|
41
|
+
* import { useTraffical } from '@traffical/svelte';
|
|
42
|
+
*
|
|
43
|
+
* const { params, ready } = useTraffical({
|
|
44
|
+
* defaults: { 'ui.hero.title': 'Welcome' },
|
|
45
|
+
* });
|
|
46
|
+
* </script>
|
|
47
|
+
*
|
|
48
|
+
* {#if ready}
|
|
49
|
+
* <h1>{params['ui.hero.title']}</h1>
|
|
50
|
+
* {:else}
|
|
51
|
+
* <h1>Loading...</h1>
|
|
52
|
+
* {/if}
|
|
53
|
+
* ```
|
|
54
|
+
*/
|
|
55
|
+
|
|
56
|
+
// =============================================================================
|
|
57
|
+
// Re-export from @traffical/core
|
|
58
|
+
// =============================================================================
|
|
59
|
+
|
|
60
|
+
export {
|
|
61
|
+
// Resolution functions
|
|
62
|
+
resolveParameters,
|
|
63
|
+
decide,
|
|
64
|
+
evaluateCondition,
|
|
65
|
+
evaluateConditions,
|
|
66
|
+
// Hashing utilities
|
|
67
|
+
fnv1a,
|
|
68
|
+
computeBucket,
|
|
69
|
+
isInBucketRange,
|
|
70
|
+
// ID generation
|
|
71
|
+
generateEventId,
|
|
72
|
+
generateDecisionId,
|
|
73
|
+
generateExposureId,
|
|
74
|
+
generateRewardId,
|
|
75
|
+
} from "@traffical/core";
|
|
76
|
+
|
|
77
|
+
// =============================================================================
|
|
78
|
+
// Re-export from @traffical/js-client
|
|
79
|
+
// =============================================================================
|
|
80
|
+
|
|
81
|
+
export {
|
|
82
|
+
// Client
|
|
83
|
+
TrafficalClient,
|
|
84
|
+
createTrafficalClient,
|
|
85
|
+
createTrafficalClientSync,
|
|
86
|
+
// Storage providers
|
|
87
|
+
LocalStorageProvider,
|
|
88
|
+
MemoryStorageProvider,
|
|
89
|
+
createStorageProvider,
|
|
90
|
+
// Plugins
|
|
91
|
+
createDOMBindingPlugin,
|
|
92
|
+
} from "@traffical/js-client";
|
|
93
|
+
|
|
94
|
+
// =============================================================================
|
|
95
|
+
// Svelte-specific exports
|
|
96
|
+
// =============================================================================
|
|
97
|
+
|
|
98
|
+
// Context
|
|
99
|
+
export {
|
|
100
|
+
initTraffical,
|
|
101
|
+
getTrafficalContext,
|
|
102
|
+
hasTrafficalContext,
|
|
103
|
+
} from "./context.svelte.js";
|
|
104
|
+
|
|
105
|
+
// Hooks
|
|
106
|
+
export {
|
|
107
|
+
useTraffical,
|
|
108
|
+
useTrafficalTrack,
|
|
109
|
+
useTrafficalReward,
|
|
110
|
+
useTrafficalClient,
|
|
111
|
+
useTrafficalPlugin,
|
|
112
|
+
} from "./hooks.svelte.js";
|
|
113
|
+
|
|
114
|
+
// Provider component
|
|
115
|
+
export { default as TrafficalProvider } from "./TrafficalProvider.svelte";
|
|
116
|
+
|
|
117
|
+
// =============================================================================
|
|
118
|
+
// Types
|
|
119
|
+
// =============================================================================
|
|
120
|
+
|
|
121
|
+
export type {
|
|
122
|
+
// Provider config
|
|
123
|
+
TrafficalProviderConfig,
|
|
124
|
+
TrafficalContextValue,
|
|
125
|
+
// Hook types
|
|
126
|
+
UseTrafficalOptions,
|
|
127
|
+
UseTrafficalResult,
|
|
128
|
+
BoundTrackOptions,
|
|
129
|
+
TrackEventOptions,
|
|
130
|
+
// Deprecated types (kept for backward compatibility)
|
|
131
|
+
BoundTrackRewardOptions,
|
|
132
|
+
TrackRewardOptions,
|
|
133
|
+
// SvelteKit types
|
|
134
|
+
LoadTrafficalBundleOptions,
|
|
135
|
+
LoadTrafficalBundleResult,
|
|
136
|
+
// Re-exported types
|
|
137
|
+
ConfigBundle,
|
|
138
|
+
Context,
|
|
139
|
+
DecisionResult,
|
|
140
|
+
ParameterValue,
|
|
141
|
+
TrafficalClient as TrafficalClientType,
|
|
142
|
+
TrafficalPlugin,
|
|
143
|
+
} from "./types.js";
|
|
144
|
+
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @traffical/svelte - SvelteKit Helpers Tests
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, test, expect, mock } from "bun:test";
|
|
6
|
+
import { loadTrafficalBundle, resolveParamsSSR } from "./sveltekit.js";
|
|
7
|
+
import type { ConfigBundle } from "@traffical/core";
|
|
8
|
+
|
|
9
|
+
// =============================================================================
|
|
10
|
+
// Test Fixtures
|
|
11
|
+
// =============================================================================
|
|
12
|
+
|
|
13
|
+
const mockBundle: ConfigBundle = {
|
|
14
|
+
version: new Date().toISOString(),
|
|
15
|
+
orgId: "org_test",
|
|
16
|
+
projectId: "proj_test",
|
|
17
|
+
env: "test",
|
|
18
|
+
hashing: {
|
|
19
|
+
unitKey: "userId",
|
|
20
|
+
bucketCount: 10000,
|
|
21
|
+
},
|
|
22
|
+
parameters: [
|
|
23
|
+
{
|
|
24
|
+
key: "checkout.ctaText",
|
|
25
|
+
type: "string",
|
|
26
|
+
default: "Buy Now",
|
|
27
|
+
layerId: "layer_1",
|
|
28
|
+
namespace: "checkout",
|
|
29
|
+
},
|
|
30
|
+
],
|
|
31
|
+
layers: [
|
|
32
|
+
{
|
|
33
|
+
id: "layer_1",
|
|
34
|
+
policies: [],
|
|
35
|
+
},
|
|
36
|
+
],
|
|
37
|
+
domBindings: [],
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
// =============================================================================
|
|
41
|
+
// loadTrafficalBundle Tests
|
|
42
|
+
// =============================================================================
|
|
43
|
+
|
|
44
|
+
describe("loadTrafficalBundle", () => {
|
|
45
|
+
test("returns bundle on successful fetch", async () => {
|
|
46
|
+
const mockFetch = mock(() =>
|
|
47
|
+
Promise.resolve({
|
|
48
|
+
ok: true,
|
|
49
|
+
json: () => Promise.resolve(mockBundle),
|
|
50
|
+
} as Response)
|
|
51
|
+
) as unknown as typeof fetch;
|
|
52
|
+
|
|
53
|
+
const result = await loadTrafficalBundle({
|
|
54
|
+
orgId: "org_123",
|
|
55
|
+
projectId: "proj_456",
|
|
56
|
+
env: "production",
|
|
57
|
+
apiKey: "pk_test",
|
|
58
|
+
fetch: mockFetch,
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
expect(result.bundle).toEqual(mockBundle);
|
|
62
|
+
expect(result.error).toBeUndefined();
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test("returns null bundle on HTTP error", async () => {
|
|
66
|
+
const mockFetch = mock(() =>
|
|
67
|
+
Promise.resolve({
|
|
68
|
+
ok: false,
|
|
69
|
+
status: 404,
|
|
70
|
+
statusText: "Not Found",
|
|
71
|
+
} as Response)
|
|
72
|
+
) as unknown as typeof fetch;
|
|
73
|
+
|
|
74
|
+
const result = await loadTrafficalBundle({
|
|
75
|
+
orgId: "org_123",
|
|
76
|
+
projectId: "proj_456",
|
|
77
|
+
env: "production",
|
|
78
|
+
apiKey: "pk_test",
|
|
79
|
+
fetch: mockFetch,
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
expect(result.bundle).toBeNull();
|
|
83
|
+
expect(result.error).toBe("HTTP 404: Not Found");
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
test("returns null bundle on network error", async () => {
|
|
87
|
+
const mockFetch = mock(() =>
|
|
88
|
+
Promise.reject(new Error("Network error"))
|
|
89
|
+
) as unknown as typeof fetch;
|
|
90
|
+
|
|
91
|
+
const result = await loadTrafficalBundle({
|
|
92
|
+
orgId: "org_123",
|
|
93
|
+
projectId: "proj_456",
|
|
94
|
+
env: "production",
|
|
95
|
+
apiKey: "pk_test",
|
|
96
|
+
fetch: mockFetch,
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
expect(result.bundle).toBeNull();
|
|
100
|
+
expect(result.error).toBe("Network error");
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
test("uses correct URL format", async () => {
|
|
104
|
+
let capturedUrl = "";
|
|
105
|
+
const mockFetch = mock((url: string) => {
|
|
106
|
+
capturedUrl = url;
|
|
107
|
+
return Promise.resolve({
|
|
108
|
+
ok: true,
|
|
109
|
+
json: () => Promise.resolve(mockBundle),
|
|
110
|
+
} as Response);
|
|
111
|
+
}) as unknown as typeof fetch;
|
|
112
|
+
|
|
113
|
+
await loadTrafficalBundle({
|
|
114
|
+
orgId: "org_123",
|
|
115
|
+
projectId: "proj_456",
|
|
116
|
+
env: "staging",
|
|
117
|
+
apiKey: "pk_test",
|
|
118
|
+
fetch: mockFetch,
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
expect(capturedUrl).toBe(
|
|
122
|
+
"https://sdk.traffical.io/v1/config/proj_456?env=staging"
|
|
123
|
+
);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
test("uses custom baseUrl when provided", async () => {
|
|
127
|
+
let capturedUrl = "";
|
|
128
|
+
const mockFetch = mock((url: string) => {
|
|
129
|
+
capturedUrl = url;
|
|
130
|
+
return Promise.resolve({
|
|
131
|
+
ok: true,
|
|
132
|
+
json: () => Promise.resolve(mockBundle),
|
|
133
|
+
} as Response);
|
|
134
|
+
}) as unknown as typeof fetch;
|
|
135
|
+
|
|
136
|
+
await loadTrafficalBundle({
|
|
137
|
+
orgId: "org_123",
|
|
138
|
+
projectId: "proj_456",
|
|
139
|
+
env: "production",
|
|
140
|
+
apiKey: "pk_test",
|
|
141
|
+
fetch: mockFetch,
|
|
142
|
+
baseUrl: "https://custom.api.com",
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
expect(capturedUrl).toBe(
|
|
146
|
+
"https://custom.api.com/v1/config/proj_456?env=production"
|
|
147
|
+
);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
test("includes authorization header", async () => {
|
|
151
|
+
let capturedOptions: RequestInit | undefined;
|
|
152
|
+
const mockFetch = mock((_url: string, options?: RequestInit) => {
|
|
153
|
+
capturedOptions = options;
|
|
154
|
+
return Promise.resolve({
|
|
155
|
+
ok: true,
|
|
156
|
+
json: () => Promise.resolve(mockBundle),
|
|
157
|
+
} as Response);
|
|
158
|
+
}) as unknown as typeof fetch;
|
|
159
|
+
|
|
160
|
+
await loadTrafficalBundle({
|
|
161
|
+
orgId: "org_123",
|
|
162
|
+
projectId: "proj_456",
|
|
163
|
+
env: "production",
|
|
164
|
+
apiKey: "pk_secret_key",
|
|
165
|
+
fetch: mockFetch,
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
expect(capturedOptions?.headers).toEqual({
|
|
169
|
+
"Content-Type": "application/json",
|
|
170
|
+
Authorization: "Bearer pk_secret_key",
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
// =============================================================================
|
|
176
|
+
// resolveParamsSSR Tests
|
|
177
|
+
// =============================================================================
|
|
178
|
+
|
|
179
|
+
describe("resolveParamsSSR", () => {
|
|
180
|
+
test("returns defaults when bundle is null", () => {
|
|
181
|
+
const defaults = {
|
|
182
|
+
"checkout.ctaText": "Default",
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
const result = resolveParamsSSR(null, { userId: "user_123" }, defaults);
|
|
186
|
+
|
|
187
|
+
expect(result).toEqual(defaults);
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
test("resolves parameters from bundle", () => {
|
|
191
|
+
const defaults = {
|
|
192
|
+
"checkout.ctaText": "Fallback",
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
const result = resolveParamsSSR(
|
|
196
|
+
mockBundle,
|
|
197
|
+
{ userId: "user_123" },
|
|
198
|
+
defaults
|
|
199
|
+
);
|
|
200
|
+
|
|
201
|
+
expect(result["checkout.ctaText"]).toBe("Buy Now");
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
test("uses context for resolution", () => {
|
|
205
|
+
const defaults = {
|
|
206
|
+
"checkout.ctaText": "Fallback",
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
// Both should resolve to the same value since we're using defaults
|
|
210
|
+
const result1 = resolveParamsSSR(mockBundle, { userId: "user_1" }, defaults);
|
|
211
|
+
const result2 = resolveParamsSSR(mockBundle, { userId: "user_2" }, defaults);
|
|
212
|
+
|
|
213
|
+
// Without active policies, results should be the same (bundle defaults)
|
|
214
|
+
expect(result1["checkout.ctaText"]).toBe("Buy Now");
|
|
215
|
+
expect(result2["checkout.ctaText"]).toBe("Buy Now");
|
|
216
|
+
});
|
|
217
|
+
});
|
|
218
|
+
|
package/src/sveltekit.ts
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @traffical/svelte - SvelteKit Helpers
|
|
3
|
+
*
|
|
4
|
+
* Server-side utilities for SvelteKit load functions.
|
|
5
|
+
* Enables SSR with pre-fetched config bundles.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { resolveParameters } from "@traffical/core";
|
|
9
|
+
import type { ConfigBundle, Context, ParameterValue } from "@traffical/core";
|
|
10
|
+
import type {
|
|
11
|
+
LoadTrafficalBundleOptions,
|
|
12
|
+
LoadTrafficalBundleResult,
|
|
13
|
+
} from "./types.js";
|
|
14
|
+
|
|
15
|
+
// =============================================================================
|
|
16
|
+
// Constants
|
|
17
|
+
// =============================================================================
|
|
18
|
+
|
|
19
|
+
const DEFAULT_BASE_URL = "https://sdk.traffical.io";
|
|
20
|
+
|
|
21
|
+
// =============================================================================
|
|
22
|
+
// Load Functions
|
|
23
|
+
// =============================================================================
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Loads the Traffical config bundle in a SvelteKit load function.
|
|
27
|
+
*
|
|
28
|
+
* Call this in your +layout.server.ts or +page.server.ts to fetch the config
|
|
29
|
+
* bundle on the server, enabling SSR without FOOC (Flash of Original Content).
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* ```typescript
|
|
33
|
+
* // src/routes/+layout.server.ts
|
|
34
|
+
* import { loadTrafficalBundle } from '@traffical/svelte/sveltekit';
|
|
35
|
+
* import { TRAFFICAL_API_KEY } from '$env/static/private';
|
|
36
|
+
*
|
|
37
|
+
* export async function load({ fetch }) {
|
|
38
|
+
* const { bundle } = await loadTrafficalBundle({
|
|
39
|
+
* orgId: 'org_123',
|
|
40
|
+
* projectId: 'proj_456',
|
|
41
|
+
* env: 'production',
|
|
42
|
+
* apiKey: TRAFFICAL_API_KEY,
|
|
43
|
+
* fetch,
|
|
44
|
+
* });
|
|
45
|
+
*
|
|
46
|
+
* return {
|
|
47
|
+
* traffical: { bundle },
|
|
48
|
+
* };
|
|
49
|
+
* }
|
|
50
|
+
* ```
|
|
51
|
+
*/
|
|
52
|
+
export async function loadTrafficalBundle(
|
|
53
|
+
options: LoadTrafficalBundleOptions
|
|
54
|
+
): Promise<LoadTrafficalBundleResult> {
|
|
55
|
+
const baseUrl = options.baseUrl ?? DEFAULT_BASE_URL;
|
|
56
|
+
const url = `${baseUrl}/v1/config/${options.projectId}?env=${options.env}`;
|
|
57
|
+
|
|
58
|
+
try {
|
|
59
|
+
const response = await options.fetch(url, {
|
|
60
|
+
method: "GET",
|
|
61
|
+
headers: {
|
|
62
|
+
"Content-Type": "application/json",
|
|
63
|
+
Authorization: `Bearer ${options.apiKey}`,
|
|
64
|
+
},
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
if (!response.ok) {
|
|
68
|
+
return {
|
|
69
|
+
bundle: null,
|
|
70
|
+
error: `HTTP ${response.status}: ${response.statusText}`,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const bundle = (await response.json()) as ConfigBundle;
|
|
75
|
+
return { bundle };
|
|
76
|
+
} catch (err) {
|
|
77
|
+
return {
|
|
78
|
+
bundle: null,
|
|
79
|
+
error: err instanceof Error ? err.message : String(err),
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// =============================================================================
|
|
85
|
+
// SSR Resolution
|
|
86
|
+
// =============================================================================
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Resolves parameters on the server for SSR.
|
|
90
|
+
*
|
|
91
|
+
* Use this to pre-resolve specific parameters in your load function,
|
|
92
|
+
* enabling server-side rendering with the correct values.
|
|
93
|
+
*
|
|
94
|
+
* @example
|
|
95
|
+
* ```typescript
|
|
96
|
+
* // src/routes/checkout/+page.server.ts
|
|
97
|
+
* import { loadTrafficalBundle, resolveParamsSSR } from '@traffical/svelte/sveltekit';
|
|
98
|
+
*
|
|
99
|
+
* export async function load({ fetch, cookies }) {
|
|
100
|
+
* const { bundle } = await loadTrafficalBundle({ ... });
|
|
101
|
+
*
|
|
102
|
+
* // Get user context from cookies/session
|
|
103
|
+
* const userId = cookies.get('userId');
|
|
104
|
+
*
|
|
105
|
+
* // Pre-resolve params for this page
|
|
106
|
+
* const checkoutParams = resolveParamsSSR(
|
|
107
|
+
* bundle,
|
|
108
|
+
* { userId },
|
|
109
|
+
* {
|
|
110
|
+
* 'checkout.ctaText': 'Buy Now',
|
|
111
|
+
* 'checkout.ctaColor': '#000',
|
|
112
|
+
* }
|
|
113
|
+
* );
|
|
114
|
+
*
|
|
115
|
+
* return {
|
|
116
|
+
* traffical: { bundle },
|
|
117
|
+
* checkoutParams,
|
|
118
|
+
* };
|
|
119
|
+
* }
|
|
120
|
+
* ```
|
|
121
|
+
*/
|
|
122
|
+
export function resolveParamsSSR<T extends Record<string, ParameterValue>>(
|
|
123
|
+
bundle: ConfigBundle | null,
|
|
124
|
+
context: Context,
|
|
125
|
+
defaults: T
|
|
126
|
+
): T {
|
|
127
|
+
if (!bundle) {
|
|
128
|
+
return defaults;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return resolveParameters(bundle, context, defaults);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// =============================================================================
|
|
135
|
+
// Types Re-export
|
|
136
|
+
// =============================================================================
|
|
137
|
+
|
|
138
|
+
export type { LoadTrafficalBundleOptions, LoadTrafficalBundleResult };
|
|
139
|
+
|