@podium/podlet 5.0.0 → 5.0.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.
package/lib/podlet.js CHANGED
@@ -5,33 +5,320 @@
5
5
 
6
6
  import {
7
7
  HttpIncoming,
8
+ // @ts-ignore
8
9
  pathnameBuilder,
9
10
  AssetCss,
10
11
  AssetJs,
11
12
  } from '@podium/utils';
13
+ // @ts-ignore
12
14
  import * as schema from '@podium/schemas';
13
15
  import Metrics from '@metrics/client';
16
+ // @ts-ignore
14
17
  import abslog from 'abslog';
18
+ // @ts-ignore
15
19
  import objobj from 'objobj';
16
20
  import * as utils from '@podium/utils';
21
+ // @ts-ignore
17
22
  import Proxy from '@podium/proxy';
18
- import { join, dirname } from 'path';
19
- import { fileURLToPath } from 'url';
20
- import fs from 'fs';
23
+ import { join, dirname } from 'node:path';
24
+ import { fileURLToPath } from 'node:url';
25
+ import fs from 'node:fs';
21
26
 
22
27
  const currentDirectory = dirname(fileURLToPath(import.meta.url));
23
- const pkgJson = fs.readFileSync(join(currentDirectory, '../package.json'), 'utf-8');
28
+ const pkgJson = fs.readFileSync(
29
+ join(currentDirectory, '../package.json'),
30
+ 'utf-8',
31
+ );
24
32
  const pkg = JSON.parse(pkgJson);
25
33
 
34
+ // @ts-ignore
26
35
  const { template } = utils;
27
36
 
28
- const _sanitize = Symbol('_sanitize');
29
- const _addCssAsset = Symbol('_addCssAsset');
30
- const _addJsAsset = Symbol('_addJsAsset');
31
- const _currentScope = Symbol('_currentScope');
32
-
37
+ /**
38
+ * @typedef {(...args: any) => void} LogFunction
39
+ * @typedef {{ trace: LogFunction, debug: LogFunction, info: LogFunction, warn: LogFunction, error: LogFunction, fatal: LogFunction }} AbsLogger
40
+ *
41
+ * @typedef {Object} PodletOptions
42
+ * @property {string} name - (required) podlet name
43
+ * @property {string} version - (required) podlet version
44
+ * @property {string} pathname - (required) podlet pathname
45
+ * @property {string} [manifest] - path where the podlet manifest file is served from (default '/manifest.json')
46
+ * @property {string} [content] - path where the podlet content HTML markup is served from (default '/')
47
+ * @property {string} [fallback] - path where the podlet fallback HTML markup is served from (default '/fallback')
48
+ * @property {boolean} [development] - a boolean flag that, when true, enables additional development setup (default false)
49
+ * @property {Console | AbsLogger} [logger] - a logger to use when provided. Can be the console object if console logging is desired but can also be any Log4j compatible logging object as well. Nothing is logged if no logger is provided. (default null)
50
+ * @property {import("@podium/proxy").default.PodiumProxyOptions} proxy - options that can be provided to configure the @podium/proxy instance used by the podlet. See that module for details.
51
+ *
52
+ * @typedef {{ debug: 'true' | 'false', locale: string, deviceType: string, requestedBy: string, mountOrigin: string, mountPathname: string, publicPathname: string }} PodletContext
53
+ * @typedef {{ as?: string | false | null, crossorigin?: string | null | boolean, disabled?: boolean | '' | null, hreflang?: string | false | null, title?: string | false | null, media?: string | false | null, rel?: string | false | null, type?: string | false | null, value: string | false | null, data?: Array<{ key: string; value: string }>, strategy?: "beforeInteractive" | "afterInteractive" | "lazy", scope?: "content" | "fallback" | "all", [key: string]: any }} AssetCssLike
54
+ * @typedef {{ value: string | null, crossorigin?: string | null | boolean, type?: string | null | false, integrity?: string | null | false, referrerpolicy?: string | null | false, nomodule?: boolean | null | '', async?: boolean | null | '', defer?: boolean | null | '', data?: Array<{ key: string; value: string }>, strategy?: "beforeInteractive" | "afterInteractive" | "lazy", scope?: "content" | "fallback" | "all", [key: string]: any }} AssetJsLike
55
+ */
33
56
 
