@beignet/core
Version:
Core framework primitives for Beignet
192 lines • 7.14 kB
JavaScript
/** Error thrown when an event payload cannot survive canonical JSON transport. */
export class EventTransportError extends Error {
/** Stable event name. */
eventName;
/** Whether the value is not JSON-safe or changes under repeated parsing. */
reason;
/** Payload path associated with a JSON-safety failure. */
path;
constructor(args) {
super(`Event "${args.eventName}" payload ${args.message}`, {
cause: args.cause,
});
this.name = "EventTransportError";
this.eventName = args.eventName;
this.reason = args.reason;
this.path = args.path;
}
}
function childTransportPath(path, key) {
return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(key)
? `${path}.${key}`
: `${path}[${JSON.stringify(key)}]`;
}
function throwJsonSafetyError(args) {
throw new EventTransportError({
eventName: args.eventName,
reason: "not-json-safe",
path: args.path,
message: `is not transport-safe at ${args.path}: ${args.message}`,
});
}
export function toEventTransportValue(eventName, value, path = "payload", seen = new WeakSet()) {
if (value === null)
return null;
if (typeof value === "string" || typeof value === "boolean")
return value;
if (typeof value === "number") {
if (!Number.isFinite(value)) {
return throwJsonSafetyError({
eventName,
path,
message: "numbers must be finite.",
});
}
return Object.is(value, -0) ? 0 : value;
}
if (typeof value !== "object") {
return throwJsonSafetyError({
eventName,
path,
message: `received ${typeof value}; use null, strings, finite numbers, booleans, arrays, or plain objects.`,
});
}
try {
if (value instanceof Date) {
return throwJsonSafetyError({
eventName,
path,
message: "Date values are not supported; use an ISO string or number.",
});
}
if (seen.has(value)) {
return throwJsonSafetyError({
eventName,
path,
message: "circular references are not supported.",
});
}
seen.add(value);
if (Array.isArray(value)) {
const output = [];
const ownKeys = Reflect.ownKeys(value);
const hasUnsupportedKey = ownKeys.some((key) => {
if (key === "length")
return false;
if (typeof key !== "string")
return true;
const index = Number(key);
return (!Number.isInteger(index) ||
index < 0 ||
index >= value.length ||
String(index) !== key);
});
if (hasUnsupportedKey) {
return throwJsonSafetyError({
eventName,
path,
message: "arrays cannot contain symbol or custom properties.",
});
}
if (ownKeys.length !== value.length + 1) {
return throwJsonSafetyError({
eventName,
path,
message: "sparse arrays are not supported.",
});
}
for (let index = 0; index < value.length; index += 1) {
const key = String(index);
const itemPath = `${path}[${index}]`;
const descriptor = Object.getOwnPropertyDescriptor(value, key);
if (!descriptor) {
return throwJsonSafetyError({
eventName,
path: itemPath,
message: "sparse arrays are not supported.",
});
}
if (!("value" in descriptor) || !descriptor.enumerable) {
return throwJsonSafetyError({
eventName,
path: itemPath,
message: "array items must be enumerable data properties.",
});
}
output.push(toEventTransportValue(eventName, descriptor.value, itemPath, seen));
}
return output;
}
const prototype = Object.getPrototypeOf(value);
if (prototype !== Object.prototype && prototype !== null) {
return throwJsonSafetyError({
eventName,
path,
message: "objects must be plain objects.",
});
}
const output = {};
for (const key of Reflect.ownKeys(value)) {
if (typeof key !== "string") {
return throwJsonSafetyError({
eventName,
path,
message: "symbol properties are not supported.",
});
}
const propertyPath = childTransportPath(path, key);
const descriptor = Object.getOwnPropertyDescriptor(value, key);
if (!descriptor || !("value" in descriptor) || !descriptor.enumerable) {
return throwJsonSafetyError({
eventName,
path: propertyPath,
message: "properties must be enumerable data properties.",
});
}
Object.defineProperty(output, key, {
configurable: true,
enumerable: true,
writable: true,
value: toEventTransportValue(eventName, descriptor.value, propertyPath, seen),
});
}
return output;
}
catch (error) {
if (error instanceof EventTransportError)
throw error;
throw new EventTransportError({
eventName,
reason: "not-json-safe",
path,
message: `could not be inspected as transport data at ${path}.`,
cause: error,
});
}
finally {
seen.delete(value);
}
}
export function eventTransportValuesEqual(left, right) {
if (left === right)
return true;
if (left === null || right === null || typeof left !== typeof right) {
return false;
}
if (Array.isArray(left) || Array.isArray(right)) {
if (!Array.isArray(left) || !Array.isArray(right))
return false;
return (left.length === right.length &&
left.every((value, index) => eventTransportValuesEqual(value, right[index])));
}
if (typeof left !== "object" || typeof right !== "object")
return false;
const leftRecord = left;
const rightRecord = right;
const leftKeys = Object.keys(leftRecord).sort();
const rightKeys = Object.keys(rightRecord).sort();
return (leftKeys.length === rightKeys.length &&
leftKeys.every((key, index) => key === rightKeys[index] &&
eventTransportValuesEqual(leftRecord[key], rightRecord[key])));
}
//# sourceMappingURL=transport.js.map