function isSerializable(val, seenObjects = new Set()) {
	if (!isSerializablePrimitiveOrPlainObject(val)) {
		return false;
	}
	if (typeof val === "object" && val !== null) {
		if (seenObjects.has(val)) {
			/*
			 * Since this is a depth-first traversal, encountering
			 * the same object again means there is a circular reference.
			 * Objects with circular references are not serializable.
			 */
			return false;
		}
		for (const property in val) {
			if (Object.hasOwn(val, property)) {
				if (!isSerializablePrimitiveOrPlainObject(val[property])) {
					return false;
				}
				if (
					typeof val[property] === "object" &&
					val[property] !== null
				) {
					if (
						/*
						 * We're creating a new Set of seen objects because we want to
						 * ensure that `val` doesn't appear again in this path, but it can appear
						 * in other paths. This allows for resuing objects in the graph, as long as
						 * there are no cycles.
						 */
						!isSerializable(
							val[property],
							new Set([...seenObjects, val]),
						)
					) {
						return false;
					}
				}
			}
		}
	}
	return true;
}
Check if a value is serializable.
Functions or objects like RegExp cannot be serialized by JSON.stringify().
Inspired by: https://stackoverflow.com/questions/30579940/reliable-way-to-check-if-objects-is-serializable-in-javascript
@param {any} val The value
@param {Set<Object>} seenObjects Objects already seen in this path from the root object.
@returns {boolean} `true` if the value is serializable