@prisma/param-graph
Advanced tools
| /** | ||
| * Binary serialization for ParamGraph. | ||
| * | ||
| * This module handles compact binary encoding/decoding of the param graph structure. | ||
| * The format uses a hybrid approach: JSON string array for field names + binary blob | ||
| * for structural data (nodes, edges, roots). | ||
| * | ||
| * ## Serialized Representation | ||
| * | ||
| * ``` | ||
| * SerializedParamGraph { | ||
| * strings: string[] // String table (field names, enum names, root keys) | ||
| * graph: string // Base64url-encoded binary blob | ||
| * } | ||
| * ``` | ||
| * | ||
| * ## Why Hybrid? | ||
| * | ||
| * - **Strings stay as JSON**: V8's JSON.parse is highly optimized for string arrays | ||
| * - **Structure goes binary**: Indices, flags, masks benefit from compact encoding | ||
| * - **Best of both**: Fast parsing + compact size where it matters | ||
| * | ||
| * ## Variable-Length Encoding | ||
| * | ||
| * All integer values (except fixed-size fields like `scalarMask` and `flags`) use | ||
| * unsigned LEB128 (Little Endian Base 128) variable-length encoding: | ||
| * | ||
| * - Values 0-127: 1 byte | ||
| * - Values 128-16383: 2 bytes | ||
| * - Values 16384-2097151: 3 bytes | ||
| * - And so on... | ||
| * | ||
| * Optional values use value+1 encoding: 0 means "none/undefined", N+1 means actual value N. | ||
| * | ||
| * ## Binary Blob Layout | ||
| * | ||
| * ``` | ||
| * ┌───────────────────────────────────────────────────────────────────┐ | ||
| * │ HEADER │ | ||
| * ├───────────────────────────────────────────────────────────────────┤ | ||
| * │ inputNodeCount: varuint │ | ||
| * │ outputNodeCount: varuint │ | ||
| * │ rootCount: varuint │ | ||
| * └───────────────────────────────────────────────────────────────────┘ | ||
| * | ||
| * ┌───────────────────────────────────────────────────────────────────┐ | ||
| * │ INPUT NODES (repeated inputNodeCount times) │ | ||
| * ├───────────────────────────────────────────────────────────────────┤ | ||
| * │ edgeCount: varuint │ | ||
| * │ edges[] │ | ||
| * └───────────────────────────────────────────────────────────────────┘ | ||
| * | ||
| * ┌───────────────────────────────────────────────────────────────────┐ | ||
| * │ INPUT EDGE │ | ||
| * ├───────────────────────────────────────────────────────────────────┤ | ||
| * │ fieldIndex: varuint │ | ||
| * │ scalarMask: u16 │ | ||
| * │ childNodeId: varuint (0=none, N+1=actual) │ | ||
| * │ enumNameIndex: varuint (0=none, N+1=actual) │ | ||
| * │ flags: u8 │ | ||
| * └───────────────────────────────────────────────────────────────────┘ | ||
| * | ||
| * ┌───────────────────────────────────────────────────────────────────┐ | ||
| * │ OUTPUT NODES (repeated outputNodeCount times) │ | ||
| * ├───────────────────────────────────────────────────────────────────┤ | ||
| * │ edgeCount: varuint │ | ||
| * │ edges[] │ | ||
| * └───────────────────────────────────────────────────────────────────┘ | ||
| * | ||
| * ┌───────────────────────────────────────────────────────────────────┐ | ||
| * │ OUTPUT EDGE │ | ||
| * ├───────────────────────────────────────────────────────────────────┤ | ||
| * │ fieldIndex: varuint │ | ||
| * │ argsNodeId: varuint (0=none, N+1=actual) │ | ||
| * │ outputNodeId: varuint (0=none, N+1=actual) │ | ||
| * └───────────────────────────────────────────────────────────────────┘ | ||
| * | ||
| * ┌───────────────────────────────────────────────────────────────────┐ | ||
| * │ ROOTS (repeated rootCount times) │ | ||
| * ├───────────────────────────────────────────────────────────────────┤ | ||
| * │ keyIndex: varuint │ | ||
| * │ argsNodeId: varuint (0=none, N+1=actual) │ | ||
| * │ outputNodeId: varuint (0=none, N+1=actual) │ | ||
| * └───────────────────────────────────────────────────────────────────┘ | ||
| * ``` | ||
| * | ||
| * ## Embedding in Generated Client | ||
| * | ||
| * ```js | ||
| * config.parameterizationSchema = { | ||
| * strings: JSON.parse('["where","id","email",...]'), | ||
| * graph: "base64url_encoded_binary_blob..." | ||
| * } | ||
| * ``` | ||
| */ | ||
| import type { ParamGraphData } from './types'; | ||
| /** | ||
| * Serialized format stored in the generated client. | ||
| */ | ||
| export interface SerializedParamGraph { | ||
| /** String table (field names, enum names, root keys) */ | ||
| strings: string[]; | ||
| /** Base64url-encoded binary blob for structural data */ | ||
| graph: string; | ||
| } | ||
| /** | ||
| * Serializes a ParamGraphData to the compact binary format. | ||
| */ | ||
| export declare function serializeParamGraph(data: ParamGraphData): SerializedParamGraph; | ||
| /** | ||
| * Deserializes a binary-encoded ParamGraph. | ||
| */ | ||
| export declare function deserializeParamGraph(serialized: SerializedParamGraph): ParamGraphData; |
| "use strict"; | ||
| var __defProp = Object.defineProperty; | ||
| var __getOwnPropDesc = Object.getOwnPropertyDescriptor; | ||
| var __getOwnPropNames = Object.getOwnPropertyNames; | ||
| var __hasOwnProp = Object.prototype.hasOwnProperty; | ||
| var __export = (target, all) => { | ||
| for (var name in all) | ||
| __defProp(target, name, { get: all[name], enumerable: true }); | ||
| }; | ||
| var __copyProps = (to, from, except, desc) => { | ||
| if (from && typeof from === "object" || typeof from === "function") { | ||
| for (let key of __getOwnPropNames(from)) | ||
| if (!__hasOwnProp.call(to, key) && key !== except) | ||
| __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); | ||
| } | ||
| return to; | ||
| }; | ||
| var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); | ||
| var serialization_exports = {}; | ||
| __export(serialization_exports, { | ||
| deserializeParamGraph: () => deserializeParamGraph, | ||
| serializeParamGraph: () => serializeParamGraph | ||
| }); | ||
| module.exports = __toCommonJS(serialization_exports); | ||
| function serializeParamGraph(data) { | ||
| return new Serializer(data).serialize(); | ||
| } | ||
| function deserializeParamGraph(serialized) { | ||
| return new Deserializer(serialized).deserialize(); | ||
| } | ||
| function encodeBase64url(bytes) { | ||
| return Buffer.from(bytes.buffer, bytes.byteOffset, bytes.byteLength).toString("base64url"); | ||
| } | ||
| function decodeBase64url(str) { | ||
| return Buffer.from(str, "base64url"); | ||
| } | ||
| function varuintSize(value) { | ||
| let size = 1; | ||
| while (value >= 128) { | ||
| size++; | ||
| value >>>= 7; | ||
| } | ||
| return size; | ||
| } | ||
| class Serializer { | ||
| #data; | ||
| #buffer; | ||
| #view; | ||
| #offset = 0; | ||
| #rootKeys; | ||
| constructor(data) { | ||
| this.#data = data; | ||
| this.#rootKeys = Object.keys(data.roots); | ||
| const size = this.#calculateBufferSize(); | ||
| this.#buffer = new ArrayBuffer(size); | ||
| this.#view = new DataView(this.#buffer); | ||
| } | ||
| serialize() { | ||
| this.#writeHeader(); | ||
| this.#writeInputNodes(); | ||
| this.#writeOutputNodes(); | ||
| this.#writeRoots(); | ||
| return { | ||
| strings: this.#data.strings, | ||
| graph: encodeBase64url(new Uint8Array(this.#buffer, 0, this.#offset)) | ||
| }; | ||
| } | ||
| #writeVaruint(value) { | ||
| while (value >= 128) { | ||
| this.#view.setUint8(this.#offset++, value & 127 | 128); | ||
| value >>>= 7; | ||
| } | ||
| this.#view.setUint8(this.#offset++, value); | ||
| } | ||
| #writeOptionalVaruint(value) { | ||
| this.#writeVaruint(value === void 0 ? 0 : value + 1); | ||
| } | ||
| #writeByte(value) { | ||
| this.#view.setUint8(this.#offset, value); | ||
| this.#offset += 1; | ||
| } | ||
| #writeU16(value) { | ||
| this.#view.setUint16(this.#offset, value, true); | ||
| this.#offset += 2; | ||
| } | ||
| #calculateBufferSize() { | ||
| let size = 0; | ||
| size += varuintSize(this.#data.inputNodes.length); | ||
| size += varuintSize(this.#data.outputNodes.length); | ||
| size += varuintSize(this.#rootKeys.length); | ||
| for (const node of this.#data.inputNodes) { | ||
| const fieldIndices = Object.keys(node.edges).map(Number); | ||
| size += varuintSize(fieldIndices.length); | ||
| for (const fieldIndex of fieldIndices) { | ||
| const edge = node.edges[fieldIndex]; | ||
| size += varuintSize(fieldIndex); | ||
| size += 2; | ||
| size += varuintSize(edge.childNodeId === void 0 ? 0 : edge.childNodeId + 1); | ||
| size += varuintSize(edge.enumNameIndex === void 0 ? 0 : edge.enumNameIndex + 1); | ||
| size += 1; | ||
| } | ||
| } | ||
| for (const node of this.#data.outputNodes) { | ||
| const fieldIndices = Object.keys(node.edges).map(Number); | ||
| size += varuintSize(fieldIndices.length); | ||
| for (const fieldIndex of fieldIndices) { | ||
| const edge = node.edges[fieldIndex]; | ||
| size += varuintSize(fieldIndex); | ||
| size += varuintSize(edge.argsNodeId === void 0 ? 0 : edge.argsNodeId + 1); | ||
| size += varuintSize(edge.outputNodeId === void 0 ? 0 : edge.outputNodeId + 1); | ||
| } | ||
| } | ||
| for (const key of this.#rootKeys) { | ||
| const root = this.#data.roots[key]; | ||
| const keyIndex = this.#data.strings.indexOf(key); | ||
| size += varuintSize(keyIndex); | ||
| size += varuintSize(root.argsNodeId === void 0 ? 0 : root.argsNodeId + 1); | ||
| size += varuintSize(root.outputNodeId === void 0 ? 0 : root.outputNodeId + 1); | ||
| } | ||
| return size; | ||
| } | ||
| #writeHeader() { | ||
| this.#writeVaruint(this.#data.inputNodes.length); | ||
| this.#writeVaruint(this.#data.outputNodes.length); | ||
| this.#writeVaruint(this.#rootKeys.length); | ||
| } | ||
| #writeInputNodes() { | ||
| for (const node of this.#data.inputNodes) { | ||
| const fieldIndices = Object.keys(node.edges).map(Number); | ||
| this.#writeVaruint(fieldIndices.length); | ||
| for (const fieldIndex of fieldIndices) { | ||
| const edge = node.edges[fieldIndex]; | ||
| this.#writeVaruint(fieldIndex); | ||
| this.#writeU16(edge.scalarMask ?? 0); | ||
| this.#writeOptionalVaruint(edge.childNodeId); | ||
| this.#writeOptionalVaruint(edge.enumNameIndex); | ||
| this.#writeByte(edge.flags); | ||
| } | ||
| } | ||
| } | ||
| #writeOutputNodes() { | ||
| for (const node of this.#data.outputNodes) { | ||
| const fieldIndices = Object.keys(node.edges).map(Number); | ||
| this.#writeVaruint(fieldIndices.length); | ||
| for (const fieldIndex of fieldIndices) { | ||
| const edge = node.edges[fieldIndex]; | ||
| this.#writeVaruint(fieldIndex); | ||
| this.#writeOptionalVaruint(edge.argsNodeId); | ||
| this.#writeOptionalVaruint(edge.outputNodeId); | ||
| } | ||
| } | ||
| } | ||
| #writeRoots() { | ||
| for (const key of this.#rootKeys) { | ||
| const root = this.#data.roots[key]; | ||
| const keyIndex = this.#data.strings.indexOf(key); | ||
| if (keyIndex === -1) { | ||
| throw new Error(`Root key "${key}" not found in strings table`); | ||
| } | ||
| this.#writeVaruint(keyIndex); | ||
| this.#writeOptionalVaruint(root.argsNodeId); | ||
| this.#writeOptionalVaruint(root.outputNodeId); | ||
| } | ||
| } | ||
| } | ||
| class Deserializer { | ||
| #serialized; | ||
| #view; | ||
| #offset = 0; | ||
| constructor(serialized) { | ||
| this.#serialized = serialized; | ||
| const bytes = decodeBase64url(serialized.graph); | ||
| this.#view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength); | ||
| } | ||
| deserialize() { | ||
| const { inputNodeCount, outputNodeCount, rootCount } = this.#readHeader(); | ||
| const inputNodes = this.#readInputNodes(inputNodeCount); | ||
| const outputNodes = this.#readOutputNodes(outputNodeCount); | ||
| const roots = this.#readRoots(rootCount); | ||
| return { | ||
| strings: this.#serialized.strings, | ||
| inputNodes, | ||
| outputNodes, | ||
| roots | ||
| }; | ||
| } | ||
| #readVaruint() { | ||
| let value = 0; | ||
| let shift = 0; | ||
| let byte; | ||
| do { | ||
| byte = this.#view.getUint8(this.#offset++); | ||
| value |= (byte & 127) << shift; | ||
| shift += 7; | ||
| } while (byte >= 128); | ||
| return value; | ||
| } | ||
| #readOptionalVaruint() { | ||
| const value = this.#readVaruint(); | ||
| return value === 0 ? void 0 : value - 1; | ||
| } | ||
| #readByte() { | ||
| const value = this.#view.getUint8(this.#offset); | ||
| this.#offset += 1; | ||
| return value; | ||
| } | ||
| #readU16() { | ||
| const value = this.#view.getUint16(this.#offset, true); | ||
| this.#offset += 2; | ||
| return value; | ||
| } | ||
| #readHeader() { | ||
| const inputNodeCount = this.#readVaruint(); | ||
| const outputNodeCount = this.#readVaruint(); | ||
| const rootCount = this.#readVaruint(); | ||
| return { inputNodeCount, outputNodeCount, rootCount }; | ||
| } | ||
| #readInputNodes(count) { | ||
| const inputNodes = []; | ||
| for (let i = 0; i < count; i++) { | ||
| const edgeCount = this.#readVaruint(); | ||
| const edges = {}; | ||
| for (let j = 0; j < edgeCount; j++) { | ||
| const fieldIndex = this.#readVaruint(); | ||
| const scalarMask = this.#readU16(); | ||
| const childNodeId = this.#readOptionalVaruint(); | ||
| const enumNameIndex = this.#readOptionalVaruint(); | ||
| const flags = this.#readByte(); | ||
| const edge = { flags }; | ||
| if (scalarMask !== 0) edge.scalarMask = scalarMask; | ||
| if (childNodeId !== void 0) edge.childNodeId = childNodeId; | ||
| if (enumNameIndex !== void 0) edge.enumNameIndex = enumNameIndex; | ||
| edges[fieldIndex] = edge; | ||
| } | ||
| inputNodes.push({ edges }); | ||
| } | ||
| return inputNodes; | ||
| } | ||
| #readOutputNodes(count) { | ||
| const outputNodes = []; | ||
| for (let i = 0; i < count; i++) { | ||
| const edgeCount = this.#readVaruint(); | ||
| const edges = {}; | ||
| for (let j = 0; j < edgeCount; j++) { | ||
| const fieldIndex = this.#readVaruint(); | ||
| const argsNodeId = this.#readOptionalVaruint(); | ||
| const outputNodeId = this.#readOptionalVaruint(); | ||
| const edge = {}; | ||
| if (argsNodeId !== void 0) edge.argsNodeId = argsNodeId; | ||
| if (outputNodeId !== void 0) edge.outputNodeId = outputNodeId; | ||
| edges[fieldIndex] = edge; | ||
| } | ||
| outputNodes.push({ edges }); | ||
| } | ||
| return outputNodes; | ||
| } | ||
| #readRoots(count) { | ||
| const roots = {}; | ||
| for (let i = 0; i < count; i++) { | ||
| const keyIndex = this.#readVaruint(); | ||
| const argsNodeId = this.#readOptionalVaruint(); | ||
| const outputNodeId = this.#readOptionalVaruint(); | ||
| const key = this.#serialized.strings[keyIndex]; | ||
| const root = {}; | ||
| if (argsNodeId !== void 0) root.argsNodeId = argsNodeId; | ||
| if (outputNodeId !== void 0) root.outputNodeId = outputNodeId; | ||
| roots[key] = root; | ||
| } | ||
| return roots; | ||
| } | ||
| } | ||
| // Annotate the CommonJS export names for ESM import in node: | ||
| 0 && (module.exports = { | ||
| deserializeParamGraph, | ||
| serializeParamGraph | ||
| }); |
| function serializeParamGraph(data) { | ||
| return new Serializer(data).serialize(); | ||
| } | ||
| function deserializeParamGraph(serialized) { | ||
| return new Deserializer(serialized).deserialize(); | ||
| } | ||
| function encodeBase64url(bytes) { | ||
| return Buffer.from(bytes.buffer, bytes.byteOffset, bytes.byteLength).toString("base64url"); | ||
| } | ||
| function decodeBase64url(str) { | ||
| return Buffer.from(str, "base64url"); | ||
| } | ||
| function varuintSize(value) { | ||
| let size = 1; | ||
| while (value >= 128) { | ||
| size++; | ||
| value >>>= 7; | ||
| } | ||
| return size; | ||
| } | ||
| class Serializer { | ||
| #data; | ||
| #buffer; | ||
| #view; | ||
| #offset = 0; | ||
| #rootKeys; | ||
| constructor(data) { | ||
| this.#data = data; | ||
| this.#rootKeys = Object.keys(data.roots); | ||
| const size = this.#calculateBufferSize(); | ||
| this.#buffer = new ArrayBuffer(size); | ||
| this.#view = new DataView(this.#buffer); | ||
| } | ||
| serialize() { | ||
| this.#writeHeader(); | ||
| this.#writeInputNodes(); | ||
| this.#writeOutputNodes(); | ||
| this.#writeRoots(); | ||
| return { | ||
| strings: this.#data.strings, | ||
| graph: encodeBase64url(new Uint8Array(this.#buffer, 0, this.#offset)) | ||
| }; | ||
| } | ||
| #writeVaruint(value) { | ||
| while (value >= 128) { | ||
| this.#view.setUint8(this.#offset++, value & 127 | 128); | ||
| value >>>= 7; | ||
| } | ||
| this.#view.setUint8(this.#offset++, value); | ||
| } | ||
| #writeOptionalVaruint(value) { | ||
| this.#writeVaruint(value === void 0 ? 0 : value + 1); | ||
| } | ||
| #writeByte(value) { | ||
| this.#view.setUint8(this.#offset, value); | ||
| this.#offset += 1; | ||
| } | ||
| #writeU16(value) { | ||
| this.#view.setUint16(this.#offset, value, true); | ||
| this.#offset += 2; | ||
| } | ||
| #calculateBufferSize() { | ||
| let size = 0; | ||
| size += varuintSize(this.#data.inputNodes.length); | ||
| size += varuintSize(this.#data.outputNodes.length); | ||
| size += varuintSize(this.#rootKeys.length); | ||
| for (const node of this.#data.inputNodes) { | ||
| const fieldIndices = Object.keys(node.edges).map(Number); | ||
| size += varuintSize(fieldIndices.length); | ||
| for (const fieldIndex of fieldIndices) { | ||
| const edge = node.edges[fieldIndex]; | ||
| size += varuintSize(fieldIndex); | ||
| size += 2; | ||
| size += varuintSize(edge.childNodeId === void 0 ? 0 : edge.childNodeId + 1); | ||
| size += varuintSize(edge.enumNameIndex === void 0 ? 0 : edge.enumNameIndex + 1); | ||
| size += 1; | ||
| } | ||
| } | ||
| for (const node of this.#data.outputNodes) { | ||
| const fieldIndices = Object.keys(node.edges).map(Number); | ||
| size += varuintSize(fieldIndices.length); | ||
| for (const fieldIndex of fieldIndices) { | ||
| const edge = node.edges[fieldIndex]; | ||
| size += varuintSize(fieldIndex); | ||
| size += varuintSize(edge.argsNodeId === void 0 ? 0 : edge.argsNodeId + 1); | ||
| size += varuintSize(edge.outputNodeId === void 0 ? 0 : edge.outputNodeId + 1); | ||
| } | ||
| } | ||
| for (const key of this.#rootKeys) { | ||
| const root = this.#data.roots[key]; | ||
| const keyIndex = this.#data.strings.indexOf(key); | ||
| size += varuintSize(keyIndex); | ||
| size += varuintSize(root.argsNodeId === void 0 ? 0 : root.argsNodeId + 1); | ||
| size += varuintSize(root.outputNodeId === void 0 ? 0 : root.outputNodeId + 1); | ||
| } | ||
| return size; | ||
| } | ||
| #writeHeader() { | ||
| this.#writeVaruint(this.#data.inputNodes.length); | ||
| this.#writeVaruint(this.#data.outputNodes.length); | ||
| this.#writeVaruint(this.#rootKeys.length); | ||
| } | ||
| #writeInputNodes() { | ||
| for (const node of this.#data.inputNodes) { | ||
| const fieldIndices = Object.keys(node.edges).map(Number); | ||
| this.#writeVaruint(fieldIndices.length); | ||
| for (const fieldIndex of fieldIndices) { | ||
| const edge = node.edges[fieldIndex]; | ||
| this.#writeVaruint(fieldIndex); | ||
| this.#writeU16(edge.scalarMask ?? 0); | ||
| this.#writeOptionalVaruint(edge.childNodeId); | ||
| this.#writeOptionalVaruint(edge.enumNameIndex); | ||
| this.#writeByte(edge.flags); | ||
| } | ||
| } | ||
| } | ||
| #writeOutputNodes() { | ||
| for (const node of this.#data.outputNodes) { | ||
| const fieldIndices = Object.keys(node.edges).map(Number); | ||
| this.#writeVaruint(fieldIndices.length); | ||
| for (const fieldIndex of fieldIndices) { | ||
| const edge = node.edges[fieldIndex]; | ||
| this.#writeVaruint(fieldIndex); | ||
| this.#writeOptionalVaruint(edge.argsNodeId); | ||
| this.#writeOptionalVaruint(edge.outputNodeId); | ||
| } | ||
| } | ||
| } | ||
| #writeRoots() { | ||
| for (const key of this.#rootKeys) { | ||
| const root = this.#data.roots[key]; | ||
| const keyIndex = this.#data.strings.indexOf(key); | ||
| if (keyIndex === -1) { | ||
| throw new Error(`Root key "${key}" not found in strings table`); | ||
| } | ||
| this.#writeVaruint(keyIndex); | ||
| this.#writeOptionalVaruint(root.argsNodeId); | ||
| this.#writeOptionalVaruint(root.outputNodeId); | ||
| } | ||
| } | ||
| } | ||
| class Deserializer { | ||
| #serialized; | ||
| #view; | ||
| #offset = 0; | ||
| constructor(serialized) { | ||
| this.#serialized = serialized; | ||
| const bytes = decodeBase64url(serialized.graph); | ||
| this.#view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength); | ||
| } | ||
| deserialize() { | ||
| const { inputNodeCount, outputNodeCount, rootCount } = this.#readHeader(); | ||
| const inputNodes = this.#readInputNodes(inputNodeCount); | ||
| const outputNodes = this.#readOutputNodes(outputNodeCount); | ||
| const roots = this.#readRoots(rootCount); | ||
| return { | ||
| strings: this.#serialized.strings, | ||
| inputNodes, | ||
| outputNodes, | ||
| roots | ||
| }; | ||
| } | ||
| #readVaruint() { | ||
| let value = 0; | ||
| let shift = 0; | ||
| let byte; | ||
| do { | ||
| byte = this.#view.getUint8(this.#offset++); | ||
| value |= (byte & 127) << shift; | ||
| shift += 7; | ||
| } while (byte >= 128); | ||
| return value; | ||
| } | ||
| #readOptionalVaruint() { | ||
| const value = this.#readVaruint(); | ||
| return value === 0 ? void 0 : value - 1; | ||
| } | ||
| #readByte() { | ||
| const value = this.#view.getUint8(this.#offset); | ||
| this.#offset += 1; | ||
| return value; | ||
| } | ||
| #readU16() { | ||
| const value = this.#view.getUint16(this.#offset, true); | ||
| this.#offset += 2; | ||
| return value; | ||
| } | ||
| #readHeader() { | ||
| const inputNodeCount = this.#readVaruint(); | ||
| const outputNodeCount = this.#readVaruint(); | ||
| const rootCount = this.#readVaruint(); | ||
| return { inputNodeCount, outputNodeCount, rootCount }; | ||
| } | ||
| #readInputNodes(count) { | ||
| const inputNodes = []; | ||
| for (let i = 0; i < count; i++) { | ||
| const edgeCount = this.#readVaruint(); | ||
| const edges = {}; | ||
| for (let j = 0; j < edgeCount; j++) { | ||
| const fieldIndex = this.#readVaruint(); | ||
| const scalarMask = this.#readU16(); | ||
| const childNodeId = this.#readOptionalVaruint(); | ||
| const enumNameIndex = this.#readOptionalVaruint(); | ||
| const flags = this.#readByte(); | ||
| const edge = { flags }; | ||
| if (scalarMask !== 0) edge.scalarMask = scalarMask; | ||
| if (childNodeId !== void 0) edge.childNodeId = childNodeId; | ||
| if (enumNameIndex !== void 0) edge.enumNameIndex = enumNameIndex; | ||
| edges[fieldIndex] = edge; | ||
| } | ||
| inputNodes.push({ edges }); | ||
| } | ||
| return inputNodes; | ||
| } | ||
| #readOutputNodes(count) { | ||
| const outputNodes = []; | ||
| for (let i = 0; i < count; i++) { | ||
| const edgeCount = this.#readVaruint(); | ||
| const edges = {}; | ||
| for (let j = 0; j < edgeCount; j++) { | ||
| const fieldIndex = this.#readVaruint(); | ||
| const argsNodeId = this.#readOptionalVaruint(); | ||
| const outputNodeId = this.#readOptionalVaruint(); | ||
| const edge = {}; | ||
| if (argsNodeId !== void 0) edge.argsNodeId = argsNodeId; | ||
| if (outputNodeId !== void 0) edge.outputNodeId = outputNodeId; | ||
| edges[fieldIndex] = edge; | ||
| } | ||
| outputNodes.push({ edges }); | ||
| } | ||
| return outputNodes; | ||
| } | ||
| #readRoots(count) { | ||
| const roots = {}; | ||
| for (let i = 0; i < count; i++) { | ||
| const keyIndex = this.#readVaruint(); | ||
| const argsNodeId = this.#readOptionalVaruint(); | ||
| const outputNodeId = this.#readOptionalVaruint(); | ||
| const key = this.#serialized.strings[keyIndex]; | ||
| const root = {}; | ||
| if (argsNodeId !== void 0) root.argsNodeId = argsNodeId; | ||
| if (outputNodeId !== void 0) root.outputNodeId = outputNodeId; | ||
| roots[key] = root; | ||
| } | ||
| return roots; | ||
| } | ||
| } | ||
| export { | ||
| deserializeParamGraph, | ||
| serializeParamGraph | ||
| }; |
| export {}; |
| "use strict"; | ||
| var import_vitest = require("vitest"); | ||
| var import_serialization = require("./serialization"); | ||
| (0, import_vitest.describe)("param-graph serialization", () => { | ||
| (0, import_vitest.test)("roundtrip with empty data", () => { | ||
| const data = { | ||
| strings: ["root1"], | ||
| inputNodes: [], | ||
| outputNodes: [], | ||
| roots: { root1: {} } | ||
| }; | ||
| const serialized = (0, import_serialization.serializeParamGraph)(data); | ||
| const deserialized = (0, import_serialization.deserializeParamGraph)(serialized); | ||
| (0, import_vitest.expect)(deserialized.strings).toEqual(data.strings); | ||
| (0, import_vitest.expect)(deserialized.inputNodes).toEqual(data.inputNodes); | ||
| (0, import_vitest.expect)(deserialized.outputNodes).toEqual(data.outputNodes); | ||
| (0, import_vitest.expect)(deserialized.roots).toEqual(data.roots); | ||
| }); | ||
| (0, import_vitest.test)("roundtrip with small data", () => { | ||
| const data = { | ||
| strings: ["findMany", "where", "id"], | ||
| inputNodes: [{ edges: { 1: { flags: 0, scalarMask: 1 } } }], | ||
| outputNodes: [{ edges: { 2: { argsNodeId: 0 } } }], | ||
| roots: { findMany: { argsNodeId: 0, outputNodeId: 0 } } | ||
| }; | ||
| const serialized = (0, import_serialization.serializeParamGraph)(data); | ||
| const deserialized = (0, import_serialization.deserializeParamGraph)(serialized); | ||
| (0, import_vitest.expect)(deserialized.strings).toEqual(data.strings); | ||
| (0, import_vitest.expect)(deserialized.inputNodes.length).toBe(data.inputNodes.length); | ||
| (0, import_vitest.expect)(deserialized.outputNodes.length).toBe(data.outputNodes.length); | ||
| (0, import_vitest.expect)(Object.keys(deserialized.roots)).toEqual(Object.keys(data.roots)); | ||
| }); | ||
| (0, import_vitest.test)("roundtrip with multiple nodes and edges", () => { | ||
| const data = { | ||
| strings: ["findMany", "create", "update", "where", "data", "id", "name", "Status"], | ||
| inputNodes: [ | ||
| { edges: { 3: { flags: 1, scalarMask: 1, childNodeId: 1 }, 4: { flags: 0, scalarMask: 2 } } }, | ||
| { edges: { 5: { flags: 2, enumNameIndex: 7 } } } | ||
| ], | ||
| outputNodes: [{ edges: { 6: { argsNodeId: 0, outputNodeId: 1 } } }, { edges: {} }], | ||
| roots: { | ||
| findMany: { argsNodeId: 0, outputNodeId: 0 }, | ||
| create: { argsNodeId: 1, outputNodeId: 1 }, | ||
| update: { argsNodeId: 0 } | ||
| } | ||
| }; | ||
| const serialized = (0, import_serialization.serializeParamGraph)(data); | ||
| const deserialized = (0, import_serialization.deserializeParamGraph)(serialized); | ||
| (0, import_vitest.expect)(deserialized.inputNodes.length).toBe(2); | ||
| (0, import_vitest.expect)(deserialized.outputNodes.length).toBe(2); | ||
| (0, import_vitest.expect)(Object.keys(deserialized.roots).length).toBe(3); | ||
| const inputEdge = deserialized.inputNodes[0].edges[3]; | ||
| (0, import_vitest.expect)(inputEdge.flags).toBe(1); | ||
| (0, import_vitest.expect)(inputEdge.scalarMask).toBe(1); | ||
| (0, import_vitest.expect)(inputEdge.childNodeId).toBe(1); | ||
| const enumEdge = deserialized.inputNodes[1].edges[5]; | ||
| (0, import_vitest.expect)(enumEdge.flags).toBe(2); | ||
| (0, import_vitest.expect)(enumEdge.enumNameIndex).toBe(7); | ||
| }); | ||
| (0, import_vitest.test)("handles undefined values correctly", () => { | ||
| const data = { | ||
| strings: ["root"], | ||
| inputNodes: [{ edges: { 0: { flags: 0 } } }], | ||
| outputNodes: [{ edges: { 0: {} } }], | ||
| roots: { root: { argsNodeId: 0 } } | ||
| }; | ||
| const serialized = (0, import_serialization.serializeParamGraph)(data); | ||
| const deserialized = (0, import_serialization.deserializeParamGraph)(serialized); | ||
| (0, import_vitest.expect)(deserialized.inputNodes[0].edges[0].childNodeId).toBeUndefined(); | ||
| (0, import_vitest.expect)(deserialized.inputNodes[0].edges[0].scalarMask).toBeUndefined(); | ||
| (0, import_vitest.expect)(deserialized.inputNodes[0].edges[0].enumNameIndex).toBeUndefined(); | ||
| (0, import_vitest.expect)(deserialized.outputNodes[0].edges[0].argsNodeId).toBeUndefined(); | ||
| (0, import_vitest.expect)(deserialized.outputNodes[0].edges[0].outputNodeId).toBeUndefined(); | ||
| (0, import_vitest.expect)(deserialized.roots["root"].outputNodeId).toBeUndefined(); | ||
| }); | ||
| (0, import_vitest.test)("base64url encoding produces URL-safe output", () => { | ||
| const data = { | ||
| strings: ["test"], | ||
| inputNodes: [{ edges: { 255: { flags: 255, scalarMask: 65535, childNodeId: 0, enumNameIndex: 0 } } }], | ||
| outputNodes: [], | ||
| roots: { test: { argsNodeId: 0 } } | ||
| }; | ||
| const serialized = (0, import_serialization.serializeParamGraph)(data); | ||
| (0, import_vitest.expect)(serialized.graph).toMatch(/^[A-Za-z0-9_-]+$/); | ||
| }); | ||
| (0, import_vitest.test)("handles large indices requiring multi-byte varints (128-16383)", () => { | ||
| const strings = Array.from({ length: 200 }, (_, i) => `field${i}`); | ||
| const data = { | ||
| strings, | ||
| inputNodes: [ | ||
| { | ||
| edges: { | ||
| // Use indices that require 2-byte encoding (128+) | ||
| 128: { flags: 1, childNodeId: 150 }, | ||
| 150: { flags: 2, enumNameIndex: 180 } | ||
| } | ||
| } | ||
| ], | ||
| outputNodes: [{ edges: { 199: { argsNodeId: 0, outputNodeId: 0 } } }], | ||
| roots: { field0: { argsNodeId: 0, outputNodeId: 0 } } | ||
| }; | ||
| const serialized = (0, import_serialization.serializeParamGraph)(data); | ||
| const deserialized = (0, import_serialization.deserializeParamGraph)(serialized); | ||
| (0, import_vitest.expect)(deserialized.inputNodes[0].edges[128].flags).toBe(1); | ||
| (0, import_vitest.expect)(deserialized.inputNodes[0].edges[128].childNodeId).toBe(150); | ||
| (0, import_vitest.expect)(deserialized.inputNodes[0].edges[150].flags).toBe(2); | ||
| (0, import_vitest.expect)(deserialized.inputNodes[0].edges[150].enumNameIndex).toBe(180); | ||
| (0, import_vitest.expect)(deserialized.outputNodes[0].edges[199].argsNodeId).toBe(0); | ||
| (0, import_vitest.expect)(deserialized.outputNodes[0].edges[199].outputNodeId).toBe(0); | ||
| }); | ||
| (0, import_vitest.test)("handles very large indices requiring 3-byte varints (16384+)", () => { | ||
| const strings = Array.from({ length: 17e3 }, (_, i) => `f${i}`); | ||
| const data = { | ||
| strings, | ||
| inputNodes: [ | ||
| { | ||
| edges: { | ||
| 16384: { flags: 1, childNodeId: 16500 } | ||
| } | ||
| } | ||
| ], | ||
| outputNodes: [{ edges: { 16999: { argsNodeId: 0, outputNodeId: 0 } } }], | ||
| roots: { f0: { argsNodeId: 0, outputNodeId: 0 } } | ||
| }; | ||
| const serialized = (0, import_serialization.serializeParamGraph)(data); | ||
| const deserialized = (0, import_serialization.deserializeParamGraph)(serialized); | ||
| (0, import_vitest.expect)(deserialized.inputNodes[0].edges[16384].flags).toBe(1); | ||
| (0, import_vitest.expect)(deserialized.inputNodes[0].edges[16384].childNodeId).toBe(16500); | ||
| (0, import_vitest.expect)(deserialized.outputNodes[0].edges[16999].argsNodeId).toBe(0); | ||
| }); | ||
| (0, import_vitest.test)("varint boundary values encode and decode correctly", () => { | ||
| const boundaryValues = [0, 1, 127, 128, 16383, 16384]; | ||
| for (const value of boundaryValues) { | ||
| const strings = Array.from({ length: Math.max(value + 1, 2) }, (_, i) => `s${i}`); | ||
| const data = { | ||
| strings, | ||
| inputNodes: [{ edges: { [value]: { flags: 0 } } }], | ||
| outputNodes: [], | ||
| roots: { s0: {} } | ||
| }; | ||
| const serialized = (0, import_serialization.serializeParamGraph)(data); | ||
| const deserialized = (0, import_serialization.deserializeParamGraph)(serialized); | ||
| (0, import_vitest.expect)(deserialized.inputNodes[0].edges[value]).toBeDefined(); | ||
| (0, import_vitest.expect)(deserialized.inputNodes[0].edges[value].flags).toBe(0); | ||
| } | ||
| }); | ||
| (0, import_vitest.test)("optional value 0 is correctly distinguished from undefined", () => { | ||
| const data = { | ||
| strings: ["root", "field"], | ||
| inputNodes: [ | ||
| { edges: { 1: { flags: 0, childNodeId: 0 } } } | ||
| // childNodeId = 0 (not undefined) | ||
| ], | ||
| outputNodes: [ | ||
| { edges: { 1: { argsNodeId: 0, outputNodeId: 0 } } } | ||
| // both are 0 (not undefined) | ||
| ], | ||
| roots: { root: { argsNodeId: 0, outputNodeId: 0 } } | ||
| // both are 0 (not undefined) | ||
| }; | ||
| const serialized = (0, import_serialization.serializeParamGraph)(data); | ||
| const deserialized = (0, import_serialization.deserializeParamGraph)(serialized); | ||
| (0, import_vitest.expect)(deserialized.inputNodes[0].edges[1].childNodeId).toBe(0); | ||
| (0, import_vitest.expect)(deserialized.outputNodes[0].edges[1].argsNodeId).toBe(0); | ||
| (0, import_vitest.expect)(deserialized.outputNodes[0].edges[1].outputNodeId).toBe(0); | ||
| (0, import_vitest.expect)(deserialized.roots["root"].argsNodeId).toBe(0); | ||
| (0, import_vitest.expect)(deserialized.roots["root"].outputNodeId).toBe(0); | ||
| }); | ||
| }); |
| import { describe, expect, test } from "vitest"; | ||
| import { deserializeParamGraph, serializeParamGraph } from "./serialization"; | ||
| describe("param-graph serialization", () => { | ||
| test("roundtrip with empty data", () => { | ||
| const data = { | ||
| strings: ["root1"], | ||
| inputNodes: [], | ||
| outputNodes: [], | ||
| roots: { root1: {} } | ||
| }; | ||
| const serialized = serializeParamGraph(data); | ||
| const deserialized = deserializeParamGraph(serialized); | ||
| expect(deserialized.strings).toEqual(data.strings); | ||
| expect(deserialized.inputNodes).toEqual(data.inputNodes); | ||
| expect(deserialized.outputNodes).toEqual(data.outputNodes); | ||
| expect(deserialized.roots).toEqual(data.roots); | ||
| }); | ||
| test("roundtrip with small data", () => { | ||
| const data = { | ||
| strings: ["findMany", "where", "id"], | ||
| inputNodes: [{ edges: { 1: { flags: 0, scalarMask: 1 } } }], | ||
| outputNodes: [{ edges: { 2: { argsNodeId: 0 } } }], | ||
| roots: { findMany: { argsNodeId: 0, outputNodeId: 0 } } | ||
| }; | ||
| const serialized = serializeParamGraph(data); | ||
| const deserialized = deserializeParamGraph(serialized); | ||
| expect(deserialized.strings).toEqual(data.strings); | ||
| expect(deserialized.inputNodes.length).toBe(data.inputNodes.length); | ||
| expect(deserialized.outputNodes.length).toBe(data.outputNodes.length); | ||
| expect(Object.keys(deserialized.roots)).toEqual(Object.keys(data.roots)); | ||
| }); | ||
| test("roundtrip with multiple nodes and edges", () => { | ||
| const data = { | ||
| strings: ["findMany", "create", "update", "where", "data", "id", "name", "Status"], | ||
| inputNodes: [ | ||
| { edges: { 3: { flags: 1, scalarMask: 1, childNodeId: 1 }, 4: { flags: 0, scalarMask: 2 } } }, | ||
| { edges: { 5: { flags: 2, enumNameIndex: 7 } } } | ||
| ], | ||
| outputNodes: [{ edges: { 6: { argsNodeId: 0, outputNodeId: 1 } } }, { edges: {} }], | ||
| roots: { | ||
| findMany: { argsNodeId: 0, outputNodeId: 0 }, | ||
| create: { argsNodeId: 1, outputNodeId: 1 }, | ||
| update: { argsNodeId: 0 } | ||
| } | ||
| }; | ||
| const serialized = serializeParamGraph(data); | ||
| const deserialized = deserializeParamGraph(serialized); | ||
| expect(deserialized.inputNodes.length).toBe(2); | ||
| expect(deserialized.outputNodes.length).toBe(2); | ||
| expect(Object.keys(deserialized.roots).length).toBe(3); | ||
| const inputEdge = deserialized.inputNodes[0].edges[3]; | ||
| expect(inputEdge.flags).toBe(1); | ||
| expect(inputEdge.scalarMask).toBe(1); | ||
| expect(inputEdge.childNodeId).toBe(1); | ||
| const enumEdge = deserialized.inputNodes[1].edges[5]; | ||
| expect(enumEdge.flags).toBe(2); | ||
| expect(enumEdge.enumNameIndex).toBe(7); | ||
| }); | ||
| test("handles undefined values correctly", () => { | ||
| const data = { | ||
| strings: ["root"], | ||
| inputNodes: [{ edges: { 0: { flags: 0 } } }], | ||
| outputNodes: [{ edges: { 0: {} } }], | ||
| roots: { root: { argsNodeId: 0 } } | ||
| }; | ||
| const serialized = serializeParamGraph(data); | ||
| const deserialized = deserializeParamGraph(serialized); | ||
| expect(deserialized.inputNodes[0].edges[0].childNodeId).toBeUndefined(); | ||
| expect(deserialized.inputNodes[0].edges[0].scalarMask).toBeUndefined(); | ||
| expect(deserialized.inputNodes[0].edges[0].enumNameIndex).toBeUndefined(); | ||
| expect(deserialized.outputNodes[0].edges[0].argsNodeId).toBeUndefined(); | ||
| expect(deserialized.outputNodes[0].edges[0].outputNodeId).toBeUndefined(); | ||
| expect(deserialized.roots["root"].outputNodeId).toBeUndefined(); | ||
| }); | ||
| test("base64url encoding produces URL-safe output", () => { | ||
| const data = { | ||
| strings: ["test"], | ||
| inputNodes: [{ edges: { 255: { flags: 255, scalarMask: 65535, childNodeId: 0, enumNameIndex: 0 } } }], | ||
| outputNodes: [], | ||
| roots: { test: { argsNodeId: 0 } } | ||
| }; | ||
| const serialized = serializeParamGraph(data); | ||
| expect(serialized.graph).toMatch(/^[A-Za-z0-9_-]+$/); | ||
| }); | ||
| test("handles large indices requiring multi-byte varints (128-16383)", () => { | ||
| const strings = Array.from({ length: 200 }, (_, i) => `field${i}`); | ||
| const data = { | ||
| strings, | ||
| inputNodes: [ | ||
| { | ||
| edges: { | ||
| // Use indices that require 2-byte encoding (128+) | ||
| 128: { flags: 1, childNodeId: 150 }, | ||
| 150: { flags: 2, enumNameIndex: 180 } | ||
| } | ||
| } | ||
| ], | ||
| outputNodes: [{ edges: { 199: { argsNodeId: 0, outputNodeId: 0 } } }], | ||
| roots: { field0: { argsNodeId: 0, outputNodeId: 0 } } | ||
| }; | ||
| const serialized = serializeParamGraph(data); | ||
| const deserialized = deserializeParamGraph(serialized); | ||
| expect(deserialized.inputNodes[0].edges[128].flags).toBe(1); | ||
| expect(deserialized.inputNodes[0].edges[128].childNodeId).toBe(150); | ||
| expect(deserialized.inputNodes[0].edges[150].flags).toBe(2); | ||
| expect(deserialized.inputNodes[0].edges[150].enumNameIndex).toBe(180); | ||
| expect(deserialized.outputNodes[0].edges[199].argsNodeId).toBe(0); | ||
| expect(deserialized.outputNodes[0].edges[199].outputNodeId).toBe(0); | ||
| }); | ||
| test("handles very large indices requiring 3-byte varints (16384+)", () => { | ||
| const strings = Array.from({ length: 17e3 }, (_, i) => `f${i}`); | ||
| const data = { | ||
| strings, | ||
| inputNodes: [ | ||
| { | ||
| edges: { | ||
| 16384: { flags: 1, childNodeId: 16500 } | ||
| } | ||
| } | ||
| ], | ||
| outputNodes: [{ edges: { 16999: { argsNodeId: 0, outputNodeId: 0 } } }], | ||
| roots: { f0: { argsNodeId: 0, outputNodeId: 0 } } | ||
| }; | ||
| const serialized = serializeParamGraph(data); | ||
| const deserialized = deserializeParamGraph(serialized); | ||
| expect(deserialized.inputNodes[0].edges[16384].flags).toBe(1); | ||
| expect(deserialized.inputNodes[0].edges[16384].childNodeId).toBe(16500); | ||
| expect(deserialized.outputNodes[0].edges[16999].argsNodeId).toBe(0); | ||
| }); | ||
| test("varint boundary values encode and decode correctly", () => { | ||
| const boundaryValues = [0, 1, 127, 128, 16383, 16384]; | ||
| for (const value of boundaryValues) { | ||
| const strings = Array.from({ length: Math.max(value + 1, 2) }, (_, i) => `s${i}`); | ||
| const data = { | ||
| strings, | ||
| inputNodes: [{ edges: { [value]: { flags: 0 } } }], | ||
| outputNodes: [], | ||
| roots: { s0: {} } | ||
| }; | ||
| const serialized = serializeParamGraph(data); | ||
| const deserialized = deserializeParamGraph(serialized); | ||
| expect(deserialized.inputNodes[0].edges[value]).toBeDefined(); | ||
| expect(deserialized.inputNodes[0].edges[value].flags).toBe(0); | ||
| } | ||
| }); | ||
| test("optional value 0 is correctly distinguished from undefined", () => { | ||
| const data = { | ||
| strings: ["root", "field"], | ||
| inputNodes: [ | ||
| { edges: { 1: { flags: 0, childNodeId: 0 } } } | ||
| // childNodeId = 0 (not undefined) | ||
| ], | ||
| outputNodes: [ | ||
| { edges: { 1: { argsNodeId: 0, outputNodeId: 0 } } } | ||
| // both are 0 (not undefined) | ||
| ], | ||
| roots: { root: { argsNodeId: 0, outputNodeId: 0 } } | ||
| // both are 0 (not undefined) | ||
| }; | ||
| const serialized = serializeParamGraph(data); | ||
| const deserialized = deserializeParamGraph(serialized); | ||
| expect(deserialized.inputNodes[0].edges[1].childNodeId).toBe(0); | ||
| expect(deserialized.outputNodes[0].edges[1].argsNodeId).toBe(0); | ||
| expect(deserialized.outputNodes[0].edges[1].outputNodeId).toBe(0); | ||
| expect(deserialized.roots["root"].argsNodeId).toBe(0); | ||
| expect(deserialized.roots["root"].outputNodeId).toBe(0); | ||
| }); | ||
| }); |
| /** | ||
| * Internal data types for ParamGraph. | ||
| * | ||
| * These types represent the in-memory structure of the param graph, | ||
| * used both during building and after deserialization. | ||
| */ | ||
| /** | ||
| * Complete param graph data structure. | ||
| * This is the internal representation, not the serialized format. | ||
| */ | ||
| export interface ParamGraphData { | ||
| /** String table containing field names and enum names */ | ||
| strings: string[]; | ||
| /** Input nodes for argument objects and input types */ | ||
| inputNodes: InputNodeData[]; | ||
| /** Output nodes for selection traversal */ | ||
| outputNodes: OutputNodeData[]; | ||
| /** Root mapping: "Model.action" -> entry */ | ||
| roots: Record<string, RootEntryData>; | ||
| } | ||
| /** | ||
| * Input node data: describes parameterizable fields in an input object. | ||
| */ | ||
| export interface InputNodeData { | ||
| /** Map from string-table index to edge descriptor */ | ||
| edges: Record<number, InputEdgeData>; | ||
| } | ||
| /** | ||
| * Output node data: describes fields in a selection set. | ||
| */ | ||
| export interface OutputNodeData { | ||
| /** Map from string-table index to edge descriptor */ | ||
| edges: Record<number, OutputEdgeData>; | ||
| } | ||
| /** | ||
| * Input edge data: describes what a field accepts. | ||
| */ | ||
| export interface InputEdgeData { | ||
| /** Bit flags describing field capabilities (see EdgeFlag) */ | ||
| flags: number; | ||
| /** Child input node id (for object values) */ | ||
| childNodeId?: number; | ||
| /** Scalar type mask (see ScalarMask) */ | ||
| scalarMask?: number; | ||
| /** Enum name index into string table */ | ||
| enumNameIndex?: number; | ||
| } | ||
| /** | ||
| * Output edge data: describes a field in a selection set. | ||
| */ | ||
| export interface OutputEdgeData { | ||
| /** Args node id for this field */ | ||
| argsNodeId?: number; | ||
| /** Next output node id for nested selection */ | ||
| outputNodeId?: number; | ||
| } | ||
| /** | ||
| * Root entry data: entry point for an operation. | ||
| */ | ||
| export interface RootEntryData { | ||
| /** Args node id */ | ||
| argsNodeId?: number; | ||
| /** Output node id */ | ||
| outputNodeId?: number; | ||
| } |
| "use strict"; | ||
| var __defProp = Object.defineProperty; | ||
| var __getOwnPropDesc = Object.getOwnPropertyDescriptor; | ||
| var __getOwnPropNames = Object.getOwnPropertyNames; | ||
| var __hasOwnProp = Object.prototype.hasOwnProperty; | ||
| var __copyProps = (to, from, except, desc) => { | ||
| if (from && typeof from === "object" || typeof from === "function") { | ||
| for (let key of __getOwnPropNames(from)) | ||
| if (!__hasOwnProp.call(to, key) && key !== except) | ||
| __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); | ||
| } | ||
| return to; | ||
| }; | ||
| var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); | ||
| var types_exports = {}; | ||
| module.exports = __toCommonJS(types_exports); |
+6
-1
@@ -1,2 +0,7 @@ | ||
| export type { EdgeFlagValue, InputEdge, InputNode, NodeId, OutputEdge, OutputNode, ParamGraph, RootEntry, ScalarMaskValue, } from './param-graph'; | ||
| export type { EnumLookup, InputEdge, InputNode, OutputEdge, OutputNode, RootEntry } from './param-graph'; | ||
| export type { InputEdgeData, InputNodeData, OutputEdgeData, OutputNodeData, ParamGraphData, RootEntryData, } from './param-graph'; | ||
| export type { EdgeFlagValue, ScalarMaskValue } from './param-graph'; | ||
| export { ParamGraph } from './param-graph'; | ||
| export { EdgeFlag, getScalarMask, hasFlag, ScalarMask, scalarTypeToMask } from './param-graph'; | ||
| export type { SerializedParamGraph } from './serialization'; | ||
| export { deserializeParamGraph, serializeParamGraph } from './serialization'; |
+14
-6
@@ -21,17 +21,25 @@ "use strict"; | ||
| __export(index_exports, { | ||
| EdgeFlag: () => import_param_graph.EdgeFlag, | ||
| ScalarMask: () => import_param_graph.ScalarMask, | ||
| getScalarMask: () => import_param_graph.getScalarMask, | ||
| hasFlag: () => import_param_graph.hasFlag, | ||
| scalarTypeToMask: () => import_param_graph.scalarTypeToMask | ||
| EdgeFlag: () => import_param_graph2.EdgeFlag, | ||
| ParamGraph: () => import_param_graph.ParamGraph, | ||
| ScalarMask: () => import_param_graph2.ScalarMask, | ||
| deserializeParamGraph: () => import_serialization.deserializeParamGraph, | ||
| getScalarMask: () => import_param_graph2.getScalarMask, | ||
| hasFlag: () => import_param_graph2.hasFlag, | ||
| scalarTypeToMask: () => import_param_graph2.scalarTypeToMask, | ||
| serializeParamGraph: () => import_serialization.serializeParamGraph | ||
| }); | ||
| module.exports = __toCommonJS(index_exports); | ||
| var import_param_graph = require("./param-graph"); | ||
| var import_param_graph2 = require("./param-graph"); | ||
| var import_serialization = require("./serialization"); | ||
| // Annotate the CommonJS export names for ESM import in node: | ||
| 0 && (module.exports = { | ||
| EdgeFlag, | ||
| ParamGraph, | ||
| ScalarMask, | ||
| deserializeParamGraph, | ||
| getScalarMask, | ||
| hasFlag, | ||
| scalarTypeToMask | ||
| scalarTypeToMask, | ||
| serializeParamGraph | ||
| }); |
+6
-1
@@ -0,8 +1,13 @@ | ||
| import { ParamGraph } from "./param-graph"; | ||
| import { EdgeFlag, getScalarMask, hasFlag, ScalarMask, scalarTypeToMask } from "./param-graph"; | ||
| import { deserializeParamGraph, serializeParamGraph } from "./serialization"; | ||
| export { | ||
| EdgeFlag, | ||
| ParamGraph, | ||
| ScalarMask, | ||
| deserializeParamGraph, | ||
| getScalarMask, | ||
| hasFlag, | ||
| scalarTypeToMask | ||
| scalarTypeToMask, | ||
| serializeParamGraph | ||
| }; |
+76
-91
| /** | ||
| * ParamGraph: Compact schema for runtime parameterization. | ||
| * ParamGraph: Runtime class for schema-aware parameterization. | ||
| * | ||
| * This data structure is generated from DMMF at client generation time | ||
| * and embedded in the generated client. It enables schema-aware | ||
| * parameterization where values are only parameterized when both schema | ||
| * rules and runtime value types agree. | ||
| * This class provides a readable API for navigating the param graph structure | ||
| * at runtime. It's created once per PrismaClient instance from the serialized | ||
| * format embedded in the generated client. | ||
| */ | ||
| import type { SerializedParamGraph } from './serialization'; | ||
| import type { InputEdgeData, InputNodeData, OutputEdgeData, OutputNodeData, ParamGraphData, RootEntryData } from './types'; | ||
| /** | ||
| * Compact schema embedded in the generated client. | ||
| * Function type for looking up enum values by name. | ||
| * This allows ParamGraph to remain decoupled from RuntimeDataModel. | ||
| */ | ||
| export type ParamGraph = { | ||
| export type EnumLookup = (enumName: string) => readonly string[] | undefined; | ||
| /** | ||
| * Readable view of root entry. | ||
| */ | ||
| export interface RootEntry { | ||
| readonly argsNodeId: number | undefined; | ||
| readonly outputNodeId: number | undefined; | ||
| } | ||
| /** | ||
| * Readable view of input node. | ||
| */ | ||
| export interface InputNode { | ||
| readonly id: number; | ||
| } | ||
| /** | ||
| * Readable view of output node. | ||
| */ | ||
| export interface OutputNode { | ||
| readonly id: number; | ||
| } | ||
| /** | ||
| * Readable view of input edge. | ||
| */ | ||
| export interface InputEdge { | ||
| readonly flags: number; | ||
| readonly childNodeId: number | undefined; | ||
| readonly scalarMask: number; | ||
| readonly enumNameIndex: number | undefined; | ||
| } | ||
| /** | ||
| * Readable view of output edge. | ||
| */ | ||
| export interface OutputEdge { | ||
| readonly argsNodeId: number | undefined; | ||
| readonly outputNodeId: number | undefined; | ||
| } | ||
| /** | ||
| * ParamGraph provides runtime access to the schema information | ||
| * needed for parameterization decisions. | ||
| */ | ||
| export declare class ParamGraph { | ||
| #private; | ||
| private constructor(); | ||
| /** | ||
| * String table to avoid repeating field names. | ||
| * Field names are referenced by index throughout the graph. | ||
| * Creates a ParamGraph from serialized format. | ||
| * This is the primary factory method for runtime use. | ||
| */ | ||
| s: string[]; | ||
| static deserialize(serialized: SerializedParamGraph, enumLookup: EnumLookup): ParamGraph; | ||
| /** | ||
| * User enum names for runtime membership checks. | ||
| * Values are looked up via `runtimeDataModel.enums[enumName].values`. | ||
| * Creates a ParamGraph from builder data. | ||
| * Used by the builder for testing and direct construction. | ||
| */ | ||
| e: string[]; | ||
| static fromData(data: ParamGraphData, enumLookup: EnumLookup): ParamGraph; | ||
| /** | ||
| * Input nodes used for argument objects and input types. | ||
| * Each node describes which fields are parameterizable or lead to | ||
| * parameterizable descendants. | ||
| * Look up a root entry by "Model.action" or "action". | ||
| */ | ||
| i: InputNode[]; | ||
| root(key: string): RootEntry | undefined; | ||
| /** | ||
| * Output nodes used for selection traversal. | ||
| * Each node describes which fields have arguments or lead to | ||
| * nested selections with arguments. | ||
| * Get an input node by ID. | ||
| */ | ||
| o: OutputNode[]; | ||
| inputNode(id: number | undefined): InputNode | undefined; | ||
| /** | ||
| * Root mapping: "Model.action" or "action" (for non-model ops). | ||
| * Points to the args node (input) and root output node. | ||
| * Get an output node by ID. | ||
| */ | ||
| r: Record<string, RootEntry>; | ||
| }; | ||
| /** | ||
| * Entry point for a root operation. | ||
| */ | ||
| export type RootEntry = { | ||
| /** Args node id (into `i` array) */ | ||
| a?: NodeId; | ||
| /** Output node id (into `o` array) */ | ||
| o?: NodeId; | ||
| }; | ||
| /** | ||
| * Node ID is an index into `i` or `o` array. | ||
| */ | ||
| export type NodeId = number; | ||
| /** | ||
| * Input node: describes parameterizable fields in an input object. | ||
| * Only fields that are parameterizable or lead to parameterizable | ||
| * descendants are present. | ||
| */ | ||
| export type InputNode = { | ||
| outputNode(id: number | undefined): OutputNode | undefined; | ||
| /** | ||
| * Map from string-table index to edge descriptor. | ||
| * Omitted if the node has no fields (shouldn't happen in practice). | ||
| * Get an input edge for a field name within a node. | ||
| */ | ||
| f?: Record<number, InputEdge>; | ||
| }; | ||
| /** | ||
| * Output node: describes fields in a selection set that have args | ||
| * or nested selections that may contain parameterizable args. | ||
| */ | ||
| export type OutputNode = { | ||
| inputEdge(node: InputNode | undefined, fieldName: string): InputEdge | undefined; | ||
| /** | ||
| * Map from string-table index to edge descriptor. | ||
| * Get an output edge for a field name within a node. | ||
| */ | ||
| f?: Record<number, OutputEdge>; | ||
| }; | ||
| /** | ||
| * Edge descriptor for input fields. | ||
| * Encodes what kinds of values the field accepts and how to handle them. | ||
| */ | ||
| export type InputEdge = { | ||
| outputEdge(node: OutputNode | undefined, fieldName: string): OutputEdge | undefined; | ||
| /** | ||
| * Bit flags describing field capabilities. | ||
| * See EdgeFlag enum below. | ||
| * Get enum values for an edge that references a user enum. | ||
| * Returns undefined if the edge doesn't reference an enum. | ||
| */ | ||
| k: number; | ||
| enumValues(edge: InputEdge | undefined): readonly string[] | undefined; | ||
| /** | ||
| * Child input node id (for object values or list of objects). | ||
| * Present when the field accepts input object types. | ||
| * Get a string from the string table by index. | ||
| */ | ||
| c?: NodeId; | ||
| /** | ||
| * Scalar type mask for allowed scalar categories. | ||
| * Present when field accepts scalar values. | ||
| * See ScalarMask enum below. | ||
| */ | ||
| m?: number; | ||
| /** | ||
| * Enum name id (index into `en` array). | ||
| * Present when field accepts a user enum without a plain String scalar. | ||
| * Used for runtime membership validation. | ||
| */ | ||
| e?: number; | ||
| }; | ||
| getString(index: number): string | undefined; | ||
| } | ||
| /** | ||
| * Edge descriptor for output fields. | ||
| * Bit flags for InputEdge.flags describing what the field accepts. | ||
| */ | ||
| export type OutputEdge = { | ||
| /** Args node for this field (if it accepts arguments) */ | ||
| a?: NodeId; | ||
| /** Next output node for nested selection traversal */ | ||
| o?: NodeId; | ||
| }; | ||
| /** | ||
| * Bit flags for InputEdge.k describing what the field accepts. | ||
| */ | ||
| export declare const EdgeFlag: { | ||
@@ -151,3 +135,3 @@ /** | ||
| * Bit mask for scalar type categories. | ||
| * Used in InputEdge.m to validate runtime value types. | ||
| * Used in InputEdge.scalarMask to validate runtime value types. | ||
| */ | ||
@@ -178,1 +162,2 @@ export declare const ScalarMask: { | ||
| export declare function scalarTypeToMask(typeName: string): number; | ||
| export type { InputEdgeData, InputNodeData, OutputEdgeData, OutputNodeData, ParamGraphData, RootEntryData }; |
+134
-2
@@ -22,2 +22,3 @@ "use strict"; | ||
| EdgeFlag: () => EdgeFlag, | ||
| ParamGraph: () => ParamGraph, | ||
| ScalarMask: () => ScalarMask, | ||
@@ -29,2 +30,132 @@ getScalarMask: () => getScalarMask, | ||
| module.exports = __toCommonJS(param_graph_exports); | ||
| var import_serialization = require("./serialization"); | ||
| class ParamGraph { | ||
| #data; | ||
| #stringIndex; | ||
| #enumLookup; | ||
| constructor(data, enumLookup) { | ||
| this.#data = data; | ||
| this.#enumLookup = enumLookup; | ||
| this.#stringIndex = /* @__PURE__ */ new Map(); | ||
| for (let i = 0; i < data.strings.length; i++) { | ||
| this.#stringIndex.set(data.strings[i], i); | ||
| } | ||
| } | ||
| /** | ||
| * Creates a ParamGraph from serialized format. | ||
| * This is the primary factory method for runtime use. | ||
| */ | ||
| static deserialize(serialized, enumLookup) { | ||
| const data = (0, import_serialization.deserializeParamGraph)(serialized); | ||
| return new ParamGraph(data, enumLookup); | ||
| } | ||
| /** | ||
| * Creates a ParamGraph from builder data. | ||
| * Used by the builder for testing and direct construction. | ||
| */ | ||
| static fromData(data, enumLookup) { | ||
| return new ParamGraph(data, enumLookup); | ||
| } | ||
| /** | ||
| * Look up a root entry by "Model.action" or "action". | ||
| */ | ||
| root(key) { | ||
| const entry = this.#data.roots[key]; | ||
| if (!entry) { | ||
| return void 0; | ||
| } | ||
| return { | ||
| argsNodeId: entry.argsNodeId, | ||
| outputNodeId: entry.outputNodeId | ||
| }; | ||
| } | ||
| /** | ||
| * Get an input node by ID. | ||
| */ | ||
| inputNode(id) { | ||
| if (id === void 0 || id < 0 || id >= this.#data.inputNodes.length) { | ||
| return void 0; | ||
| } | ||
| return { id }; | ||
| } | ||
| /** | ||
| * Get an output node by ID. | ||
| */ | ||
| outputNode(id) { | ||
| if (id === void 0 || id < 0 || id >= this.#data.outputNodes.length) { | ||
| return void 0; | ||
| } | ||
| return { id }; | ||
| } | ||
| /** | ||
| * Get an input edge for a field name within a node. | ||
| */ | ||
| inputEdge(node, fieldName) { | ||
| if (!node) { | ||
| return void 0; | ||
| } | ||
| const nodeData = this.#data.inputNodes[node.id]; | ||
| if (!nodeData) { | ||
| return void 0; | ||
| } | ||
| const fieldIndex = this.#stringIndex.get(fieldName); | ||
| if (fieldIndex === void 0) { | ||
| return void 0; | ||
| } | ||
| const edge = nodeData.edges[fieldIndex]; | ||
| if (!edge) { | ||
| return void 0; | ||
| } | ||
| return { | ||
| flags: edge.flags, | ||
| childNodeId: edge.childNodeId, | ||
| scalarMask: edge.scalarMask ?? 0, | ||
| enumNameIndex: edge.enumNameIndex | ||
| }; | ||
| } | ||
| /** | ||
| * Get an output edge for a field name within a node. | ||
| */ | ||
| outputEdge(node, fieldName) { | ||
| if (!node) { | ||
| return void 0; | ||
| } | ||
| const nodeData = this.#data.outputNodes[node.id]; | ||
| if (!nodeData) { | ||
| return void 0; | ||
| } | ||
| const fieldIndex = this.#stringIndex.get(fieldName); | ||
| if (fieldIndex === void 0) { | ||
| return void 0; | ||
| } | ||
| const edge = nodeData.edges[fieldIndex]; | ||
| if (!edge) { | ||
| return void 0; | ||
| } | ||
| return { | ||
| argsNodeId: edge.argsNodeId, | ||
| outputNodeId: edge.outputNodeId | ||
| }; | ||
| } | ||
| /** | ||
| * Get enum values for an edge that references a user enum. | ||
| * Returns undefined if the edge doesn't reference an enum. | ||
| */ | ||
| enumValues(edge) { | ||
| if (edge?.enumNameIndex === void 0) { | ||
| return void 0; | ||
| } | ||
| const enumName = this.#data.strings[edge.enumNameIndex]; | ||
| if (!enumName) { | ||
| return void 0; | ||
| } | ||
| return this.#enumLookup(enumName); | ||
| } | ||
| /** | ||
| * Get a string from the string table by index. | ||
| */ | ||
| getString(index) { | ||
| return this.#data.strings[index]; | ||
| } | ||
| } | ||
| const EdgeFlag = { | ||
@@ -74,6 +205,6 @@ /** | ||
| function hasFlag(edge, flag) { | ||
| return (edge.k & flag) !== 0; | ||
| return (edge.flags & flag) !== 0; | ||
| } | ||
| function getScalarMask(edge) { | ||
| return edge.m ?? 0; | ||
| return edge.scalarMask; | ||
| } | ||
@@ -108,2 +239,3 @@ function scalarTypeToMask(typeName) { | ||
| EdgeFlag, | ||
| ParamGraph, | ||
| ScalarMask, | ||
@@ -110,0 +242,0 @@ getScalarMask, |
+133
-2
@@ -0,1 +1,131 @@ | ||
| import { deserializeParamGraph } from "./serialization"; | ||
| class ParamGraph { | ||
| #data; | ||
| #stringIndex; | ||
| #enumLookup; | ||
| constructor(data, enumLookup) { | ||
| this.#data = data; | ||
| this.#enumLookup = enumLookup; | ||
| this.#stringIndex = /* @__PURE__ */ new Map(); | ||
| for (let i = 0; i < data.strings.length; i++) { | ||
| this.#stringIndex.set(data.strings[i], i); | ||
| } | ||
| } | ||
| /** | ||
| * Creates a ParamGraph from serialized format. | ||
| * This is the primary factory method for runtime use. | ||
| */ | ||
| static deserialize(serialized, enumLookup) { | ||
| const data = deserializeParamGraph(serialized); | ||
| return new ParamGraph(data, enumLookup); | ||
| } | ||
| /** | ||
| * Creates a ParamGraph from builder data. | ||
| * Used by the builder for testing and direct construction. | ||
| */ | ||
| static fromData(data, enumLookup) { | ||
| return new ParamGraph(data, enumLookup); | ||
| } | ||
| /** | ||
| * Look up a root entry by "Model.action" or "action". | ||
| */ | ||
| root(key) { | ||
| const entry = this.#data.roots[key]; | ||
| if (!entry) { | ||
| return void 0; | ||
| } | ||
| return { | ||
| argsNodeId: entry.argsNodeId, | ||
| outputNodeId: entry.outputNodeId | ||
| }; | ||
| } | ||
| /** | ||
| * Get an input node by ID. | ||
| */ | ||
| inputNode(id) { | ||
| if (id === void 0 || id < 0 || id >= this.#data.inputNodes.length) { | ||
| return void 0; | ||
| } | ||
| return { id }; | ||
| } | ||
| /** | ||
| * Get an output node by ID. | ||
| */ | ||
| outputNode(id) { | ||
| if (id === void 0 || id < 0 || id >= this.#data.outputNodes.length) { | ||
| return void 0; | ||
| } | ||
| return { id }; | ||
| } | ||
| /** | ||
| * Get an input edge for a field name within a node. | ||
| */ | ||
| inputEdge(node, fieldName) { | ||
| if (!node) { | ||
| return void 0; | ||
| } | ||
| const nodeData = this.#data.inputNodes[node.id]; | ||
| if (!nodeData) { | ||
| return void 0; | ||
| } | ||
| const fieldIndex = this.#stringIndex.get(fieldName); | ||
| if (fieldIndex === void 0) { | ||
| return void 0; | ||
| } | ||
| const edge = nodeData.edges[fieldIndex]; | ||
| if (!edge) { | ||
| return void 0; | ||
| } | ||
| return { | ||
| flags: edge.flags, | ||
| childNodeId: edge.childNodeId, | ||
| scalarMask: edge.scalarMask ?? 0, | ||
| enumNameIndex: edge.enumNameIndex | ||
| }; | ||
| } | ||
| /** | ||
| * Get an output edge for a field name within a node. | ||
| */ | ||
| outputEdge(node, fieldName) { | ||
| if (!node) { | ||
| return void 0; | ||
| } | ||
| const nodeData = this.#data.outputNodes[node.id]; | ||
| if (!nodeData) { | ||
| return void 0; | ||
| } | ||
| const fieldIndex = this.#stringIndex.get(fieldName); | ||
| if (fieldIndex === void 0) { | ||
| return void 0; | ||
| } | ||
| const edge = nodeData.edges[fieldIndex]; | ||
| if (!edge) { | ||
| return void 0; | ||
| } | ||
| return { | ||
| argsNodeId: edge.argsNodeId, | ||
| outputNodeId: edge.outputNodeId | ||
| }; | ||
| } | ||
| /** | ||
| * Get enum values for an edge that references a user enum. | ||
| * Returns undefined if the edge doesn't reference an enum. | ||
| */ | ||
| enumValues(edge) { | ||
| if (edge?.enumNameIndex === void 0) { | ||
| return void 0; | ||
| } | ||
| const enumName = this.#data.strings[edge.enumNameIndex]; | ||
| if (!enumName) { | ||
| return void 0; | ||
| } | ||
| return this.#enumLookup(enumName); | ||
| } | ||
| /** | ||
| * Get a string from the string table by index. | ||
| */ | ||
| getString(index) { | ||
| return this.#data.strings[index]; | ||
| } | ||
| } | ||
| const EdgeFlag = { | ||
@@ -45,6 +175,6 @@ /** | ||
| function hasFlag(edge, flag) { | ||
| return (edge.k & flag) !== 0; | ||
| return (edge.flags & flag) !== 0; | ||
| } | ||
| function getScalarMask(edge) { | ||
| return edge.m ?? 0; | ||
| return edge.scalarMask; | ||
| } | ||
@@ -78,2 +208,3 @@ function scalarTypeToMask(typeName) { | ||
| EdgeFlag, | ||
| ParamGraph, | ||
| ScalarMask, | ||
@@ -80,0 +211,0 @@ getScalarMask, |
+3
-2
| { | ||
| "name": "@prisma/param-graph", | ||
| "version": "7.3.0-integration-parameterization.15", | ||
| "version": "7.4.0-dev.22", | ||
| "description": "This package is intended for Prisma's internal use", | ||
@@ -32,4 +32,5 @@ "main": "dist/index.js", | ||
| "dev": "DEV=true tsx helpers/build.ts", | ||
| "build": "tsx helpers/build.ts" | ||
| "build": "tsx helpers/build.ts", | ||
| "test": "vitest run" | ||
| } | ||
| } |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
75438
208.38%18
100%1737
321.6%3
Infinity%1
Infinity%