@prezly/theme-kit-nextjs 10.9.0 → 10.9.2

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.
Files changed (52) hide show
  1. package/README.md +73 -0
  2. package/build/adapters/app-helper/server.cjs +2 -4
  3. package/build/adapters/app-helper/server.mjs +2 -4
  4. package/build/adapters/intl/server.cjs +2 -3
  5. package/build/adapters/intl/server.mjs +2 -3
  6. package/build/adapters/metadata/lib/generateCategoryPageMetadata.cjs +5 -8
  7. package/build/adapters/metadata/lib/generateCategoryPageMetadata.mjs +5 -8
  8. package/build/adapters/metadata/lib/generateMediaGalleryPageMetadata.cjs +13 -8
  9. package/build/adapters/metadata/lib/generateMediaGalleryPageMetadata.mjs +13 -8
  10. package/build/adapters/metadata/lib/generateMediaPageMetadata.cjs +3 -5
  11. package/build/adapters/metadata/lib/generateMediaPageMetadata.mjs +3 -5
  12. package/build/adapters/metadata/lib/generateRootMetadata.cjs +14 -5
  13. package/build/adapters/metadata/lib/generateRootMetadata.mjs +14 -5
  14. package/build/adapters/metadata/lib/generateSearchPageMetadata.cjs +3 -5
  15. package/build/adapters/metadata/lib/generateSearchPageMetadata.mjs +3 -5
  16. package/build/adapters/metadata/lib/generateStoryPageMetadata.cjs +17 -10
  17. package/build/adapters/metadata/lib/generateStoryPageMetadata.mjs +17 -10
  18. package/build/adapters/metadata/lib/utils/generatePageMetadata.cjs +16 -7
  19. package/build/adapters/metadata/lib/utils/generatePageMetadata.mjs +16 -7
  20. package/build/adapters/metadata/server.cjs +1 -3
  21. package/build/adapters/metadata/server.mjs +1 -3
  22. package/build/adapters/prezly/cache/configure.d.ts +2 -0
  23. package/build/adapters/prezly/cache/redis.cjs +54 -22
  24. package/build/adapters/prezly/cache/redis.mjs +54 -22
  25. package/build/adapters/prezly/cache/redis.test.cjs +103 -0
  26. package/build/adapters/prezly/cache/redis.test.mjs +101 -0
  27. package/build/adapters/prezly/requests.cjs +152 -0
  28. package/build/adapters/prezly/requests.d.ts +17 -0
  29. package/build/adapters/prezly/requests.mjs +144 -0
  30. package/build/adapters/prezly/requests.test.cjs +201 -0
  31. package/build/adapters/prezly/requests.test.mjs +199 -0
  32. package/build/adapters/prezly/server.cjs +42 -18
  33. package/build/adapters/prezly/server.mjs +41 -18
  34. package/build/adapters/prezly/server.test.cjs +304 -0
  35. package/build/adapters/prezly/server.test.mjs +302 -0
  36. package/build/adapters/routing/lib/RoutingAdapter.client.cjs +5 -9
  37. package/build/adapters/routing/lib/RoutingAdapter.client.mjs +5 -9
  38. package/build/adapters/routing/lib/RoutingAdapter.server.cjs +5 -6
  39. package/build/adapters/routing/lib/RoutingAdapter.server.mjs +5 -6
  40. package/build/adapters/theme-settings/server.cjs +10 -1
  41. package/build/adapters/theme-settings/server.mjs +10 -1
  42. package/build/middleware/intl-middleware.cjs +22 -11
  43. package/build/middleware/intl-middleware.mjs +22 -11
  44. package/build/next-config/index.cjs +5 -7
  45. package/build/next-config/index.mjs +5 -7
  46. package/build/playwright/config.cjs +5 -5
  47. package/build/playwright/config.mjs +5 -5
  48. package/build/robots/server.cjs +1 -3
  49. package/build/robots/server.mjs +1 -3
  50. package/build/sentry/common.cjs +3 -7
  51. package/build/sentry/common.mjs +3 -7
  52. package/package.json +112 -109
