UNPKG

@hpx7/delta-pack

Version:

A TypeScript code generator and runtime for binary serialization based on schemas.

555 lines (554 loc) 21.9 kB
import * as _ from "./helpers"; import { isPrimitiveType } from "./schema"; export function load(schema, objectName) { const typeVal = schema[objectName]; if (!typeVal) { throw new Error(`Type ${objectName} not found in schema`); } // Support object, union, and enum types as root types if (typeVal.type !== "object" && typeVal.type !== "union" && typeVal.type !== "enum") { throw new Error(`Type ${objectName} must be an object, union, or enum type, got ${typeVal.type}`); } function _fromJson(objVal, objType) { if (objType.type === "string") { return _.parseString(objVal); } else if (objType.type === "int") { return _.parseInt(objVal); } else if (objType.type === "uint") { return _.parseUInt(objVal); } else if (objType.type === "float") { return _.parseFloat(objVal); } else if (objType.type === "boolean") { return _.parseBoolean(objVal); } else if (objType.type === "enum") { const enumObj = Object.fromEntries(objType.options.map((opt, i) => [opt, i])); return _.parseEnum(objVal, enumObj); } else if (objType.type === "reference") { const refType = schema[objType.reference]; if (!refType) { throw new Error(`Unknown reference type: ${objType.reference}`); } return _fromJson(objVal, refType); } else if (objType.type === "object") { if (typeof objVal !== "object" || objVal == null || Object.getPrototypeOf(objVal) !== Object.prototype) { throw new Error(`Invalid object: ${objVal}`); } return _.mapValues(objType.properties, (typeVal, key) => { const fieldVal = objVal[key]; return _.tryParseField(() => _fromJson(fieldVal, typeVal), key); }); } else if (objType.type === "array") { return _.parseArray(objVal, (elem) => _fromJson(elem, objType.value)); } else if (objType.type === "record") { return _.parseRecord(objVal, (key) => _fromJson(key, objType.key), (val) => _fromJson(val, objType.value)); } else if (objType.type === "union") { if (typeof objVal !== "object" || objVal == null) { throw new Error(`Invalid union: ${JSON.stringify(objVal)}`); } // check if it's delta-pack format: { type: "TypeName", val: ... } if ("type" in objVal && typeof objVal.type === "string" && "val" in objVal) { const optionType = objType.options.find((opt) => opt.reference === objVal.type); if (!optionType) { throw new Error(`Unknown union type option: ${objVal.type}`); } const refType = schema[optionType.reference]; return { type: objVal.type, val: _fromJson(objVal.val, refType), }; } // check if it's protobuf format: { TypeName: ... } const entries = Object.entries(objVal); if (entries.length === 1) { const [fieldName, fieldValue] = entries[0]; const optionType = objType.options.find((opt) => opt.reference === fieldName); if (!optionType) { throw new Error(`Unknown union type option: ${fieldName}`); } const refType = schema[optionType.reference]; return { type: fieldName, val: _fromJson(fieldValue, refType), }; } throw new Error(`Invalid union: ${JSON.stringify(objVal)}`); } else if (objType.type === "optional") { return _.parseOptional(objVal, (val) => _fromJson(val, objType.value)); } } function _toJson(objVal, objType) { if (objType.type === "string" || objType.type === "int" || objType.type === "uint" || objType.type === "float" || objType.type === "boolean" || objType.type === "enum") { return objVal; } else if (objType.type === "reference") { const refType = schema[objType.reference]; if (!refType) { throw new Error(`Unknown reference type: ${objType.reference}`); } return _toJson(objVal, refType); } else if (objType.type === "object") { const result = {}; for (const [key, typeVal] of Object.entries(objType.properties)) { const fieldVal = objVal[key]; // Skip optional properties that are undefined if (typeVal.type === "optional" && fieldVal == null) { continue; } result[key] = _toJson(fieldVal, typeVal); } return result; } else if (objType.type === "array") { const arr = objVal; return arr.map((elem) => _toJson(elem, objType.value)); } else if (objType.type === "record") { const map = objVal; return _.mapToObject(map, (val) => _toJson(val, objType.value)); } else if (objType.type === "union") { const unionObj = objVal; const refType = schema[unionObj.type]; return { [unionObj.type]: _toJson(unionObj.val, refType), }; } else if (objType.type === "optional") { if (objVal == null) { return null; } return _toJson(objVal, objType.value); } return objVal; } function _encode(objVal, objType, tracker) { if (objType.type === "string") { tracker.pushString(objVal); } else if (objType.type === "int") { tracker.pushInt(objVal); } else if (objType.type === "uint") { tracker.pushUInt(objVal); } else if (objType.type === "float") { if (objType.precision) { tracker.pushFloatQuantized(objVal, objType.precision); } else { tracker.pushFloat(objVal); } } else if (objType.type === "boolean") { tracker.pushBoolean(objVal); } else if (objType.type === "enum") { const enumObj = Object.fromEntries(objType.options.map((opt, i) => [opt, i])); tracker.pushUInt(enumObj[objVal]); } else if (objType.type === "reference") { const refType = schema[objType.reference]; if (!refType) { throw new Error(`Unknown reference type: ${objType.reference}`); } _encode(objVal, refType, tracker); } else if (objType.type === "object") { for (const [key, typeVal] of Object.entries(objType.properties)) { const fieldVal = objVal[key]; _encode(fieldVal, typeVal, tracker); } } else if (objType.type === "array") { const arr = objVal; tracker.pushArray(arr, (elem) => _encode(elem, objType.value, tracker)); } else if (objType.type === "record") { const map = objVal; tracker.pushRecord(map, (key) => _encode(key, objType.key, tracker), (val) => _encode(val, objType.value, tracker)); } else if (objType.type === "union") { const unionObj = objVal; const variantIndex = objType.options.findIndex((opt) => opt.reference === unionObj.type); if (variantIndex === -1) { throw new Error(`Unknown union variant: ${unionObj.type}`); } tracker.pushUInt(variantIndex); const refType = schema[unionObj.type]; _encode(unionObj.val, refType, tracker); } else if (objType.type === "optional") { tracker.pushOptional(objVal, (val) => _encode(val, objType.value, tracker)); } } function _decode(objType, tracker) { if (objType.type === "string") { return tracker.nextString(); } else if (objType.type === "int") { return tracker.nextInt(); } else if (objType.type === "uint") { return tracker.nextUInt(); } else if (objType.type === "float") { if (objType.precision) { return tracker.nextFloatQuantized(objType.precision); } else { return tracker.nextFloat(); } } else if (objType.type === "boolean") { return tracker.nextBoolean(); } else if (objType.type === "enum") { const idx = tracker.nextUInt(); return objType.options[idx]; } else if (objType.type === "reference") { const refType = schema[objType.reference]; if (!refType) { throw new Error(`Unknown reference type: ${objType.reference}`); } return _decode(refType, tracker); } else if (objType.type === "object") { const result = {}; for (const [key, typeVal] of Object.entries(objType.properties)) { result[key] = _decode(typeVal, tracker); } return result; } else if (objType.type === "array") { const length = tracker.nextUInt(); const arr = []; for (let i = 0; i < length; i++) { arr.push(_decode(objType.value, tracker)); } return arr; } else if (objType.type === "record") { const size = tracker.nextUInt(); const map = new Map(); for (let i = 0; i < size; i++) { const key = _decode(objType.key, tracker); const val = _decode(objType.value, tracker); map.set(key, val); } return map; } else if (objType.type === "union") { const variantIndex = tracker.nextUInt(); const variant = objType.options[variantIndex]; if (!variant) { throw new Error(`Invalid union variant index: ${variantIndex}`); } const refType = schema[variant.reference]; return { type: variant.reference, val: _decode(refType, tracker), }; } else if (objType.type === "optional") { const hasValue = tracker.nextBoolean(); if (!hasValue) { return undefined; } return _decode(objType.value, tracker); } } function _equals(a, b, objType) { if (objType.type === "string" || objType.type === "int" || objType.type === "uint") { return a === b; } else if (objType.type === "float") { if (objType.precision) { return _.equalsFloatQuantized(a, b, objType.precision); } else { return _.equalsFloat(a, b); } } else if (objType.type === "boolean") { return a === b; } else if (objType.type === "enum") { return a === b; } else if (objType.type === "reference") { const refType = schema[objType.reference]; if (!refType) { throw new Error(`Unknown reference type: ${objType.reference}`); } return _equals(a, b, refType); } else if (objType.type === "object") { for (const [key, typeVal] of Object.entries(objType.properties)) { if (!_equals(a[key], b[key], typeVal)) { return false; } } return true; } else if (objType.type === "array") { const arrA = a; const arrB = b; if (arrA.length !== arrB.length) return false; for (let i = 0; i < arrA.length; i++) { if (!_equals(arrA[i], arrB[i], objType.value)) { return false; } } return true; } else if (objType.type === "record") { const mapA = a; const mapB = b; if (mapA.size !== mapB.size) return false; for (const [key, val] of mapA) { if (!mapB.has(key)) return false; if (!_equals(val, mapB.get(key), objType.value)) { return false; } } return true; } else if (objType.type === "union") { const unionA = a; const unionB = b; if (unionA.type !== unionB.type) return false; const refType = schema[unionA.type]; return _equals(unionA.val, unionB.val, refType); } else if (objType.type === "optional") { if (a == null && b == null) return true; if (a == null || b == null) return false; return _equals(a, b, objType.value); } return true; } function _encodeDiff(a, b, objType, tracker) { if (objType.type === "string") { tracker.pushStringDiff(a, b); } else if (objType.type === "int") { tracker.pushIntDiff(a, b); } else if (objType.type === "uint") { tracker.pushUIntDiff(a, b); } else if (objType.type === "float") { if (objType.precision) { return tracker.pushFloatQuantizedDiff(a, b, objType.precision); } else { tracker.pushFloatDiff(a, b); } } else if (objType.type === "boolean") { tracker.pushBooleanDiff(a, b); } else if (objType.type === "enum") { const enumObj = Object.fromEntries(objType.options.map((opt, i) => [opt, i])); tracker.pushUIntDiff(enumObj[a], enumObj[b]); } else if (objType.type === "reference") { const refType = schema[objType.reference]; if (!refType) { throw new Error(`Unknown reference type: ${objType.reference}`); } _encodeDiff(a, b, refType, tracker); } else if (objType.type === "object") { const dirty = b._dirty; const changed = dirty == null ? !_equals(a, b, objType) : dirty.size > 0; tracker.pushBoolean(changed); if (!changed) return; for (const [key, typeVal] of Object.entries(objType.properties)) { if (dirty != null && !dirty.has(key)) { tracker.pushBoolean(false); } else { _encodeDiff(a[key], b[key], typeVal, tracker); } } } else if (objType.type === "array") { const arrA = a; const arrB = b; tracker.pushArrayDiff(arrA, arrB, (x, y) => _equals(x, y, objType.value), (x) => _encode(x, objType.value, tracker), (x, y) => _encodeDiff(x, y, objType.value, tracker)); } else if (objType.type === "record") { const mapA = a; const mapB = b; tracker.pushRecordDiff(mapA, mapB, (x, y) => _equals(x, y, objType.value), (x) => _encode(x, objType.key, tracker), (x) => _encode(x, objType.value, tracker), (x, y) => _encodeDiff(x, y, objType.value, tracker)); } else if (objType.type === "union") { const unionA = a; const unionB = b; if (unionB.type !== unionA.type) { // Type changed - encode new discriminator and value tracker.pushBoolean(false); const variantIndex = objType.options.findIndex((opt) => opt.reference === unionB.type); tracker.pushUInt(variantIndex); const refType = schema[unionB.type]; _encode(unionB.val, refType, tracker); } else { // Same type - encode diff tracker.pushBoolean(true); const refType = schema[unionA.type]; _encodeDiff(unionA.val, unionB.val, refType, tracker); } } else if (objType.type === "optional") { const valueType = objType.value; // Use pushOptionalDiffPrimitive for primitives (including primitive references like UserId) // Use pushOptionalDiff for objects/complex types if (isPrimitiveType(valueType, schema)) { tracker.pushOptionalDiffPrimitive(a, b, (x) => _encode(x, valueType, tracker)); } else { tracker.pushOptionalDiff(a, b, (x) => _encode(x, valueType, tracker), (x, y) => _encodeDiff(x, y, valueType, tracker)); } } } function _decodeDiff(a, objType, tracker) { if (objType.type === "string") { return tracker.nextStringDiff(a); } else if (objType.type === "int") { return tracker.nextIntDiff(a); } else if (objType.type === "uint") { return tracker.nextUIntDiff(a); } else if (objType.type === "float") { if (objType.precision) { return tracker.nextFloatQuantizedDiff(a, objType.precision); } else { return tracker.nextFloatDiff(a); } } else if (objType.type === "boolean") { const changed = tracker.nextBoolean(); return changed ? !a : a; } else if (objType.type === "enum") { const oldEnumObj = Object.fromEntries(objType.options.map((opt, i) => [opt, i])); const oldIdx = oldEnumObj[a]; const newIdx = tracker.nextUIntDiff(oldIdx); return objType.options[newIdx]; } else if (objType.type === "reference") { const refType = schema[objType.reference]; if (!refType) { throw new Error(`Unknown reference type: ${objType.reference}`); } return _decodeDiff(a, refType, tracker); } else if (objType.type === "object") { const changed = tracker.nextBoolean(); if (!changed) return a; const result = {}; for (const [key, typeVal] of Object.entries(objType.properties)) { result[key] = _decodeDiff(a[key], typeVal, tracker); } return result; } else if (objType.type === "array") { const arrA = a; return tracker.nextArrayDiff(arrA, () => _decode(objType.value, tracker), (x) => _decodeDiff(x, objType.value, tracker)); } else if (objType.type === "record") { const mapA = a; return tracker.nextRecordDiff(mapA, () => _decode(objType.key, tracker), () => _decode(objType.value, tracker), (x) => _decodeDiff(x, objType.value, tracker)); } else if (objType.type === "union") { const unionA = a; const sameType = tracker.nextBoolean(); if (!sameType) { // Type changed - decode new discriminator and value const variantIndex = tracker.nextUInt(); const variant = objType.options[variantIndex]; if (!variant) { throw new Error(`Invalid union variant index: ${variantIndex}`); } const refType = schema[variant.reference]; return { type: variant.reference, val: _decode(refType, tracker), }; } else { // Same type - decode diff const refType = schema[unionA.type]; return { type: unionA.type, val: _decodeDiff(unionA.val, refType, tracker), }; } } else if (objType.type === "optional") { const valueType = objType.value; // Use nextOptionalDiffPrimitive for primitives, nextOptionalDiff for complex types if (isPrimitiveType(valueType, schema)) { return tracker.nextOptionalDiffPrimitive(a, () => _decode(valueType, tracker)); } else { return tracker.nextOptionalDiff(a, () => _decode(valueType, tracker), (x) => _decodeDiff(x, valueType, tracker)); } } return a; } return { fromJson: (obj) => _fromJson(obj, typeVal), toJson: (obj) => _toJson(obj, typeVal), encode: (obj) => { const tracker = new _.Tracker(); _encode(obj, typeVal, tracker); return tracker.toBuffer(); }, decode: (buf) => { const tracker = _.Tracker.parse(buf); return _decode(typeVal, tracker); }, encodeDiff: (a, b) => { const tracker = new _.Tracker(); _encodeDiff(a, b, typeVal, tracker); return tracker.toBuffer(); }, decodeDiff: (a, diff) => { const tracker = _.Tracker.parse(diff); return _decodeDiff(a, typeVal, tracker); }, equals: (a, b) => _equals(a, b, typeVal), }; }