34
57
  export default class PodiumPodlet {
58
+ /**
59
+ * Podium document template. A custom document template is set by using the .view() method in the podlet and layout modules.
60
+ *
61
+ * @see https://podium-lib.io/docs/api/document
62
+ *
63
+ * @example ```js
64
+ * const podlet = new Podlet({ ... });
65
+ * podlet.view(myDocumentTemplate);
66
+ * ```
67
+ */
68
+ #view = template;
69
+
70
+ /**
71
+ * The name that the podlet identifies itself by. (set in the constructor) This is used internally for things like metrics but can also be used by a layout server.
72
+ * This value must be in camelCase.
73
+ *
74
+ * @see https://podium-lib.io/docs/api/podlet/#name
75
+ *
76
+ * @example ```js
77
+ * const podlet = new Podlet({ name: 'foo', ... });
78
+ * podlet.name // foo
79
+ * ```
80
+ */
81
+ name = '';
82
+
83
+ /**
84
+ * Pathname for where a podlet is mounted in an HTTP server.
85
+ * It is important that this value matches where the entry point of a route is in an HTTP server since
86
+ * this value is used to define where the manifest is for the podlet.
87
+ * (set in the constructor)
88
+ *
89
+ * The podlet.pathname() method is used to access this property
90
+ *
91
+ * @see https://podium-lib.io/docs/api/podlet/#pathname
92
+ */
93
+ #pathname = '';
94
+
95
+ /**
96
+ * The current version of the podlet. (set in the constructor)
97
+ * It is important that this value be updated when a new version of the podlet is deployed since the page (layout)
98
+ * that the podlet is displayed in uses this value to know whether to refresh the podlet's manifest and fallback content or not.
99
+ *
100
+ * @see https://podium-lib.io/docs/api/podlet/#version
101
+ *
102
+ * @example ```js
103
+ * const podlet = new Podlet({ version: '1.0.0', ... });
104
+ * podlet.version // 1.0.0
105
+ * ```
106
+ */
107
+ version = '';
108
+
109
+ /**
110
+ * The podlet development property (set in the constructor)
111
+ * Used to make podlet development simple without the need to run a layout server locally
112
+ * Do not run a podlet in development mode when deploying to production.
113
+ *
114
+ * @see https://podium-lib.io/docs/api/podlet/#development
115
+ * @see https://podium-lib.io/docs/api/podlet/#development-mode
116
+ *
117
+ * @example ```js
118
+ * const podlet = new Podlet({ development: true, ... });
119
+ * podlet.development // true
120
+ * ```
121
+ */
122
+ development = false;
123
+
124
+ /**
125
+ * A logger. The abstract logger "Abslog" is used to make it possible to provide different kinds of loggers.
126
+ * The logger can be provided via the 'logger' constructor argument.
127
+ *
128
+ * @see https://www.npmjs.com/package/abslog
129
+ *
130
+ * @example ```js
131
+ * const podlet = new Podlet({ logger: console, ... });
132
+ * podlet.log.trace('trace log to the console')
133
+ * podlet.log.debug('debug log to the console')
134
+ * podlet.log.info('info log to the console')
135
+ * podlet.log.warn('warn log to the console')
136
+ * podlet.log.error('error log to the console')
137
+ * podlet.log.fatal('fatal log to the console')
138
+ * ```
139
+ *
140
+ * @type {AbsLogger}
141
+ */
142
+ log;
143
+
144
+ /**
145
+ * An instance of the `@podium/proxy` module
146
+ * @see https://github.com/podium-lib/proxy
147
+ */
148
+ httpProxy;
149
+
150
+ /**
151
+ * The pathname for the manifest of the podlet. Defaults to /manifest.json. (set in the constructor)
152
+ * The value should be relative to the value set on the pathname argument.
153
+ * In other words if a podlet is mounted into an HTTP server at /foo and the manifest is at /foo/component.json, pathname will be /foo and manifestRoute will be /component.json
154
+ *
155
+ * @see https://podium-lib.io/docs/api/podlet/#manifest
156
+ *
157
+ * @example ```js
158
+ * const podlet = new Podlet({ manifest: '/manifest.json', ... });
159
+ * podlet.manifestRoute // /manifest.json
160
+ * ```
161
+ */
162
+ manifestRoute = '/manifest.json';
163
+
164
+ /**
165
+ * The pathname for the content route of the podlet. Defaults to /. (set in the constructor)
166
+ * The value should be relative to the value set on the pathname argument.
167
+ * In other words if a podlet is mounted into an HTTP server at /foo and the content is at /foo/content, pathname will be /foo and contentRoute will be /content
168
+ *
169
+ * @see https://podium-lib.io/docs/api/podlet/#content
170
+ *
171
+ * @example ```js
172
+ * const podlet = new Podlet({ content: '/foo', ... });
173
+ * podlet.contentRoute // /foo
174
+ * ```
175
+ */
176
+ contentRoute = '/';
177
+
178
+ /**
179
+ * The pathname for the fallback route of the podlet. Defaults to /fallback. (set in the constructor)
180
+ * The value should be relative to the value set on the pathname argument.
181
+ * In other words if a podlet is mounted into an HTTP server at /foo and the fallback is at /foo/fallback, pathname will be /foo and fallbackRoute will be /fallback
182
+ *
183
+ * @see https://podium-lib.io/docs/api/podlet/#fallback
184
+ *
185
+ * @example ```js
186
+ * const podlet = new Podlet({ fallback: '/fallback', ... });
187
+ * podlet.fallbackRoute // /fallback
188
+ * ```
189
+ */
190
+ fallbackRoute = '';
191
+
192
+ /**
193
+ * An object that holds information about defined proxy routes. Proxy routes are defined using the podlet.proxy(...) method and up to 4 proxy routes
194
+ * may be defined per podlet.
195
+ *
196
+ * @see https://podium-lib.io/docs/api/podlet#proxy-target-name-
197
+ * @see https://podium-lib.io/docs/podlet/proxying
198
+ *
199
+ * @example ```js
200
+ * const podlet = new Podlet({ ... });
201
+ * podlet.proxy({ target: '/api', name: 'api' })
202
+ * podlet.proxyRoutes // { api: '/api' }
203
+ * ```
204
+ *
205
+ * @type {Record<string, string>}
206
+ */
207
+ proxyRoutes = {};
208
+
209
+ /**
210
+ * An object containing a set of Podium context values configured for podlet development.
211
+ * This is necessary when the podlet is in development mode because requests do not come from a layout (which is what normally sends the context information)
212
+ * These base context values can be overridden by providing a default context using the podlet.defaults() method
213
+ * in which case the baseContext and the defaultContext will be merged together to provide the development context object.
214
+ * This is not used at all when the podlet is not in development mode or when it is in development mode but the request to the podlet comes from a Podium layout.
215
+ *
216
+ * @see https://podium-lib.io/docs/podlet/context
217
+ * @see https://podium-lib.io/docs/api/podlet#development-mode
218
+ * @see https://podium-lib.io/docs/api/podlet#defaultscontext
219
+ *
220
+ * @example ```js
221
+ * const podlet = new Podlet({ name: 'foo', pathname: '/bar', ... });
222
+ * podlet.baseContext; // { debug: 'false', locale: 'en-US', deviceType: 'desktop', requestedBy: this.name, mountOrigin: '', mountPathname: '/bar', publicPathname: '/bar/podium-resource/foo' }
223
+ * ```
224
+ *
225
+ * @type {PodletContext}
226
+ */
227
+ baseContext;
228
+
229
+ /**
230
+ * An object containing a set of Podium context values configured for podlet development.
231
+ * This is necessary when the podlet is in development mode because requests do not come from a layout (which is what normally sends the context information)
232
+ * These default context values override the `baseContext` values set by the package and can be set using the podlet.defaults() method
233
+ * in which case the baseContext and the defaultContext will be merged together to provide the development context object.
234
+ * This is not used at all when the podlet is not in development mode or when it is in development mode but the request to the podlet comes from a Podium layout.
235
+ *
236
+ * @see https://podium-lib.io/docs/podlet/context
237
+ * @see https://podium-lib.io/docs/api/podlet#development-mode
238
+ * @see https://podium-lib.io/docs/api/podlet#defaultscontext
239
+ *
240
+ * @example ```js
241
+ * const podlet = new Podlet({ name: 'foo', pathname: '/bar', ... });
242
+ * podlet.defaults({ debug: 'true', locale: 'nb' });
243
+ * podlet.baseContext; // { debug: 'true', locale: 'nb', deviceType: 'desktop', requestedBy: this.name, mountOrigin: '', mountPathname: '/bar', publicPathname: '/bar/podium-resource/foo' }
244
+ * ```
245
+ *
246
+ * @type {PodletContext}
247
+ */
248
+ defaultContext;
249
+
250
+ /**
251
+ * Property that holds the podlet's CSS asset references. Objects in the array are AssetCss instances. Asset references can be added using the podlet.css() method.
252
+ *
253
+ * @see https://podium-lib.io/docs/api/podlet/#cssoptionsoptions
254
+ * @see https://podium-lib.io/docs/api/assets#assetcss
255
+ * @see https://podium-lib.io/docs/api/assets
256
+ *
257
+ * @example ```js
258
+ * const podlet = new Podlet({ ... });
259
+ * podlet.css({ value: 'https://my.assets.com/styles.css' });
260
+ * podlet.cssRoute // [ AssetCss{ value: 'https://my.assets.com/styles.css' } ]
261
+ * ```
262
+ *
263
+ * @type {AssetCss[]}
264
+ */
265
+ cssRoute = [];
266
+
267
+ /**
268
+ * Property that holds the podlet's JS asset references. Objects in the array are AssetJs instances. Asset references can be added using the podlet.js() method.
269
+ *
270
+ * @see https://podium-lib.io/docs/api/podlet/#jsoptionsoptions
271
+ * @see https://podium-lib.io/docs/api/assets#assetjs
272
+ * @see https://podium-lib.io/docs/api/assets
273
+ *
274
+ * @example ```js
275
+ * const podlet = new Podlet({ ... });
276
+ * podlet.js({ value: 'https://my.assets.com/scripts.js' });
277
+ * podlet.jsRoute // [ AssetJs{ value: 'https://my.assets.com/scripts.js' } ]
278
+ * ```
279
+ *
280
+ * @type {AssetJs[]}
281
+ */
282
+ jsRoute = [];
283
+
284
+ /**
285
+ * Metrics client stream object that can be used to consume metrics out of a Podium podlet.
286
+ * @see https://www.npmjs.com/package/@metrics/client for detailed documentation
287
+ *
288
+ * @example
289
+ * ```js
290
+ * const podlet = new Podlet(...);
291
+ * podlet.metrics.pipe(...);
292
+ * // or
293
+ * podlet.metrics.on('data', chunk => { ... });
294
+ * ```
295
+ */
296
+ metrics = new Metrics();
297
+
298
+ /**
299
+ * Creates a new instance of a Podium podlet which can be used in conjunction with your framework of choice to build podlet server apps.
300
+ * `name`, `version` and `pathname` constructor arguments are required. All other options are optional.
301
+ *
302
+ * * `name` - podlet name (**required**)
303
+ * * `version` - podlet version (**required**)
304
+ * * `pathname` - podlet pathname (**required**)
305
+ * * `manifest` - path where the podlet manifest file is served from (**default** `'/manifest.json'`)
306
+ * * `content` - path where the podlet content HTML markup is served from (**default** `'/'`)
307
+ * * `fallback` - path where the podlet fallback HTML markup is served from (**default** `'/fallback'`)
308
+ * * `development` - a boolean flag that, when true, enables additional development setup (**default** `false`)
309
+ * * `logger` - a logger to use when provided. Can be the console object if console logging is desired but can also be any Log4j compatible logging object as well. Nothing is logged if no logger is provided. (**default** `null`)
310
+ * * `proxy` - options that can be provided to configure the @podium/proxy instance used by the podlet. See that module for details. (**default**: `{}`)
311
+ *
312
+ * @see https://podium-lib.io/docs/api/podlet/#constructor
313
+ * @see https://podium-lib.io/docs/podlet/getting_started
314
+ *
315
+ * @param {PodletOptions} options
316
+ *
317
+ * @example
318
+ * ```
319
+ * const podlet = new Podlet({ name: 'foo', version: '1.0.0', pathname: '/' });
320
+ * ```
321
+ */
35
322
  constructor({
36
323
  name = '',
37
324
  version = '',
@@ -42,7 +329,7 @@ export default class PodiumPodlet {
42
329
  logger = undefined,
43
330
  development = false,
44
331
  proxy = {},
45
- } = {}) {
332
+ }) {
46
333
  if (schema.name(name).error)
47
334
  throw new Error(
48
335
  `The value, "${name}", for the required argument "name" on the Podlet constructor is not defined or not valid.`,
@@ -73,94 +360,39 @@ export default class PodiumPodlet {
73
360
  `The value, "${fallback}", for the optional argument "fallback" on the Podlet constructor is not valid.`,
74
361
  );
75
362
 
76
- Object.defineProperty(this, 'name', {
77
- value: name,
78
- });
79
-
80
- Object.defineProperty(this, 'version', {
81
- value: version,
82
- });
83
-
84
- Object.defineProperty(this, '_pathname', {
85
- value: this[_sanitize](pathname),
86
- });
87
-
88
- Object.defineProperty(this, 'manifestRoute', {
89
- value: this[_sanitize](manifest),
90
- });
91
-
92
- Object.defineProperty(this, 'contentRoute', {
93
- value: this[_sanitize](content),
94
- });
95
-
96
- Object.defineProperty(this, 'fallbackRoute', {
97
- value: this[_sanitize](fallback),
98
- });
99
-
100
- Object.defineProperty(this, 'cssRoute', {
101
- value: [],
102
- });
103
-
104
- Object.defineProperty(this, 'jsRoute', {
105
- value: [],
106
- });
107
-
108
- Object.defineProperty(this, 'proxyRoutes', {
109
- value: {},
110
- });
111
-
112
- Object.defineProperty(this, 'log', {
113
- value: abslog(logger),
363
+ this.name = name;
364
+ this.version = version;
365
+ this.#pathname = this.#sanitize(pathname);
366
+ this.manifestRoute = this.#sanitize(manifest);
367
+ this.contentRoute = this.#sanitize(content);
368
+ this.fallbackRoute = this.#sanitize(fallback);
369
+ this.log = abslog(logger);
370
+ this.development = development;
371
+ this.httpProxy = new Proxy({
372
+ pathname: this.#pathname,
373
+ logger: this.log,
374
+ ...proxy,
114
375
  });
115
-
116
- Object.defineProperty(this, 'development', {
117
- value: development,
118
- });
119
-
120
- Object.defineProperty(this, 'httpProxy', {
121
- enumerable: true,
122
- value: new Proxy(
123
- ({ pathname: this._pathname,
124
- logger: this.log, ...proxy}),
376
+ this.baseContext = {
377
+ debug: 'false',
378
+ locale: 'en-US',
379
+ deviceType: 'desktop',
380
+ requestedBy: this.name,
381
+ mountOrigin: '',
382
+ mountPathname: this.#pathname,
383
+ publicPathname: pathnameBuilder(
384
+ this.httpProxy.pathname,
385
+ this.httpProxy.prefix,
386
+ this.name,
125
387
  ),
126
- });
127
-
128
- Object.defineProperty(this, 'baseContext', {
129
- value: {
130
- debug: 'false',
131
- locale: 'en-US',
132
- deviceType: 'desktop',
133
- requestedBy: this.name,
134
- mountOrigin: '',
135
- mountPathname: this._pathname,
136
- publicPathname: pathnameBuilder(
137
- this.httpProxy.pathname,
138
- this.httpProxy.prefix,
139
- this.name,
140
- ),
141
- },
142
- writable: false,
143
- });
144
-
145
- Object.defineProperty(this, 'defaultContext', {
146
- value: {},
147
- writable: true,
148
- });
149
-
150
- Object.defineProperty(this, 'metrics', {
151
- enumerable: true,
152
- value: new Metrics(),
153
- });
154
-
155
- Object.defineProperty(this, '_view', {
156
- value: template,
157
- writable: true,
158
- });
388
+ };
159
389
 
160
390
  // Skip a tick to ensure the metric stream has been consumed
161
391
  setImmediate(() => {
162
392
  const moduleVersion = pkg.version;
163
- const segments = moduleVersion.split('.').map(value => parseInt(value, 10));
393
+ const segments = moduleVersion
394
+ .split('.')
395
+ .map((value) => parseInt(value, 10));
164
396
 
165
397
  const versionGauge = this.metrics.gauge({
166
398
  name: 'podium_podlet_version_info',
@@ -176,7 +408,8 @@ export default class PodiumPodlet {
176
408
  versionGauge.set(1);
177
409
  });
178
410
 
179
- this.metrics.on('error', error => {
411
+ // @ts-ignore
412
+ this.metrics.on('error', (error) => {
180
413
  this.log.error(
181
414
  'Error emitted by metric stream in @podium/podlet module',
182
415
  error,
@@ -188,44 +421,177 @@ export default class PodiumPodlet {
188
421
  return 'PodiumPodlet';
189
422
  }
190
423
 
424
+ /**
425
+ * Method that returns the pathname for where a podlet is mounted in an HTTP server.
426
+ * It is important that this value matches where the entry point of a route is in an HTTP server since
427
+ * this value is used to define where the manifest is for the podlet.
428
+ * (set in the constructor)
429
+ *
430
+ * @see https://podium-lib.io/docs/api/podlet/#pathname
431
+ *
432
+ * @example
433
+ * The method returns the value of `pathname` as defined in the podlet constructor
434
+ * ```js
435
+ * const podlet = new Podlet({ pathname: '/foo', ... });
436
+ * podlet.pathname() // /foo
437
+ * ```
438
+ *
439
+ * @example
440
+ * This method is typically used when defining routes to ensure the pathname is prepended to any routes
441
+ * ```js
442
+ * const podlet = new Podlet({ pathname: '/foo', content: '/bar', ... });
443
+ * app.get(podlet.pathname() + '/bar', (req, res) => res.podiumSend(...));
444
+ * ```
445
+ */
191
446
  pathname() {
192
- return this._pathname;
447
+ return this.#pathname;
193
448
  }
194
449
 
450
+ /**
451
+ * Method that returns the pathname for where a podlet's manifest route is to be mounted.
452
+ * By default the podlet's pathname value is prepended to the manifest value.
453
+ *
454
+ * @example
455
+ * Prefix is true by default which will prepend the pathname (/foo) in this example
456
+ * ```js
457
+ * const podlet = new Podlet({ pathname: '/foo', manifest: '/manifest.json', ... });
458
+ * podlet.manifest() // /foo/manifest.json
459
+ * podlet.manifest({ prefix: false }) // /manifest.json
460
+ * ```
461
+ *
462
+ * @example
463
+ * This method is typically used when defining the manifest route
464
+ * ```js
465
+ * const podlet = new Podlet({ ... });
466
+ * app.get(podlet.manifest(), (req, res) => res.send(podlet));
467
+ * ```
468
+ *
469
+ * @param {{ prefix?: boolean }} [options]
470
+ * @returns {string}
471
+ */
195
472
  manifest({ prefix = true } = {}) {
196
- return this[_sanitize](this.manifestRoute, prefix);
473
+ return this.#sanitize(this.manifestRoute, prefix);
197
474
  }
198
475
 
476
+ /**
477
+ * Method that returns the pathname for where a podlet's content route is to be mounted.
478
+ * By default the podlet's pathname value is prepended to the content value.
479
+ *
480
+ * @example
481
+ * Prefix is true by default which will prepend the pathname (/foo) in this example
482
+ * ```js
483
+ * const podlet = new Podlet({ pathname: '/foo', content: '/', ... });
484
+ * podlet.content() // /foo
485
+ * podlet.content({ prefix: false }) // /
486
+ * ```
487
+ *
488
+ * @example
489
+ * This method is typically used when defining the content route
490
+ * ```js
491
+ * const podlet = new Podlet({ ... });
492
+ * app.get(podlet.content(), (req, res) => res.podiumSend(...));
493
+ * ```
494
+ *
495
+ * @param {{ prefix?: boolean }} [options]
496
+ * @returns {string}
497
+ */
199
498
  content({ prefix = true } = {}) {
200
- return this[_sanitize](this.contentRoute, prefix);
499
+ return this.#sanitize(this.contentRoute, prefix);
201
500
  }
202
501
 
502
+ /**
503
+ * Method that returns the pathname for where a podlet's fallback route is to be mounted.
504
+ * By default the podlet's pathname value is prepended to the fallback value.
505
+ *
506
+ * @example
507
+ * Prefix is true by default which will prepend the pathname (/foo) in this example
508
+ * ```js
509
+ * const podlet = new Podlet({ pathname: '/foo', fallback: '/fallback', ... });
510
+ * podlet.fallback() // /foo/fallback
511
+ * podlet.fallback({ prefix: false }) // /fallback
512
+ * ```
513
+ *
514
+ * @example
515
+ * This method is typically used when defining the fallback route
516
+ * ```js
517
+ * const podlet = new Podlet({ ... });
518
+ * app.get(podlet.fallback(), (req, res) => res.podiumSend(...));
519
+ * ```
520
+ *
521
+ * @param {{ prefix?: boolean }} [options]
522
+ * @returns {string}
523
+ */
203
524
  fallback({ prefix = true } = {}) {
204
- return this[_sanitize](this.fallbackRoute, prefix);
525
+ return this.#sanitize(this.fallbackRoute, prefix);
205
526
  }
206
527
 
207
- [_addCssAsset](options = {}) {
528
+ /**
529
+ * Takes an AssetCss instance or an object with equivalent properties, converts it to an AssetCss instance if necessary and adds it to the
530
+ * cssRoute array.
531
+ * @param { AssetCss | AssetCssLike } options
532
+ * @returns {void}
533
+ */
534
+ #addCssAsset(options) {
208
535
  const clonedOptions = JSON.parse(JSON.stringify(options));
209
- clonedOptions.value = this[_sanitize](clonedOptions.value, clonedOptions.prefix)
210
- const args = { prefix: true, ...clonedOptions, pathname: this._pathname };
536
+ clonedOptions.value = this.#sanitize(
537
+ clonedOptions.value,
538
+ clonedOptions.prefix,
539
+ );
540
+ const args = {
541
+ prefix: true,
542
+ ...clonedOptions,
543
+ pathname: this.#pathname,
544
+ };
211
545
  this.cssRoute.push(new AssetCss(args));
212
546
  }
213
547
 
214
- css(options = {}) {
548
+ /**
549
+ * Method used to set CSS asset references for a podlet. Accepts an AssetCss object, a plain JS object with the same properties as an AssetCss object or
550
+ * an array containing AssetCss or plain JS objects. Asset references set in this way can be accessed via `podlet.cssRoute` and
551
+ * will be added to the podlet manifest file for sending to the layout
552
+ *
553
+ * @see https://podium-lib.io/docs/api/podlet/#cssoptionsoptions
554
+ * @see https://podium-lib.io/docs/api/assets#assetcss
555
+ * @see https://podium-lib.io/docs/api/assets
556
+ *
557
+ * @example ```js
558
+ * const podlet = new Podlet({ ... });
559
+ * podlet.css(new AssetCss{ value: 'https://my.assets.com/styles.css' });
560
+ * podlet.css({ value: 'https://my.assets.com/styles.css' });
561
+ * podlet.css([new AssetCss{ value: 'https://my.assets.com/styles.css' }, { value: 'https://my.assets.com/styles.css' }]);
562
+ * ```
563
+ *
564
+ * @param { AssetCss | AssetCss[] | AssetCssLike | AssetCssLike[] } options
565
+ * @returns {void}
566
+ */
567
+ css(options) {
215
568
  if (Array.isArray(options)) {
216
569
  for (const opts of options) {
217
- this[_addCssAsset](opts);
570
+ this.#addCssAsset(opts);
218
571
  }
219
572
  return;
220
573
  }
221
- this[_addCssAsset](options);
574
+ this.#addCssAsset(options);
222
575
  }
223
576
 
224
- [_addJsAsset](options = {}) {
577
+ /**
578
+ * Takes an AssetJs instance or an object with equivalent properties, converts it to an AssetJs instance if necessary and adds it to the
579
+ * jsRoute array.
580
+ * @param { AssetJs | AssetJsLike } options
581
+ * @returns {void}
582
+ */
583
+ #addJsAsset(options) {
225
584
  const clonedOptions = JSON.parse(JSON.stringify(options));
226
- clonedOptions.value = this[_sanitize](clonedOptions.value, clonedOptions.prefix)
585
+ clonedOptions.value = this.#sanitize(
586
+ clonedOptions.value,
587
+ clonedOptions.prefix,
588
+ );
227
589
 
228
- const args = { prefix: true, ...clonedOptions, pathname: this._pathname };
590
+ const args = {
591
+ prefix: true,
592
+ ...clonedOptions,
593
+ pathname: this.#pathname,
594
+ };
229
595
 
230
596
  // Convert data attribute object structure to array of key value objects
231
597
  if (typeof args.data === 'object' && args.data !== null) {
@@ -235,24 +601,69 @@ export default class PodiumPodlet {
235
601
  value: args.data[key],
236
602
  key,
237
603
  });
238
- })
604
+ });
239
605
  args.data = data;
240
606
  }
241
607
 
242
608
  this.jsRoute.push(new AssetJs(args));
243
609
  }
244
610
 
245
- js(options = {}) {
611
+ /**
612
+ * Method used to set JS asset references for a podlet. Accepts an AssetJs object, a plain JS object with the same properties as an AssetJs object or
613
+ * an array containing AssetJs or plain JS objects. Asset references set in this way can be accessed via `podlet.jsRoute` and
614
+ * will be added to the podlet manifest file for sending to the layout
615
+ *
616
+ * @see https://podium-lib.io/docs/api/podlet/#jsoptionsoptions
617
+ * @see https://podium-lib.io/docs/api/assets#assetjs
618
+ * @see https://podium-lib.io/docs/api/assets
619
+ *
620
+ * @example ```js
621
+ * const podlet = new Podlet({ ... });
622
+ * podlet.js(new AssetJs{ value: 'https://my.assets.com/scripts.js' });
623
+ * podlet.js({ value: 'https://my.assets.com/scripts.js' });
624
+ * podlet.js([new AssetJs{ value: 'https://my.assets.com/scripts.js' }, { value: 'https://my.assets.com/scripts.js' }]);
625
+ * ```
626
+ *
627
+ * @param {AssetJs | AssetJs[] | AssetJsLike | AssetJsLike[] } [options]
628
+ * @returns {void}
629
+ */
630
+ js(options) {
246
631
  if (Array.isArray(options)) {
247
632
  for (const opts of options) {
248
- this[_addJsAsset](opts);
633
+ this.#addJsAsset(opts);
249
634
  }
250
635
  return;
251
636
  }
252
- this[_addJsAsset](options);
637
+ this.#addJsAsset(options);
253
638
  }
254
639
 
255
- proxy({ target = null, name = null } = {}) {
640
+ /**
641
+ * Method for defining proxy targets to be mounted in a layout server.
642
+ * Accepts an object with `target` and `name` keys where target is the relative or absolute path to proxy requests to and name is an identifier
643
+ * to distinguish it from other proxy endpoints. It's common to define a target of "/api" and a name of just "api". The method returns the target so that
644
+ * it's possible to both define the proxy and the route at the same time.
645
+ *
646
+ * For a detailed overview of how proxying works, please see the proxying guide for further details.
647
+ *
648
+ * @see https://podium-lib.io/docs/podlet/proxying
649
+ * @see https://podium-lib.io/docs/api/podlet#proxy-target-name-
650
+ *
651
+ * @example
652
+ * ```js
653
+ * podlet.proxy({ name: 'api', target: '/api' }); // returns /api
654
+ * ```
655
+ *
656
+ * @example
657
+ * Define the proxy and route at the same time
658
+ * ```js
659
+ * // proxy mounted at /api in the app
660
+ * app.get(podlet.proxy({ name: 'api', target: '/api' }), (req, res) => res.sendStatus(200));
661
+ * ```
662
+ *
663
+ * @param {{ target: string; name: string }} options
664
+ * @returns {string}
665
+ */
666
+ proxy({ target, name }) {
256
667
  if (schema.uri(target).error)
257
668
  throw new Error(
258
669
  `Value on argument variable "target", "${target}", is not valid`,
@@ -272,12 +683,38 @@ export default class PodiumPodlet {
272
683
  this.proxyRoutes[name] = target;
273
684
 
274
685
  if (this.development) {
275
- this.httpProxy.register(this);
686
+ // @ts-ignore
687
+ this.httpProxy.register(this.name, this.toJSON());
276
688
  }
277
689
 
278
690
  return target;
279
691
  }
280
692
 
693
+ /**
694
+ * Method to alter the default context set when in development mode.
695
+ * In a production setup, this is not necessary since the context values are sent to the podlet from the layout.
696
+ * By default, the context will contain the following context values, all of which can be overridden.
697
+ *
698
+ * * `debug:` 'false',
699
+ * * `locale:` 'en-EN',
700
+ * * `deviceType:` 'desktop',
701
+ * * `requestedBy:` '<podlet name>',
702
+ * * `mountOrigin:` 'http://localhost:port',
703
+ * * `mountPathname:` '/<podlet pathname>',
704
+ * * `publicPathname:` '/:pathname/podium_resource/:manifestname',
705
+ *
706
+ * @see https://podium-lib.io/docs/api/podlet#defaultscontext
707
+ *
708
+ * @example
709
+ * Example of overriding deviceType
710
+ * ```js
711
+ * const podlet = new Podlet({ ... });
712
+ * podlet.defaults({ deviceType: 'mobile' });
713
+ * ```
714
+ *
715
+ * @param {any} context
716
+ * @returns {any}
717
+ */
281
718
  defaults(context = null) {
282
719
  if (context) {
283
720
  this.defaultContext = context;
@@ -285,15 +722,61 @@ export default class PodiumPodlet {
285
722
  return { ...this.baseContext, ...this.defaultContext };
286
723
  }
287
724
 
288
- view(fn = null) {
725
+ /**
726
+ * Method to set a Podium document template to be used when the podlet is in development mode.
727
+ * Must be used in conjunction with with the `.podiumSend()` method or the `podlet.render()` in the content/fallback route to have any effect.
728
+ * Has no effect when the podlet is not in development mode or if a request to the podlet comes from a Podium layout.
729
+ *
730
+ * @see https://podium-lib.io/docs/api/document
731
+ *
732
+ * @example
733
+ * A document template can be provided using the podlet.view method
734
+ * ```js
735
+ * const podlet = new Podlet({ ... });
736
+ * podlet.view(myDocumentTemplate);
737
+ * ```
738
+ *
739
+ * @example
740
+ * You need to call podiumSend or podlet.render to make use of the template you provided with podlet.view
741
+ * ```js
742
+ * app.get(podlet.content(), (req, res) => {
743
+ * res.podiumSend(`...podlet markup here...`);
744
+ * // or
745
+ * podlet.render(res.locals.podium, `...podlet markup here...`)
746
+ * });
747
+ * ```
748
+ *
749
+ * @template {{ [key: string]: unknown }} T
750
+ * @param {( incoming: HttpIncoming<T>, fragment: string, ...args: unknown[]) => string} fn
751
+ * @returns {void}
752
+ */
753
+ view(fn) {
754
+ // @ts-ignore
289
755
  if (!utils.isFunction(fn)) {
290
756
  throw new Error(
291
757
  `Value on argument variable "template" must be a function`,
292
758
  );
293
759
  }
294
- this._view = fn;
760
+ this.#view = fn;
295
761
  }
296
762
 
763
+ /**
764
+ * Method for serialising the podlet instance into a plain JS object. Called automatically when stringifying the podlet with JSON.stringify(podlet).
765
+ * Doing so will result in a correct Podium manifest file string and so is suitable for usage in a manifest route hook.
766
+ *
767
+ * @see https://podium-lib.io/docs/podlet/getting_started#step-7-create-a-manifest-route
768
+ *
769
+ * @example
770
+ * ```js
771
+ * app.get(podlet.manifest(), (req, res) => res.send(JSON.stringify(podlet)));
772
+ * ```
773
+ *
774
+ * @example
775
+ * In frameworks that automatically serialise JS objects, such as Express, you can omit JSON.stringify
776
+ * ```js
777
+ * app.get(podlet.manifest(), (req, res) => res.send(podlet));
778
+ * ```
779
+ */
297
780
  toJSON() {
298
781
  return {
299
782
  name: this.name,
@@ -306,24 +789,58 @@ export default class PodiumPodlet {
306
789
  };
307
790
  }
308
791
 
792
+ /**
793
+ * Method to render the document template. Will, by default, render the document template provided by Podium unless a custom document template is set using the .view method.
794
+ * In most HTTP frameworks this method can be ignored in favour of res.podiumSend().
795
+ * If present, res.podiumSend() has the advantage that it's not necessary to pass in an HttpIncoming object as the first argument.
796
+ *
797
+ * @see https://podium-lib.io/docs/api/podlet#renderhttpincoming-fragment-args
798
+ *
799
+ * @example
800
+ * ```js
801
+ * app.get(podlet.content(), (req, res) => {
802
+ * const incoming = res.locals.podium;
803
+ * const document = podlet.render(incoming, '<div>content to render</div>');
804
+ * res.send(document);
805
+ * });
806
+ * ```
807
+ *
808
+ * @template {{ [key: string]: unknown }} T
809
+ * @param {HttpIncoming<T>} incoming - Instance of Podium HttpIncoming object
810
+ * @param {string} data - the podlet content as an HTML markup string
811
+ * @param {...any} args - additional args depending on the template and what values it accepts
812
+ * @returns {string}
813
+ */
309
814
  render(incoming, data, ...args) {
310
815
  if (!incoming.development) {
311
816
  return data;
312
817
  }
313
- return this._view(incoming, data, ...args);
818
+ return this.#view(incoming, data, ...args);
314
819
  }
315
820
 
821
+ /**
822
+ * Method for processing an incoming HTTP request. This method is intended to be used to implement support for multiple HTTP frameworks and in most cases will not need to be used directly by podlet developers when creating podlet servers.
823
+ *
824
+ * What it does:
825
+ * * Handles detection of development mode and sets the appropriate defaults
826
+ * * Runs context deserializing on the incoming request and sets a context object at HttpIncoming.context.
827
+ * * Returns an HttpIncoming object.
828
+ *
829
+ * @see https://podium-lib.io/docs/api/podlet#processhttpincoming
830
+ *
831
+ * @param {HttpIncoming} incoming
832
+ * @param {{ proxy?: boolean }} [options]
833
+ * @returns {Promise<HttpIncoming>}
834
+ */
316
835
  async process(incoming, { proxy = true } = {}) {
317
836
  incoming.name = this.name;
318
837
  incoming.css = this.cssRoute.filter(
319
838
  ({ scope = 'all' }) =>
320
- scope === this[_currentScope](incoming) ||
321
- scope === 'all',
839
+ scope === this.#currentScope(incoming) || scope === 'all',
322
840
  );
323
841
  incoming.js = this.jsRoute.filter(
324
842
  ({ scope = 'all' }) =>
325
- scope === this[_currentScope](incoming) ||
326
- scope === 'all',
843
+ scope === this.#currentScope(incoming) || scope === 'all',
327
844
  );
328
845
 
329
846
  // Determine if request comes from layout server or not
@@ -349,6 +866,7 @@ export default class PodiumPodlet {
349
866
  )}"`,
350
867
  );
351
868
  } else {
869
+ // @ts-ignore
352
870
  incoming.context = utils.deserializeContext(
353
871
  incoming.request.headers,
354
872
  );
@@ -367,9 +885,26 @@ export default class PodiumPodlet {
367
885
  return incoming;
368
886
  }
369
887
 
888
+ /**
889
+ * A Connect/Express compatible middleware function which takes care of the multiple operations needed for a podlet to operate correctly. This function is more or less a wrapper for the .process() method.
890
+ * Returns an array of middleware that will create an HttpIncoming object and store it at res.locals.podium.
891
+ *
892
+ * **Important:** *This middleware must be mounted before defining any routes.*
893
+ *
894
+ * @see https://podium-lib.io/docs/api/podlet#middleware
895
+ *
896
+ * @example
897
+ * ```js
898
+ * const app = express();
899
+ * app.use(podlet.middleware());
900
+ * ```
901
+ *
902
+ * @returns {(req: any, res: any, next: function) => Promise<void>}
903
+ */
370
904
  middleware() {
371
905
  return async (req, res, next) => {
372
906
  const incoming = new HttpIncoming(req, res);
907
+ // @ts-ignore
373
908
  incoming.url = new URL(
374
909
  req.originalUrl,
375
910
  `${req.protocol}://${req.get('host')}`,
@@ -397,17 +932,33 @@ export default class PodiumPodlet {
397
932
  };
398
933
  }
399
934
 
400
- [_sanitize](uri, prefix = false) {
401
- const pathname = prefix ? this._pathname : '';
935
+ /**
936
+ * Sanitizes a uri and returns the resulting uri.
937
+ * If prefix is true (default false) and the uri is relative, the podlet pathname will be prepended to the uri
938
+ * @param {string} uri
939
+ * @param {boolean} prefix
940
+ * @returns {string}
941
+ */
942
+ #sanitize(uri, prefix = false) {
943
+ const pathname = prefix ? this.#pathname : '';
402
944
  if (uri) {
945
+ // @ts-ignore
403
946
  return utils.uriIsRelative(uri)
404
- ? utils.pathnameBuilder(pathname, uri)
947
+ ? // @ts-ignore
948
+ utils.pathnameBuilder(pathname, uri)
405
949
  : uri;
406
950
  }
407
951
  return uri;
408
952
  }
409
953
 
410
- [_currentScope](incoming) {
954
+ /**
955
+ * Determines the current scope, "content", "fallback" or "all" using url matching.
956
+ * Scopes are used with asset objects that include a "scope" property
957
+ *
958
+ * @param {HttpIncoming} incoming
959
+ * @returns {'fallback' | 'content' | 'all'}
960
+ */
961
+ #currentScope(incoming) {
411
962
  const fallback = this.fallback({ prefix: true });
412
963
  const content = this.content({ prefix: true });
413
964
  const { pathname } = incoming.url;
@@ -423,4 +974,4 @@ export default class PodiumPodlet {
423
974
 
424
975
  return 'all';
425
976
  }
426
- };
977
+ }