@contentful/optimization-node 0.1.0-alpha12 → 0.1.0-alpha14
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 +231 -345
- package/dist/260.mjs +1 -1
- package/dist/constants.cjs +1 -1
- package/dist/index.cjs +77 -18
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +52 -5
- package/dist/index.d.mts +52 -5
- package/dist/index.d.ts +52 -5
- package/dist/index.mjs +76 -17
- package/dist/index.mjs.map +1 -1
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<p align="center">
|
|
2
2
|
<a href="https://www.contentful.com/developers/docs/personalization/">
|
|
3
|
-
<img alt="Contentful Logo" title="Contentful" src="https://raw.githubusercontent.com/contentful/optimization/v0.1.0-
|
|
3
|
+
<img alt="Contentful Logo" title="Contentful" src="https://raw.githubusercontent.com/contentful/optimization/v0.1.0-alpha14/contentful-icon.png" width="150">
|
|
4
4
|
</a>
|
|
5
5
|
</p>
|
|
6
6
|
|
|
@@ -10,51 +10,43 @@
|
|
|
10
10
|
|
|
11
11
|
<div align="center">
|
|
12
12
|
|
|
13
|
-
[Guides](https://contentful.github.io/optimization/documents/Guides.html) ·
|
|
14
|
-
[Reference](https://contentful.github.io/optimization) · [Contributing](https://github.com/contentful/optimization/blob/v0.1.0-
|
|
13
|
+
[Guides](https://contentful.github.io/optimization/documents/Documentation.Guides.html) ·
|
|
14
|
+
[Reference](https://contentful.github.io/optimization) · [Contributing](https://github.com/contentful/optimization/blob/v0.1.0-alpha14/CONTRIBUTING.md)
|
|
15
15
|
|
|
16
16
|
</div>
|
|
17
17
|
|
|
18
18
|
> [!WARNING]
|
|
19
19
|
>
|
|
20
|
-
> The Optimization SDK Suite is pre-release (alpha). Breaking changes
|
|
20
|
+
> The Optimization SDK Suite is pre-release (alpha). Breaking changes can be published at any time.
|
|
21
21
|
|
|
22
|
-
The Optimization Node SDK implements
|
|
23
|
-
[Optimization Core
|
|
24
|
-
|
|
22
|
+
The Optimization Node SDK implements stateless server-side optimization behavior on top of the
|
|
23
|
+
[Optimization Core SDK](https://github.com/contentful/optimization/blob/v0.1.0-alpha14/packages/universal/core-sdk/README.md). Use it for server rendering, server
|
|
24
|
+
functions, and Node services that need request-scoped profile evaluation or event emission.
|
|
25
|
+
|
|
26
|
+
If you are integrating a Node application, start with [Getting Started](#getting-started), then use
|
|
27
|
+
[Integrating the Optimization Node SDK in a Node app](https://contentful.github.io/optimization/documents/Documentation.Guides.integrating-the-node-sdk-in-a-node-app.html)
|
|
28
|
+
for the step-by-step flow. This README keeps the package orientation and common setup options close
|
|
29
|
+
at hand; generated [reference documentation](https://contentful.github.io/optimization) remains the
|
|
30
|
+
source of truth for exported API signatures.
|
|
25
31
|
|
|
26
32
|
<details>
|
|
27
33
|
<summary>Table of Contents</summary>
|
|
28
34
|
<!-- mtoc-start -->
|
|
29
35
|
|
|
30
|
-
- [Getting
|
|
31
|
-
- [
|
|
32
|
-
- [
|
|
33
|
-
|
|
34
|
-
- [
|
|
35
|
-
- [
|
|
36
|
-
- [
|
|
37
|
-
- [
|
|
38
|
-
|
|
39
|
-
- [`getFlag`](#getflag)
|
|
40
|
-
- [`resolveOptimizedEntry`](#resolveoptimizedentry)
|
|
41
|
-
- [`getMergeTagValue`](#getmergetagvalue)
|
|
42
|
-
- [Experience API and Insights API Event Methods](#experience-api-and-insights-api-event-methods)
|
|
43
|
-
- [`identify`](#identify)
|
|
44
|
-
- [`page`](#page)
|
|
45
|
-
- [`screen`](#screen)
|
|
46
|
-
- [`track`](#track)
|
|
47
|
-
- [`trackView`](#trackview)
|
|
48
|
-
- [`trackClick`](#trackclick)
|
|
49
|
-
- [`trackHover`](#trackhover)
|
|
50
|
-
- [`trackFlagView`](#trackflagview)
|
|
51
|
-
- [Interceptors](#interceptors)
|
|
52
|
-
- [Life-cycle Interceptors](#life-cycle-interceptors)
|
|
36
|
+
- [Getting started](#getting-started)
|
|
37
|
+
- [When to use this package](#when-to-use-this-package)
|
|
38
|
+
- [Common configuration](#common-configuration)
|
|
39
|
+
- [Core workflows](#core-workflows)
|
|
40
|
+
- [Request-scoped events](#request-scoped-events)
|
|
41
|
+
- [Content resolution](#content-resolution)
|
|
42
|
+
- [Caching guidance](#caching-guidance)
|
|
43
|
+
- [Development harness](#development-harness)
|
|
44
|
+
- [Related](#related)
|
|
53
45
|
|
|
54
46
|
<!-- mtoc-end -->
|
|
55
47
|
</details>
|
|
56
48
|
|
|
57
|
-
## Getting
|
|
49
|
+
## Getting started
|
|
58
50
|
|
|
59
51
|
Install using an NPM-compatible package manager, pnpm for example:
|
|
60
52
|
|
|
@@ -68,343 +60,237 @@ Import the Optimization class; both CJS and ESM module systems are supported, ES
|
|
|
68
60
|
import ContentfulOptimization from '@contentful/optimization-node'
|
|
69
61
|
```
|
|
70
62
|
|
|
71
|
-
|
|
63
|
+
Create the SDK once per module or process, then bind consent and request context in each route:
|
|
72
64
|
|
|
73
65
|
```ts
|
|
74
|
-
const optimization = new ContentfulOptimization({
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
final argument to stateless event methods inside each incoming request.
|
|
82
|
-
|
|
83
|
-
## Caching Guidance
|
|
84
|
-
|
|
85
|
-
The Node SDK is stateless, but that does not mean all request work is cacheable.
|
|
86
|
-
|
|
87
|
-
- Cache raw Contentful delivery payloads in your application layer.
|
|
88
|
-
- Treat cached Contentful entries as immutable.
|
|
89
|
-
- Use `resolveOptimizedEntry()` and `getMergeTagValue()` as request-local helpers when rendering.
|
|
90
|
-
- Do not memoize `page()`, `identify()`, `screen()`, `track()`, `trackView()`, `trackClick()`,
|
|
91
|
-
`trackHover()`, or `trackFlagView()`. Those methods emit outbound events and/or return profile
|
|
92
|
-
state for the current request.
|
|
93
|
-
|
|
94
|
-
For a step-by-step Express-style walkthrough that covers request context, profile persistence,
|
|
95
|
-
Contentful entry resolution, and hybrid Node + browser setups, see
|
|
96
|
-
[Integrating the Optimization Node SDK in a Node App](https://github.com/contentful/optimization/blob/v0.1.0-alpha12/documentation/integrating-the-node-sdk-in-a-node-app.md).
|
|
97
|
-
|
|
98
|
-
## Development Harness
|
|
99
|
-
|
|
100
|
-
The package-local dev harness runs from `packages/node/node-sdk/dev/` and reads `.env` from this
|
|
101
|
-
package directory.
|
|
102
|
-
|
|
103
|
-
1. Start from `.env.example` and create or update `packages/node/node-sdk/.env`.
|
|
104
|
-
2. Prefer the repo-standard `PUBLIC_...` variable names shown in `.env.example`.
|
|
105
|
-
3. Start the harness from the repo root:
|
|
106
|
-
|
|
107
|
-
```sh
|
|
108
|
-
pnpm --filter @contentful/optimization-node dev
|
|
109
|
-
```
|
|
110
|
-
|
|
111
|
-
## Reference Implementations
|
|
112
|
-
|
|
113
|
-
Reference implementations illustrate how the SDK may be used under common scenarios, as well as
|
|
114
|
-
select less-common scenarios, with the most basic example solution possible.
|
|
115
|
-
|
|
116
|
-
- [Node SSR Only](https://github.com/contentful/optimization/blob/v0.1.0-alpha12/implementations/node-sdk/README.md): Example application that uses the
|
|
117
|
-
Node SDK to render an optimized Web page
|
|
118
|
-
- [Node SSR + Web Vanilla](https://github.com/contentful/optimization/blob/v0.1.0-alpha12/implementations/node-sdk+web-sdk/README.md): Example application
|
|
119
|
-
demonstrating simple profile synchronization between the Node and
|
|
120
|
-
[Web](https://github.com/contentful/optimization/blob/v0.1.0-alpha12/packages/web/web-sdk/README.md) SDKs via cookie
|
|
121
|
-
|
|
122
|
-
## Configuration
|
|
123
|
-
|
|
124
|
-
### Top-level Configuration Options
|
|
125
|
-
|
|
126
|
-
| Option | Required? | Default | Description |
|
|
127
|
-
| -------------- | --------- | --------------------------- | ------------------------------------------------------------------------------ |
|
|
128
|
-
| `api` | No | See "API Options" | Unified configuration for the Experience API and Insights API endpoints |
|
|
129
|
-
| `app` | No | `undefined` | The application definition used to attribute events to a specific consumer app |
|
|
130
|
-
| `clientId` | Yes | N/A | Shared API key for Experience API and Insights API requests |
|
|
131
|
-
| `environment` | No | `'main'` | The environment identifier |
|
|
132
|
-
| `eventBuilder` | No | See "Event Builder Options" | Event builder configuration (channel/library metadata, etc.) |
|
|
133
|
-
| `fetchOptions` | No | See "Fetch Options" | Configuration for Fetch timeout and retry functionality |
|
|
134
|
-
| `logLevel` | No | `'error'` | Minimum log level for the default console sink |
|
|
135
|
-
|
|
136
|
-
### API Options
|
|
137
|
-
|
|
138
|
-
| Option | Required? | Default | Description |
|
|
139
|
-
| ------------------- | --------- | ------------------------------------------ | ------------------------------------------------------------ |
|
|
140
|
-
| `experienceBaseUrl` | No | `'https://experience.ninetailed.co/'` | Base URL for the Experience API |
|
|
141
|
-
| `insightsBaseUrl` | No | `'https://ingest.insights.ninetailed.co/'` | Base URL for the Insights API |
|
|
142
|
-
| `enabledFeatures` | No | `['ip-enrichment', 'location']` | Enabled features the Experience API may use for each request |
|
|
143
|
-
|
|
144
|
-
Request-scoped Experience API options are passed to stateless event methods as their final argument
|
|
145
|
-
instead of the SDK constructor:
|
|
146
|
-
|
|
147
|
-
| Option | Required? | Default | Description |
|
|
148
|
-
| ----------- | --------- | ----------- | ------------------------------------------------------------------------------ |
|
|
149
|
-
| `ip` | No | `undefined` | IP address override used by the Experience API for location analysis |
|
|
150
|
-
| `locale` | No | `undefined` | Locale used to translate `location.city` and `location.country` |
|
|
151
|
-
| `plainText` | No | `undefined` | Sends performance-critical Experience API endpoints in plain text |
|
|
152
|
-
| `preflight` | No | `undefined` | Instructs the Experience API to aggregate a new profile state but not store it |
|
|
153
|
-
|
|
154
|
-
### Event Builder Options
|
|
155
|
-
|
|
156
|
-
Event builder options should only be supplied when building an SDK on top of the Optimization Node
|
|
157
|
-
SDK or any of its descendent SDKs.
|
|
158
|
-
|
|
159
|
-
| Option | Required? | Default | Description |
|
|
160
|
-
| --------- | --------- | ------------------------------------------------------------- | ---------------------------------------------------------------------------------- |
|
|
161
|
-
| `channel` | No | `'server'` | The channel that identifies where events originate from (e.g. `'web'`, `'mobile'`) |
|
|
162
|
-
| `library` | No | `{ name: '@contentful/optimization-node', version: '0.0.0' }` | The client library metadata that is attached to all events |
|
|
163
|
-
|
|
164
|
-
### Fetch Options
|
|
165
|
-
|
|
166
|
-
Fetch options allow for configuration of a Fetch API-compatible fetch method and the retry/timeout
|
|
167
|
-
logic integrated into the SDK's bundled API clients. Specify the `fetchMethod` when the host
|
|
168
|
-
application environment does not offer a `fetch` method that is compatible with the standard Fetch
|
|
169
|
-
API in its global scope.
|
|
170
|
-
|
|
171
|
-
| Option | Required? | Default | Description |
|
|
172
|
-
| ------------------ | --------- | ----------- | --------------------------------------------------------------------- |
|
|
173
|
-
| `fetchMethod` | No | `undefined` | Signature of a fetch method used by the API clients |
|
|
174
|
-
| `intervalTimeout` | No | `0` | Delay (in milliseconds) between retry attempts |
|
|
175
|
-
| `onFailedAttempt` | No | `undefined` | Callback invoked whenever a retry attempt fails |
|
|
176
|
-
| `onRequestTimeout` | No | `undefined` | Callback invoked when a request exceeds the configured timeout |
|
|
177
|
-
| `requestTimeout` | No | `3000` | Maximum time (in milliseconds) to wait for a response before aborting |
|
|
178
|
-
| `retries` | No | `1` | Maximum number of retry attempts |
|
|
179
|
-
|
|
180
|
-
Configuration method signatures:
|
|
181
|
-
|
|
182
|
-
- `fetchMethod`: `(url: string | URL, init: RequestInit) => Promise<Response>`
|
|
183
|
-
- `onFailedAttempt` and `onRequestTimeout`: `(options: FetchMethodCallbackOptions) => void`
|
|
184
|
-
|
|
185
|
-
## Optimization Methods
|
|
186
|
-
|
|
187
|
-
Arguments marked with an asterisk (\*) are always required.
|
|
188
|
-
|
|
189
|
-
### Optimization Data Resolution Methods
|
|
190
|
-
|
|
191
|
-
#### `getFlag`
|
|
192
|
-
|
|
193
|
-
Get the specified Custom Flag's value from the provided changes array.
|
|
194
|
-
|
|
195
|
-
Arguments:
|
|
196
|
-
|
|
197
|
-
- `name`\*: The name/key of the Custom Flag
|
|
198
|
-
- `changes`: Changes array
|
|
199
|
-
|
|
200
|
-
Returns:
|
|
201
|
-
|
|
202
|
-
- The resolved value for the specified Custom Flag, or `undefined` if it cannot be found.
|
|
203
|
-
|
|
204
|
-
Behavior notes:
|
|
205
|
-
|
|
206
|
-
- Node SDK is stateless; `getFlag(...)` does not auto-emit `trackFlagView`.
|
|
207
|
-
- If full map resolution is needed for advanced use cases, use
|
|
208
|
-
`optimization.flagsResolver.resolve(changes)`.
|
|
209
|
-
|
|
210
|
-
> [!NOTE]
|
|
211
|
-
>
|
|
212
|
-
> If the `changes` argument is omitted, the method will return `undefined`.
|
|
213
|
-
|
|
214
|
-
#### `resolveOptimizedEntry`
|
|
215
|
-
|
|
216
|
-
Resolve a baseline Contentful entry to an optimized variant using the provided selected
|
|
217
|
-
optimizations.
|
|
218
|
-
|
|
219
|
-
Type arguments:
|
|
220
|
-
|
|
221
|
-
- `S`: Entry skeleton type
|
|
222
|
-
- `M`: Chain modifiers
|
|
223
|
-
- `L`: Locale code
|
|
224
|
-
|
|
225
|
-
Arguments:
|
|
226
|
-
|
|
227
|
-
- `entry`\*: The baseline entry to resolve
|
|
228
|
-
- `selectedOptimizations`: Selected optimizations
|
|
229
|
-
|
|
230
|
-
Returns:
|
|
231
|
-
|
|
232
|
-
- The resolved optimized entry variant, or the supplied baseline entry if baseline is the selected
|
|
233
|
-
variant or a variant cannot be found.
|
|
234
|
-
|
|
235
|
-
> [!NOTE]
|
|
236
|
-
>
|
|
237
|
-
> If the `selectedOptimizations` argument is omitted, the method will return the baseline entry.
|
|
238
|
-
|
|
239
|
-
#### `getMergeTagValue`
|
|
240
|
-
|
|
241
|
-
Resolve a "Merge Tag" to a value based on the provided profile. A "Merge Tag" is a special Rich Text
|
|
242
|
-
fragment supported by Contentful that specifies a profile data member to be injected into the Rich
|
|
243
|
-
Text when rendered.
|
|
244
|
-
|
|
245
|
-
Arguments:
|
|
246
|
-
|
|
247
|
-
- `embeddedNodeEntryTarget`\*: The merge tag entry node to resolve
|
|
248
|
-
- `profile`: The user profile
|
|
249
|
-
|
|
250
|
-
> [!NOTE]
|
|
251
|
-
>
|
|
252
|
-
> If the `profile` argument is omitted, the method will return the merge tag's fallback value.
|
|
253
|
-
|
|
254
|
-
### Experience API and Insights API Event Methods
|
|
255
|
-
|
|
256
|
-
Pass request-scoped Experience options as the final argument to stateless event methods:
|
|
66
|
+
const optimization = new ContentfulOptimization({
|
|
67
|
+
clientId: 'your-client-id',
|
|
68
|
+
environment: 'main',
|
|
69
|
+
contentfulLocales: {
|
|
70
|
+
default: 'en-US',
|
|
71
|
+
},
|
|
72
|
+
})
|
|
257
73
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
locale: req.acceptsLanguages()[0] ?? 'en-US',
|
|
74
|
+
function appPolicyAllowsOptimizationEvent(req: { cookies?: Record<string, string> }): boolean {
|
|
75
|
+
return req.cookies?.['app-personalization-consent'] === 'granted'
|
|
261
76
|
}
|
|
262
77
|
|
|
263
|
-
|
|
78
|
+
async function renderRequest(
|
|
79
|
+
req: { cookies?: Record<string, string>; headers: { 'accept-language'?: string } },
|
|
80
|
+
profileId: string,
|
|
81
|
+
) {
|
|
82
|
+
const { contentfulLocale, eventLocale } = optimization.resolveRequestLocale(req)
|
|
83
|
+
const requestOptimization = optimization.forRequest({
|
|
84
|
+
consent: {
|
|
85
|
+
events: appPolicyAllowsOptimizationEvent(req),
|
|
86
|
+
persistence: appPolicyAllowsOptimizationEvent(req),
|
|
87
|
+
},
|
|
88
|
+
eventContext: { locale: eventLocale },
|
|
89
|
+
experienceOptions: contentfulLocale ? { locale: contentfulLocale } : undefined,
|
|
90
|
+
profile: { id: profileId },
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
return await requestOptimization.page()
|
|
94
|
+
}
|
|
264
95
|
```
|
|
265
96
|
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
`
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
`
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
- `payload`\*: Screen view event builder arguments object, including an optional `profile` property
|
|
317
|
-
with a `PartialProfile` value that requires only an `id`
|
|
318
|
-
- `requestOptions`: Optional request-scoped Experience API options passed as the final argument
|
|
319
|
-
|
|
320
|
-
#### `track`
|
|
321
|
-
|
|
322
|
-
Record an Experience API custom track event.
|
|
323
|
-
|
|
324
|
-
Arguments:
|
|
325
|
-
|
|
326
|
-
- `payload`\*: Track event builder arguments object, including an optional `profile` property with a
|
|
327
|
-
`PartialProfile` value that requires only an `id`
|
|
328
|
-
- `requestOptions`: Optional request-scoped Experience API options passed as the final argument
|
|
329
|
-
|
|
330
|
-
#### `trackView`
|
|
331
|
-
|
|
332
|
-
Record an Insights API entry view event. When the payload marks the entry as "sticky", an additional
|
|
333
|
-
Experience API entry view is recorded. This method only returns `OptimizationData` when the entry is
|
|
334
|
-
marked as "sticky".
|
|
335
|
-
|
|
336
|
-
Arguments:
|
|
97
|
+
## When to use this package
|
|
98
|
+
|
|
99
|
+
Use `@contentful/optimization-node` for server-side rendering, server functions, and Node services
|
|
100
|
+
that need stateless profile evaluation or event emission. Use the Web or React Web SDK alongside it
|
|
101
|
+
when browser-side consent, anonymous ID persistence, automatic interaction tracking, or live updates
|
|
102
|
+
are part of the same application.
|
|
103
|
+
|
|
104
|
+
## Common configuration
|
|
105
|
+
|
|
106
|
+
The Node SDK is stateless. It does not maintain consent, profile, or browser persistence state
|
|
107
|
+
between requests. For cross-SDK consent guidance, see
|
|
108
|
+
[Consent management in the Optimization SDK Suite](https://github.com/contentful/optimization/blob/v0.1.0-alpha14/documentation/concepts/consent-management-in-the-optimization-sdk-suite.md).
|
|
109
|
+
|
|
110
|
+
| Option | Required? | Default | Description |
|
|
111
|
+
| ------------------- | --------- | ---------------------- | ----------------------------------------------------------- |
|
|
112
|
+
| `clientId` | Yes | N/A | Shared API key for Experience API and Insights API requests |
|
|
113
|
+
| `environment` | No | `'main'` | Contentful environment identifier |
|
|
114
|
+
| `api` | No | See API options below | Experience API and Insights API endpoint options |
|
|
115
|
+
| `app` | No | `undefined` | Application metadata attached to outgoing event context |
|
|
116
|
+
| `contentfulLocales` | No | `undefined` | Contentful locale codes used by `resolveRequestLocale()` |
|
|
117
|
+
| `fetchOptions` | No | SDK defaults | Fetch timeout and retry behavior |
|
|
118
|
+
| `allowedEventTypes` | No | `['identify', 'page']` | Event types allowed before request event consent is granted |
|
|
119
|
+
| `eventBuilder` | No | Node SDK defaults | Event metadata overrides for SDK-layer authors |
|
|
120
|
+
| `logLevel` | No | `'error'` | Minimum log level for the default console sink |
|
|
121
|
+
|
|
122
|
+
Common `api` options:
|
|
123
|
+
|
|
124
|
+
| Option | Required? | Default | Description |
|
|
125
|
+
| ------------------- | --------- | ------------------------------------------ | ------------------------------------------------ |
|
|
126
|
+
| `experienceBaseUrl` | No | `'https://experience.ninetailed.co/'` | Base URL for the Experience API |
|
|
127
|
+
| `insightsBaseUrl` | No | `'https://ingest.insights.ninetailed.co/'` | Base URL for the Insights API |
|
|
128
|
+
| `enabledFeatures` | No | `['ip-enrichment', 'location']` | Experience API features to apply to each request |
|
|
129
|
+
|
|
130
|
+
Request-scoped Experience options belong in `experienceOptions` when creating the request-bound
|
|
131
|
+
client:
|
|
132
|
+
|
|
133
|
+
| Option | Description |
|
|
134
|
+
| ----------- | ------------------------------------------------------------- |
|
|
135
|
+
| `ip` | IP address override used by the Experience API |
|
|
136
|
+
| `locale` | Locale query parameter for localized Experience API responses |
|
|
137
|
+
| `plainText` | Sends performance-critical Experience API endpoints as text |
|
|
138
|
+
| `preflight` | Aggregates a new profile state without storing it |
|
|
139
|
+
|
|
140
|
+
Common `fetchOptions` are `fetchMethod`, `requestTimeout`, `retries`, `intervalTimeout`,
|
|
141
|
+
`onFailedAttempt`, and `onRequestTimeout`. Default retries intentionally apply only to HTTP `503`
|
|
142
|
+
responses.
|
|
143
|
+
|
|
144
|
+
Use `contentfulLocales.default` for single-locale apps. For apps that match request locale input to
|
|
145
|
+
multiple Contentful locales, keep `default` as the fallback and list the supported Contentful locale
|
|
146
|
+
codes:
|
|
337
147
|
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
Returns:
|
|
349
|
-
|
|
350
|
-
- `void`
|
|
351
|
-
|
|
352
|
-
Arguments:
|
|
353
|
-
|
|
354
|
-
- `payload`\*: Entry click event builder arguments object, including a required `profile` property
|
|
355
|
-
with a `PartialProfile` value that requires only an `id`
|
|
356
|
-
- `requestOptions`: Optional request-scoped Experience API options accepted for signature
|
|
357
|
-
consistency; currently unused
|
|
358
|
-
|
|
359
|
-
#### `trackHover`
|
|
148
|
+
```ts
|
|
149
|
+
const optimization = new ContentfulOptimization({
|
|
150
|
+
clientId: 'your-client-id',
|
|
151
|
+
contentfulLocales: {
|
|
152
|
+
default: 'en-US',
|
|
153
|
+
supported: ['en-US', 'de-DE', 'fr-FR'],
|
|
154
|
+
},
|
|
155
|
+
})
|
|
156
|
+
```
|
|
360
157
|
|
|
361
|
-
|
|
158
|
+
Copy `contentfulLocales.default` and optional `contentfulLocales.supported` from Contentful locale
|
|
159
|
+
settings or the CMA locale list. The returned `contentfulLocale`, when present, is the configured
|
|
160
|
+
Contentful locale code to use for CDA fetches and the Experience API request option. Use
|
|
161
|
+
`eventLocale` separately in event context. Merge tags that reference localized profile fields such
|
|
162
|
+
as `location.city` and `location.country` then resolve in a language consistent with the rendered
|
|
163
|
+
content. See
|
|
164
|
+
[Locale handling in the Optimization SDK Suite](https://contentful.github.io/optimization/documents/Documentation.Concepts.Locale_handling_in_the_Optimization_SDK_Suite.html)
|
|
165
|
+
for the full locale model.
|
|
362
166
|
|
|
363
|
-
|
|
167
|
+
For every option, callback payload, and exported type, use the generated
|
|
168
|
+
[Node SDK reference](https://contentful.github.io/optimization/modules/_contentful_optimization-node.html).
|
|
364
169
|
|
|
365
|
-
|
|
170
|
+
## Core workflows
|
|
366
171
|
|
|
367
|
-
|
|
172
|
+
### Request-scoped events
|
|
368
173
|
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
consistency; currently unused
|
|
174
|
+
Build event context from the incoming request, bind application-owned consent state with
|
|
175
|
+
`forRequest()`, then call `page()`, `identify()`, `screen()`, `track()`, or sticky `trackView()` on
|
|
176
|
+
the returned request object:
|
|
373
177
|
|
|
374
|
-
|
|
178
|
+
```ts
|
|
179
|
+
import type { Request } from 'express'
|
|
180
|
+
|
|
181
|
+
app.get('/products/:slug', async (req, res) => {
|
|
182
|
+
const { contentfulLocale, eventLocale } = optimization.resolveRequestLocale(req)
|
|
183
|
+
const requestOptimization = optimization.forRequest({
|
|
184
|
+
consent: {
|
|
185
|
+
events: appPolicyAllowsOptimizationEvent(req),
|
|
186
|
+
persistence: appPolicyAllowsOptimizationEvent(req),
|
|
187
|
+
},
|
|
188
|
+
eventContext: {
|
|
189
|
+
locale: eventLocale,
|
|
190
|
+
},
|
|
191
|
+
experienceOptions: contentfulLocale ? { locale: contentfulLocale } : undefined,
|
|
192
|
+
profile: { id: req.cookies.profileId },
|
|
193
|
+
})
|
|
194
|
+
const optimizationData = await requestOptimization.page({
|
|
195
|
+
properties: { path: req.path },
|
|
196
|
+
})
|
|
197
|
+
|
|
198
|
+
if (requestOptimization.canPersistProfile && optimizationData?.profile.id) {
|
|
199
|
+
persistProfileId(res, optimizationData.profile.id)
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
res.render('product', { optimizationData })
|
|
203
|
+
})
|
|
204
|
+
```
|
|
375
205
|
|
|
376
|
-
|
|
377
|
-
|
|
206
|
+
For default-on application policies that do not render an end-user consent UI, replace the
|
|
207
|
+
request-specific consent lookup with accepted consent:
|
|
378
208
|
|
|
379
|
-
|
|
209
|
+
```ts
|
|
210
|
+
const requestOptimization = optimization.forRequest({
|
|
211
|
+
consent: { events: true, persistence: true },
|
|
212
|
+
eventContext: { locale: eventLocale },
|
|
213
|
+
profile,
|
|
214
|
+
})
|
|
215
|
+
```
|
|
380
216
|
|
|
381
|
-
- `
|
|
217
|
+
Node SDK event calls fail closed except for the default pre-consent allowlist, `identify` and
|
|
218
|
+
`page`. Those allowlisted events are sent with `context.gdpr.isConsentGiven: false` when request
|
|
219
|
+
event consent is not granted. Pass `allowedEventTypes: []` to require strict opt-in for all
|
|
220
|
+
stateless event methods.
|
|
382
221
|
|
|
383
|
-
|
|
222
|
+
In stateless runtimes, Insights-backed methods require a request-bound profile for delivery.
|
|
223
|
+
Non-sticky `trackView`, `trackClick`, `trackHover`, and `trackFlagView` require a profile ID passed
|
|
224
|
+
to `forRequest()`.
|
|
384
225
|
|
|
385
|
-
|
|
386
|
-
with a `PartialProfile` value that requires only an `id`
|
|
387
|
-
- `requestOptions`: Optional request-scoped Experience API options accepted for signature
|
|
388
|
-
consistency; currently unused
|
|
226
|
+
### Content resolution
|
|
389
227
|
|
|
390
|
-
|
|
228
|
+
Fetch Contentful entries in your application layer, then resolve variants with returned optimization
|
|
229
|
+
data:
|
|
391
230
|
|
|
392
|
-
|
|
231
|
+
```ts
|
|
232
|
+
const resolvedEntry = optimization.resolveOptimizedEntry(
|
|
233
|
+
baselineEntry,
|
|
234
|
+
optimizationData?.selectedOptimizations,
|
|
235
|
+
)
|
|
236
|
+
```
|
|
393
237
|
|
|
394
|
-
|
|
238
|
+
Fetch entries with one CDA locale in the app layer. For localized apps, configure
|
|
239
|
+
`contentfulLocales`, then use the `contentfulLocale` returned by `resolveRequestLocale()` for CDA
|
|
240
|
+
fetches and the per-call `{ locale }` request option so MergeTags that read localized profile fields
|
|
241
|
+
match the entry language. When `contentfulLocale` is absent because no `contentfulLocales` config is
|
|
242
|
+
present, omit the CDA and Experience API locale options intentionally. Do not pass all-locale CDA
|
|
243
|
+
responses from `withAllLocales` or `locale=*` into `resolveOptimizedEntry()`; the resolver expects
|
|
244
|
+
direct single-locale field values. See
|
|
245
|
+
[Entry personalization and variant resolution](https://contentful.github.io/optimization/documents/Documentation.Concepts.Entry_personalization_and_variant_resolution.html#single-locale-cda-entry-contract)
|
|
246
|
+
for the entry contract and
|
|
247
|
+
[Locale handling in the Optimization SDK Suite](https://contentful.github.io/optimization/documents/Documentation.Concepts.Locale_handling_in_the_Optimization_SDK_Suite.html)
|
|
248
|
+
for request locale behavior.
|
|
249
|
+
|
|
250
|
+
Use `getMergeTagValue()` for Contentful Rich Text merge tags and `getFlag()` for Custom Flags. If a
|
|
251
|
+
merge tag references localized profile fields such as `location.city` or `location.country`, its
|
|
252
|
+
resolved value follows the localized profile data returned by the Experience API. The Node SDK is
|
|
253
|
+
stateless, so `getFlag()` does not automatically emit flag-view tracking.
|
|
254
|
+
|
|
255
|
+
### Caching guidance
|
|
256
|
+
|
|
257
|
+
Cache raw Contentful delivery payloads in your application layer, not profile-evaluated SDK event
|
|
258
|
+
results.
|
|
259
|
+
|
|
260
|
+
| Data or call | Cache across requests? | Reason |
|
|
261
|
+
| --------------------------------------------------------------------------- | ---------------------- | -------------------------------------------------------------------- |
|
|
262
|
+
| Raw Contentful entries fetched from CDA | Yes | They are content snapshots and can be resolved per request |
|
|
263
|
+
| `resolveOptimizedEntry()` and `getMergeTagValue()` results | Request-local only | Results depend on the current profile and optimization data |
|
|
264
|
+
| `page()`, `identify()`, `screen()`, `track()`, and sticky `trackView()` | No | These calls perform side effects and return request-specific profile |
|
|
265
|
+
| Non-sticky `trackView()`, `trackClick()`, `trackHover()`, `trackFlagView()` | No | These calls emit Insights API events |
|
|
266
|
+
|
|
267
|
+
The
|
|
268
|
+
[Node SDK integration guide](https://contentful.github.io/optimization/documents/Documentation.Guides.integrating-the-node-sdk-in-a-node-app.html)
|
|
269
|
+
covers request context, profile persistence, Contentful entry resolution, and hybrid Node + browser
|
|
270
|
+
setups in detail.
|
|
271
|
+
|
|
272
|
+
## Development harness
|
|
395
273
|
|
|
396
|
-
-
|
|
397
|
-
|
|
398
|
-
internal state
|
|
274
|
+
The package-local dev harness runs from `packages/node/node-sdk/dev/` and reads `.env` from this
|
|
275
|
+
package directory.
|
|
399
276
|
|
|
400
|
-
|
|
277
|
+
1. Start from `.env.example` and create or update `packages/node/node-sdk/.env`.
|
|
278
|
+
2. Prefer the repo-standard `PUBLIC_...` variable names shown in `.env.example`.
|
|
279
|
+
3. Start the harness from the repo root:
|
|
401
280
|
|
|
402
|
-
```
|
|
403
|
-
optimization
|
|
404
|
-
|
|
405
|
-
})
|
|
406
|
-
```
|
|
281
|
+
```sh
|
|
282
|
+
pnpm --filter @contentful/optimization-node dev
|
|
283
|
+
```
|
|
407
284
|
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
285
|
+
## Related
|
|
286
|
+
|
|
287
|
+
- [Integrating the Optimization Node SDK in a Node app](https://contentful.github.io/optimization/documents/Documentation.Guides.integrating-the-node-sdk-in-a-node-app.html) -
|
|
288
|
+
step-by-step server-side integration guide
|
|
289
|
+
- [Node SDK generated reference](https://contentful.github.io/optimization/modules/_contentful_optimization-node.html) -
|
|
290
|
+
exported API reference
|
|
291
|
+
- [Node reference implementation](https://github.com/contentful/optimization/blob/v0.1.0-alpha14/implementations/node-sdk/README.md) - server-rendered
|
|
292
|
+
implementation using request-scoped SDK calls
|
|
293
|
+
- [Node + Web reference implementation](https://github.com/contentful/optimization/blob/v0.1.0-alpha14/implementations/node-sdk+web-sdk/README.md) -
|
|
294
|
+
hybrid SSR and browser implementation
|
|
295
|
+
- [Optimization Web SDK](https://github.com/contentful/optimization/blob/v0.1.0-alpha14/packages/web/web-sdk/README.md) - browser SDK used when the same application
|
|
296
|
+
also needs client-side consent, persistence, tracking, or live updates
|
package/dist/260.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const OPTIMIZATION_NODE_SDK_VERSION = "0.1.0-
|
|
1
|
+
const OPTIMIZATION_NODE_SDK_VERSION = "0.1.0-alpha14";
|
|
2
2
|
const OPTIMIZATION_NODE_SDK_NAME = "@contentful/optimization-node";
|
|
3
3
|
export { ANONYMOUS_ID_COOKIE, ANONYMOUS_ID_KEY } from "@contentful/optimization-core/constants";
|
|
4
4
|
export { OPTIMIZATION_NODE_SDK_NAME, OPTIMIZATION_NODE_SDK_VERSION };
|
package/dist/constants.cjs
CHANGED
|
@@ -30,7 +30,7 @@ __webpack_require__.d(__webpack_exports__, {
|
|
|
30
30
|
OPTIMIZATION_NODE_SDK_VERSION: ()=>OPTIMIZATION_NODE_SDK_VERSION
|
|
31
31
|
});
|
|
32
32
|
const constants_namespaceObject = require("@contentful/optimization-core/constants");
|
|
33
|
-
const OPTIMIZATION_NODE_SDK_VERSION = "0.1.0-
|
|
33
|
+
const OPTIMIZATION_NODE_SDK_VERSION = "0.1.0-alpha14";
|
|
34
34
|
const OPTIMIZATION_NODE_SDK_NAME = "@contentful/optimization-node";
|
|
35
35
|
exports.ANONYMOUS_ID_COOKIE = __webpack_exports__.ANONYMOUS_ID_COOKIE;
|
|
36
36
|
exports.ANONYMOUS_ID_KEY = __webpack_exports__.ANONYMOUS_ID_KEY;
|
package/dist/index.cjs
CHANGED
|
@@ -6,7 +6,7 @@ var __webpack_modules__ = {
|
|
|
6
6
|
OPTIMIZATION_NODE_SDK_VERSION: ()=>OPTIMIZATION_NODE_SDK_VERSION
|
|
7
7
|
});
|
|
8
8
|
require("@contentful/optimization-core/constants");
|
|
9
|
-
const OPTIMIZATION_NODE_SDK_VERSION = "0.1.0-
|
|
9
|
+
const OPTIMIZATION_NODE_SDK_VERSION = "0.1.0-alpha14";
|
|
10
10
|
const OPTIMIZATION_NODE_SDK_NAME = "@contentful/optimization-node";
|
|
11
11
|
}
|
|
12
12
|
};
|
|
@@ -50,26 +50,85 @@ var __webpack_exports__ = {};
|
|
|
50
50
|
default: ()=>src
|
|
51
51
|
});
|
|
52
52
|
const optimization_core_namespaceObject = require("@contentful/optimization-core");
|
|
53
|
-
const external_es_toolkit_namespaceObject = require("es-toolkit");
|
|
54
53
|
var constants = __webpack_require__("./src/constants.ts");
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
54
|
+
const DEFAULT_RUNTIME_LOCALE = 'en-US';
|
|
55
|
+
const DEFAULT_ACCEPT_LANGUAGE_QUALITY = 1;
|
|
56
|
+
const QUALITY_PARAM_PATTERN = /;\s*q=/;
|
|
57
|
+
const DEFAULT_NODE_ALLOWED_EVENT_TYPES = [
|
|
58
|
+
'identify',
|
|
59
|
+
'page'
|
|
60
|
+
];
|
|
61
|
+
function normalizeHeaderValue(header) {
|
|
62
|
+
if ('string' == typeof header) return header;
|
|
63
|
+
return Array.isArray(header) ? header.filter((item)=>'string' == typeof item).join(',') : void 0;
|
|
64
|
+
}
|
|
65
|
+
function getAcceptLanguageHeader(input) {
|
|
66
|
+
if ('string' == typeof input) return input;
|
|
67
|
+
if ('object' != typeof input || null === input) return;
|
|
68
|
+
const headerFromGetter = input.get?.('accept-language');
|
|
69
|
+
if ('string' == typeof headerFromGetter) return headerFromGetter;
|
|
70
|
+
const { headers } = input;
|
|
71
|
+
if ('object' != typeof headers || null === headers) return;
|
|
72
|
+
const get = Reflect.get(headers, 'get');
|
|
73
|
+
const header = 'function' == typeof get ? Reflect.apply(get, headers, [
|
|
74
|
+
'accept-language'
|
|
75
|
+
]) : Reflect.get(headers, 'accept-language') ?? Reflect.get(headers, 'Accept-Language');
|
|
76
|
+
return normalizeHeaderValue(header);
|
|
77
|
+
}
|
|
78
|
+
function parseAcceptLanguage(acceptLanguage) {
|
|
79
|
+
const candidates = [];
|
|
80
|
+
for (const [index, value] of (acceptLanguage ?? '').split(',').entries()){
|
|
81
|
+
const [rawLocale, rawQuality] = value.split(QUALITY_PARAM_PATTERN);
|
|
82
|
+
const locale = (0, optimization_core_namespaceObject.normalizeLocale)(rawLocale);
|
|
83
|
+
if (void 0 === locale) continue;
|
|
84
|
+
const parsedQuality = void 0 === rawQuality ? DEFAULT_ACCEPT_LANGUAGE_QUALITY : Number(rawQuality);
|
|
85
|
+
const quality = Number.isFinite(parsedQuality) ? parsedQuality : DEFAULT_ACCEPT_LANGUAGE_QUALITY;
|
|
86
|
+
if (quality > 0) candidates.push({
|
|
87
|
+
index,
|
|
88
|
+
locale,
|
|
89
|
+
quality
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
return candidates.sort((left, right)=>right.quality - left.quality || left.index - right.index).map((candidate)=>candidate.locale);
|
|
93
|
+
}
|
|
94
|
+
function getRequestLocaleCandidates(input) {
|
|
95
|
+
const parsedHeaderLocales = parseAcceptLanguage(getAcceptLanguageHeader(input));
|
|
96
|
+
if (parsedHeaderLocales.length > 0) return parsedHeaderLocales;
|
|
97
|
+
if ('object' != typeof input || null === input) return [];
|
|
98
|
+
return input.acceptsLanguages?.().map(optimization_core_namespaceObject.normalizeLocale).filter((locale)=>void 0 !== locale) ?? [];
|
|
68
99
|
}
|
|
69
100
|
class ContentfulOptimization extends optimization_core_namespaceObject.CoreStateless {
|
|
70
|
-
constructor(config){
|
|
71
|
-
const
|
|
72
|
-
super(
|
|
101
|
+
constructor({ app, allowedEventTypes, eventBuilder, ...config }){
|
|
102
|
+
const { library, getConsent: _getConsent, ...eventBuilderConfig } = eventBuilder ?? {};
|
|
103
|
+
super({
|
|
104
|
+
...config,
|
|
105
|
+
allowedEventTypes: allowedEventTypes ?? DEFAULT_NODE_ALLOWED_EVENT_TYPES,
|
|
106
|
+
eventBuilder: {
|
|
107
|
+
app,
|
|
108
|
+
channel: 'server',
|
|
109
|
+
library: {
|
|
110
|
+
name: constants.OPTIMIZATION_NODE_SDK_NAME,
|
|
111
|
+
version: constants.OPTIMIZATION_NODE_SDK_VERSION,
|
|
112
|
+
...library
|
|
113
|
+
},
|
|
114
|
+
getConsent: ()=>false,
|
|
115
|
+
...eventBuilderConfig
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
resolveRequestLocale(input) {
|
|
120
|
+
const candidates = getRequestLocaleCandidates(input);
|
|
121
|
+
const eventLocale = candidates[0] ?? DEFAULT_RUNTIME_LOCALE;
|
|
122
|
+
const contentfulLocale = (0, optimization_core_namespaceObject.resolveContentfulLocale)({
|
|
123
|
+
candidates,
|
|
124
|
+
contentfulLocales: this.config.contentfulLocales
|
|
125
|
+
}) ?? this.locale;
|
|
126
|
+
return void 0 === contentfulLocale ? {
|
|
127
|
+
eventLocale
|
|
128
|
+
} : {
|
|
129
|
+
eventLocale,
|
|
130
|
+
contentfulLocale
|
|
131
|
+
};
|
|
73
132
|
}
|
|
74
133
|
}
|
|
75
134
|
const src_ContentfulOptimization = ContentfulOptimization;
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","sources":["../src/constants.ts","webpack/runtime/define_property_getters","webpack/runtime/has_own_property","webpack/runtime/make_namespace_object","../src/ContentfulOptimization.ts","../src/index.ts"],"sourcesContent":["// eslint-disable-next-line @typescript-eslint/naming-convention -- Replaced at build-time\ndeclare const __OPTIMIZATION_VERSION__: string | undefined\n// eslint-disable-next-line @typescript-eslint/naming-convention -- Replaced at build-time\ndeclare const __OPTIMIZATION_PACKAGE_NAME__: string | undefined\n\n/**\n * The current version of the Optimization Node SDK, injected at build-time.\n *\n * @defaultValue `'0.0.0'` when the build-time replacement is unavailable.\n *\n * @public\n */\nexport const OPTIMIZATION_NODE_SDK_VERSION =\n typeof __OPTIMIZATION_VERSION__ === 'string' ? __OPTIMIZATION_VERSION__ : '0.0.0'\n\n/**\n * The package name of the Optimization Node SDK, injected at build-time.\n *\n * @defaultValue `'@contentful/optimization-node'` when the build-time replacement is unavailable.\n *\n * @public\n */\nexport const OPTIMIZATION_NODE_SDK_NAME =\n typeof __OPTIMIZATION_PACKAGE_NAME__ === 'string'\n ? __OPTIMIZATION_PACKAGE_NAME__\n : '@contentful/optimization-node'\n\n/**\n * Re-exports of anonymous identifier constants used by the Optimization Core.\n *\n * @remarks\n * This constant is surfaced here to provide a stable import path for Node SDK\n * consumers. It represents the cookie key used by the Optimization Framework\n * to persist an anonymous identifier for tracking optimization and insights\n * events when no explicit profile is known.\n *\n * @example\n * ```ts\n * import { ANONYMOUS_ID_COOKIE, ANONYMOUS_ID_KEY } from '@contentful/optimization-node/constants'\n * const cookieId = request.cookies[ANONYMOUS_ID_COOKIE]\n * const storageId = localStorage.getItem(ANONYMOUS_ID_KEY)\n * ```\n *\n * @see {@link ANONYMOUS_ID_COOKIE} and {@link ANONYMOUS_ID_KEY} in\n * `@contentful/optimization-core` for the\n * authoritative definition and documentation.\n *\n * @public\n */\nexport { ANONYMOUS_ID_COOKIE, ANONYMOUS_ID_KEY } from '@contentful/optimization-core/constants'\n","__webpack_require__.d = (exports, definition) => {\n\tfor(var key in definition) {\n if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n Object.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n }\n }\n};","__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))","// define __esModule on exports\n__webpack_require__.r = (exports) => {\n\tif(typeof Symbol !== 'undefined' && Symbol.toStringTag) {\n\t\tObject.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });\n\t}\n\tObject.defineProperty(exports, '__esModule', { value: true });\n};","import { CoreStateless } from '@contentful/optimization-core'\nimport type { App } from '@contentful/optimization-core/api-schemas'\nimport { merge } from 'es-toolkit'\nimport { OPTIMIZATION_NODE_SDK_NAME, OPTIMIZATION_NODE_SDK_VERSION } from './constants'\n\ntype CoreStatelessConfig = ConstructorParameters<typeof CoreStateless>[0]\n\n/**\n * Configuration for the Node-specific ContentfulOptimization SDK.\n *\n * @remarks\n * This configuration extends {@link CoreStatelessConfig} but allows partial\n * overrides of the event-builder configuration. SDKs commonly inject their own\n * library metadata or channel definitions.\n *\n * @see {@link CoreStatelessConfig}\n *\n * @public\n */\nexport interface OptimizationNodeConfig extends Omit<CoreStatelessConfig, 'eventBuilder'> {\n /**\n * The application definition used to attribute events to a specific consumer app.\n *\n * @remarks\n * When not provided, events will not contain app metadata in their context.\n */\n app?: App\n\n /**\n * Partial overrides for the event builder configuration.\n *\n * @remarks\n * Any provided fields are merged with the default Node SDK metadata.\n * This differs from {@link CoreStatelessConfig} eventBuilder, which expects\n * a full configuration object.\n */\n eventBuilder?: Partial<Omit<CoreStatelessConfig['eventBuilder'], 'app'>>\n}\n\n/**\n * Merge user-supplied configuration with defaults for the Node SDK.\n *\n * @param config - The input configuration supplied by the caller.\n * @returns A fully composed {@link CoreStatelessConfig} object suitable for\n * constructing the core runtime.\n *\n * @remarks\n * Ensures that the Node SDK always identifies itself via a `server` channel\n * and a `library` metadata block unless explicitly overridden.\n *\n * @internal\n */\nfunction mergeConfig(config: OptimizationNodeConfig): CoreStatelessConfig {\n const { app, ...restConfig } = config\n\n const defaultConfig: Partial<CoreStatelessConfig> = {\n eventBuilder: {\n app,\n channel: 'server',\n library: { name: OPTIMIZATION_NODE_SDK_NAME, version: OPTIMIZATION_NODE_SDK_VERSION },\n },\n }\n return merge(defaultConfig, restConfig)\n}\n\n/**\n * Node-specific ContentfulOptimization SDK built on {@link CoreStateless}.\n *\n * @remarks\n * This class adapts the stateless ContentfulOptimization Core for Node runtimes by\n * applying environment-appropriate defaults (e.g., server channel, Node SDK\n * library metadata). No core runtime behavior is modified; only configuration\n * defaults differ.\n *\n * @example\n * ```ts\n * import ContentfulOptimization from '@contentful/optimization-node'\n *\n * const sdk = new ContentfulOptimization({\n * clientId: 'abc-123',\n * environment: 'main',\n * logLevel: 'info',\n * })\n *\n * const requestOptions = { locale: 'en-US' }\n *\n * await sdk.track({ event: 'server_event', properties: { id: 1 } }, requestOptions)\n * ```\n *\n * @see {@link CoreStateless}\n *\n * @public\n */\nclass ContentfulOptimization extends CoreStateless {\n /**\n * Create an instance of the Node SDK with merged defaults.\n *\n * @param config - Partial Node-specific configuration. Any eventBuilder\n * fields provided are merged with Node's defaults.\n *\n * @example\n * ```ts\n * import ContentfulOptimization from '@contentful/optimization-node'\n *\n * const optimization = new ContentfulOptimization({ clientId: 'my-client-id' })\n * ```\n */\n constructor(config: OptimizationNodeConfig) {\n const mergedConfig: CoreStatelessConfig = mergeConfig(config)\n\n super(mergedConfig)\n }\n}\n\nexport default ContentfulOptimization\n","/**\n * Contentful ContentfulOptimization Node SDK.\n *\n * @remarks\n * Adds Node-specific defaults via the {@link ContentfulOptimization} class.\n * Core and transitive API exports are available from dedicated entrypoints:\n * `@contentful/optimization-node/core-sdk`,\n * `@contentful/optimization-node/api-client`,\n * and `@contentful/optimization-node/api-schemas`.\n *\n * @packageDocumentation\n */\n\nimport ContentfulOptimization from './ContentfulOptimization'\n\nexport { OPTIMIZATION_NODE_SDK_NAME, OPTIMIZATION_NODE_SDK_VERSION } from './constants'\nexport * from './ContentfulOptimization'\n\nexport default ContentfulOptimization\n"],"names":["OPTIMIZATION_NODE_SDK_VERSION","__OPTIMIZATION_VERSION__","OPTIMIZATION_NODE_SDK_NAME","__OPTIMIZATION_PACKAGE_NAME__","__webpack_require__","definition","key","Object","obj","prop","Symbol","mergeConfig","config","app","restConfig","defaultConfig","merge","ContentfulOptimization","CoreStateless","mergedConfig"],"mappings":";;;;;;;;QAYO,MAAMA,gCACoCC;QAS1C,MAAMC,6BAEPC;;;;;;;;;;;;;;ICxBNC,oBAAoB,CAAC,GAAG,CAAC,UAASC;QACjC,IAAI,IAAIC,OAAOD,WACR,IAAGD,oBAAoB,CAAC,CAACC,YAAYC,QAAQ,CAACF,oBAAoB,CAAC,CAAC,UAASE,MACzEC,OAAO,cAAc,CAAC,UAASD,KAAK;YAAE,YAAY;YAAM,KAAKD,UAAU,CAACC,IAAI;QAAC;IAGzF;;;ICNAF,oBAAoB,CAAC,GAAG,CAACI,KAAKC,OAAUF,OAAO,SAAS,CAAC,cAAc,CAAC,IAAI,CAACC,KAAKC;;;ICClFL,oBAAoB,CAAC,GAAG,CAAC;QACxB,IAAG,AAAkB,MAAlB,OAAOM,UAA0BA,OAAO,WAAW,EACrDH,OAAO,cAAc,CAAC,UAASG,OAAO,WAAW,EAAE;YAAE,OAAO;QAAS;QAEtEH,OAAO,cAAc,CAAC,UAAS,cAAc;YAAE,OAAO;QAAK;IAC5D;;;;;;;;;;;;;IC8CA,SAASI,YAAYC,MAA8B;QACjD,MAAM,EAAEC,GAAG,EAAE,GAAGC,YAAY,GAAGF;QAE/B,MAAMG,gBAA8C;YAClD,cAAc;gBACZF;gBACA,SAAS;gBACT,SAAS;oBAAE,MAAMX,UAAAA,0BAA0BA;oBAAE,SAASF,UAAAA,6BAA6BA;gBAAC;YACtF;QACF;QACA,OAAOgB,AAAAA,IAAAA,oCAAAA,KAAAA,AAAAA,EAAMD,eAAeD;IAC9B;IA8BA,MAAMG,+BAA+BC,kCAAAA,aAAaA;QAchD,YAAYN,MAA8B,CAAE;YAC1C,MAAMO,eAAoCR,YAAYC;YAEtD,KAAK,CAACO;QACR;IACF;IAEA,mCAAeF;IChGf,YAAeA"}
|
|
1
|
+
{"version":3,"file":"index.cjs","sources":["../src/constants.ts","webpack/runtime/define_property_getters","webpack/runtime/has_own_property","webpack/runtime/make_namespace_object","../src/ContentfulOptimization.ts","../src/index.ts"],"sourcesContent":["// eslint-disable-next-line @typescript-eslint/naming-convention -- Replaced at build-time\ndeclare const __OPTIMIZATION_VERSION__: string | undefined\n// eslint-disable-next-line @typescript-eslint/naming-convention -- Replaced at build-time\ndeclare const __OPTIMIZATION_PACKAGE_NAME__: string | undefined\n\n/**\n * The current version of the Optimization Node SDK, injected at build-time.\n *\n * @defaultValue `'0.0.0'` when the build-time replacement is unavailable.\n *\n * @public\n */\nexport const OPTIMIZATION_NODE_SDK_VERSION =\n typeof __OPTIMIZATION_VERSION__ === 'string' ? __OPTIMIZATION_VERSION__ : '0.0.0'\n\n/**\n * The package name of the Optimization Node SDK, injected at build-time.\n *\n * @defaultValue `'@contentful/optimization-node'` when the build-time replacement is unavailable.\n *\n * @public\n */\nexport const OPTIMIZATION_NODE_SDK_NAME =\n typeof __OPTIMIZATION_PACKAGE_NAME__ === 'string'\n ? __OPTIMIZATION_PACKAGE_NAME__\n : '@contentful/optimization-node'\n\n/**\n * Re-exports of anonymous identifier constants used by the Optimization Core.\n *\n * @remarks\n * This constant is surfaced here to provide a stable import path for Node SDK\n * consumers. It represents the cookie key used by the Optimization Framework\n * to persist an anonymous identifier for tracking optimization and insights\n * events when no explicit profile is known.\n *\n * @example\n * ```ts\n * import { ANONYMOUS_ID_COOKIE, ANONYMOUS_ID_KEY } from '@contentful/optimization-node/constants'\n * const cookieId = request.cookies[ANONYMOUS_ID_COOKIE]\n * const storageId = localStorage.getItem(ANONYMOUS_ID_KEY)\n * ```\n *\n * @see {@link ANONYMOUS_ID_COOKIE} and {@link ANONYMOUS_ID_KEY} in\n * `@contentful/optimization-core` for the\n * authoritative definition and documentation.\n *\n * @public\n */\nexport { ANONYMOUS_ID_COOKIE, ANONYMOUS_ID_KEY } from '@contentful/optimization-core/constants'\n","__webpack_require__.d = (exports, definition) => {\n\tfor(var key in definition) {\n if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n Object.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n }\n }\n};","__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))","// define __esModule on exports\n__webpack_require__.r = (exports) => {\n\tif(typeof Symbol !== 'undefined' && Symbol.toStringTag) {\n\t\tObject.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });\n\t}\n\tObject.defineProperty(exports, '__esModule', { value: true });\n};","import {\n CoreStateless,\n normalizeLocale,\n resolveContentfulLocale,\n type CoreStatelessConfig,\n type EventType,\n} from '@contentful/optimization-core'\nimport type { App } from '@contentful/optimization-core/api-schemas'\nimport { OPTIMIZATION_NODE_SDK_NAME, OPTIMIZATION_NODE_SDK_VERSION } from './constants'\n\nconst DEFAULT_RUNTIME_LOCALE = 'en-US'\nconst DEFAULT_ACCEPT_LANGUAGE_QUALITY = 1\nconst QUALITY_PARAM_PATTERN = /;\\s*q=/\nconst DEFAULT_NODE_ALLOWED_EVENT_TYPES: EventType[] = ['identify', 'page']\n\ntype NodeEventBuilderConfig = Partial<Omit<NonNullable<CoreStatelessConfig['eventBuilder']>, 'app'>>\ntype PublicNodeEventBuilderConfig = Omit<NodeEventBuilderConfig, 'getConsent'>\n\ninterface HeaderRequestLike {\n acceptsLanguages?: () => string[]\n get?: (name: string) => string | null | undefined\n headers?: unknown\n}\n\n/**\n * Locale pair resolved from an incoming server request.\n *\n * @public\n */\nexport interface ResolvedRequestLocale {\n /** Locale to place on SDK event context. */\n readonly eventLocale: string\n /** Contentful locale to use for CDA fetches and Experience API request options, when configured. */\n readonly contentfulLocale?: string\n}\n\n/**\n * Accepted inputs for {@link ContentfulOptimization.resolveRequestLocale}.\n *\n * @public\n */\nexport type RequestLocaleInput = string | null | undefined | HeaderRequestLike\n\n/**\n * Configuration for the Node-specific ContentfulOptimization SDK.\n *\n * @remarks\n * This configuration extends {@link CoreStatelessConfig} but allows partial\n * overrides of the event-builder configuration. SDKs commonly inject their own\n * library metadata or channel definitions.\n *\n * @see {@link CoreStatelessConfig}\n *\n * @public\n */\nexport interface OptimizationNodeConfig extends Omit<CoreStatelessConfig, 'eventBuilder'> {\n /**\n * The application definition used to attribute events to a specific consumer app.\n *\n * @remarks\n * When not provided, events will not contain app metadata in their context.\n */\n app?: App\n\n /**\n * Partial overrides for the event builder configuration.\n *\n * @remarks\n * Any provided fields are merged with the default Node SDK metadata.\n * Request-scoped consent should be bound with `forRequest()`, not configured\n * on the SDK singleton.\n *\n * This differs from {@link CoreStatelessConfig} eventBuilder, which expects\n * a full configuration object.\n */\n eventBuilder?: PublicNodeEventBuilderConfig\n}\n\nfunction normalizeHeaderValue(header: unknown): string | undefined {\n if (typeof header === 'string') {\n return header\n }\n\n return Array.isArray(header)\n ? header.filter((item): item is string => typeof item === 'string').join(',')\n : undefined\n}\n\nfunction getAcceptLanguageHeader(input: RequestLocaleInput): string | undefined {\n if (typeof input === 'string') {\n return input\n }\n\n if (typeof input !== 'object' || input === null) {\n return undefined\n }\n\n const headerFromGetter = input.get?.('accept-language')\n\n if (typeof headerFromGetter === 'string') {\n return headerFromGetter\n }\n\n const { headers } = input\n\n if (typeof headers !== 'object' || headers === null) {\n return undefined\n }\n\n const get: unknown = Reflect.get(headers, 'get')\n const header: unknown =\n typeof get === 'function'\n ? Reflect.apply(get, headers, ['accept-language'])\n : (Reflect.get(headers, 'accept-language') ?? Reflect.get(headers, 'Accept-Language'))\n\n return normalizeHeaderValue(header)\n}\n\nfunction parseAcceptLanguage(acceptLanguage: string | undefined): string[] {\n const candidates: Array<{ index: number; locale: string; quality: number }> = []\n\n for (const [index, value] of (acceptLanguage ?? '').split(',').entries()) {\n const [rawLocale, rawQuality] = value.split(QUALITY_PARAM_PATTERN)\n const locale = normalizeLocale(rawLocale)\n\n if (locale === undefined) {\n continue\n }\n\n const parsedQuality =\n rawQuality === undefined ? DEFAULT_ACCEPT_LANGUAGE_QUALITY : Number(rawQuality)\n const quality = Number.isFinite(parsedQuality) ? parsedQuality : DEFAULT_ACCEPT_LANGUAGE_QUALITY\n\n if (quality > 0) {\n candidates.push({ index, locale, quality })\n }\n }\n\n return candidates\n .sort((left, right) => right.quality - left.quality || left.index - right.index)\n .map((candidate) => candidate.locale)\n}\n\nfunction getRequestLocaleCandidates(input: RequestLocaleInput): string[] {\n const parsedHeaderLocales = parseAcceptLanguage(getAcceptLanguageHeader(input))\n\n if (parsedHeaderLocales.length > 0) {\n return parsedHeaderLocales\n }\n\n if (typeof input !== 'object' || input === null) {\n return []\n }\n\n return (\n input\n .acceptsLanguages?.()\n .map(normalizeLocale)\n .filter((locale): locale is string => locale !== undefined) ?? []\n )\n}\n\n/**\n * Node-specific ContentfulOptimization SDK built on {@link CoreStateless}.\n *\n * @remarks\n * This class adapts the stateless ContentfulOptimization Core for Node runtimes by\n * applying environment-appropriate defaults (e.g., server channel, Node SDK\n * library metadata). No core runtime behavior is modified; only configuration\n * defaults differ.\n *\n * @example\n * ```ts\n * import ContentfulOptimization from '@contentful/optimization-node'\n *\n * const sdk = new ContentfulOptimization({\n * clientId: 'abc-123',\n * environment: 'main',\n * logLevel: 'info',\n * })\n *\n * const requestOptimization = sdk.forRequest({\n * consent: true,\n * experienceOptions: { locale: 'fr-CA' },\n * profile: { id: 'profile-id' },\n * })\n *\n * await requestOptimization.page()\n * ```\n *\n * @see {@link CoreStateless}\n *\n * @public\n */\nclass ContentfulOptimization extends CoreStateless {\n /**\n * Create an instance of the Node SDK with merged defaults.\n *\n * @param config - Partial Node-specific configuration. Any eventBuilder\n * fields provided are merged with Node's defaults.\n *\n * @example\n * ```ts\n * import ContentfulOptimization from '@contentful/optimization-node'\n *\n * const optimization = new ContentfulOptimization({ clientId: 'my-client-id' })\n * ```\n */\n constructor({ app, allowedEventTypes, eventBuilder, ...config }: OptimizationNodeConfig) {\n const {\n library,\n getConsent: _getConsent,\n ...eventBuilderConfig\n } = (eventBuilder ?? {}) as NodeEventBuilderConfig\n\n super({\n ...config,\n allowedEventTypes: allowedEventTypes ?? DEFAULT_NODE_ALLOWED_EVENT_TYPES,\n eventBuilder: {\n app,\n channel: 'server',\n library: {\n name: OPTIMIZATION_NODE_SDK_NAME,\n version: OPTIMIZATION_NODE_SDK_VERSION,\n ...library,\n },\n getConsent: () => false,\n ...eventBuilderConfig,\n },\n })\n }\n\n /**\n * Resolve request and Contentful locales from `Accept-Language`.\n *\n * @param input - Raw `Accept-Language` header or a request-like object.\n * @returns Event-context locale and Contentful CDA locale.\n *\n * @example\n * ```ts\n * const { eventLocale, contentfulLocale } = optimization.resolveRequestLocale(req)\n * ```\n */\n resolveRequestLocale(input: RequestLocaleInput): ResolvedRequestLocale {\n const candidates = getRequestLocaleCandidates(input)\n const eventLocale = candidates[0] ?? DEFAULT_RUNTIME_LOCALE\n const contentfulLocale =\n resolveContentfulLocale({\n candidates,\n contentfulLocales: this.config.contentfulLocales,\n }) ?? this.locale\n\n return contentfulLocale === undefined ? { eventLocale } : { eventLocale, contentfulLocale }\n }\n}\n\nexport default ContentfulOptimization\n","/**\n * Contentful ContentfulOptimization Node SDK.\n *\n * @remarks\n * Adds Node-specific defaults via the {@link ContentfulOptimization} class.\n * Core and transitive API exports are available from dedicated entrypoints:\n * `@contentful/optimization-node/core-sdk`,\n * `@contentful/optimization-node/api-client`,\n * and `@contentful/optimization-node/api-schemas`.\n *\n * @packageDocumentation\n */\n\nimport ContentfulOptimization from './ContentfulOptimization'\n\nexport { OPTIMIZATION_NODE_SDK_NAME, OPTIMIZATION_NODE_SDK_VERSION } from './constants'\nexport * from './ContentfulOptimization'\n\nexport default ContentfulOptimization\n"],"names":["OPTIMIZATION_NODE_SDK_VERSION","__OPTIMIZATION_VERSION__","OPTIMIZATION_NODE_SDK_NAME","__OPTIMIZATION_PACKAGE_NAME__","__webpack_require__","definition","key","Object","obj","prop","Symbol","DEFAULT_RUNTIME_LOCALE","DEFAULT_ACCEPT_LANGUAGE_QUALITY","QUALITY_PARAM_PATTERN","DEFAULT_NODE_ALLOWED_EVENT_TYPES","normalizeHeaderValue","header","Array","item","undefined","getAcceptLanguageHeader","input","headerFromGetter","headers","get","Reflect","parseAcceptLanguage","acceptLanguage","candidates","index","value","rawLocale","rawQuality","locale","normalizeLocale","parsedQuality","Number","quality","left","right","candidate","getRequestLocaleCandidates","parsedHeaderLocales","ContentfulOptimization","CoreStateless","app","allowedEventTypes","eventBuilder","config","library","_getConsent","eventBuilderConfig","eventLocale","contentfulLocale","resolveContentfulLocale"],"mappings":"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"}
|
package/dist/index.d.cts
CHANGED
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
|
|
14
14
|
import type { App } from '@contentful/optimization-core/api-schemas';
|
|
15
15
|
import { CoreStateless } from '@contentful/optimization-core';
|
|
16
|
+
import { CoreStatelessConfig } from '@contentful/optimization-core';
|
|
16
17
|
|
|
17
18
|
/**
|
|
18
19
|
* Node-specific ContentfulOptimization SDK built on {@link CoreStateless}.
|
|
@@ -33,9 +34,13 @@ import { CoreStateless } from '@contentful/optimization-core';
|
|
|
33
34
|
* logLevel: 'info',
|
|
34
35
|
* })
|
|
35
36
|
*
|
|
36
|
-
* const
|
|
37
|
+
* const requestOptimization = sdk.forRequest({
|
|
38
|
+
* consent: true,
|
|
39
|
+
* experienceOptions: { locale: 'fr-CA' },
|
|
40
|
+
* profile: { id: 'profile-id' },
|
|
41
|
+
* })
|
|
37
42
|
*
|
|
38
|
-
* await
|
|
43
|
+
* await requestOptimization.page()
|
|
39
44
|
* ```
|
|
40
45
|
*
|
|
41
46
|
* @see {@link CoreStateless}
|
|
@@ -56,11 +61,29 @@ declare class ContentfulOptimization extends CoreStateless {
|
|
|
56
61
|
* const optimization = new ContentfulOptimization({ clientId: 'my-client-id' })
|
|
57
62
|
* ```
|
|
58
63
|
*/
|
|
59
|
-
constructor(config: OptimizationNodeConfig);
|
|
64
|
+
constructor({ app, allowedEventTypes, eventBuilder, ...config }: OptimizationNodeConfig);
|
|
65
|
+
/**
|
|
66
|
+
* Resolve request and Contentful locales from `Accept-Language`.
|
|
67
|
+
*
|
|
68
|
+
* @param input - Raw `Accept-Language` header or a request-like object.
|
|
69
|
+
* @returns Event-context locale and Contentful CDA locale.
|
|
70
|
+
*
|
|
71
|
+
* @example
|
|
72
|
+
* ```ts
|
|
73
|
+
* const { eventLocale, contentfulLocale } = optimization.resolveRequestLocale(req)
|
|
74
|
+
* ```
|
|
75
|
+
*/
|
|
76
|
+
resolveRequestLocale(input: RequestLocaleInput): ResolvedRequestLocale;
|
|
60
77
|
}
|
|
61
78
|
export default ContentfulOptimization;
|
|
62
79
|
|
|
63
|
-
declare
|
|
80
|
+
declare interface HeaderRequestLike {
|
|
81
|
+
acceptsLanguages?: () => string[];
|
|
82
|
+
get?: (name: string) => string | null | undefined;
|
|
83
|
+
headers?: unknown;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
declare type NodeEventBuilderConfig = Partial<Omit<NonNullable<CoreStatelessConfig['eventBuilder']>, 'app'>>;
|
|
64
87
|
|
|
65
88
|
/**
|
|
66
89
|
* The package name of the Optimization Node SDK, injected at build-time.
|
|
@@ -105,10 +128,34 @@ export declare interface OptimizationNodeConfig extends Omit<CoreStatelessConfig
|
|
|
105
128
|
*
|
|
106
129
|
* @remarks
|
|
107
130
|
* Any provided fields are merged with the default Node SDK metadata.
|
|
131
|
+
* Request-scoped consent should be bound with `forRequest()`, not configured
|
|
132
|
+
* on the SDK singleton.
|
|
133
|
+
*
|
|
108
134
|
* This differs from {@link CoreStatelessConfig} eventBuilder, which expects
|
|
109
135
|
* a full configuration object.
|
|
110
136
|
*/
|
|
111
|
-
eventBuilder?:
|
|
137
|
+
eventBuilder?: PublicNodeEventBuilderConfig;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
declare type PublicNodeEventBuilderConfig = Omit<NodeEventBuilderConfig, 'getConsent'>;
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Accepted inputs for {@link ContentfulOptimization.resolveRequestLocale}.
|
|
144
|
+
*
|
|
145
|
+
* @public
|
|
146
|
+
*/
|
|
147
|
+
export declare type RequestLocaleInput = string | null | undefined | HeaderRequestLike;
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Locale pair resolved from an incoming server request.
|
|
151
|
+
*
|
|
152
|
+
* @public
|
|
153
|
+
*/
|
|
154
|
+
export declare interface ResolvedRequestLocale {
|
|
155
|
+
/** Locale to place on SDK event context. */
|
|
156
|
+
readonly eventLocale: string;
|
|
157
|
+
/** Contentful locale to use for CDA fetches and Experience API request options, when configured. */
|
|
158
|
+
readonly contentfulLocale?: string;
|
|
112
159
|
}
|
|
113
160
|
|
|
114
161
|
export { }
|
package/dist/index.d.mts
CHANGED
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
|
|
14
14
|
import type { App } from '@contentful/optimization-core/api-schemas';
|
|
15
15
|
import { CoreStateless } from '@contentful/optimization-core';
|
|
16
|
+
import { CoreStatelessConfig } from '@contentful/optimization-core';
|
|
16
17
|
|
|
17
18
|
/**
|
|
18
19
|
* Node-specific ContentfulOptimization SDK built on {@link CoreStateless}.
|
|
@@ -33,9 +34,13 @@ import { CoreStateless } from '@contentful/optimization-core';
|
|
|
33
34
|
* logLevel: 'info',
|
|
34
35
|
* })
|
|
35
36
|
*
|
|
36
|
-
* const
|
|
37
|
+
* const requestOptimization = sdk.forRequest({
|
|
38
|
+
* consent: true,
|
|
39
|
+
* experienceOptions: { locale: 'fr-CA' },
|
|
40
|
+
* profile: { id: 'profile-id' },
|
|
41
|
+
* })
|
|
37
42
|
*
|
|
38
|
-
* await
|
|
43
|
+
* await requestOptimization.page()
|
|
39
44
|
* ```
|
|
40
45
|
*
|
|
41
46
|
* @see {@link CoreStateless}
|
|
@@ -56,11 +61,29 @@ declare class ContentfulOptimization extends CoreStateless {
|
|
|
56
61
|
* const optimization = new ContentfulOptimization({ clientId: 'my-client-id' })
|
|
57
62
|
* ```
|
|
58
63
|
*/
|
|
59
|
-
constructor(config: OptimizationNodeConfig);
|
|
64
|
+
constructor({ app, allowedEventTypes, eventBuilder, ...config }: OptimizationNodeConfig);
|
|
65
|
+
/**
|
|
66
|
+
* Resolve request and Contentful locales from `Accept-Language`.
|
|
67
|
+
*
|
|
68
|
+
* @param input - Raw `Accept-Language` header or a request-like object.
|
|
69
|
+
* @returns Event-context locale and Contentful CDA locale.
|
|
70
|
+
*
|
|
71
|
+
* @example
|
|
72
|
+
* ```ts
|
|
73
|
+
* const { eventLocale, contentfulLocale } = optimization.resolveRequestLocale(req)
|
|
74
|
+
* ```
|
|
75
|
+
*/
|
|
76
|
+
resolveRequestLocale(input: RequestLocaleInput): ResolvedRequestLocale;
|
|
60
77
|
}
|
|
61
78
|
export default ContentfulOptimization;
|
|
62
79
|
|
|
63
|
-
declare
|
|
80
|
+
declare interface HeaderRequestLike {
|
|
81
|
+
acceptsLanguages?: () => string[];
|
|
82
|
+
get?: (name: string) => string | null | undefined;
|
|
83
|
+
headers?: unknown;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
declare type NodeEventBuilderConfig = Partial<Omit<NonNullable<CoreStatelessConfig['eventBuilder']>, 'app'>>;
|
|
64
87
|
|
|
65
88
|
/**
|
|
66
89
|
* The package name of the Optimization Node SDK, injected at build-time.
|
|
@@ -105,10 +128,34 @@ export declare interface OptimizationNodeConfig extends Omit<CoreStatelessConfig
|
|
|
105
128
|
*
|
|
106
129
|
* @remarks
|
|
107
130
|
* Any provided fields are merged with the default Node SDK metadata.
|
|
131
|
+
* Request-scoped consent should be bound with `forRequest()`, not configured
|
|
132
|
+
* on the SDK singleton.
|
|
133
|
+
*
|
|
108
134
|
* This differs from {@link CoreStatelessConfig} eventBuilder, which expects
|
|
109
135
|
* a full configuration object.
|
|
110
136
|
*/
|
|
111
|
-
eventBuilder?:
|
|
137
|
+
eventBuilder?: PublicNodeEventBuilderConfig;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
declare type PublicNodeEventBuilderConfig = Omit<NodeEventBuilderConfig, 'getConsent'>;
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Accepted inputs for {@link ContentfulOptimization.resolveRequestLocale}.
|
|
144
|
+
*
|
|
145
|
+
* @public
|
|
146
|
+
*/
|
|
147
|
+
export declare type RequestLocaleInput = string | null | undefined | HeaderRequestLike;
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Locale pair resolved from an incoming server request.
|
|
151
|
+
*
|
|
152
|
+
* @public
|
|
153
|
+
*/
|
|
154
|
+
export declare interface ResolvedRequestLocale {
|
|
155
|
+
/** Locale to place on SDK event context. */
|
|
156
|
+
readonly eventLocale: string;
|
|
157
|
+
/** Contentful locale to use for CDA fetches and Experience API request options, when configured. */
|
|
158
|
+
readonly contentfulLocale?: string;
|
|
112
159
|
}
|
|
113
160
|
|
|
114
161
|
export { }
|
package/dist/index.d.ts
CHANGED
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
|
|
14
14
|
import type { App } from '@contentful/optimization-core/api-schemas';
|
|
15
15
|
import { CoreStateless } from '@contentful/optimization-core';
|
|
16
|
+
import { CoreStatelessConfig } from '@contentful/optimization-core';
|
|
16
17
|
|
|
17
18
|
/**
|
|
18
19
|
* Node-specific ContentfulOptimization SDK built on {@link CoreStateless}.
|
|
@@ -33,9 +34,13 @@ import { CoreStateless } from '@contentful/optimization-core';
|
|
|
33
34
|
* logLevel: 'info',
|
|
34
35
|
* })
|
|
35
36
|
*
|
|
36
|
-
* const
|
|
37
|
+
* const requestOptimization = sdk.forRequest({
|
|
38
|
+
* consent: true,
|
|
39
|
+
* experienceOptions: { locale: 'fr-CA' },
|
|
40
|
+
* profile: { id: 'profile-id' },
|
|
41
|
+
* })
|
|
37
42
|
*
|
|
38
|
-
* await
|
|
43
|
+
* await requestOptimization.page()
|
|
39
44
|
* ```
|
|
40
45
|
*
|
|
41
46
|
* @see {@link CoreStateless}
|
|
@@ -56,11 +61,29 @@ declare class ContentfulOptimization extends CoreStateless {
|
|
|
56
61
|
* const optimization = new ContentfulOptimization({ clientId: 'my-client-id' })
|
|
57
62
|
* ```
|
|
58
63
|
*/
|
|
59
|
-
constructor(config: OptimizationNodeConfig);
|
|
64
|
+
constructor({ app, allowedEventTypes, eventBuilder, ...config }: OptimizationNodeConfig);
|
|
65
|
+
/**
|
|
66
|
+
* Resolve request and Contentful locales from `Accept-Language`.
|
|
67
|
+
*
|
|
68
|
+
* @param input - Raw `Accept-Language` header or a request-like object.
|
|
69
|
+
* @returns Event-context locale and Contentful CDA locale.
|
|
70
|
+
*
|
|
71
|
+
* @example
|
|
72
|
+
* ```ts
|
|
73
|
+
* const { eventLocale, contentfulLocale } = optimization.resolveRequestLocale(req)
|
|
74
|
+
* ```
|
|
75
|
+
*/
|
|
76
|
+
resolveRequestLocale(input: RequestLocaleInput): ResolvedRequestLocale;
|
|
60
77
|
}
|
|
61
78
|
export default ContentfulOptimization;
|
|
62
79
|
|
|
63
|
-
declare
|
|
80
|
+
declare interface HeaderRequestLike {
|
|
81
|
+
acceptsLanguages?: () => string[];
|
|
82
|
+
get?: (name: string) => string | null | undefined;
|
|
83
|
+
headers?: unknown;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
declare type NodeEventBuilderConfig = Partial<Omit<NonNullable<CoreStatelessConfig['eventBuilder']>, 'app'>>;
|
|
64
87
|
|
|
65
88
|
/**
|
|
66
89
|
* The package name of the Optimization Node SDK, injected at build-time.
|
|
@@ -105,10 +128,34 @@ export declare interface OptimizationNodeConfig extends Omit<CoreStatelessConfig
|
|
|
105
128
|
*
|
|
106
129
|
* @remarks
|
|
107
130
|
* Any provided fields are merged with the default Node SDK metadata.
|
|
131
|
+
* Request-scoped consent should be bound with `forRequest()`, not configured
|
|
132
|
+
* on the SDK singleton.
|
|
133
|
+
*
|
|
108
134
|
* This differs from {@link CoreStatelessConfig} eventBuilder, which expects
|
|
109
135
|
* a full configuration object.
|
|
110
136
|
*/
|
|
111
|
-
eventBuilder?:
|
|
137
|
+
eventBuilder?: PublicNodeEventBuilderConfig;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
declare type PublicNodeEventBuilderConfig = Omit<NodeEventBuilderConfig, 'getConsent'>;
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Accepted inputs for {@link ContentfulOptimization.resolveRequestLocale}.
|
|
144
|
+
*
|
|
145
|
+
* @public
|
|
146
|
+
*/
|
|
147
|
+
export declare type RequestLocaleInput = string | null | undefined | HeaderRequestLike;
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Locale pair resolved from an incoming server request.
|
|
151
|
+
*
|
|
152
|
+
* @public
|
|
153
|
+
*/
|
|
154
|
+
export declare interface ResolvedRequestLocale {
|
|
155
|
+
/** Locale to place on SDK event context. */
|
|
156
|
+
readonly eventLocale: string;
|
|
157
|
+
/** Contentful locale to use for CDA fetches and Experience API request options, when configured. */
|
|
158
|
+
readonly contentfulLocale?: string;
|
|
112
159
|
}
|
|
113
160
|
|
|
114
161
|
export { }
|
package/dist/index.mjs
CHANGED
|
@@ -1,24 +1,83 @@
|
|
|
1
|
-
import { merge } from "es-toolkit";
|
|
2
1
|
import { __rspack_external__contentful_optimization_core_7d4758c6 } from "./987.mjs";
|
|
3
2
|
import { OPTIMIZATION_NODE_SDK_NAME, OPTIMIZATION_NODE_SDK_VERSION } from "./260.mjs";
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
3
|
+
const DEFAULT_RUNTIME_LOCALE = 'en-US';
|
|
4
|
+
const DEFAULT_ACCEPT_LANGUAGE_QUALITY = 1;
|
|
5
|
+
const QUALITY_PARAM_PATTERN = /;\s*q=/;
|
|
6
|
+
const DEFAULT_NODE_ALLOWED_EVENT_TYPES = [
|
|
7
|
+
'identify',
|
|
8
|
+
'page'
|
|
9
|
+
];
|
|
10
|
+
function normalizeHeaderValue(header) {
|
|
11
|
+
if ('string' == typeof header) return header;
|
|
12
|
+
return Array.isArray(header) ? header.filter((item)=>'string' == typeof item).join(',') : void 0;
|
|
13
|
+
}
|
|
14
|
+
function getAcceptLanguageHeader(input) {
|
|
15
|
+
if ('string' == typeof input) return input;
|
|
16
|
+
if ('object' != typeof input || null === input) return;
|
|
17
|
+
const headerFromGetter = input.get?.('accept-language');
|
|
18
|
+
if ('string' == typeof headerFromGetter) return headerFromGetter;
|
|
19
|
+
const { headers } = input;
|
|
20
|
+
if ('object' != typeof headers || null === headers) return;
|
|
21
|
+
const get = Reflect.get(headers, 'get');
|
|
22
|
+
const header = 'function' == typeof get ? Reflect.apply(get, headers, [
|
|
23
|
+
'accept-language'
|
|
24
|
+
]) : Reflect.get(headers, 'accept-language') ?? Reflect.get(headers, 'Accept-Language');
|
|
25
|
+
return normalizeHeaderValue(header);
|
|
26
|
+
}
|
|
27
|
+
function parseAcceptLanguage(acceptLanguage) {
|
|
28
|
+
const candidates = [];
|
|
29
|
+
for (const [index, value] of (acceptLanguage ?? '').split(',').entries()){
|
|
30
|
+
const [rawLocale, rawQuality] = value.split(QUALITY_PARAM_PATTERN);
|
|
31
|
+
const locale = (0, __rspack_external__contentful_optimization_core_7d4758c6.normalizeLocale)(rawLocale);
|
|
32
|
+
if (void 0 === locale) continue;
|
|
33
|
+
const parsedQuality = void 0 === rawQuality ? DEFAULT_ACCEPT_LANGUAGE_QUALITY : Number(rawQuality);
|
|
34
|
+
const quality = Number.isFinite(parsedQuality) ? parsedQuality : DEFAULT_ACCEPT_LANGUAGE_QUALITY;
|
|
35
|
+
if (quality > 0) candidates.push({
|
|
36
|
+
index,
|
|
37
|
+
locale,
|
|
38
|
+
quality
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
return candidates.sort((left, right)=>right.quality - left.quality || left.index - right.index).map((candidate)=>candidate.locale);
|
|
42
|
+
}
|
|
43
|
+
function getRequestLocaleCandidates(input) {
|
|
44
|
+
const parsedHeaderLocales = parseAcceptLanguage(getAcceptLanguageHeader(input));
|
|
45
|
+
if (parsedHeaderLocales.length > 0) return parsedHeaderLocales;
|
|
46
|
+
if ('object' != typeof input || null === input) return [];
|
|
47
|
+
return input.acceptsLanguages?.().map(__rspack_external__contentful_optimization_core_7d4758c6.normalizeLocale).filter((locale)=>void 0 !== locale) ?? [];
|
|
17
48
|
}
|
|
18
49
|
class ContentfulOptimization extends __rspack_external__contentful_optimization_core_7d4758c6.CoreStateless {
|
|
19
|
-
constructor(config){
|
|
20
|
-
const
|
|
21
|
-
super(
|
|
50
|
+
constructor({ app, allowedEventTypes, eventBuilder, ...config }){
|
|
51
|
+
const { library, getConsent: _getConsent, ...eventBuilderConfig } = eventBuilder ?? {};
|
|
52
|
+
super({
|
|
53
|
+
...config,
|
|
54
|
+
allowedEventTypes: allowedEventTypes ?? DEFAULT_NODE_ALLOWED_EVENT_TYPES,
|
|
55
|
+
eventBuilder: {
|
|
56
|
+
app,
|
|
57
|
+
channel: 'server',
|
|
58
|
+
library: {
|
|
59
|
+
name: OPTIMIZATION_NODE_SDK_NAME,
|
|
60
|
+
version: OPTIMIZATION_NODE_SDK_VERSION,
|
|
61
|
+
...library
|
|
62
|
+
},
|
|
63
|
+
getConsent: ()=>false,
|
|
64
|
+
...eventBuilderConfig
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
resolveRequestLocale(input) {
|
|
69
|
+
const candidates = getRequestLocaleCandidates(input);
|
|
70
|
+
const eventLocale = candidates[0] ?? DEFAULT_RUNTIME_LOCALE;
|
|
71
|
+
const contentfulLocale = (0, __rspack_external__contentful_optimization_core_7d4758c6.resolveContentfulLocale)({
|
|
72
|
+
candidates,
|
|
73
|
+
contentfulLocales: this.config.contentfulLocales
|
|
74
|
+
}) ?? this.locale;
|
|
75
|
+
return void 0 === contentfulLocale ? {
|
|
76
|
+
eventLocale
|
|
77
|
+
} : {
|
|
78
|
+
eventLocale,
|
|
79
|
+
contentfulLocale
|
|
80
|
+
};
|
|
22
81
|
}
|
|
23
82
|
}
|
|
24
83
|
const src_ContentfulOptimization = ContentfulOptimization;
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","sources":["../src/ContentfulOptimization.ts","../src/index.ts"],"sourcesContent":["import { CoreStateless } from '@contentful/optimization-core'\nimport type { App } from '@contentful/optimization-core/api-schemas'\nimport { merge } from 'es-toolkit'\nimport { OPTIMIZATION_NODE_SDK_NAME, OPTIMIZATION_NODE_SDK_VERSION } from './constants'\n\ntype CoreStatelessConfig = ConstructorParameters<typeof CoreStateless>[0]\n\n/**\n * Configuration for the Node-specific ContentfulOptimization SDK.\n *\n * @remarks\n * This configuration extends {@link CoreStatelessConfig} but allows partial\n * overrides of the event-builder configuration. SDKs commonly inject their own\n * library metadata or channel definitions.\n *\n * @see {@link CoreStatelessConfig}\n *\n * @public\n */\nexport interface OptimizationNodeConfig extends Omit<CoreStatelessConfig, 'eventBuilder'> {\n /**\n * The application definition used to attribute events to a specific consumer app.\n *\n * @remarks\n * When not provided, events will not contain app metadata in their context.\n */\n app?: App\n\n /**\n * Partial overrides for the event builder configuration.\n *\n * @remarks\n * Any provided fields are merged with the default Node SDK metadata.\n * This differs from {@link CoreStatelessConfig} eventBuilder, which expects\n * a full configuration object.\n */\n eventBuilder?: Partial<Omit<CoreStatelessConfig['eventBuilder'], 'app'>>\n}\n\n/**\n * Merge user-supplied configuration with defaults for the Node SDK.\n *\n * @param config - The input configuration supplied by the caller.\n * @returns A fully composed {@link CoreStatelessConfig} object suitable for\n * constructing the core runtime.\n *\n * @remarks\n * Ensures that the Node SDK always identifies itself via a `server` channel\n * and a `library` metadata block unless explicitly overridden.\n *\n * @internal\n */\nfunction mergeConfig(config: OptimizationNodeConfig): CoreStatelessConfig {\n const { app, ...restConfig } = config\n\n const defaultConfig: Partial<CoreStatelessConfig> = {\n eventBuilder: {\n app,\n channel: 'server',\n library: { name: OPTIMIZATION_NODE_SDK_NAME, version: OPTIMIZATION_NODE_SDK_VERSION },\n },\n }\n return merge(defaultConfig, restConfig)\n}\n\n/**\n * Node-specific ContentfulOptimization SDK built on {@link CoreStateless}.\n *\n * @remarks\n * This class adapts the stateless ContentfulOptimization Core for Node runtimes by\n * applying environment-appropriate defaults (e.g., server channel, Node SDK\n * library metadata). No core runtime behavior is modified; only configuration\n * defaults differ.\n *\n * @example\n * ```ts\n * import ContentfulOptimization from '@contentful/optimization-node'\n *\n * const sdk = new ContentfulOptimization({\n * clientId: 'abc-123',\n * environment: 'main',\n * logLevel: 'info',\n * })\n *\n * const requestOptions = { locale: 'en-US' }\n *\n * await sdk.track({ event: 'server_event', properties: { id: 1 } }, requestOptions)\n * ```\n *\n * @see {@link CoreStateless}\n *\n * @public\n */\nclass ContentfulOptimization extends CoreStateless {\n /**\n * Create an instance of the Node SDK with merged defaults.\n *\n * @param config - Partial Node-specific configuration. Any eventBuilder\n * fields provided are merged with Node's defaults.\n *\n * @example\n * ```ts\n * import ContentfulOptimization from '@contentful/optimization-node'\n *\n * const optimization = new ContentfulOptimization({ clientId: 'my-client-id' })\n * ```\n */\n constructor(config: OptimizationNodeConfig) {\n const mergedConfig: CoreStatelessConfig = mergeConfig(config)\n\n super(mergedConfig)\n }\n}\n\nexport default ContentfulOptimization\n","/**\n * Contentful ContentfulOptimization Node SDK.\n *\n * @remarks\n * Adds Node-specific defaults via the {@link ContentfulOptimization} class.\n * Core and transitive API exports are available from dedicated entrypoints:\n * `@contentful/optimization-node/core-sdk`,\n * `@contentful/optimization-node/api-client`,\n * and `@contentful/optimization-node/api-schemas`.\n *\n * @packageDocumentation\n */\n\nimport ContentfulOptimization from './ContentfulOptimization'\n\nexport { OPTIMIZATION_NODE_SDK_NAME, OPTIMIZATION_NODE_SDK_VERSION } from './constants'\nexport * from './ContentfulOptimization'\n\nexport default ContentfulOptimization\n"],"names":["mergeConfig","config","app","restConfig","defaultConfig","OPTIMIZATION_NODE_SDK_NAME","OPTIMIZATION_NODE_SDK_VERSION","merge","ContentfulOptimization","CoreStateless","mergedConfig"],"mappings":";;;AAoDA,SAASA,YAAYC,MAA8B;IACjD,MAAM,EAAEC,GAAG,EAAE,GAAGC,YAAY,GAAGF;IAE/B,MAAMG,gBAA8C;QAClD,cAAc;YACZF;YACA,SAAS;YACT,SAAS;gBAAE,MAAMG;gBAA4B,SAASC;YAA8B;QACtF;IACF;IACA,OAAOC,MAAMH,eAAeD;AAC9B;AA8BA,MAAMK,+BAA+BC,yDAAAA,aAAaA;IAchD,YAAYR,MAA8B,CAAE;QAC1C,MAAMS,eAAoCV,YAAYC;QAEtD,KAAK,CAACS;IACR;AACF;AAEA,mCAAeF;AChGf,YAAeA"}
|
|
1
|
+
{"version":3,"file":"index.mjs","sources":["../src/ContentfulOptimization.ts","../src/index.ts"],"sourcesContent":["import {\n CoreStateless,\n normalizeLocale,\n resolveContentfulLocale,\n type CoreStatelessConfig,\n type EventType,\n} from '@contentful/optimization-core'\nimport type { App } from '@contentful/optimization-core/api-schemas'\nimport { OPTIMIZATION_NODE_SDK_NAME, OPTIMIZATION_NODE_SDK_VERSION } from './constants'\n\nconst DEFAULT_RUNTIME_LOCALE = 'en-US'\nconst DEFAULT_ACCEPT_LANGUAGE_QUALITY = 1\nconst QUALITY_PARAM_PATTERN = /;\\s*q=/\nconst DEFAULT_NODE_ALLOWED_EVENT_TYPES: EventType[] = ['identify', 'page']\n\ntype NodeEventBuilderConfig = Partial<Omit<NonNullable<CoreStatelessConfig['eventBuilder']>, 'app'>>\ntype PublicNodeEventBuilderConfig = Omit<NodeEventBuilderConfig, 'getConsent'>\n\ninterface HeaderRequestLike {\n acceptsLanguages?: () => string[]\n get?: (name: string) => string | null | undefined\n headers?: unknown\n}\n\n/**\n * Locale pair resolved from an incoming server request.\n *\n * @public\n */\nexport interface ResolvedRequestLocale {\n /** Locale to place on SDK event context. */\n readonly eventLocale: string\n /** Contentful locale to use for CDA fetches and Experience API request options, when configured. */\n readonly contentfulLocale?: string\n}\n\n/**\n * Accepted inputs for {@link ContentfulOptimization.resolveRequestLocale}.\n *\n * @public\n */\nexport type RequestLocaleInput = string | null | undefined | HeaderRequestLike\n\n/**\n * Configuration for the Node-specific ContentfulOptimization SDK.\n *\n * @remarks\n * This configuration extends {@link CoreStatelessConfig} but allows partial\n * overrides of the event-builder configuration. SDKs commonly inject their own\n * library metadata or channel definitions.\n *\n * @see {@link CoreStatelessConfig}\n *\n * @public\n */\nexport interface OptimizationNodeConfig extends Omit<CoreStatelessConfig, 'eventBuilder'> {\n /**\n * The application definition used to attribute events to a specific consumer app.\n *\n * @remarks\n * When not provided, events will not contain app metadata in their context.\n */\n app?: App\n\n /**\n * Partial overrides for the event builder configuration.\n *\n * @remarks\n * Any provided fields are merged with the default Node SDK metadata.\n * Request-scoped consent should be bound with `forRequest()`, not configured\n * on the SDK singleton.\n *\n * This differs from {@link CoreStatelessConfig} eventBuilder, which expects\n * a full configuration object.\n */\n eventBuilder?: PublicNodeEventBuilderConfig\n}\n\nfunction normalizeHeaderValue(header: unknown): string | undefined {\n if (typeof header === 'string') {\n return header\n }\n\n return Array.isArray(header)\n ? header.filter((item): item is string => typeof item === 'string').join(',')\n : undefined\n}\n\nfunction getAcceptLanguageHeader(input: RequestLocaleInput): string | undefined {\n if (typeof input === 'string') {\n return input\n }\n\n if (typeof input !== 'object' || input === null) {\n return undefined\n }\n\n const headerFromGetter = input.get?.('accept-language')\n\n if (typeof headerFromGetter === 'string') {\n return headerFromGetter\n }\n\n const { headers } = input\n\n if (typeof headers !== 'object' || headers === null) {\n return undefined\n }\n\n const get: unknown = Reflect.get(headers, 'get')\n const header: unknown =\n typeof get === 'function'\n ? Reflect.apply(get, headers, ['accept-language'])\n : (Reflect.get(headers, 'accept-language') ?? Reflect.get(headers, 'Accept-Language'))\n\n return normalizeHeaderValue(header)\n}\n\nfunction parseAcceptLanguage(acceptLanguage: string | undefined): string[] {\n const candidates: Array<{ index: number; locale: string; quality: number }> = []\n\n for (const [index, value] of (acceptLanguage ?? '').split(',').entries()) {\n const [rawLocale, rawQuality] = value.split(QUALITY_PARAM_PATTERN)\n const locale = normalizeLocale(rawLocale)\n\n if (locale === undefined) {\n continue\n }\n\n const parsedQuality =\n rawQuality === undefined ? DEFAULT_ACCEPT_LANGUAGE_QUALITY : Number(rawQuality)\n const quality = Number.isFinite(parsedQuality) ? parsedQuality : DEFAULT_ACCEPT_LANGUAGE_QUALITY\n\n if (quality > 0) {\n candidates.push({ index, locale, quality })\n }\n }\n\n return candidates\n .sort((left, right) => right.quality - left.quality || left.index - right.index)\n .map((candidate) => candidate.locale)\n}\n\nfunction getRequestLocaleCandidates(input: RequestLocaleInput): string[] {\n const parsedHeaderLocales = parseAcceptLanguage(getAcceptLanguageHeader(input))\n\n if (parsedHeaderLocales.length > 0) {\n return parsedHeaderLocales\n }\n\n if (typeof input !== 'object' || input === null) {\n return []\n }\n\n return (\n input\n .acceptsLanguages?.()\n .map(normalizeLocale)\n .filter((locale): locale is string => locale !== undefined) ?? []\n )\n}\n\n/**\n * Node-specific ContentfulOptimization SDK built on {@link CoreStateless}.\n *\n * @remarks\n * This class adapts the stateless ContentfulOptimization Core for Node runtimes by\n * applying environment-appropriate defaults (e.g., server channel, Node SDK\n * library metadata). No core runtime behavior is modified; only configuration\n * defaults differ.\n *\n * @example\n * ```ts\n * import ContentfulOptimization from '@contentful/optimization-node'\n *\n * const sdk = new ContentfulOptimization({\n * clientId: 'abc-123',\n * environment: 'main',\n * logLevel: 'info',\n * })\n *\n * const requestOptimization = sdk.forRequest({\n * consent: true,\n * experienceOptions: { locale: 'fr-CA' },\n * profile: { id: 'profile-id' },\n * })\n *\n * await requestOptimization.page()\n * ```\n *\n * @see {@link CoreStateless}\n *\n * @public\n */\nclass ContentfulOptimization extends CoreStateless {\n /**\n * Create an instance of the Node SDK with merged defaults.\n *\n * @param config - Partial Node-specific configuration. Any eventBuilder\n * fields provided are merged with Node's defaults.\n *\n * @example\n * ```ts\n * import ContentfulOptimization from '@contentful/optimization-node'\n *\n * const optimization = new ContentfulOptimization({ clientId: 'my-client-id' })\n * ```\n */\n constructor({ app, allowedEventTypes, eventBuilder, ...config }: OptimizationNodeConfig) {\n const {\n library,\n getConsent: _getConsent,\n ...eventBuilderConfig\n } = (eventBuilder ?? {}) as NodeEventBuilderConfig\n\n super({\n ...config,\n allowedEventTypes: allowedEventTypes ?? DEFAULT_NODE_ALLOWED_EVENT_TYPES,\n eventBuilder: {\n app,\n channel: 'server',\n library: {\n name: OPTIMIZATION_NODE_SDK_NAME,\n version: OPTIMIZATION_NODE_SDK_VERSION,\n ...library,\n },\n getConsent: () => false,\n ...eventBuilderConfig,\n },\n })\n }\n\n /**\n * Resolve request and Contentful locales from `Accept-Language`.\n *\n * @param input - Raw `Accept-Language` header or a request-like object.\n * @returns Event-context locale and Contentful CDA locale.\n *\n * @example\n * ```ts\n * const { eventLocale, contentfulLocale } = optimization.resolveRequestLocale(req)\n * ```\n */\n resolveRequestLocale(input: RequestLocaleInput): ResolvedRequestLocale {\n const candidates = getRequestLocaleCandidates(input)\n const eventLocale = candidates[0] ?? DEFAULT_RUNTIME_LOCALE\n const contentfulLocale =\n resolveContentfulLocale({\n candidates,\n contentfulLocales: this.config.contentfulLocales,\n }) ?? this.locale\n\n return contentfulLocale === undefined ? { eventLocale } : { eventLocale, contentfulLocale }\n }\n}\n\nexport default ContentfulOptimization\n","/**\n * Contentful ContentfulOptimization Node SDK.\n *\n * @remarks\n * Adds Node-specific defaults via the {@link ContentfulOptimization} class.\n * Core and transitive API exports are available from dedicated entrypoints:\n * `@contentful/optimization-node/core-sdk`,\n * `@contentful/optimization-node/api-client`,\n * and `@contentful/optimization-node/api-schemas`.\n *\n * @packageDocumentation\n */\n\nimport ContentfulOptimization from './ContentfulOptimization'\n\nexport { OPTIMIZATION_NODE_SDK_NAME, OPTIMIZATION_NODE_SDK_VERSION } from './constants'\nexport * from './ContentfulOptimization'\n\nexport default ContentfulOptimization\n"],"names":["DEFAULT_RUNTIME_LOCALE","DEFAULT_ACCEPT_LANGUAGE_QUALITY","QUALITY_PARAM_PATTERN","DEFAULT_NODE_ALLOWED_EVENT_TYPES","normalizeHeaderValue","header","Array","item","undefined","getAcceptLanguageHeader","input","headerFromGetter","headers","get","Reflect","parseAcceptLanguage","acceptLanguage","candidates","index","value","rawLocale","rawQuality","locale","normalizeLocale","parsedQuality","Number","quality","left","right","candidate","getRequestLocaleCandidates","parsedHeaderLocales","ContentfulOptimization","CoreStateless","app","allowedEventTypes","eventBuilder","config","library","_getConsent","eventBuilderConfig","OPTIMIZATION_NODE_SDK_NAME","OPTIMIZATION_NODE_SDK_VERSION","eventLocale","contentfulLocale","resolveContentfulLocale"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@contentful/optimization-node",
|
|
3
|
-
"version": "0.1.0-
|
|
3
|
+
"version": "0.1.0-alpha14",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -80,13 +80,13 @@
|
|
|
80
80
|
"buildTools": {
|
|
81
81
|
"bundleSize": {
|
|
82
82
|
"gzipBudgets": {
|
|
83
|
-
"index.cjs":
|
|
84
|
-
"index.mjs":
|
|
83
|
+
"index.cjs": 2000,
|
|
84
|
+
"index.mjs": 1500
|
|
85
85
|
}
|
|
86
86
|
}
|
|
87
87
|
},
|
|
88
88
|
"dependencies": {
|
|
89
|
-
"es-toolkit": "^1.
|
|
90
|
-
"@contentful/optimization-core": "0.1.0-
|
|
89
|
+
"es-toolkit": "^1.45.1",
|
|
90
|
+
"@contentful/optimization-core": "0.1.0-alpha14"
|
|
91
91
|
}
|
|
92
92
|
}
|