@@ -0,0 +1,144 @@
1
+ var _registry$key;
2
+ 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); }
3
+ 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); }); }; }
4
+ function _slicedToArray(r, e) { return _arrayWithHoles(r) || _iterableToArrayLimit(r, e) || _unsupportedIterableToArray(r, e) || _nonIterableRest(); }
5
+ function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
6
+ function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } }
7
+ function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
8
+ function _iterableToArrayLimit(r, l) { var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (null != t) { var e, n, i, u, a = [], f = !0, o = !1; try { if (i = (t = t.call(r)).next, 0 === l) { if (Object(t) !== t) return; f = !1; } else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0); } catch (r) { o = !0, n = r; } finally { try { if (!f && null != t.return && (u = t.return(), Object(u) !== u)) return; } finally { if (o) throw n; } } return a; } }
9
+ function _arrayWithHoles(r) { if (Array.isArray(r)) return r; }
10
+ 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; }
11
+ 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; }
12
+ function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }
13
+ function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; }
14
+ function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
15
+ function abortReason(signal) {
16
+ if (signal.reason instanceof Error) return signal.reason;
17
+ var error = new Error('Content request aborted.');
18
+ error.name = 'AbortError';
19
+ return error;
20
+ }
21
+
22
+ /** Limit actual HTTP requests, not aggregate ContentDelivery methods. */
23
+ export class FetchScheduler {
24
+ constructor() {
25
+ var limits = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
26
+ _defineProperty(this, "active", 0);
27
+ _defineProperty(this, "queue", []);
28
+ _defineProperty(this, "limits", void 0);
29
+ this.limits = _objectSpread({
30
+ concurrency: 32,
31
+ queueLimit: 256,
32
+ queueTimeout: 1000,
33
+ requestTimeout: 30000
34
+ }, limits);
35
+ for (var _ref3 of Object.entries(this.limits)) {
36
+ var _ref2 = _slicedToArray(_ref3, 2);
37
+ var name = _ref2[0];
38
+ var value = _ref2[1];
39
+ if (!Number.isInteger(value) || value < (name === 'queueLimit' ? 0 : 1)) {
40
+ throw new RangeError('Invalid content request limits.');
41
+ }
42
+ }
43
+ }
44
+ run(invoke, signal) {
45
+ if (signal !== null && signal !== void 0 && signal.aborted) return Promise.reject(abortReason(signal));
46
+ return new Promise((_resolve, reject) => {
47
+ var task = {
48
+ invoke,
49
+ resolve: value => _resolve(value),
50
+ reject,
51
+ signal,
52
+ clearQueueWait: () => {}
53
+ };
54
+ if (this.active < this.limits.concurrency) {
55
+ this.start(task);
56
+ return;
57
+ }
58
+ if (this.queue.length >= this.limits.queueLimit) {
59
+ reject(new Error('Content request queue is full.'));
60
+ return;
61
+ }
62
+ var cancel = error => {
63
+ var index = this.queue.indexOf(task);
64
+ if (index < 0) return;
65
+ this.queue.splice(index, 1);
66
+ task.clearQueueWait();
67
+ reject(error);
68
+ };
69
+ var onAbort = () => cancel(abortReason(signal));
70
+ var timer = setTimeout(() => cancel(new Error('Content request queue wait timed out.')), this.limits.queueTimeout);
71
+ task.clearQueueWait = () => {
72
+ clearTimeout(timer);
73
+ signal === null || signal === void 0 || signal.removeEventListener('abort', onAbort);
74
+ };
75
+ signal === null || signal === void 0 || signal.addEventListener('abort', onAbort, {
76
+ once: true
77
+ });
78
+ this.queue.push(task);
79
+ });
80
+ }
81
+ start(task) {
82
+ var _task$signal2;
83
+ task.clearQueueWait();
84
+ this.active += 1;
85
+ var controller = new AbortController();
86
+ var rejectAbort = () => task.reject(abortReason(controller.signal));
87
+ var forwardAbort = () => {
88
+ var _task$signal;
89
+ return controller.abort((_task$signal = task.signal) === null || _task$signal === void 0 ? void 0 : _task$signal.reason);
90
+ };
91
+ controller.signal.addEventListener('abort', rejectAbort, {
92
+ once: true
93
+ });
94
+ (_task$signal2 = task.signal) === null || _task$signal2 === void 0 || _task$signal2.addEventListener('abort', forwardAbort, {
95
+ once: true
96
+ });
97
+ var timer = setTimeout(() => controller.abort(new Error('Content request timed out.')), this.limits.requestTimeout);
98
+ void Promise.resolve().then(() => {
99
+ if (controller.signal.aborted) throw abortReason(controller.signal);
100
+ return task.invoke(controller.signal);
101
+ }).then(value => {
102
+ if (!controller.signal.aborted) task.resolve(value);
103
+ }, task.reject).finally(() => {
104
+ var _task$signal3;
105
+ clearTimeout(timer);
106
+ controller.signal.removeEventListener('abort', rejectAbort);
107
+ (_task$signal3 = task.signal) === null || _task$signal3 === void 0 || _task$signal3.removeEventListener('abort', forwardAbort);
108
+ // A custom fetch that ignores abort keeps its slot until it really
109
+ // settles. Timeouts must not create unlimited overlapping requests.
110
+ this.active -= 1;
111
+ while (this.active < this.limits.concurrency && this.queue.length > 0) {
112
+ this.start(this.queue.shift());
113
+ }
114
+ });
115
+ }
116
+ }
117
+ var key = Symbol.for('@prezly/theme-kit-nextjs/content-fetch-scheduler/v1');
118
+ var registry = globalThis;
119
+ var sharedContentFetches = (_registry$key = registry[key]) !== null && _registry$key !== void 0 ? _registry$key : registry[key] = new FetchScheduler();
120
+ export function createBoundedFetch(fetchImpl) {
121
+ var scheduler = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : sharedContentFetches;
122
+ return (input, init) => {
123
+ var _init$signal;
124
+ 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;
125
+ return scheduler.run(/*#__PURE__*/function () {
126
+ var _ref4 = _asyncToGenerator(function* (requestSignal) {
127
+ var response = yield fetchImpl(input, _objectSpread(_objectSpread({}, init), {}, {
128
+ signal: requestSignal
129
+ }));
130
+ // Keep the deadline and slot through body consumption, not just headers.
131
+ // Only the internal content SDK uses this wrapper; the raw SDK is unchanged.
132
+ var body = response.body === null ? null : yield response.arrayBuffer();
133
+ return new Response(body, {
134
+ status: response.status,
135
+ statusText: response.statusText,
136
+ headers: response.headers
137
+ });
138
+ });
139
+ return function (_x) {
140
+ return _ref4.apply(this, arguments);
141
+ };
142
+ }(), signal);
143
+ };
144
+ }
@@ -0,0 +1,201 @@
1
+ "use strict";
2
+
3
+ var _nodeEvents = require("node:events");
4
+ var _nodeHttp = require("node:http");
5
+ var _requests = require("./requests.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
+ function deferred() {
9
+ var resolve;
10
+ var promise = new Promise(yes => {
11
+ resolve = yes;
12
+ });
13
+ return {
14
+ promise,
15
+ resolve
16
+ };
17
+ }
18
+ var tick = /*#__PURE__*/function () {
19
+ var _ref = _asyncToGenerator(function* () {
20
+ for (var i = 0; i < 10; i++) yield Promise.resolve();
21
+ });
22
+ return function tick() {
23
+ return _ref.apply(this, arguments);
24
+ };
25
+ }();
26
+ describe('Content HTTP admission', () => {
27
+ beforeEach(() => jest.useFakeTimers());
28
+ afterEach(() => jest.useRealTimers());
29
+ it('limits active work, serves queued work in order, and rejects overflow', /*#__PURE__*/_asyncToGenerator(function* () {
30
+ var scheduler = new _requests.FetchScheduler({
31
+ concurrency: 1,
32
+ queueLimit: 2
33
+ });
34
+ var first = deferred();
35
+ var started = [];
36
+ var a = scheduler.run(/*#__PURE__*/_asyncToGenerator(function* () {
37
+ started.push(1);
38
+ return first.promise;
39
+ }));
40
+ var b = scheduler.run(/*#__PURE__*/_asyncToGenerator(function* () {
41
+ started.push(2);
42
+ return 2;
43
+ }));
44
+ var c = scheduler.run(/*#__PURE__*/_asyncToGenerator(function* () {
45
+ started.push(3);
46
+ return 3;
47
+ }));
48
+ var overflow = jest.fn(/*#__PURE__*/_asyncToGenerator(function* () {
49
+ return 4;
50
+ }));
51
+ yield expect(scheduler.run(overflow)).rejects.toThrow('queue is full');
52
+ expect(started).toEqual([1]);
53
+ expect(overflow).not.toHaveBeenCalled();
54
+ first.resolve(1);
55
+ expect(yield Promise.all([a, b, c])).toEqual([1, 2, 3]);
56
+ expect(started).toEqual([1, 2, 3]);
57
+ yield tick();
58
+ expect(jest.getTimerCount()).toBe(0);
59
+ }));
60
+ it('removes expired and canceled queued work before it reaches the origin', /*#__PURE__*/_asyncToGenerator(function* () {
61
+ var scheduler = new _requests.FetchScheduler({
62
+ concurrency: 1,
63
+ queueLimit: 1
64
+ });
65
+ var first = deferred();
66
+ var a = scheduler.run(() => first.promise);
67
+ var queued = jest.fn(/*#__PURE__*/_asyncToGenerator(function* () {
68
+ return 2;
69
+ }));
70
+ var expired = expect(scheduler.run(queued)).rejects.toThrow('queue wait timed out');
71
+ yield jest.advanceTimersByTimeAsync(1000);
72
+ yield expired;
73
+ var controller = new AbortController();
74
+ var canceled = expect(scheduler.run(queued, controller.signal)).rejects.toThrow('cancel');
75
+ controller.abort(new Error('cancel'));
76
+ yield canceled;
77
+ var recovered = scheduler.run(/*#__PURE__*/_asyncToGenerator(function* () {
78
+ return 3;
79
+ }));
80
+ first.resolve(1);
81
+ expect(yield Promise.all([a, recovered])).toEqual([1, 3]);
82
+ expect(queued).not.toHaveBeenCalled();
83
+ }));
84
+ it('aborts a timed out transport and admits a subsequent retry', /*#__PURE__*/_asyncToGenerator(function* () {
85
+ var scheduler = new _requests.FetchScheduler({
86
+ concurrency: 1,
87
+ requestTimeout: 100
88
+ });
89
+ var signal;
90
+ var timedOut = expect(scheduler.run(s => {
91
+ signal = s;
92
+ return new Promise((_, reject) => s.addEventListener('abort', () => reject(s.reason)));
93
+ })).rejects.toThrow('timed out');
94
+ yield jest.advanceTimersByTimeAsync(100);
95
+ yield timedOut;
96
+ expect(signal.aborted).toBe(true);
97
+ expect(yield scheduler.run(/*#__PURE__*/_asyncToGenerator(function* () {
98
+ return 'recovered';
99
+ }))).toBe('recovered');
100
+ }));
101
+ it('retains the physical slot if custom work ignores abort', /*#__PURE__*/_asyncToGenerator(function* () {
102
+ var scheduler = new _requests.FetchScheduler({
103
+ concurrency: 1,
104
+ queueLimit: 0,
105
+ requestTimeout: 100
106
+ });
107
+ var pending = deferred();
108
+ var timedOut = expect(scheduler.run(() => pending.promise)).rejects.toThrow('timed out');
109
+ yield jest.advanceTimersByTimeAsync(100);
110
+ yield timedOut;
111
+ yield expect(scheduler.run(/*#__PURE__*/_asyncToGenerator(function* () {
112
+ return 2;
113
+ }))).rejects.toThrow('queue is full');
114
+ pending.resolve(1);
115
+ yield tick();
116
+ expect(yield scheduler.run(/*#__PURE__*/_asyncToGenerator(function* () {
117
+ return 3;
118
+ }))).toBe(3);
119
+ }));
120
+ it('forwards active cancellation and never starts an already canceled call', /*#__PURE__*/_asyncToGenerator(function* () {
121
+ var scheduler = new _requests.FetchScheduler({
122
+ concurrency: 1
123
+ });
124
+ var controller = new AbortController();
125
+ var signal;
126
+ var canceled = expect(scheduler.run(s => {
127
+ signal = s;
128
+ return new Promise((_, reject) => s.addEventListener('abort', () => reject(s.reason)));
129
+ }, controller.signal)).rejects.toThrow('cancel');
130
+ yield tick();
131
+ controller.abort(new Error('cancel'));
132
+ yield canceled;
133
+ expect(signal.aborted).toBe(true);
134
+ var invoke = jest.fn(/*#__PURE__*/_asyncToGenerator(function* () {
135
+ return 1;
136
+ }));
137
+ yield expect(scheduler.run(invoke, controller.signal)).rejects.toThrow('cancel');
138
+ expect(invoke).not.toHaveBeenCalled();
139
+ yield tick();
140
+ expect(yield scheduler.run(/*#__PURE__*/_asyncToGenerator(function* () {
141
+ return 'ok';
142
+ }))).toBe('ok');
143
+ }));
144
+ it('keeps the deadline through response body consumption', /*#__PURE__*/_asyncToGenerator(function* () {
145
+ var scheduler = new _requests.FetchScheduler({
146
+ requestTimeout: 100
147
+ });
148
+ var transport = /*#__PURE__*/function () {
149
+ var _ref17 = _asyncToGenerator(function* (_input, init) {
150
+ return new Response(new ReadableStream({
151
+ start(controller) {
152
+ init.signal.addEventListener('abort', () => controller.error(init.signal.reason));
153
+ }
154
+ }));
155
+ });
156
+ return function transport(_x, _x2) {
157
+ return _ref17.apply(this, arguments);
158
+ };
159
+ }();
160
+ var bounded = (0, _requests.createBoundedFetch)(transport, scheduler);
161
+ var timedOut = expect(bounded('https://example.test')).rejects.toThrow('timed out');
162
+ yield jest.advanceTimersByTimeAsync(100);
163
+ yield timedOut;
164
+ var empty = (0, _requests.createBoundedFetch)(/*#__PURE__*/_asyncToGenerator(function* () {
165
+ return new Response(null, {
166
+ status: 204
167
+ });
168
+ }), scheduler);
169
+ expect((yield empty('https://example.test')).status).toBe(204);
170
+ }));
171
+ });
172
+ it('aborts a real HTTP response stalled after headers and then recovers', /*#__PURE__*/_asyncToGenerator(function* () {
173
+ jest.useRealTimers();
174
+ var server = (0, _nodeHttp.createServer)((request, response) => {
175
+ response.writeHead(200, {
176
+ 'Content-Type': 'application/json'
177
+ });
178
+ if (request.url === '/stall') {
179
+ response.write('{');
180
+ } else {
181
+ response.end('{"ok":true}');
182
+ }
183
+ });
184
+ server.listen(0, '127.0.0.1');
185
+ yield (0, _nodeEvents.once)(server, 'listening');
186
+ var url = "http://127.0.0.1:".concat(server.address().port);
187
+ var fetch = (0, _requests.createBoundedFetch)(globalThis.fetch, new _requests.FetchScheduler({
188
+ concurrency: 1,
189
+ requestTimeout: 200
190
+ }));
191
+ try {
192
+ yield expect(fetch("".concat(url, "/stall"))).rejects.toThrow('timed out');
193
+ var result = yield fetch("".concat(url, "/ok"));
194
+ expect(yield result.json()).toEqual({
195
+ ok: true
196
+ });
197
+ } finally {
198
+ server.closeAllConnections();
199
+ yield new Promise(resolve => server.close(() => resolve()));
200
+ }
201
+ }));
@@ -0,0 +1,199 @@
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 { once } from 'node:events';
4
+ import { createServer } from 'node:http';
5
+ import { createBoundedFetch, FetchScheduler } from "./requests.mjs";
6
+ function deferred() {
7
+ var resolve;
8
+ var promise = new Promise(yes => {
9
+ resolve = yes;
10
+ });
11
+ return {
12
+ promise,
13
+ resolve
14
+ };
15
+ }
16
+ var tick = /*#__PURE__*/function () {
17
+ var _ref = _asyncToGenerator(function* () {
18
+ for (var i = 0; i < 10; i++) yield Promise.resolve();
19
+ });
20
+ return function tick() {
21
+ return _ref.apply(this, arguments);
22
+ };
23
+ }();
24
+ describe('Content HTTP admission', () => {
25
+ beforeEach(() => jest.useFakeTimers());
26
+ afterEach(() => jest.useRealTimers());
27
+ it('limits active work, serves queued work in order, and rejects overflow', /*#__PURE__*/_asyncToGenerator(function* () {
28
+ var scheduler = new FetchScheduler({
29
+ concurrency: 1,
30
+ queueLimit: 2
31
+ });
32
+ var first = deferred();
33
+ var started = [];
34
+ var a = scheduler.run(/*#__PURE__*/_asyncToGenerator(function* () {
35
+ started.push(1);
36
+ return first.promise;
37
+ }));
38
+ var b = scheduler.run(/*#__PURE__*/_asyncToGenerator(function* () {
39
+ started.push(2);
40
+ return 2;
41
+ }));
42
+ var c = scheduler.run(/*#__PURE__*/_asyncToGenerator(function* () {
43
+ started.push(3);
44
+ return 3;
45
+ }));
46
+ var overflow = jest.fn(/*#__PURE__*/_asyncToGenerator(function* () {
47
+ return 4;
48
+ }));
49
+ yield expect(scheduler.run(overflow)).rejects.toThrow('queue is full');
50
+ expect(started).toEqual([1]);
51
+ expect(overflow).not.toHaveBeenCalled();
52
+ first.resolve(1);
53
+ expect(yield Promise.all([a, b, c])).toEqual([1, 2, 3]);
54
+ expect(started).toEqual([1, 2, 3]);
55
+ yield tick();
56
+ expect(jest.getTimerCount()).toBe(0);
57
+ }));
58
+ it('removes expired and canceled queued work before it reaches the origin', /*#__PURE__*/_asyncToGenerator(function* () {
59
+ var scheduler = new FetchScheduler({
60
+ concurrency: 1,
61
+ queueLimit: 1
62
+ });
63
+ var first = deferred();
64
+ var a = scheduler.run(() => first.promise);
65
+ var queued = jest.fn(/*#__PURE__*/_asyncToGenerator(function* () {
66
+ return 2;
67
+ }));
68
+ var expired = expect(scheduler.run(queued)).rejects.toThrow('queue wait timed out');
69
+ yield jest.advanceTimersByTimeAsync(1000);
70
+ yield expired;
71
+ var controller = new AbortController();
72
+ var canceled = expect(scheduler.run(queued, controller.signal)).rejects.toThrow('cancel');
73
+ controller.abort(new Error('cancel'));
74
+ yield canceled;
75
+ var recovered = scheduler.run(/*#__PURE__*/_asyncToGenerator(function* () {
76
+ return 3;
77
+ }));
78
+ first.resolve(1);
79
+ expect(yield Promise.all([a, recovered])).toEqual([1, 3]);
80
+ expect(queued).not.toHaveBeenCalled();
81
+ }));
82
+ it('aborts a timed out transport and admits a subsequent retry', /*#__PURE__*/_asyncToGenerator(function* () {
83
+ var scheduler = new FetchScheduler({
84
+ concurrency: 1,
85
+ requestTimeout: 100
86
+ });
87
+ var signal;
88
+ var timedOut = expect(scheduler.run(s => {
89
+ signal = s;
90
+ return new Promise((_, reject) => s.addEventListener('abort', () => reject(s.reason)));
91
+ })).rejects.toThrow('timed out');
92
+ yield jest.advanceTimersByTimeAsync(100);
93
+ yield timedOut;
94
+ expect(signal.aborted).toBe(true);
95
+ expect(yield scheduler.run(/*#__PURE__*/_asyncToGenerator(function* () {
96
+ return 'recovered';
97
+ }))).toBe('recovered');
98
+ }));
99
+ it('retains the physical slot if custom work ignores abort', /*#__PURE__*/_asyncToGenerator(function* () {
100
+ var scheduler = new FetchScheduler({
101
+ concurrency: 1,
102
+ queueLimit: 0,
103
+ requestTimeout: 100
104
+ });
105
+ var pending = deferred();
106
+ var timedOut = expect(scheduler.run(() => pending.promise)).rejects.toThrow('timed out');
107
+ yield jest.advanceTimersByTimeAsync(100);
108
+ yield timedOut;
109
+ yield expect(scheduler.run(/*#__PURE__*/_asyncToGenerator(function* () {
110
+ return 2;
111
+ }))).rejects.toThrow('queue is full');
112
+ pending.resolve(1);
113
+ yield tick();
114
+ expect(yield scheduler.run(/*#__PURE__*/_asyncToGenerator(function* () {
115
+ return 3;
116
+ }))).toBe(3);
117
+ }));
118
+ it('forwards active cancellation and never starts an already canceled call', /*#__PURE__*/_asyncToGenerator(function* () {
119
+ var scheduler = new FetchScheduler({
120
+ concurrency: 1
121
+ });
122
+ var controller = new AbortController();
123
+ var signal;
124
+ var canceled = expect(scheduler.run(s => {
125
+ signal = s;
126
+ return new Promise((_, reject) => s.addEventListener('abort', () => reject(s.reason)));
127
+ }, controller.signal)).rejects.toThrow('cancel');
128
+ yield tick();
129
+ controller.abort(new Error('cancel'));
130
+ yield canceled;
131
+ expect(signal.aborted).toBe(true);
132
+ var invoke = jest.fn(/*#__PURE__*/_asyncToGenerator(function* () {
133
+ return 1;
134
+ }));
135
+ yield expect(scheduler.run(invoke, controller.signal)).rejects.toThrow('cancel');
136
+ expect(invoke).not.toHaveBeenCalled();
137
+ yield tick();
138
+ expect(yield scheduler.run(/*#__PURE__*/_asyncToGenerator(function* () {
139
+ return 'ok';
140
+ }))).toBe('ok');
141
+ }));
142
+ it('keeps the deadline through response body consumption', /*#__PURE__*/_asyncToGenerator(function* () {
143
+ var scheduler = new FetchScheduler({
144
+ requestTimeout: 100
145
+ });
146
+ var transport = /*#__PURE__*/function () {
147
+ var _ref17 = _asyncToGenerator(function* (_input, init) {
148
+ return new Response(new ReadableStream({
149
+ start(controller) {
150
+ init.signal.addEventListener('abort', () => controller.error(init.signal.reason));
151
+ }
152
+ }));
153
+ });
154
+ return function transport(_x, _x2) {
155
+ return _ref17.apply(this, arguments);
156
+ };
157
+ }();
158
+ var bounded = createBoundedFetch(transport, scheduler);
159
+ var timedOut = expect(bounded('https://example.test')).rejects.toThrow('timed out');
160
+ yield jest.advanceTimersByTimeAsync(100);
161
+ yield timedOut;
162
+ var empty = createBoundedFetch(/*#__PURE__*/_asyncToGenerator(function* () {
163
+ return new Response(null, {
164
+ status: 204
165
+ });
166
+ }), scheduler);
167
+ expect((yield empty('https://example.test')).status).toBe(204);
168
+ }));
169
+ });
170
+ it('aborts a real HTTP response stalled after headers and then recovers', /*#__PURE__*/_asyncToGenerator(function* () {
171
+ jest.useRealTimers();
172
+ var server = createServer((request, response) => {
173
+ response.writeHead(200, {
174
+ 'Content-Type': 'application/json'
175
+ });
176
+ if (request.url === '/stall') {
177
+ response.write('{');
178
+ } else {
179
+ response.end('{"ok":true}');
180
+ }
181
+ });
182
+ server.listen(0, '127.0.0.1');
183
+ yield once(server, 'listening');
184
+ var url = "http://127.0.0.1:".concat(server.address().port);
185
+ var fetch = createBoundedFetch(globalThis.fetch, new FetchScheduler({
186
+ concurrency: 1,
187
+ requestTimeout: 200
188
+ }));
189
+ try {
190
+ yield expect(fetch("".concat(url, "/stall"))).rejects.toThrow('timed out');
191
+ var result = yield fetch("".concat(url, "/ok"));
192
+ expect(yield result.json()).toEqual({
193
+ ok: true
194
+ });
195
+ } finally {
196
+ server.closeAllConnections();
197
+ yield new Promise(resolve => server.close(() => resolve()));
198
+ }
199
+ }));
@@ -4,9 +4,14 @@ Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
6
  exports.PrezlyAdapter = void 0;
7
+ var _sha = require("@noble/hashes/sha256");
8
+ var _utils = require("@noble/hashes/utils");
9
+ var _jsonStableStringify = _interopRequireDefault(require("json-stable-stringify"));
7
10
  var _sdk = require("@prezly/sdk");
8
11
  var _themeKitCore = require("@prezly/theme-kit-core");
9
12
  var _index = require("./cache/index.cjs");
13
+ var _requests = require("./requests.cjs");
14
+ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
10
15
  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; }
