@prezly/theme-kit-nextjs 10.9.2 → 10.10.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 +24 -2
- package/build/adapters/prezly/cache/configure.cjs +6 -3
- package/build/adapters/prezly/cache/configure.d.ts +4 -0
- package/build/adapters/prezly/cache/configure.mjs +6 -3
- package/build/adapters/prezly/cache/redis.cjs +51 -15
- package/build/adapters/prezly/cache/redis.d.ts +3 -2
- package/build/adapters/prezly/cache/redis.mjs +51 -15
- package/build/adapters/prezly/cache/redis.test.cjs +104 -7
- package/build/adapters/prezly/cache/redis.test.mjs +104 -7
- package/build/adapters/prezly/requests.cjs +54 -8
- package/build/adapters/prezly/requests.d.ts +3 -2
- package/build/adapters/prezly/requests.mjs +55 -9
- package/build/adapters/prezly/server.cjs +14 -5
- package/build/adapters/prezly/server.d.ts +2 -1
- package/build/adapters/prezly/server.mjs +14 -5
- package/build/adapters/prezly/telemetry.cjs +56 -0
- package/build/adapters/prezly/telemetry.d.ts +4 -0
- package/build/adapters/prezly/telemetry.mjs +49 -0
- package/build/adapters/prezly/telemetry.test.cjs +167 -0
- package/build/adapters/prezly/telemetry.test.mjs +165 -0
- package/package.json +109 -112
|
@@ -5,6 +5,8 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
5
5
|
});
|
|
6
6
|
exports.FetchScheduler = void 0;
|
|
7
7
|
exports.createBoundedFetch = createBoundedFetch;
|
|
8
|
+
var _themeKitCore = require("@prezly/theme-kit-core");
|
|
9
|
+
var _telemetry = require("./telemetry.cjs");
|
|
8
10
|
var _registry$key;
|
|
9
11
|
function asyncGeneratorStep(n, t, e, r, o, a, c) { try { var i = n[a](c), u = i.value; } catch (n) { return void e(n); } i.done ? t(u) : Promise.resolve(u).then(r, o); }
|
|
10
12
|
function _asyncToGenerator(n) { return function () { var t = this, e = arguments; return new Promise(function (r, o) { var a = n.apply(t, e); function _next(n) { asyncGeneratorStep(a, r, o, _next, _throw, "next", n); } function _throw(n) { asyncGeneratorStep(a, r, o, _next, _throw, "throw", n); } _next(void 0); }); }; }
|
|
@@ -48,33 +50,62 @@ class FetchScheduler {
|
|
|
48
50
|
}
|
|
49
51
|
}
|
|
50
52
|
}
|
|
51
|
-
run(invoke, signal) {
|
|
52
|
-
|
|
53
|
+
run(invoke, signal, telemetry) {
|
|
54
|
+
var route = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 'other';
|
|
55
|
+
if (signal !== null && signal !== void 0 && signal.aborted) {
|
|
56
|
+
_themeKitCore.ContentDelivery.emit(telemetry, {
|
|
57
|
+
type: 'fetch_queue',
|
|
58
|
+
route,
|
|
59
|
+
outcome: 'aborted',
|
|
60
|
+
seconds: 0
|
|
61
|
+
});
|
|
62
|
+
return Promise.reject(abortReason(signal));
|
|
63
|
+
}
|
|
53
64
|
return new Promise((_resolve, reject) => {
|
|
54
65
|
var task = {
|
|
55
66
|
invoke,
|
|
56
67
|
resolve: value => _resolve(value),
|
|
57
68
|
reject,
|
|
58
69
|
signal,
|
|
59
|
-
clearQueueWait: () => {}
|
|
70
|
+
clearQueueWait: () => {},
|
|
71
|
+
telemetry,
|
|
72
|
+
route
|
|
60
73
|
};
|
|
61
74
|
if (this.active < this.limits.concurrency) {
|
|
62
75
|
this.start(task);
|
|
63
76
|
return;
|
|
64
77
|
}
|
|
65
78
|
if (this.queue.length >= this.limits.queueLimit) {
|
|
79
|
+
_themeKitCore.ContentDelivery.emit(telemetry, {
|
|
80
|
+
type: 'fetch_queue',
|
|
81
|
+
route,
|
|
82
|
+
outcome: 'full',
|
|
83
|
+
seconds: 0
|
|
84
|
+
});
|
|
66
85
|
reject(new Error('Content request queue is full.'));
|
|
67
86
|
return;
|
|
68
87
|
}
|
|
69
|
-
|
|
88
|
+
task.queuedAt = telemetry ? _themeKitCore.ContentDelivery.telemetryNow() : undefined;
|
|
89
|
+
_themeKitCore.ContentDelivery.emit(telemetry, {
|
|
90
|
+
type: 'fetch_queue',
|
|
91
|
+
route,
|
|
92
|
+
outcome: 'queued'
|
|
93
|
+
});
|
|
94
|
+
var cancel = (error, outcome) => {
|
|
70
95
|
var index = this.queue.indexOf(task);
|
|
71
96
|
if (index < 0) return;
|
|
72
97
|
this.queue.splice(index, 1);
|
|
73
98
|
task.clearQueueWait();
|
|
99
|
+
_themeKitCore.ContentDelivery.emit(telemetry, {
|
|
100
|
+
type: 'fetch_queue',
|
|
101
|
+
route,
|
|
102
|
+
outcome,
|
|
103
|
+
seconds: task.queuedAt === undefined ? 0 : _themeKitCore.ContentDelivery.elapsedSeconds(task.queuedAt)
|
|
104
|
+
});
|
|
74
105
|
reject(error);
|
|
75
106
|
};
|
|
76
|
-
var onAbort = () => cancel(abortReason(signal));
|
|
77
|
-
var timer = setTimeout(() => cancel(new Error('Content request queue wait timed out.')), this.limits.queueTimeout);
|
|
107
|
+
var onAbort = () => cancel(abortReason(signal), 'aborted');
|
|
108
|
+
var timer = setTimeout(() => cancel(new Error('Content request queue wait timed out.'), 'wait_timeout'), this.limits.queueTimeout);
|
|
78
109
|
task.clearQueueWait = () => {
|
|
79
110
|
clearTimeout(timer);
|
|
80
111
|
signal === null || signal === void 0 || signal.removeEventListener('abort', onAbort);
|
|
@@ -88,6 +119,12 @@ class FetchScheduler {
|
|
|
88
119
|
start(task) {
|
|
89
120
|
var _task$signal2;
|
|
90
121
|
task.clearQueueWait();
|
|
122
|
+
_themeKitCore.ContentDelivery.emit(task.telemetry, {
|
|
123
|
+
type: 'fetch_queue',
|
|
124
|
+
route: task.route,
|
|
125
|
+
outcome: 'accepted',
|
|
126
|
+
seconds: task.queuedAt === undefined ? 0 : _themeKitCore.ContentDelivery.elapsedSeconds(task.queuedAt)
|
|
127
|
+
});
|
|
91
128
|
this.active += 1;
|
|
92
129
|
var controller = new AbortController();
|
|
93
130
|
var rejectAbort = () => task.reject(abortReason(controller.signal));
|
|
@@ -101,7 +138,15 @@ class FetchScheduler {
|
|
|
101
138
|
(_task$signal2 = task.signal) === null || _task$signal2 === void 0 || _task$signal2.addEventListener('abort', forwardAbort, {
|
|
102
139
|
once: true
|
|
103
140
|
});
|
|
104
|
-
var timer = setTimeout(() =>
|
|
141
|
+
var timer = setTimeout(() => {
|
|
142
|
+
if (!controller.signal.aborted) {
|
|
143
|
+
_themeKitCore.ContentDelivery.emit(task.telemetry, {
|
|
144
|
+
type: 'fetch_timeout',
|
|
145
|
+
route: task.route
|
|
146
|
+
});
|
|
147
|
+
controller.abort(new Error('Content request timed out.'));
|
|
148
|
+
}
|
|
149
|
+
}, this.limits.requestTimeout);
|
|
105
150
|
void Promise.resolve().then(() => {
|
|
106
151
|
if (controller.signal.aborted) throw abortReason(controller.signal);
|
|
107
152
|
return task.invoke(controller.signal);
|
|
@@ -127,6 +172,7 @@ var registry = globalThis;
|
|
|
127
172
|
var sharedContentFetches = (_registry$key = registry[key]) !== null && _registry$key !== void 0 ? _registry$key : registry[key] = new FetchScheduler();
|
|
128
173
|
function createBoundedFetch(fetchImpl) {
|
|
129
174
|
var scheduler = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : sharedContentFetches;
|
|
175
|
+
var telemetry = arguments.length > 2 ? arguments[2] : undefined;
|
|
130
176
|
return (input, init) => {
|
|
131
177
|
var _init$signal;
|
|
132
178
|
var signal = (_init$signal = init === null || init === void 0 ? void 0 : init.signal) !== null && _init$signal !== void 0 ? _init$signal : input instanceof Request ? input.signal : undefined;
|
|
@@ -147,6 +193,6 @@ function createBoundedFetch(fetchImpl) {
|
|
|
147
193
|
return function (_x) {
|
|
148
194
|
return _ref4.apply(this, arguments);
|
|
149
195
|
};
|
|
150
|
-
}(), signal);
|
|
196
|
+
}(), signal, telemetry, telemetry ? (0, _telemetry.upstreamRoute)(input) : 'other');
|
|
151
197
|
};
|
|
152
198
|
}
|
|
@@ -10,8 +10,9 @@ export declare class FetchScheduler {
|
|
|
10
10
|
private readonly queue;
|
|
11
11
|
private readonly limits;
|
|
12
12
|
constructor(limits?: Partial<Limits>);
|
|
13
|
-
run<T>(invoke: (signal: AbortSignal) => Promise<T>, signal?: AbortSignal | null): Promise<T>;
|
|
13
|
+
run<T>(invoke: (signal: AbortSignal) => Promise<T>, signal?: AbortSignal | null, telemetry?: ContentDelivery.Telemetry, route?: ContentDelivery.UpstreamRoute): Promise<T>;
|
|
14
14
|
private start;
|
|
15
15
|
}
|
|
16
|
-
export declare function createBoundedFetch(fetchImpl: typeof fetch, scheduler?: FetchScheduler): typeof fetch;
|
|
16
|
+
export declare function createBoundedFetch(fetchImpl: typeof fetch, scheduler?: FetchScheduler, telemetry?: ContentDelivery.Telemetry): typeof fetch;
|
|
17
|
+
import { ContentDelivery } from '@prezly/theme-kit-core';
|
|
17
18
|
export {};
|
|
@@ -41,33 +41,62 @@ export class FetchScheduler {
|
|
|
41
41
|
}
|
|
42
42
|
}
|
|
43
43
|
}
|
|
44
|
-
run(invoke, signal) {
|
|
45
|
-
|
|
44
|
+
run(invoke, signal, telemetry) {
|
|
45
|
+
var route = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 'other';
|
|
46
|
+
if (signal !== null && signal !== void 0 && signal.aborted) {
|
|
47
|
+
ContentDelivery.emit(telemetry, {
|
|
48
|
+
type: 'fetch_queue',
|
|
49
|
+
route,
|
|
50
|
+
outcome: 'aborted',
|
|
51
|
+
seconds: 0
|
|
52
|
+
});
|
|
53
|
+
return Promise.reject(abortReason(signal));
|
|
54
|
+
}
|
|
46
55
|
return new Promise((_resolve, reject) => {
|
|
47
56
|
var task = {
|
|
48
57
|
invoke,
|
|
49
58
|
resolve: value => _resolve(value),
|
|
50
59
|
reject,
|
|
51
60
|
signal,
|
|
52
|
-
clearQueueWait: () => {}
|
|
61
|
+
clearQueueWait: () => {},
|
|
62
|
+
telemetry,
|
|
63
|
+
route
|
|
53
64
|
};
|
|
54
65
|
if (this.active < this.limits.concurrency) {
|
|
55
66
|
this.start(task);
|
|
56
67
|
return;
|
|
57
68
|
}
|
|
58
69
|
if (this.queue.length >= this.limits.queueLimit) {
|
|
70
|
+
ContentDelivery.emit(telemetry, {
|
|
71
|
+
type: 'fetch_queue',
|
|
72
|
+
route,
|
|
73
|
+
outcome: 'full',
|
|
74
|
+
seconds: 0
|
|
75
|
+
});
|
|
59
76
|
reject(new Error('Content request queue is full.'));
|
|
60
77
|
return;
|
|
61
78
|
}
|
|
62
|
-
|
|
79
|
+
task.queuedAt = telemetry ? ContentDelivery.telemetryNow() : undefined;
|
|
80
|
+
ContentDelivery.emit(telemetry, {
|
|
81
|
+
type: 'fetch_queue',
|
|
82
|
+
route,
|
|
83
|
+
outcome: 'queued'
|
|
84
|
+
});
|
|
85
|
+
var cancel = (error, outcome) => {
|
|
63
86
|
var index = this.queue.indexOf(task);
|
|
64
87
|
if (index < 0) return;
|
|
65
88
|
this.queue.splice(index, 1);
|
|
66
89
|
task.clearQueueWait();
|
|
90
|
+
ContentDelivery.emit(telemetry, {
|
|
91
|
+
type: 'fetch_queue',
|
|
92
|
+
route,
|
|
93
|
+
outcome,
|
|
94
|
+
seconds: task.queuedAt === undefined ? 0 : ContentDelivery.elapsedSeconds(task.queuedAt)
|
|
95
|
+
});
|
|
67
96
|
reject(error);
|
|
68
97
|
};
|
|
69
|
-
var onAbort = () => cancel(abortReason(signal));
|
|
70
|
-
var timer = setTimeout(() => cancel(new Error('Content request queue wait timed out.')), this.limits.queueTimeout);
|
|
98
|
+
var onAbort = () => cancel(abortReason(signal), 'aborted');
|
|
99
|
+
var timer = setTimeout(() => cancel(new Error('Content request queue wait timed out.'), 'wait_timeout'), this.limits.queueTimeout);
|
|
71
100
|
task.clearQueueWait = () => {
|
|
72
101
|
clearTimeout(timer);
|
|
73
102
|
signal === null || signal === void 0 || signal.removeEventListener('abort', onAbort);
|
|
@@ -81,6 +110,12 @@ export class FetchScheduler {
|
|
|
81
110
|
start(task) {
|
|
82
111
|
var _task$signal2;
|
|
83
112
|
task.clearQueueWait();
|
|
113
|
+
ContentDelivery.emit(task.telemetry, {
|
|
114
|
+
type: 'fetch_queue',
|
|
115
|
+
route: task.route,
|
|
116
|
+
outcome: 'accepted',
|
|
117
|
+
seconds: task.queuedAt === undefined ? 0 : ContentDelivery.elapsedSeconds(task.queuedAt)
|
|
118
|
+
});
|
|
84
119
|
this.active += 1;
|
|
85
120
|
var controller = new AbortController();
|
|
86
121
|
var rejectAbort = () => task.reject(abortReason(controller.signal));
|
|
@@ -94,7 +129,15 @@ export class FetchScheduler {
|
|
|
94
129
|
(_task$signal2 = task.signal) === null || _task$signal2 === void 0 || _task$signal2.addEventListener('abort', forwardAbort, {
|
|
95
130
|
once: true
|
|
96
131
|
});
|
|
97
|
-
var timer = setTimeout(() =>
|
|
132
|
+
var timer = setTimeout(() => {
|
|
133
|
+
if (!controller.signal.aborted) {
|
|
134
|
+
ContentDelivery.emit(task.telemetry, {
|
|
135
|
+
type: 'fetch_timeout',
|
|
136
|
+
route: task.route
|
|
137
|
+
});
|
|
138
|
+
controller.abort(new Error('Content request timed out.'));
|
|
139
|
+
}
|
|
140
|
+
}, this.limits.requestTimeout);
|
|
98
141
|
void Promise.resolve().then(() => {
|
|
99
142
|
if (controller.signal.aborted) throw abortReason(controller.signal);
|
|
100
143
|
return task.invoke(controller.signal);
|
|
@@ -119,6 +162,7 @@ var registry = globalThis;
|
|
|
119
162
|
var sharedContentFetches = (_registry$key = registry[key]) !== null && _registry$key !== void 0 ? _registry$key : registry[key] = new FetchScheduler();
|
|
120
163
|
export function createBoundedFetch(fetchImpl) {
|
|
121
164
|
var scheduler = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : sharedContentFetches;
|
|
165
|
+
var telemetry = arguments.length > 2 ? arguments[2] : undefined;
|
|
122
166
|
return (input, init) => {
|
|
123
167
|
var _init$signal;
|
|
124
168
|
var signal = (_init$signal = init === null || init === void 0 ? void 0 : init.signal) !== null && _init$signal !== void 0 ? _init$signal : input instanceof Request ? input.signal : undefined;
|
|
@@ -139,6 +183,8 @@ export function createBoundedFetch(fetchImpl) {
|
|
|
139
183
|
return function (_x) {
|
|
140
184
|
return _ref4.apply(this, arguments);
|
|
141
185
|
};
|
|
142
|
-
}(), signal);
|
|
186
|
+
}(), signal, telemetry, telemetry ? upstreamRoute(input) : 'other');
|
|
143
187
|
};
|
|
144
|
-
}
|
|
188
|
+
}
|
|
189
|
+
import { ContentDelivery } from '@prezly/theme-kit-core';
|
|
190
|
+
import { upstreamRoute } from "./telemetry.mjs";
|
|
@@ -11,6 +11,7 @@ var _sdk = require("@prezly/sdk");
|
|
|
11
11
|
var _themeKitCore = require("@prezly/theme-kit-core");
|
|
12
12
|
var _index = require("./cache/index.cjs");
|
|
13
13
|
var _requests = require("./requests.cjs");
|
|
14
|
+
var _telemetry = require("./telemetry.cjs");
|
|
14
15
|
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
15
16
|
function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
|
|
16
17
|
function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
|
|
@@ -22,7 +23,8 @@ var PrezlyAdapter;
|
|
|
22
23
|
function connect(config) {
|
|
23
24
|
var _ref = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {},
|
|
24
25
|
cacheConfig = _ref.cache,
|
|
25
|
-
fetch = _ref.fetch
|
|
26
|
+
fetch = _ref.fetch,
|
|
27
|
+
observer = _ref.telemetry;
|
|
26
28
|
function usePrezlyClient() {
|
|
27
29
|
var _cacheConfig$namespac, _cacheConfig$memory;
|
|
28
30
|
var _Resolvable$resolve = _themeKitCore.Resolvable.resolve(config),
|
|
@@ -32,15 +34,21 @@ var PrezlyAdapter;
|
|
|
32
34
|
newsroom = _Resolvable$resolve.newsroom,
|
|
33
35
|
theme = _Resolvable$resolve.theme,
|
|
34
36
|
formats = _Resolvable$resolve.formats;
|
|
37
|
+
var telemetry = observer ? {
|
|
38
|
+
observe: observer,
|
|
39
|
+
source: !baseUrl || baseUrl.replace(/\/$/, '') === 'https://api.prezly.com' ? 'prezly' : 'custom'
|
|
40
|
+
} : undefined;
|
|
35
41
|
var client = (0, _sdk.createPrezlyClient)({
|
|
36
|
-
fetch,
|
|
42
|
+
fetch: telemetry ? (0, _telemetry.createObservedFetch)(fetch !== null && fetch !== void 0 ? fetch : globalThis.fetch, telemetry, 'raw') : fetch,
|
|
37
43
|
accessToken,
|
|
38
44
|
baseUrl,
|
|
39
45
|
headers
|
|
40
46
|
});
|
|
41
|
-
var cache = cacheConfig ? (0, _index.configure)(_objectSpread({
|
|
47
|
+
var cache = cacheConfig ? (0, _index.configure)(_objectSpread(_objectSpread({
|
|
42
48
|
namespace: 'content:'
|
|
43
|
-
}, cacheConfig)
|
|
49
|
+
}, cacheConfig), {}, {
|
|
50
|
+
telemetry
|
|
51
|
+
})) : undefined;
|
|
44
52
|
// Custom fetches may depend on hidden request context. Preserve their
|
|
45
53
|
// existing cache behavior unless the caller declares that identity.
|
|
46
54
|
var scope = cache && (!fetch || (cacheConfig === null || cacheConfig === void 0 ? void 0 : cacheConfig.requestScope) !== undefined) ? (0, _utils.bytesToHex)((0, _sha.sha256)((0, _jsonStableStringify.default)({
|
|
@@ -58,10 +66,11 @@ var PrezlyAdapter;
|
|
|
58
66
|
accessToken,
|
|
59
67
|
baseUrl,
|
|
60
68
|
headers,
|
|
61
|
-
fetch: (0, _requests.createBoundedFetch)(fetch !== null && fetch !== void 0 ? fetch : globalThis.fetch)
|
|
69
|
+
fetch: (0, _requests.createBoundedFetch)((0, _telemetry.createObservedFetch)(fetch !== null && fetch !== void 0 ? fetch : globalThis.fetch, telemetry, 'content'), undefined, telemetry)
|
|
62
70
|
}) : client;
|
|
63
71
|
var contentDelivery = _themeKitCore.ContentDelivery.createClient(contentClient, newsroom, theme, {
|
|
64
72
|
formats,
|
|
73
|
+
telemetry,
|
|
65
74
|
cache: cache ? _objectSpread(_objectSpread({}, cache), {}, {
|
|
66
75
|
scope
|
|
67
76
|
}) : undefined
|
|
@@ -14,9 +14,10 @@ export declare namespace PrezlyAdapter {
|
|
|
14
14
|
interface Options {
|
|
15
15
|
cache?: CacheConfiguration;
|
|
16
16
|
fetch?: typeof fetch;
|
|
17
|
+
telemetry?: ContentDelivery.TelemetryObserver;
|
|
17
18
|
}
|
|
18
19
|
type CacheConfiguration = CacheConfig;
|
|
19
|
-
function connect(config: Resolvable<Configuration>, { cache: cacheConfig, fetch }?: Options): {
|
|
20
|
+
function connect(config: Resolvable<Configuration>, { cache: cacheConfig, fetch, telemetry: observer }?: Options): {
|
|
20
21
|
usePrezlyClient: () => {
|
|
21
22
|
client: import("@prezly/sdk").PrezlyClient;
|
|
22
23
|
contentDelivery: {
|
|
@@ -10,12 +10,14 @@ import { createPrezlyClient } from '@prezly/sdk';
|
|
|
10
10
|
import { ContentDelivery, Resolvable } from '@prezly/theme-kit-core';
|
|
11
11
|
import { configure as configureCache } from "./cache/index.mjs";
|
|
12
12
|
import { createBoundedFetch } from "./requests.mjs";
|
|
13
|
+
import { createObservedFetch } from "./telemetry.mjs";
|
|
13
14
|
export var PrezlyAdapter;
|
|
14
15
|
(function (_PrezlyAdapter) {
|
|
15
16
|
function connect(config) {
|
|
16
17
|
var _ref = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {},
|
|
17
18
|
cacheConfig = _ref.cache,
|
|
18
|
-
fetch = _ref.fetch
|
|
19
|
+
fetch = _ref.fetch,
|
|
20
|
+
observer = _ref.telemetry;
|
|
19
21
|
function usePrezlyClient() {
|
|
20
22
|
var _cacheConfig$namespac, _cacheConfig$memory;
|
|
21
23
|
var _Resolvable$resolve = Resolvable.resolve(config),
|
|
@@ -25,15 +27,21 @@ export var PrezlyAdapter;
|
|
|
25
27
|
newsroom = _Resolvable$resolve.newsroom,
|
|
26
28
|
theme = _Resolvable$resolve.theme,
|
|
27
29
|
formats = _Resolvable$resolve.formats;
|
|
30
|
+
var telemetry = observer ? {
|
|
31
|
+
observe: observer,
|
|
32
|
+
source: !baseUrl || baseUrl.replace(/\/$/, '') === 'https://api.prezly.com' ? 'prezly' : 'custom'
|
|
33
|
+
} : undefined;
|
|
28
34
|
var client = createPrezlyClient({
|
|
29
|
-
fetch,
|
|
35
|
+
fetch: telemetry ? createObservedFetch(fetch !== null && fetch !== void 0 ? fetch : globalThis.fetch, telemetry, 'raw') : fetch,
|
|
30
36
|
accessToken,
|
|
31
37
|
baseUrl,
|
|
32
38
|
headers
|
|
33
39
|
});
|
|
34
|
-
var cache = cacheConfig ? configureCache(_objectSpread({
|
|
40
|
+
var cache = cacheConfig ? configureCache(_objectSpread(_objectSpread({
|
|
35
41
|
namespace: 'content:'
|
|
36
|
-
}, cacheConfig)
|
|
42
|
+
}, cacheConfig), {}, {
|
|
43
|
+
telemetry
|
|
44
|
+
})) : undefined;
|
|
37
45
|
// Custom fetches may depend on hidden request context. Preserve their
|
|
38
46
|
// existing cache behavior unless the caller declares that identity.
|
|
39
47
|
var scope = cache && (!fetch || (cacheConfig === null || cacheConfig === void 0 ? void 0 : cacheConfig.requestScope) !== undefined) ? bytesToHex(sha256(stableStringify({
|
|
@@ -51,10 +59,11 @@ export var PrezlyAdapter;
|
|
|
51
59
|
accessToken,
|
|
52
60
|
baseUrl,
|
|
53
61
|
headers,
|
|
54
|
-
fetch: createBoundedFetch(fetch !== null && fetch !== void 0 ? fetch : globalThis.fetch)
|
|
62
|
+
fetch: createBoundedFetch(createObservedFetch(fetch !== null && fetch !== void 0 ? fetch : globalThis.fetch, telemetry, 'content'), undefined, telemetry)
|
|
55
63
|
}) : client;
|
|
56
64
|
var contentDelivery = ContentDelivery.createClient(contentClient, newsroom, theme, {
|
|
57
65
|
formats,
|
|
66
|
+
telemetry,
|
|
58
67
|
cache: cache ? _objectSpread(_objectSpread({}, cache), {}, {
|
|
59
68
|
scope
|
|
60
69
|
}) : undefined
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.createObservedFetch = createObservedFetch;
|
|
7
|
+
exports.upstreamRoute = upstreamRoute;
|
|
8
|
+
var _themeKitCore = require("@prezly/theme-kit-core");
|
|
9
|
+
function asyncGeneratorStep(n, t, e, r, o, a, c) { try { var i = n[a](c), u = i.value; } catch (n) { return void e(n); } i.done ? t(u) : Promise.resolve(u).then(r, o); }
|
|
10
|
+
function _asyncToGenerator(n) { return function () { var t = this, e = arguments; return new Promise(function (r, o) { var a = n.apply(t, e); function _next(n) { asyncGeneratorStep(a, r, o, _next, _throw, "next", n); } function _throw(n) { asyncGeneratorStep(a, r, o, _next, _throw, "throw", n); } _next(void 0); }); }; }
|
|
11
|
+
function upstreamRoute(input) {
|
|
12
|
+
try {
|
|
13
|
+
var path = new URL(typeof input === 'string' ? input : input instanceof URL ? input.href : input.url).pathname;
|
|
14
|
+
if (/^\/v2\/stories\/by-slug\/[^/]+$/.test(path)) return 'story_by_slug';
|
|
15
|
+
if (/^\/v2\/stories(?:\/search)?$/.test(path)) return 'stories';
|
|
16
|
+
if (/^\/v2\/stories\/[^/]+$/.test(path)) return 'story';
|
|
17
|
+
if (/^\/v2\/newsrooms\/[^/]+\/themes\/[^/]+$/.test(path)) return 'theme';
|
|
18
|
+
if (/^\/v2\/newsrooms\/[^/]+\/languages(?:\/[^/]+)?$/.test(path)) return 'languages';
|
|
19
|
+
if (/^\/v2\/newsrooms\/[^/]+\/categories(?:\/[^/]+)?$/.test(path)) return 'categories';
|
|
20
|
+
if (/^\/v2\/newsrooms\/[^/]+\/contacts(?:\/search)?$/.test(path)) return 'contacts';
|
|
21
|
+
if (/^\/v2\/newsrooms\/[^/]+\/galleries(?:\/search)?$/.test(path)) return 'galleries';
|
|
22
|
+
if (/^\/v2\/newsrooms\/[^/]+\/galleries\/[^/]+$/.test(path)) return 'gallery';
|
|
23
|
+
if (/^\/v2\/newsrooms\/[^/]+$/.test(path)) return 'newsroom';
|
|
24
|
+
} catch (_unused) {
|
|
25
|
+
// Invalid/custom inputs keep their original fetch behavior.
|
|
26
|
+
}
|
|
27
|
+
return 'other';
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Observe response headers only; preserve the Response and streaming body. */
|
|
31
|
+
function createObservedFetch(fetchImpl, telemetry, client) {
|
|
32
|
+
if (!telemetry) return fetchImpl;
|
|
33
|
+
return /*#__PURE__*/function () {
|
|
34
|
+
var _ref = _asyncToGenerator(function* (input, init) {
|
|
35
|
+
var route = upstreamRoute(input);
|
|
36
|
+
var start = _themeKitCore.ContentDelivery.telemetryNow();
|
|
37
|
+
var status = 'error';
|
|
38
|
+
try {
|
|
39
|
+
var response = yield fetchImpl(input, init);
|
|
40
|
+
status = response.status >= 200 && response.status < 600 ? "".concat(Math.floor(response.status / 100), "xx") : 'other';
|
|
41
|
+
return response;
|
|
42
|
+
} finally {
|
|
43
|
+
_themeKitCore.ContentDelivery.emit(telemetry, {
|
|
44
|
+
type: 'upstream',
|
|
45
|
+
route,
|
|
46
|
+
client,
|
|
47
|
+
status,
|
|
48
|
+
seconds: _themeKitCore.ContentDelivery.elapsedSeconds(start)
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
return function (_x, _x2) {
|
|
53
|
+
return _ref.apply(this, arguments);
|
|
54
|
+
};
|
|
55
|
+
}();
|
|
56
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { ContentDelivery } from '@prezly/theme-kit-core';
|
|
2
|
+
export declare function upstreamRoute(input: Parameters<typeof fetch>[0]): ContentDelivery.UpstreamRoute;
|
|
3
|
+
/** Observe response headers only; preserve the Response and streaming body. */
|
|
4
|
+
export declare function createObservedFetch(fetchImpl: typeof fetch, telemetry: ContentDelivery.Telemetry | undefined, client: 'raw' | 'content'): typeof fetch;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
function asyncGeneratorStep(n, t, e, r, o, a, c) { try { var i = n[a](c), u = i.value; } catch (n) { return void e(n); } i.done ? t(u) : Promise.resolve(u).then(r, o); }
|
|
2
|
+
function _asyncToGenerator(n) { return function () { var t = this, e = arguments; return new Promise(function (r, o) { var a = n.apply(t, e); function _next(n) { asyncGeneratorStep(a, r, o, _next, _throw, "next", n); } function _throw(n) { asyncGeneratorStep(a, r, o, _next, _throw, "throw", n); } _next(void 0); }); }; }
|
|
3
|
+
import { ContentDelivery } from '@prezly/theme-kit-core';
|
|
4
|
+
export function upstreamRoute(input) {
|
|
5
|
+
try {
|
|
6
|
+
var path = new URL(typeof input === 'string' ? input : input instanceof URL ? input.href : input.url).pathname;
|
|
7
|
+
if (/^\/v2\/stories\/by-slug\/[^/]+$/.test(path)) return 'story_by_slug';
|
|
8
|
+
if (/^\/v2\/stories(?:\/search)?$/.test(path)) return 'stories';
|
|
9
|
+
if (/^\/v2\/stories\/[^/]+$/.test(path)) return 'story';
|
|
10
|
+
if (/^\/v2\/newsrooms\/[^/]+\/themes\/[^/]+$/.test(path)) return 'theme';
|
|
11
|
+
if (/^\/v2\/newsrooms\/[^/]+\/languages(?:\/[^/]+)?$/.test(path)) return 'languages';
|
|
12
|
+
if (/^\/v2\/newsrooms\/[^/]+\/categories(?:\/[^/]+)?$/.test(path)) return 'categories';
|
|
13
|
+
if (/^\/v2\/newsrooms\/[^/]+\/contacts(?:\/search)?$/.test(path)) return 'contacts';
|
|
14
|
+
if (/^\/v2\/newsrooms\/[^/]+\/galleries(?:\/search)?$/.test(path)) return 'galleries';
|
|
15
|
+
if (/^\/v2\/newsrooms\/[^/]+\/galleries\/[^/]+$/.test(path)) return 'gallery';
|
|
16
|
+
if (/^\/v2\/newsrooms\/[^/]+$/.test(path)) return 'newsroom';
|
|
17
|
+
} catch (_unused) {
|
|
18
|
+
// Invalid/custom inputs keep their original fetch behavior.
|
|
19
|
+
}
|
|
20
|
+
return 'other';
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Observe response headers only; preserve the Response and streaming body. */
|
|
24
|
+
export function createObservedFetch(fetchImpl, telemetry, client) {
|
|
25
|
+
if (!telemetry) return fetchImpl;
|
|
26
|
+
return /*#__PURE__*/function () {
|
|
27
|
+
var _ref = _asyncToGenerator(function* (input, init) {
|
|
28
|
+
var route = upstreamRoute(input);
|
|
29
|
+
var start = ContentDelivery.telemetryNow();
|
|
30
|
+
var status = 'error';
|
|
31
|
+
try {
|
|
32
|
+
var response = yield fetchImpl(input, init);
|
|
33
|
+
status = response.status >= 200 && response.status < 600 ? "".concat(Math.floor(response.status / 100), "xx") : 'other';
|
|
34
|
+
return response;
|
|
35
|
+
} finally {
|
|
36
|
+
ContentDelivery.emit(telemetry, {
|
|
37
|
+
type: 'upstream',
|
|
38
|
+
route,
|
|
39
|
+
client,
|
|
40
|
+
status,
|
|
41
|
+
seconds: ContentDelivery.elapsedSeconds(start)
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
return function (_x, _x2) {
|
|
46
|
+
return _ref.apply(this, arguments);
|
|
47
|
+
};
|
|
48
|
+
}();
|
|
49
|
+
}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
var _telemetry = require("./telemetry.cjs");
|
|
4
|
+
var _requests = require("./requests.cjs");
|
|
5
|
+
var _server = require("./server.cjs");
|
|
6
|
+
function asyncGeneratorStep(n, t, e, r, o, a, c) { try { var i = n[a](c), u = i.value; } catch (n) { return void e(n); } i.done ? t(u) : Promise.resolve(u).then(r, o); }
|
|
7
|
+
function _asyncToGenerator(n) { return function () { var t = this, e = arguments; return new Promise(function (r, o) { var a = n.apply(t, e); function _next(n) { asyncGeneratorStep(a, r, o, _next, _throw, "next", n); } function _throw(n) { asyncGeneratorStep(a, r, o, _next, _throw, "throw", n); } _next(void 0); }); }; }
|
|
8
|
+
var nativeFetch = globalThis.fetch;
|
|
9
|
+
var tick = () => new Promise(resolve => setImmediate(resolve));
|
|
10
|
+
afterEach(() => {
|
|
11
|
+
globalThis.fetch = nativeFetch;
|
|
12
|
+
jest.useRealTimers();
|
|
13
|
+
});
|
|
14
|
+
it.each([['/v2/newsrooms/private-id', 'newsroom'], ['/v2/newsrooms/private-id/languages/en_GB', 'languages'], ['/v2/newsrooms/private-id/categories', 'categories'], ['/v2/stories/by-slug/private-slug?token=secret', 'story_by_slug'], ['/v2/stories/search', 'stories'], ['/v2/stories/private-uuid', 'story'], ['/custom/private-path', 'other']])('classifies %s without retaining URL values', (path, expected) => {
|
|
15
|
+
expect((0, _telemetry.upstreamRoute)("https://private-origin.test".concat(path))).toBe(expected);
|
|
16
|
+
});
|
|
17
|
+
it('observes raw response headers without consuming the body or adding a signal', /*#__PURE__*/_asyncToGenerator(function* () {
|
|
18
|
+
var events = [];
|
|
19
|
+
var response = new Response('private-body');
|
|
20
|
+
var read = jest.spyOn(response, 'arrayBuffer');
|
|
21
|
+
var fetchMock = jest.fn(/*#__PURE__*/_asyncToGenerator(function* () {
|
|
22
|
+
return response;
|
|
23
|
+
}));
|
|
24
|
+
var options = {
|
|
25
|
+
headers: {
|
|
26
|
+
Authorization: 'private-token'
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
var observed = (0, _telemetry.createObservedFetch)(fetchMock, {
|
|
30
|
+
observe: e => {
|
|
31
|
+
events.push(e);
|
|
32
|
+
},
|
|
33
|
+
source: 'custom'
|
|
34
|
+
}, 'raw');
|
|
35
|
+
expect(yield observed('https://private-origin.test/v2/stories/private-uuid', options)).toBe(response);
|
|
36
|
+
expect(read).not.toHaveBeenCalled();
|
|
37
|
+
expect(fetchMock).toHaveBeenCalledWith('https://private-origin.test/v2/stories/private-uuid', options);
|
|
38
|
+
expect(events).toEqual([expect.objectContaining({
|
|
39
|
+
type: 'upstream',
|
|
40
|
+
client: 'raw',
|
|
41
|
+
route: 'story',
|
|
42
|
+
source: 'custom',
|
|
43
|
+
status: '2xx'
|
|
44
|
+
})]);
|
|
45
|
+
expect(JSON.stringify(events)).not.toMatch(/private-/);
|
|
46
|
+
}));
|
|
47
|
+
it('keeps telemetry outside the source/cache identity when adapters share work', /*#__PURE__*/_asyncToGenerator(function* () {
|
|
48
|
+
var events = [];
|
|
49
|
+
var config = {
|
|
50
|
+
newsroom: 'private-room',
|
|
51
|
+
accessToken: 'private-token'
|
|
52
|
+
};
|
|
53
|
+
var cache = {
|
|
54
|
+
memory: true,
|
|
55
|
+
latestVersion: 1,
|
|
56
|
+
namespace: 'metrics-adapter-sharing'
|
|
57
|
+
};
|
|
58
|
+
globalThis.fetch = jest.fn(/*#__PURE__*/_asyncToGenerator(function* () {
|
|
59
|
+
return Response.json({
|
|
60
|
+
newsroom: {
|
|
61
|
+
name: 'room'
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
}));
|
|
65
|
+
var observed = _server.PrezlyAdapter.connect(config, {
|
|
66
|
+
cache,
|
|
67
|
+
telemetry: e => {
|
|
68
|
+
events.push(e);
|
|
69
|
+
}
|
|
70
|
+
}).usePrezlyClient();
|
|
71
|
+
var plain = _server.PrezlyAdapter.connect(config, {
|
|
72
|
+
cache
|
|
73
|
+
}).usePrezlyClient();
|
|
74
|
+
yield Promise.all([observed.contentDelivery.newsroom(), plain.contentDelivery.newsroom()]);
|
|
75
|
+
expect(globalThis.fetch).toHaveBeenCalledTimes(1);
|
|
76
|
+
yield tick();
|
|
77
|
+
yield observed.contentDelivery.newsroom();
|
|
78
|
+
expect(globalThis.fetch).toHaveBeenCalledTimes(1);
|
|
79
|
+
expect(events.filter(e => e.type === 'upstream')).toHaveLength(1);
|
|
80
|
+
expect(events.some(e => e.type === 'cache_hit')).toBe(true);
|
|
81
|
+
}));
|
|
82
|
+
it('reports queued, full, accepted and completed wait without changing admission limits', /*#__PURE__*/_asyncToGenerator(function* () {
|
|
83
|
+
var events = [];
|
|
84
|
+
var telemetry = {
|
|
85
|
+
observe: e => {
|
|
86
|
+
events.push(e);
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
var scheduler = new _requests.FetchScheduler({
|
|
90
|
+
concurrency: 1,
|
|
91
|
+
queueLimit: 1
|
|
92
|
+
});
|
|
93
|
+
var release;
|
|
94
|
+
var active = scheduler.run(() => new Promise(r => {
|
|
95
|
+
release = r;
|
|
96
|
+
}), undefined, telemetry, 'story');
|
|
97
|
+
var queued = scheduler.run(/*#__PURE__*/_asyncToGenerator(function* () {
|
|
98
|
+
return 42;
|
|
99
|
+
}), undefined, telemetry, 'story');
|
|
100
|
+
yield expect(scheduler.run(/*#__PURE__*/_asyncToGenerator(function* () {
|
|
101
|
+
return 0;
|
|
102
|
+
}), undefined, telemetry, 'story')).rejects.toThrow('queue is full');
|
|
103
|
+
yield tick();
|
|
104
|
+
release();
|
|
105
|
+
yield active;
|
|
106
|
+
expect(yield queued).toBe(42);
|
|
107
|
+
yield tick();
|
|
108
|
+
expect(events.filter(e => e.type === 'fetch_queue').map(e => e.outcome)).toEqual(['accepted', 'queued', 'full', 'accepted']);
|
|
109
|
+
}));
|
|
110
|
+
it('reports queue timeout and body deadline independently of successful HTTP headers', /*#__PURE__*/_asyncToGenerator(function* () {
|
|
111
|
+
jest.useFakeTimers();
|
|
112
|
+
var events = [];
|
|
113
|
+
var telemetry = {
|
|
114
|
+
observe: e => {
|
|
115
|
+
events.push(e);
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
var scheduler = new _requests.FetchScheduler({
|
|
119
|
+
concurrency: 1,
|
|
120
|
+
queueLimit: 1,
|
|
121
|
+
queueTimeout: 10,
|
|
122
|
+
requestTimeout: 30
|
|
123
|
+
});
|
|
124
|
+
var finish;
|
|
125
|
+
var fetchMock = jest.fn(/*#__PURE__*/_asyncToGenerator(function* () {
|
|
126
|
+
return new Response(new ReadableStream({
|
|
127
|
+
start(controller) {
|
|
128
|
+
finish = () => controller.close();
|
|
129
|
+
}
|
|
130
|
+
}));
|
|
131
|
+
}));
|
|
132
|
+
var fetch = (0, _requests.createBoundedFetch)((0, _telemetry.createObservedFetch)(fetchMock, telemetry, 'content'), scheduler, telemetry);
|
|
133
|
+
var first = fetch('https://example.test/v2/stories/search');
|
|
134
|
+
var failed = expect(first).rejects.toThrow('timed out');
|
|
135
|
+
var next = fetch('https://example.test/v2/stories/search');
|
|
136
|
+
var queued = expect(next).rejects.toThrow('queue wait timed out');
|
|
137
|
+
yield jest.advanceTimersByTimeAsync(31);
|
|
138
|
+
yield failed;
|
|
139
|
+
yield queued;
|
|
140
|
+
expect(events.filter(e => e.type === 'upstream')).toEqual([expect.objectContaining({
|
|
141
|
+
status: '2xx'
|
|
142
|
+
})]);
|
|
143
|
+
expect(events.filter(e => e.type === 'fetch_timeout')).toHaveLength(1);
|
|
144
|
+
expect(events).toContainEqual(expect.objectContaining({
|
|
145
|
+
type: 'fetch_queue',
|
|
146
|
+
outcome: 'wait_timeout'
|
|
147
|
+
}));
|
|
148
|
+
finish();
|
|
149
|
+
yield jest.advanceTimersByTimeAsync(0);
|
|
150
|
+
expect(jest.getTimerCount()).toBe(0);
|
|
151
|
+
}));
|
|
152
|
+
it('a throwing or rejecting observer cannot change successful fetch outcomes', /*#__PURE__*/_asyncToGenerator(function* () {
|
|
153
|
+
var response = new Response('ok');
|
|
154
|
+
for (var observe of [() => {
|
|
155
|
+
throw Error('metrics');
|
|
156
|
+
}, /*#__PURE__*/_asyncToGenerator(function* () {
|
|
157
|
+
throw Error('metrics');
|
|
158
|
+
})]) {
|
|
159
|
+
var fetch = (0, _telemetry.createObservedFetch)(/*#__PURE__*/_asyncToGenerator(function* () {
|
|
160
|
+
return response;
|
|
161
|
+
}), {
|
|
162
|
+
observe
|
|
163
|
+
}, 'raw');
|
|
164
|
+
expect(yield fetch('https://example.test')).toBe(response);
|
|
165
|
+
}
|
|
166
|
+
yield tick();
|
|
167
|
+
}));
|