UNPKG

@beignet/core

Version:

Core framework primitives for Beignet

192 lines 7.14 kB
/** 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