11
16
  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; }
12
17
  function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }
@@ -15,32 +20,51 @@ function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e =
15
20
  var PrezlyAdapter;
16
21
  (function (_PrezlyAdapter) {
17
22
  function connect(config) {
18
- var {
19
- cache: cacheConfig,
20
- fetch
21
- } = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
23
+ var _ref = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {},
24
+ cacheConfig = _ref.cache,
25
+ fetch = _ref.fetch;
22
26
  function usePrezlyClient() {
23
- var {
24
- // sdk client properties
25
- accessToken,
26
- baseUrl,
27
- headers,
28
- // contentDelivery client properties
29
- newsroom,
30
- theme,
31
- formats
32
- } = _themeKitCore.Resolvable.resolve(config);
27
+ var _cacheConfig$namespac, _cacheConfig$memory;
28
+ var _Resolvable$resolve = _themeKitCore.Resolvable.resolve(config),
29
+ accessToken = _Resolvable$resolve.accessToken,
30
+ baseUrl = _Resolvable$resolve.baseUrl,
31
+ headers = _Resolvable$resolve.headers,
32
+ newsroom = _Resolvable$resolve.newsroom,
33
+ theme = _Resolvable$resolve.theme,
34
+ formats = _Resolvable$resolve.formats;
33
35
  var client = (0, _sdk.createPrezlyClient)({
34
36
  fetch,
35
37
  accessToken,
36
38
  baseUrl,
37
39
  headers
38
40
  });
39
- var contentDelivery = _themeKitCore.ContentDelivery.createClient(client, newsroom, theme, {
41
+ var cache = cacheConfig ? (0, _index.configure)(_objectSpread({
42
+ namespace: 'content:'
43
+ }, cacheConfig)) : undefined;
44
+ // Custom fetches may depend on hidden request context. Preserve their
45
+ // existing cache behavior unless the caller declares that identity.
46
+ var scope = cache && (!fetch || (cacheConfig === null || cacheConfig === void 0 ? void 0 : cacheConfig.requestScope) !== undefined) ? (0, _utils.bytesToHex)((0, _sha.sha256)((0, _jsonStableStringify.default)({
47
+ baseUrl: baseUrl !== null && baseUrl !== void 0 ? baseUrl : 'https://api.prezly.com',
48
+ accessToken,
49
+ headers: headers !== null && headers !== void 0 ? headers : {},
50
+ cache: {
51
+ namespace: (_cacheConfig$namespac = cacheConfig === null || cacheConfig === void 0 ? void 0 : cacheConfig.namespace) !== null && _cacheConfig$namespac !== void 0 ? _cacheConfig$namespac : 'content:',
52
+ memory: (_cacheConfig$memory = cacheConfig === null || cacheConfig === void 0 ? void 0 : cacheConfig.memory) !== null && _cacheConfig$memory !== void 0 ? _cacheConfig$memory : false,
53
+ redis: cacheConfig === null || cacheConfig === void 0 ? void 0 : cacheConfig.redis
54
+ },
55
+ requestScope: cacheConfig === null || cacheConfig === void 0 ? void 0 : cacheConfig.requestScope
56
+ }))) : undefined;
57
+ var contentClient = cache ? (0, _sdk.createPrezlyClient)({
58
+ accessToken,
59
+ baseUrl,
60
+ headers,
61
+ fetch: (0, _requests.createBoundedFetch)(fetch !== null && fetch !== void 0 ? fetch : globalThis.fetch)
62
+ }) : client;
63
+ var contentDelivery = _themeKitCore.ContentDelivery.createClient(contentClient, newsroom, theme, {
40
64
  formats,
41
- cache: cacheConfig ? (0, _index.configure)(_objectSpread({
42
- namespace: 'content:'
43
- }, cacheConfig)) : undefined
65
+ cache: cache ? _objectSpread(_objectSpread({}, cache), {}, {
66
+ scope
67
+ }) : undefined
44
68
  });
45
69
  return {
46
70
  client,