Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 15 additions & 14 deletions lib/asset-css.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ export default class PodiumAssetCss {
#hreflang;
#prefix;
#title;
/** @type {string} */
#value;
#media;
#type;
Expand All @@ -53,19 +54,19 @@ export default class PodiumAssetCss {
/**
* @constructor
* @param {object} options
* @param {boolean | string | null} [options.crossorigin]
* @param {string | null} [options.hreflang=""]
* @param {boolean | null} [options.prefix=false]
* @param {boolean | null} [options.disabled=false]
* @param {string | null} [options.pathname=""]
* @param {string | null} [options.title=""]
* @param {string | null} [options.media=""]
* @param {string | null} [options.type="text/css"]
* @param {string | null} [options.rel="stylesheet"]
* @param {string | null} [options.as=""]
* @param {string | null} [options.value]
* @param {string | null} [options.strategy]
* @param {string | null} [options.scope]
* @param {boolean | string} [options.crossorigin]
* @param {string} [options.hreflang=""]
* @param {boolean} [options.prefix=false]
* @param {boolean} [options.disabled=false]
* @param {string} [options.pathname=""]
* @param {string} [options.title=""]
* @param {string} [options.media=""]
* @param {string} [options.type="text/css"]
* @param {string} [options.rel="stylesheet"]
* @param {string} [options.as=""]
* @param {string} [options.value]
* @param {string} [options.strategy]
* @param {string} [options.scope]
*/
constructor({
crossorigin = undefined,
Expand All @@ -89,7 +90,7 @@ export default class PodiumAssetCss {

this.#pathname = pathname;
this.#prefix = prefix;
this.#value = value;
this.#value = /** @type {string} */ (value);

this.#crossorigin = crossorigin;
this.#disabled = disabled;
Expand Down
36 changes: 18 additions & 18 deletions lib/asset-js.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ export default class PodiumAssetJs {
#pathname;
#nomodule;
#prefix;
/** @type {string} */
#value;
#async;
#defer;
Expand All @@ -55,19 +56,19 @@ export default class PodiumAssetJs {
/**
* @constructor
* @param {object} options
* @param {string | null} [options.referrerpolicy]
* @param {boolean | string | null} [options.crossorigin]
* @param {string | null} [options.integrity]
* @param {string | null} [options.pathname]
* @param {boolean | null} [options.nomodule=false]
* @param {boolean | null} [options.prefix=false]
* @param {string | null} [options.value]
* @param {boolean | null} [options.async=false]
* @param {boolean | null} [options.defer=false]
* @param {string | null} [options.type="default"]
* @param {array} [options.data]
* @param {string | null} [options.strategy]
* @param {string | null} [options.scope]
* @param {string} [options.referrerpolicy]
* @param {boolean | string} [options.crossorigin]
* @param {string} [options.integrity]
* @param {string} [options.pathname]
* @param {boolean} [options.nomodule=false]
* @param {boolean} [options.prefix=false]
* @param {string} [options.value]
* @param {boolean} [options.async=false]
* @param {boolean} [options.defer=false]
* @param {string} [options.type="default"]
* @param {Array<{ key: string, value?: unknown }>} [options.data]
* @param {string} [options.strategy]
* @param {string} [options.scope]
*/
constructor({
referrerpolicy = '',
Expand All @@ -92,7 +93,7 @@ export default class PodiumAssetJs {

this.#pathname = pathname;
this.#prefix = prefix;
this.#value = value;
this.#value = /** @type {string} */ (value);

this.#referrerpolicy = referrerpolicy;
this.#crossorigin = crossorigin;
Expand Down Expand Up @@ -239,10 +240,9 @@ export default class PodiumAssetJs {
.filter(([key, value]) => value && key !== 'value')
.flatMap(([key, value]) => {
if (key === 'data') {
// @ts-ignore
return value.map(
({ key, value }) => `data-${key}=${value}`,
);
return /** @type {Array<{ key: string, value?: unknown }>} */ (
value
).map(({ key, value }) => `data-${key}=${value}`);
}
return [`${key}=${value}`];
});
Expand Down
8 changes: 6 additions & 2 deletions lib/assets.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,12 @@ import { EventEmitter } from 'node:events';
export class Assets extends EventEmitter {
#expectedAssets = new Set();
#receivedAssets = new Map();
#js;
#css;

/** @type {Array<import('./asset-js.js').JavaScriptAsset | string>} */
#js = [];

/** @type {Array<import('./asset-css.js').CssAsset | string>} */
#css = [];

// pointless constructor with super to appease TS which cryptically errors otherwise.
constructor() {
Expand Down
9 changes: 7 additions & 2 deletions lib/html-document.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export const document = (incoming, body = '', head = '') => {
incoming.view.locale ||
incoming.context['podium-locale'] ||
incoming.context.locale ||
incoming.params.locale ||
incoming.params?.locale ||
'en-US';

// backwards compatibility for scripts and styles
Expand All @@ -28,13 +28,17 @@ export const document = (incoming, body = '', head = '') => {
<meta charset="${incoming.view.encoding ? incoming.view.encoding : 'utf-8'}">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta http-equiv="X-UA-Compatible" content="IE=Edge">
${styles.map(utils.buildLinkElement).join('\n ')}
${
// @ts-expect-error We filter out strings before this
styles.map(utils.buildLinkElement).join('\n ')
}
${scripts
.filter(
(script) =>
typeof script !== 'string' &&
script.strategy === 'beforeInteractive',
)
// @ts-expect-error We filter out strings before this
.map(utils.buildScriptElement)
.join('\n ')}
<title>${incoming.view.title ? incoming.view.title : ''}</title>
Expand All @@ -49,6 +53,7 @@ export const document = (incoming, body = '', head = '') => {
script.strategy === 'afterInteractive' ||
!script.strategy,
)
// @ts-expect-error We filter out strings before this
.map(utils.buildScriptElement)
.join('\n ')}
${scripts
Expand Down
2 changes: 2 additions & 0 deletions lib/html-utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ export const buildScriptAttributes = (obj) => {
* @returns {object}
*/
export const buildReactScriptAttributes = (obj) => {
/** @type {Record<string, unknown>} */
const attrs = {};
for (const { key, value } of buildScriptAttributes(obj)) {
if (key === 'crossorigin') attrs.crossOrigin = value || '';
Expand Down Expand Up @@ -145,6 +146,7 @@ export const buildLinkAttributes = (obj) => {
* @returns {object}
*/
export const buildReactLinkAttributes = (obj) => {
/** @type {Record<string, unknown>} */
const attrs = {};
for (const { key, value } of buildLinkAttributes(obj)) {
if (key === 'crossorigin') attrs.crossOrigin = value || '';
Expand Down
7 changes: 5 additions & 2 deletions lib/html.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import escapeHtml from 'escape-html';
/**
* Escape an untrusted string so it can be safely included in HTML.
*
* @param {unknown} string
* @param {string | null | undefined} string
* @returns {string}
*/
export function escape(string) {
Expand Down Expand Up @@ -38,7 +38,7 @@ export function html(strings, ...values) {
/** @type {DangerouslyIncludeUnescapedHTML} */ (value).content,
);
} else {
result.push(escape(value));
result.push(escape(String(value)));
}
}
result.push(strings.at(-1));
Expand All @@ -61,6 +61,9 @@ export function html(strings, ...values) {
export class DangerouslyIncludeUnescapedHTML {
#content;

/**
* @param {{ __content: string}} param0
*/
constructor({ __content }) {
this.#content = __content;
}
Expand Down
25 changes: 19 additions & 6 deletions lib/http-incoming.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,16 @@ import { Assets } from './assets.js';

const inspect = Symbol.for('nodejs.util.inspect.custom');

/**
* @param {unknown} request
* @returns {URL}
*/
const urlFromRequest = (request) => {
// @ts-expect-error
const protocol = request?.protocol || 'http';
// @ts-expect-error
const host = request?.headers?.host || 'localhost';
// @ts-expect-error
const url = request?.url || '';
return new URL(url, `${protocol.replace(':', '')}://${host}`);
};
Expand Down Expand Up @@ -135,15 +142,21 @@ export default class HttpIncoming {

podlets.forEach((podlet) => {
if (podlet.css) {
podlet.css.forEach((item) => {
this.#css.push(item);
});
podlet.css.forEach(
/** @param {import("./asset-css.js").CssAsset} item */
(item) => {
this.#css.push(item);
},
);
}

if (podlet.js) {
podlet.js.forEach((item) => {
this.#js.push(item);
});
podlet.js.forEach(
/** @param {import("./asset-js.js").JavaScriptAsset} item */
(item) => {
this.#js.push(item);
},
);
}
});
}
Expand Down
25 changes: 19 additions & 6 deletions lib/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -114,15 +114,20 @@ export const uriRelativeToAbsolute = (input = '', base = '', extra = '') => {
* @returns {object} The http response object
*/
export const setAtLocalsPodium = (response = {}, property, value) => {
// @ts-expect-error We know response is an object
if (!response.locals) {
// @ts-expect-error We know response is an object
response.locals = {};
}

// @ts-expect-error We know response is an object
if (!response.locals.podium) {
// @ts-expect-error We know response is an object
response.locals.podium = {};
}

if (isString(property) && property !== '') {
// @ts-expect-error We know response is an object
response.locals.podium[property] = value;
}

Expand All @@ -133,20 +138,23 @@ export const setAtLocalsPodium = (response = {}, property, value) => {
* Get the value from a property on .locals.podium on an HTTP response object.
* Ensures that .locals.podium exists on the http response object.
*
* @param {object} [response] An HTTP response object.
* @param {unknown} [response] An HTTP response object.
* @param {string} [property] Property for the value.
* @returns {object | null} The property, or `null` if it does not exist.
*/
export const getFromLocalsPodium = (response = {}, property) => {
// @ts-expect-error We know response is an object
if (!response.locals) {
return null;
}

// @ts-expect-error We know response is an object
if (!response.locals.podium) {
return null;
}

if (isString(property) && property !== '') {
// @ts-expect-error We know response is an object
return response.locals.podium[property];
}

Expand Down Expand Up @@ -175,8 +183,8 @@ export const duplicateOnLocalsPodium = (
/**
* Serialize a context object into an HTTP header object, calling the function if a context value is callable.
*
* @param {object} [headers={}] An HTTP headers object the context will be copied to.
* @param {object} [context={}] A context object to copy from.
* @param {Record<string, string>} [headers={}] An HTTP headers object the context will be copied to.
* @param {Record<string, string | ((arg: unknown) => string)>} [context={}] A context object to copy from.
* @param {unknown} [arg=""] An argument value passed on to the function if a context value is a function.
* @returns {object} An object with deserialized context properties and values.
*
Expand All @@ -193,14 +201,18 @@ export const duplicateOnLocalsPodium = (
* ```
*/
export const serializeContext = (headers = {}, context = {}, arg = '') => {
/** @type {Record<string, string>} */
const localHeaders = headers;
Object.keys(context).forEach((key) => {
if (isString(context[key])) {
localHeaders[key] = context[key];
localHeaders[key] = /** @type {string} */ (context[key]);
}

if (isFunction(context[key])) {
localHeaders[key] = context[key](arg);
const contextFn = /** @type {((arg: unknown) => string)} */ (
context[key]
);
localHeaders[key] = contextFn(arg);
}
});
return localHeaders;
Expand All @@ -209,7 +221,7 @@ export const serializeContext = (headers = {}, context = {}, arg = '') => {
/**
* Deserialize a context object from an HTTP header object.
*
* @param {object} [headers={}] An HTTP headers object the context will be extracted from.
* @param {Record<string, string>} [headers={}] An HTTP headers object the context will be extracted from.
* @param {string} [prefix="podium"] The prefix used to mark what properties are context properties.
* @returns {object} An object with deserialized context properties and values.
*
Expand All @@ -225,6 +237,7 @@ export const serializeContext = (headers = {}, context = {}, arg = '') => {
* ```
*/
export const deserializeContext = (headers = {}, prefix = 'podium') => {
/** @type {Record<string, string>} */
const context = {};
Object.keys(headers).forEach((key) => {
if (key.startsWith(prefix)) {
Expand Down
Loading
Loading