@podium/client 5.3.0-next.1 → 5.3.1

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/CHANGELOG.md CHANGED
@@ -1,9 +1,66 @@
1
- # [5.3.0-next.1](https://github.com/podium-lib/client/compare/v5.2.0...v5.3.0-next.1) (2024-11-13)
1
+ ## [5.3.1](https://github.com/podium-lib/client/compare/v5.3.0...v5.3.1) (2025-04-11)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * add statusCode label to podium_client_resolver_content_resolve metric ([#456](https://github.com/podium-lib/client/issues/456)) ([e8d45b0](https://github.com/podium-lib/client/commit/e8d45b0564f59573a80b5cdfccc7382713251e27))
7
+ * error with statusCode label ([9b0682e](https://github.com/podium-lib/client/commit/9b0682e27b1b685c23df59e55f8651b0632bf998))
8
+
9
+ # [5.3.0](https://github.com/podium-lib/client/compare/v5.2.7...v5.3.0) (2025-02-07)
2
10
 
3
11
 
4
12
  ### Features
5
13
 
6
- * track podlet assets ([39022a3](https://github.com/podium-lib/client/commit/39022a3c87184e5f524828591c8185d8a3fc2995))
14
+ * add metric to track register podlets ([673a4e7](https://github.com/podium-lib/client/commit/673a4e7554282570d1a074ae7206873af7183dbe))
15
+
16
+ ## [5.2.7](https://github.com/podium-lib/client/compare/v5.2.6...v5.2.7) (2025-02-06)
17
+
18
+
19
+ ### Bug Fixes
20
+
21
+ * list exports so types work for imports other than root ([#454](https://github.com/podium-lib/client/issues/454)) ([b186bec](https://github.com/podium-lib/client/commit/b186bece4fd0feb82f4e64c4acf5f27dd6b06659))
22
+
23
+ ## [5.2.6](https://github.com/podium-lib/client/compare/v5.2.5...v5.2.6) (2025-01-16)
24
+
25
+
26
+ ### Bug Fixes
27
+
28
+ * **deps:** update dependency undici to v6.21.1 ([#453](https://github.com/podium-lib/client/issues/453)) ([2cf54ca](https://github.com/podium-lib/client/commit/2cf54cac312cf6cbb91da0e0f636433f2de7d892))
29
+
30
+ ## [5.2.5](https://github.com/podium-lib/client/compare/v5.2.4...v5.2.5) (2024-11-27)
31
+
32
+
33
+ ### Bug Fixes
34
+
35
+ * correctly build asset urls from link headers ([#446](https://github.com/podium-lib/client/issues/446)) ([5a30079](https://github.com/podium-lib/client/commit/5a30079fb47ee15a49ae6ff77348c400df3fe99a))
36
+
37
+ ## [5.2.4](https://github.com/podium-lib/client/compare/v5.2.3...v5.2.4) (2024-11-21)
38
+
39
+
40
+ ### Bug Fixes
41
+
42
+ * reducing logl evel to info on 404 errors ([e6d6d04](https://github.com/podium-lib/client/commit/e6d6d04a4b30729691cafa68521bcfc743c3db64))
43
+
44
+ ## [5.2.3](https://github.com/podium-lib/client/compare/v5.2.2...v5.2.3) (2024-11-13)
45
+
46
+
47
+ ### Bug Fixes
48
+
49
+ * **deps:** update dependency undici to v6.21.0 ([#445](https://github.com/podium-lib/client/issues/445)) ([6d26507](https://github.com/podium-lib/client/commit/6d26507709beffe5301f89ff7a5d03c358bbb423))
50
+
51
+ ## [5.2.2](https://github.com/podium-lib/client/compare/v5.2.1...v5.2.2) (2024-11-13)
52
+
53
+
54
+ ### Bug Fixes
55
+
56
+ * **deps:** update dependency @metrics/client to v2.5.4 ([#444](https://github.com/podium-lib/client/issues/444)) ([4f6bc04](https://github.com/podium-lib/client/commit/4f6bc0470a7ad01f32c6ea757cf853f7d2125db5))
57
+
58
+ ## [5.2.1](https://github.com/podium-lib/client/compare/v5.2.0...v5.2.1) (2024-11-13)
59
+
60
+
61
+ ### Bug Fixes
62
+
63
+ * **deps:** update dependency @podium/utils to v5.4.0 ([#443](https://github.com/podium-lib/client/issues/443)) ([efbe56b](https://github.com/podium-lib/client/commit/efbe56bbed26871566705856adbfa622702bdfa0))
7
64
 
8
65
  # [5.2.0](https://github.com/podium-lib/client/compare/v5.1.18...v5.2.0) (2024-11-06)
9
66
 
package/README.md CHANGED
@@ -2,9 +2,7 @@
2
2
 
3
3
  Client for fetching podium component fragments over HTTP.
4
4
 
5
- [![Dependencies](https://img.shields.io/david/podium-lib/client.svg)](https://david-dm.org/podium-lib/client)
6
5
  [![GitHub Actions status](https://github.com/podium-lib/client/workflows/Run%20Lint%20and%20Tests/badge.svg)](https://github.com/podium-lib/client/actions?query=workflow%3A%22Run+Lint+and+Tests%22)
7
- [![Known Vulnerabilities](https://snyk.io/test/github/podium-lib/client/badge.svg?targetFile=package.json)](https://snyk.io/test/github/podium-lib/client?targetFile=package.json)
8
6
 
9
7
  This module is intended for internal use in Podium and is not a module an end
10
8
  user would use directly. End users will typically interact with this module
@@ -152,6 +150,7 @@ The following values can be provided:
152
150
  - `throwable` - {Boolean} - Defines whether an error should be thrown if a failure occurs during the process of fetching a podium component. Defaults to `false` - Optional.
153
151
  - `excludeBy` - {Object} - Lets you define a set of rules where a `fetch` call will not be resolved if it matches. - Optional.
154
152
  - `includeBy` - {Object} - Inverse of `excludeBy`. Setting both at the same time will throw. - Optional.
153
+ - `earlyHints` - {boolean} - Can be used to disable early hints from being sent to the browser for this resource, see [HTTP Status 103 Early Hints](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/103).
155
154
 
156
155
  ##### `excludeBy` and `includeBy`
157
156
 
package/lib/client.js CHANGED
@@ -68,6 +68,7 @@ export default class PodiumClient extends EventEmitter {
68
68
  #resources;
69
69
  #registry;
70
70
  #metrics;
71
+ #counter;
71
72
  #histogram;
72
73
  #options;
73
74
  #state;
@@ -144,6 +145,16 @@ export default class PodiumClient extends EventEmitter {
144
145
  },
145
146
  });
146
147
 
148
+ this.#counter = this.#metrics.counter({
149
+ name: 'podium_client_registered_podlet_count',
150
+ description: 'Number of podlets registered with the client',
151
+ labels: {
152
+ clientName: this.#options.name,
153
+ resourceName: undefined,
154
+ resourceUri: undefined,
155
+ },
156
+ });
157
+
147
158
  this.#histogram = this.#metrics.histogram({
148
159
  name: 'podium_client_refresh_manifests',
149
160
  description: 'Time taken for podium client to refresh manifests',
@@ -221,6 +232,13 @@ export default class PodiumClient extends EventEmitter {
221
232
  resourceOptions,
222
233
  );
223
234
 
235
+ this.#counter.inc({
236
+ labels: {
237
+ resourceName: options.name,
238
+ resourceUri: options.uri,
239
+ },
240
+ });
241
+
224
242
  resource.metrics.pipe(this.#metrics);
225
243
 
226
244
  Object.defineProperty(this, options.name, {
@@ -1,6 +1,6 @@
1
1
  import { PassThrough } from 'stream';
2
2
  import assert from 'assert';
3
- import { filterAssets } from './utils.js';
3
+ import { toPreloadAssetObjects, filterAssets } from './utils.js';
4
4
 
5
5
  /**
6
6
  * @typedef {object} PodiumClientHttpOutgoingOptions
@@ -70,7 +70,6 @@ export default class PodletClientHttpOutgoing extends PassThrough {
70
70
  #uri;
71
71
  #js;
72
72
  #css;
73
- #assetsReceived = false;
74
73
 
75
74
  /**
76
75
  * @constructor
@@ -333,26 +332,6 @@ export default class PodletClientHttpOutgoing extends PassThrough {
333
332
  return this.#isFallback;
334
333
  }
335
334
 
336
- get assetsReceived() {
337
- return this.#assetsReceived;
338
- }
339
-
340
- /**
341
- * Set the assetsReceived flag.
342
- * This is used to signal to the assets object that the client has received assets for a given podlet so it can track
343
- * which podlets have sent their assets.
344
- * @param {boolean} value
345
- */
346
- set assetsReceived(value) {
347
- this.#assetsReceived = value;
348
- if (this.#assetsReceived) {
349
- this.#incoming?.assets?.addReceivedAsset(this.#name, {
350
- js: this.js,
351
- css: this.css,
352
- });
353
- }
354
- }
355
-
356
335
  pushFallback() {
357
336
  // @ts-expect-error Internal property
358
337
  this.push(this.#manifest._fallback);
@@ -370,8 +349,21 @@ export default class PodletClientHttpOutgoing extends PassThrough {
370
349
  : filterAssets('fallback', this.#manifest.css);
371
350
  this.push(null);
372
351
  this.#isFallback = true;
373
- // assume the assets from the podlet have failed and fallback assets will be used
374
- this.assetsReceived = true;
352
+ // assume the hints from the podlet have failed and fallback assets will be used
353
+ this.hintsReceived = true;
354
+ }
355
+
356
+ writeEarlyHints(cb = () => {}) {
357
+ if (this.#incoming.response.writeEarlyHints) {
358
+ const preloads = toPreloadAssetObjects([
359
+ ...(this.js || []),
360
+ ...(this.css || []),
361
+ ]);
362
+ const link = preloads.map((preload) => preload.toHeader());
363
+ if (link.length) {
364
+ this.#incoming.response.writeEarlyHints({ link }, cb);
365
+ }
366
+ }
375
367
  }
376
368
 
377
369
  get [Symbol.toStringTag]() {
@@ -159,7 +159,10 @@ export default class PodletClientContentResolver {
159
159
  body,
160
160
  } = await this.#http.request(uri, reqOptions);
161
161
 
162
- const parsedAssetObjects = parseLinkHeaders(hdrs.link);
162
+ const parsedAssetObjects = parseLinkHeaders(
163
+ hdrs.link,
164
+ outgoing.manifestUri,
165
+ );
163
166
 
164
167
  const scriptObjects = parsedAssetObjects.filter(
165
168
  (asset) => asset instanceof AssetJs,
@@ -178,6 +181,7 @@ export default class PodletClientContentResolver {
178
181
  timer({
179
182
  labels: {
180
183
  status: 'failure',
184
+ statusCode,
181
185
  },
182
186
  });
183
187
 
@@ -202,10 +206,11 @@ export default class PodletClientContentResolver {
202
206
  timer({
203
207
  labels: {
204
208
  status: 'failure',
209
+ statusCode,
205
210
  },
206
211
  });
207
212
 
208
- this.#log.warn(
213
+ this.#log.debug(
209
214
  `remote resource responded with non 200 http status code for content - code: ${statusCode} - resource: ${outgoing.name} - url: ${uri}`,
210
215
  );
211
216
  outgoing.success = true;
@@ -235,6 +240,7 @@ export default class PodletClientContentResolver {
235
240
  timer({
236
241
  labels: {
237
242
  status: 'success',
243
+ statusCode,
238
244
  },
239
245
  });
240
246
 
@@ -267,15 +273,19 @@ export default class PodletClientContentResolver {
267
273
  }),
268
274
  );
269
275
 
270
- // mark assets as received
271
- outgoing.assetsReceived = true;
272
-
273
276
  // @ts-ignore
274
277
  pipeline([body, outgoing], (err) => {
275
278
  if (err) {
276
279
  this.#log.warn('error while piping content stream', err);
277
280
  }
278
281
  });
282
+
283
+ timer({
284
+ labels: {
285
+ status: 'success',
286
+ statusCode,
287
+ },
288
+ });
279
289
  } catch (error) {
280
290
  if (error.isBoom) throw error;
281
291
 
@@ -292,6 +302,7 @@ export default class PodletClientContentResolver {
292
302
  timer({
293
303
  labels: {
294
304
  status: 'failure',
305
+ statusCode: error.statusCode || 500,
295
306
  },
296
307
  });
297
308
 
@@ -311,12 +322,6 @@ export default class PodletClientContentResolver {
311
322
  return outgoing;
312
323
  }
313
324
 
314
- timer({
315
- labels: {
316
- status: 'success',
317
- },
318
- });
319
-
320
325
  this.#log.debug(
321
326
  `successfully read content from remote resource - resource: ${outgoing.name} - url: ${uri}`,
322
327
  );
@@ -120,7 +120,10 @@ export default class PodletClientFallbackResolver {
120
120
  headers: resHeaders,
121
121
  } = await this.#http.request(outgoing.fallbackUri, reqOptions);
122
122
 
123
- const parsedAssetObjects = parseLinkHeaders(resHeaders.link);
123
+ const parsedAssetObjects = parseLinkHeaders(
124
+ resHeaders.link,
125
+ outgoing.manifestUri,
126
+ );
124
127
 
125
128
  const scriptObjects = parsedAssetObjects.filter(
126
129
  (asset) => asset instanceof AssetJs,
package/lib/resolver.js CHANGED
@@ -11,6 +11,7 @@ import Cache from './resolver.cache.js';
11
11
  * @typedef {object} PodletClientResolverOptions
12
12
  * @property {string} clientName
13
13
  * @property {import('abslog').AbstractLoggerOptions} [logger]
14
+ * @property {boolean} [earlyHints]
14
15
  */
15
16
 
16
17
  export default class PodletClientResolver {
package/lib/resource.js CHANGED
@@ -105,8 +105,6 @@ export default class PodiumClientResource {
105
105
  throw new TypeError(
106
106
  'you must pass an instance of "HttpIncoming" as the first argument to the .fetch() method',
107
107
  );
108
- // set expectation to receive a response from this podlet.
109
- incoming.assets.addExpectedAsset(this.#options.name);
110
108
  const outgoing = new HttpOutgoing(this.#options, reqOptions, incoming);
111
109
 
112
110
  if (this.#options.excludeBy) {
@@ -194,8 +192,6 @@ export default class PodiumClientResource {
194
192
  throw new TypeError(
195
193
  'you must pass an instance of "HttpIncoming" as the first argument to the .stream() method',
196
194
  );
197
- // set expectation to receive a response from this podlet.
198
- incoming.assets.addExpectedAsset(this.#options.name);
199
195
  const outgoing = new HttpOutgoing(this.#options, reqOptions, incoming);
200
196
  this.#state.setInitializingState();
201
197
  this.#resolver.resolve(outgoing);
package/lib/utils.js CHANGED
@@ -1,4 +1,4 @@
1
- import { AssetJs, AssetCss } from '@podium/utils';
1
+ import { AssetJs, AssetCss, uriRelativeToAbsolute } from '@podium/utils';
2
2
 
3
3
  /**
4
4
  * Checks if a header object has a header.
@@ -75,14 +75,17 @@ export const filterAssets = (scope, assets) => {
75
75
  };
76
76
 
77
77
  // parse link headers in AssetCss and AssetJs objects
78
- export const parseLinkHeaders = (headers) => {
78
+ export const parseLinkHeaders = (headers, manifestUri) => {
79
79
  const links = [];
80
80
 
81
81
  if (!headers) return links;
82
82
  headers.split(',').forEach((link) => {
83
83
  const parts = link.split(';');
84
84
  const [href, ...rest] = parts;
85
- const value = href.replace(/<|>|"/g, '').trim();
85
+ const value = uriRelativeToAbsolute(
86
+ href.replace(/<|>|"/g, '').trim(),
87
+ manifestUri,
88
+ );
86
89
 
87
90
  const asset = { value };
88
91
  for (const key of rest) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@podium/client",
3
- "version": "5.3.0-next.1",
3
+ "version": "5.3.1",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "keywords": [
@@ -28,6 +28,60 @@
28
28
  ],
29
29
  "main": "./lib/client.js",
30
30
  "types": "./types/client.d.ts",
31
+ "exports": {
32
+ ".": {
33
+ "types": "./types/client.js",
34
+ "default": "./lib/client.js"
35
+ },
36
+ "./lib/client.js": {
37
+ "types": "./types/client.js",
38
+ "default": "./lib/client.js"
39
+ },
40
+ "./lib/http-outgoing.js": {
41
+ "types": "./types/http-outgoing.js",
42
+ "default": "./lib/http-outgoing.js"
43
+ },
44
+ "./lib/http.js": {
45
+ "types": "./types/http.js",
46
+ "default": "./lib/http.js"
47
+ },
48
+ "./lib/resolver.cache.js": {
49
+ "types": "./types/resolver.cache.js",
50
+ "default": "./lib/resolver.cache.js"
51
+ },
52
+ "./lib/resolver.content.js": {
53
+ "types": "./types/resolver.content.js",
54
+ "default": "./lib/resolver.content.js"
55
+ },
56
+ "./lib/resolver.fallback.js": {
57
+ "types": "./types/resolver.fallback.js",
58
+ "default": "./lib/resolver.fallback.js"
59
+ },
60
+ "./lib/resolver.js": {
61
+ "types": "./types/resolver.js",
62
+ "default": "./lib/resolver.js"
63
+ },
64
+ "./lib/resolver.manifest.js": {
65
+ "types": "./types/resolver.manifest.js",
66
+ "default": "./lib/resolver.manifest.js"
67
+ },
68
+ "./lib/resource.js": {
69
+ "types": "./types/resource.js",
70
+ "default": "./lib/resource.js"
71
+ },
72
+ "./lib/response.js": {
73
+ "types": "./types/response.js",
74
+ "default": "./lib/response.js"
75
+ },
76
+ "./lib/state.js": {
77
+ "types": "./types/state.js",
78
+ "default": "./lib/state.js"
79
+ },
80
+ "./lib/utils.js": {
81
+ "types": "./types/utils.js",
82
+ "default": "./lib/utils.js"
83
+ }
84
+ },
31
85
  "scripts": {
32
86
  "lint": "eslint .",
33
87
  "lint:fix": "eslint --fix .",
@@ -38,17 +92,18 @@
38
92
  },
39
93
  "dependencies": {
40
94
  "@hapi/boom": "10.0.1",
41
- "@metrics/client": "2.5.3",
95
+ "@metrics/client": "2.5.4",
42
96
  "@podium/schemas": "5.1.0",
43
97
  "@podium/utils": "5.4.0",
44
98
  "abslog": "2.4.4",
45
99
  "http-cache-semantics": "^4.0.3",
46
100
  "lodash.clonedeep": "^4.5.0",
47
101
  "ttl-mem-cache": "4.1.0",
48
- "undici": "6.20.1"
102
+ "undici": "6.21.1"
49
103
  },
50
104
  "devDependencies": {
51
- "@podium/eslint-config": "1.0.0",
105
+ "@podium/eslint-config": "1.0.5",
106
+ "@podium/podlet": "5.2.4",
52
107
  "@podium/semantic-release-config": "2.0.0",
53
108
  "@podium/test-utils": "3.1.0-next.5",
54
109
  "@podium/typescript-config": "1.0.0",
@@ -60,15 +115,15 @@
60
115
  "@sinonjs/fake-timers": "11.3.1",
61
116
  "@types/readable-stream": "4.0.18",
62
117
  "benchmark": "2.1.4",
63
- "eslint": "9.6.0",
118
+ "eslint": "9.17.0",
64
119
  "eslint-config-prettier": "9.1.0",
65
- "eslint-plugin-prettier": "5.1.3",
66
- "express": "4.21.1",
120
+ "eslint-plugin-prettier": "5.2.1",
121
+ "express": "4.21.2",
67
122
  "get-stream": "9.0.1",
68
123
  "http-proxy": "1.18.1",
69
124
  "is-stream": "4.0.1",
70
- "npm-run-all2": "6.2.3",
71
- "prettier": "3.3.2",
125
+ "npm-run-all2": "6.2.6",
126
+ "prettier": "3.4.2",
72
127
  "semantic-release": "24.1.2",
73
128
  "tap": "18.7.2",
74
129
  "typescript": "5.6.3",
@@ -124,15 +124,9 @@ export default class PodletClientHttpOutgoing extends PassThrough {
124
124
  * @see https://podium-lib.io/docs/podlet/fallbacks
125
125
  */
126
126
  get isFallback(): boolean;
127
- /**
128
- * Set the assetsReceived flag.
129
- * This is used to signal to the assets object that the client has received assets for a given podlet so it can track
130
- * which podlets have sent their assets.
131
- * @param {boolean} value
132
- */
133
- set assetsReceived(value: boolean);
134
- get assetsReceived(): boolean;
135
127
  pushFallback(): void;
128
+ hintsReceived: boolean;
129
+ writeEarlyHints(cb?: () => void): void;
136
130
  get [Symbol.toStringTag](): string;
137
131
  #private;
138
132
  }
@@ -2,6 +2,7 @@
2
2
  * @typedef {object} PodletClientResolverOptions
3
3
  * @property {string} clientName
4
4
  * @property {import('abslog').AbstractLoggerOptions} [logger]
5
+ * @property {boolean} [earlyHints]
5
6
  */
6
7
  export default class PodletClientResolver {
7
8
  /**
@@ -31,5 +32,6 @@ export default class PodletClientResolver {
31
32
  export type PodletClientResolverOptions = {
32
33
  clientName: string;
33
34
  logger?: import("abslog").AbstractLoggerOptions;
35
+ earlyHints?: boolean;
34
36
  };
35
37
  import Metrics from '@metrics/client';
package/types/utils.d.ts CHANGED
@@ -2,5 +2,5 @@ export function isHeaderDefined(headers: object, header: string): boolean;
2
2
  export function hasManifestChange(item: object): boolean;
3
3
  export function validateIncoming(incoming?: object): boolean;
4
4
  export function filterAssets<T extends import("@podium/utils").AssetCss | import("@podium/utils").AssetJs>(scope: "content" | "fallback" | "all", assets: T[]): T[];
5
- export function parseLinkHeaders(headers: any): any[];
5
+ export function parseLinkHeaders(headers: any, manifestUri: any): any[];
6
6
  export function toPreloadAssetObjects(assetObjects: any): any;