New Research: Supply Chain Attack on Axios Pulls Malicious Dependency from npm.Details
Socket
Book a DemoSign in
Socket

codables

Package Overview
Dependencies
Maintainers
1
Versions
6
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

codables - npm Package Compare versions

Comparing version
0.0.1
to
0.1.0
+1058
dist/index.cjs
"use strict";
var __defProp = Object.defineProperty;
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
const TYPED_ARRAY_MAP = {
uint8: Uint8Array,
uint8clamped: Uint8ClampedArray,
uint16: Uint16Array,
uint32: Uint32Array,
int8: Int8Array,
int16: Int16Array,
int32: Int32Array,
float32: Float32Array,
float64: Float64Array
};
function getIsTypedArray(value) {
for (const type of Object.values(TYPED_ARRAY_MAP)) {
if (value instanceof type) return true;
}
return false;
}
function getTypedArrayType(value) {
for (const name of Object.keys(TYPED_ARRAY_MAP)) {
if (value instanceof TYPED_ARRAY_MAP[name])
return name;
}
throw new Error(`Unknown typed array type: ${value}`);
}
function getTypedArrayConstructor(type) {
const TypedArrayClass = TYPED_ARRAY_MAP[type];
if (!TypedArrayClass) {
throw new Error(`Unknown typed array type: ${type}`);
}
return TypedArrayClass;
}
function getSpecialNumberType(value) {
switch (value) {
case Infinity:
return "Infinity";
case -Infinity:
return "-Infinity";
case 0:
if (1 / value === -Infinity) return "-0";
}
if (isNaN(value)) return "NaN";
return null;
}
function decodeSpecialNumber(value) {
switch (value) {
case "NaN":
return NaN;
case "Infinity":
return Infinity;
case "-Infinity":
return -Infinity;
case "-0":
return -0;
default:
throw new Error(`Invalid special number type: ${value}`);
}
}
function resolveThunk(thunk) {
let result = thunk;
while (typeof result === "function") {
result = result();
}
return result;
}
function getSymbolKey(symbol) {
const nativeKey = Symbol.keyFor(symbol);
if (nativeKey) return nativeKey;
const toStringResult = symbol.toString();
return toStringResult.slice(7, -1);
}
function removeUndefinedProperties(input) {
const result = {};
for (const key of Object.keys(input)) {
const value = input[key];
if (value !== void 0) {
result[key] = value;
}
}
return result;
}
function createTag(name, data) {
const tag = {};
tag[`$$${name}`] = data;
return tag;
}
const DEFAULT_PRIORITY = 10;
class CodableType {
constructor(definition) {
__publicField(this, "name");
__publicField(this, "priority");
__publicField(this, "dependencies");
__publicField(this, "tagKey");
var _a, _b;
this.definition = definition;
this.name = definition.name;
this.tagKey = `$$${this.name}`;
this.priority = ((_a = definition.options) == null ? void 0 : _a.priority) ?? DEFAULT_PRIORITY;
this.dependencies = ((_b = definition.options) == null ? void 0 : _b.dependencies) ?? null;
}
encode(value, context) {
return this.definition.encode(value, context);
}
encodeTag(value, context) {
return createTag(this.name, this.encode(value, context));
}
decode(data, context) {
return this.definition.decode(data, context);
}
canHandle(value) {
return this.definition.canHandle(value);
}
createTag(data) {
return createTag(this.name, data);
}
}
function createCodableType(name, canHandle, encode2, decode2, options) {
return new CodableType({
name,
canHandle,
encode: encode2,
decode: decode2,
options
});
}
function getIsCodableType(value) {
return value instanceof CodableType;
}
function getErrorExtraProperties(error) {
const properties = {};
let isEmpty = true;
for (const key of Object.keys(error)) {
if (key === "message" || key === "name" || key === "cause") continue;
properties[key] = error[key];
isEmpty = false;
}
if (isEmpty) return null;
return properties;
}
function getIsValidDate(date) {
return !isNaN(date.getTime());
}
const $$date = createCodableType(
"Date",
(value) => value instanceof Date,
(date) => {
if (!getIsValidDate(date)) return null;
return date.toISOString();
},
(maybeISOString) => {
if (maybeISOString === null) return /* @__PURE__ */ new Date("invalid");
return new Date(maybeISOString);
}
);
const $$set = createCodableType(
"Set",
(value) => value instanceof Set,
(set) => [...set],
(array) => new Set(array)
);
const $$map = createCodableType(
"Map",
(value) => value instanceof Map,
(map) => [...map.entries()],
(entries) => new Map(entries)
);
const $$error = createCodableType(
"Error",
(value) => value instanceof Error,
(error, context) => {
var _a;
const shouldIncludeErrorStack = ((_a = context.options) == null ? void 0 : _a.includeErrorStack) ?? false;
const extraProperties = getErrorExtraProperties(error) ?? void 0;
const name = error.name && error.name !== "Error" ? error.name : void 0;
const cause = error.cause;
const message = error.message;
const stack = shouldIncludeErrorStack ? error.stack : void 0;
if (!extraProperties && !name && !cause && !stack) {
return message;
}
return removeUndefinedProperties({
message,
name,
cause,
properties: extraProperties,
stack
});
},
(messageOrData) => {
if (typeof messageOrData === "string") return new Error(messageOrData);
const { message, name, cause, properties, stack } = messageOrData;
const error = new Error(message, { cause });
if (stack) {
error.stack = stack;
}
if (name && name !== "Error") {
error.name = name;
}
if (properties) {
Object.assign(error, properties);
}
return error;
}
);
const $$undefined = createCodableType(
"undefined",
(value) => value === void 0,
() => null,
() => void 0
);
const $$bigInt = createCodableType(
"BigInt",
(value) => typeof value === "bigint",
(bigInt) => bigInt.toString(),
(string) => BigInt(string)
);
const $$regexp = createCodableType(
"RegExp",
(value) => value instanceof RegExp,
({ source, flags }) => {
if (flags) return [source, flags];
return source;
},
(sourceOrSourceAndFlags) => {
if (typeof sourceOrSourceAndFlags === "string") {
return new RegExp(sourceOrSourceAndFlags);
}
const [source, flags] = sourceOrSourceAndFlags;
return new RegExp(source, flags);
}
);
const $$url = createCodableType(
"URL",
(value) => value instanceof URL,
(url) => url.toString(),
(string) => new URL(string)
);
const symbolsRegistry = /* @__PURE__ */ new Map();
const $$symbol = createCodableType(
"Symbol",
(value) => typeof value === "symbol",
(symbol) => {
const symbolKey = getSymbolKey(symbol);
symbolsRegistry.set(symbolKey, symbol);
return symbolKey;
},
(symbolKey) => symbolsRegistry.get(symbolKey) ?? Symbol.for(symbolKey)
);
const $$typedArray = createCodableType(
"typedArray",
getIsTypedArray,
(value) => {
return {
type: getTypedArrayType(value),
data: [...value]
};
},
({ type, data }) => {
return new (getTypedArrayConstructor(type))(data);
}
);
const $$num = createCodableType(
"num",
(value) => typeof value === "number" && !!getSpecialNumberType(value),
getSpecialNumberType,
decodeSpecialNumber
);
const $$urlSearchParams = createCodableType(
"URLSearchParams",
(value) => value instanceof URLSearchParams,
(urlSearchParams) => urlSearchParams.toString(),
(string) => new URLSearchParams(string)
);
const builtinTypesMap = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
__proto__: null,
$$bigInt,
$$date,
$$error,
$$map,
$$num,
$$regexp,
$$set,
$$symbol,
$$typedArray,
$$undefined,
$$url,
$$urlSearchParams
}, Symbol.toStringTag, { value: "Module" }));
function getTagValue(tag) {
return Object.values(tag)[0];
}
const OBJECT_PROTOTYPE = Object.prototype;
function getCodableTypeOf(input) {
if (input === null) return "primitive";
if (input === void 0) return "undefined";
const typeOfInput = typeof input;
switch (typeOfInput) {
case "boolean":
case "string":
return "primitive";
case "number":
if (getSpecialNumberType(input)) return "special-number";
return "primitive";
case "symbol":
return "symbol";
case "bigint":
return "bigint";
case "function":
return "function";
}
if (Array.isArray(input)) return "array";
const inputPrototype = Object.getPrototypeOf(input);
if (inputPrototype === OBJECT_PROTOTYPE || inputPrototype === null) {
return "record";
}
return "custom-object";
}
const ANY_TAG_KEY_REGEXP = /^~*\$\$.+/;
function getDecodableTypeOf(input, context) {
switch (typeof input) {
case "boolean":
case "string":
case "number":
return "primitive";
case "symbol":
case "bigint":
case "function":
case "undefined":
throw new Error(`undefined is not a valid JSON value`);
}
if (input === null) return "primitive";
if (Array.isArray(input)) return "array";
const keys = Object.keys(input);
if (keys.length !== 1) return "record";
const key = keys[0];
if (key.startsWith("$$")) {
if (key === "$$") return "record";
if (key === "$$ref") {
if (typeof input.$$ref === "string") return "ref-tag";
return "record";
}
return key;
}
if (ANY_TAG_KEY_REGEXP.test(key)) return "escaped-tag";
return "record";
}
function assert(condition, message) {
if (!condition) {
if (message instanceof Error) {
throw message;
}
throw new Error(message);
}
}
function assertGet(value, message) {
assert(value !== null && value !== void 0, message);
return value;
}
function analyzeEncodedData(data, context) {
const decodableTypeOf = getDecodableTypeOf(data);
switch (decodableTypeOf) {
case "primitive":
return;
case "ref-tag":
context.presentRefAliases.add(data.$$ref);
return;
case "array":
for (let i = 0; i < data.length; i++) {
analyzeEncodedData(data[i], context);
}
return;
case "record":
for (const key of Object.keys(data)) {
analyzeEncodedData(data[key], context);
}
return;
case "escaped-tag":
context.hasEscapedTags = true;
analyzeEncodedData(getTagValue(data), context);
return;
}
context.hasCustomTypes = true;
analyzeEncodedData(data[decodableTypeOf], context);
}
class DecodeContext {
constructor(data, options) {
__publicField(this, "hasEscapedTags", false);
__publicField(this, "hasCustomTypes", false);
// Is ready instantly after analyzing the data
__publicField(this, "presentRefAliases", /* @__PURE__ */ new Set());
// Will be filled while decoding the data
__publicField(this, "resolvedRefs", /* @__PURE__ */ new Map());
__publicField(this, "hasRefAliases");
__publicField(this, "externalReferencesMap");
this.options = options;
analyzeEncodedData(data, this);
this.externalReferencesMap = new Map(Object.entries((options == null ? void 0 : options.externalReferences) ?? {}));
this.hasRefAliases = this.presentRefAliases.size > 0;
}
/**
* No custom types, no ref aliases, no escaped tags. Encoded data is regular JSON-compatible.
*/
get isPlainJSON() {
return !this.hasEscapedTags && !this.hasCustomTypes && !this.hasRefAliases;
}
/**
* Some object needed by some alias (list prepared before) is ready to be used.
*/
registerRef(path, object) {
if (!this.hasRefAliases || !this.presentRefAliases.has(path)) return;
this.resolvedRefs.set(path, object);
}
/**
* Alias requested value it referenced
*/
resolveRefAlias(path) {
return this.resolvedRefs.get(path) ?? null;
}
}
class EncodeContext {
constructor(options) {
__publicField(this, "unknownMode");
__publicField(this, "preserveReferences");
__publicField(this, "refFirstSeenPath", /* @__PURE__ */ new Map());
this.options = options;
this.unknownMode = (options == null ? void 0 : options.unknownInputMode) ?? "null";
this.preserveReferences = (options == null ? void 0 : options.preserveReferences) ?? true;
}
/**
* Call it the first time some object is seen.
*/
registerNewSeenObject(object, path) {
this.refFirstSeenPath.set(object, path);
}
/**
* Returns where the object was first seen at.
*/
getAlreadySeenObjectPath(object) {
return this.refFirstSeenPath.get(object) ?? null;
}
}
function getMetadataKey(Class) {
return Class[Symbol.metadata] ?? null;
}
if (!Symbol.metadata) {
Reflect.set(Symbol, "metadata", Symbol.for("Symbol.metadata"));
}
class PrivateMetadata {
constructor() {
__publicField(this, "registry", /* @__PURE__ */ new WeakMap());
}
getFor(Class) {
const key = getMetadataKey(Class);
if (!key) return null;
return this.get(key);
}
get(key) {
return this.registry.get(key) ?? null;
}
getOrInit(key, initializer) {
if (this.registry.has(key)) return this.registry.get(key);
const value = initializer();
this.registry.set(key, value);
return value;
}
has(key) {
return this.registry.has(key);
}
set(key, value) {
this.registry.set(key, value);
}
init(key, value) {
if (this.registry.has(key)) return this.registry.get(key);
this.registry.set(key, value);
return value;
}
}
const codableClassRegistry = new PrivateMetadata();
const codableClassFieldsRegistry = new PrivateMetadata();
const externalClassFieldsRegistry = new PrivateMetadata();
function registerCodableClass(key, metadata) {
return codableClassRegistry.init(key, metadata);
}
function getIsCodableClass(Class) {
const key = getMetadataKey(Class);
if (!key) return false;
return codableClassRegistry.has(key);
}
function getCodableClassType(Class) {
var _a;
const key = getMetadataKey(Class);
if (!key) return null;
const codableType = (_a = codableClassRegistry.get(key)) == null ? void 0 : _a.codableType;
if (!codableType) return null;
return codableType;
}
class ExternalReference {
constructor(key, isOptional = false) {
this.key = key;
this.isOptional = isOptional;
}
}
function externalReference(key, isOptional = false) {
return new ExternalReference(key, isOptional);
}
const $$externalReference = createCodableType(
"external",
(value) => value instanceof ExternalReference,
(ref) => {
return { key: ref.key, isOptional: ref.isOptional };
},
({ key, isOptional }, context) => {
if (!context.externalReferencesMap.has(key)) {
if (isOptional) return void 0;
throw new Error(`External reference "${key}" not found`);
}
return context.externalReferencesMap.get(key);
}
);
function getIsForbiddenProperty(property) {
switch (property) {
case "__proto__":
case "prototype":
case "constructor":
return true;
default:
return false;
}
}
function getIsRecord(value) {
if (!value) return false;
const valuePrototype = Object.getPrototypeOf(value);
return valuePrototype === Object.prototype || valuePrototype === null;
}
function getIsObject(value) {
return typeof value === "object" && value !== null;
}
function getIsNotNull(value) {
return value !== null;
}
function copyJSON(json) {
if (Array.isArray(json)) {
const result = [];
for (let index = 0; index < json.length; index++) {
result[index] = copyJSON(json[index]);
}
return result;
} else if (getIsRecord(json)) {
const result = {};
for (const key of Object.keys(json)) {
if (getIsForbiddenProperty(key)) continue;
result[key] = copyJSON(json[key]);
}
return result;
}
return json;
}
function escapePathSegment(segment) {
if (typeof segment === "number") return `${segment}`;
return segment.replaceAll("~", "~0").replaceAll("/", "~1");
}
function addPathSegment(currentPointer, newSegment) {
switch (currentPointer) {
case "/":
case "":
return `/${escapePathSegment(newSegment)}`;
default:
return `${currentPointer}/${escapePathSegment(newSegment)}`;
}
}
function resolveRefAlias(input, context, currentPath) {
const referencedObject = context.resolveRefAlias(input.$$ref);
if (!referencedObject) {
console.warn(`Reference could not be resolved while decoding (${input.$$ref}) at ${currentPath}`);
return null;
}
return referencedObject;
}
function resolveTypeTag(tag, tagKey, context, coder, path) {
const typeName = tagKey.slice(2);
const matchingType = coder.getTypeByName(typeName);
if (!matchingType) {
console.warn(`Unknown custom type: ${typeName} at ${path}. Returning the raw value.`);
return tag[tagKey];
}
const decodedTypeInput = decodeInput(tag[tagKey], context, coder, addPathSegment(path, tagKey));
return matchingType.decode(decodedTypeInput, context);
}
function decodeArray(input, context, coder, path) {
const result = [];
context.registerRef(path, result);
for (let key = 0; key < input.length; key++) {
const fullPath = addPathSegment(path, key);
const decoded = decodeInput(input[key], context, coder, fullPath);
result[key] = decoded;
if (context.hasRefAliases && getIsObject(decoded)) {
context.registerRef(fullPath, decoded);
}
}
return result;
}
function decodeRecord(input, context, coder, path) {
const result = {};
context.registerRef(path, result);
for (const key of Object.keys(input)) {
if (getIsForbiddenProperty(key)) continue;
const fullPath = addPathSegment(path, key);
const decoded = decodeInput(input[key], context, coder, fullPath);
result[key] = decoded;
if (context.hasRefAliases && getIsObject(decoded)) {
context.registerRef(fullPath, decoded);
}
}
return result;
}
function unescapeTag(input) {
const key = Object.keys(input)[0];
return {
[key.slice(1)]: input[key]
};
}
function decodeInput(input, context, coder, path) {
let decodableTypeOf = getDecodableTypeOf(input);
switch (decodableTypeOf) {
case "escaped-tag": {
input = unescapeTag(input);
decodableTypeOf = "record";
}
case "primitive": {
return input;
}
case "ref-tag": {
return resolveRefAlias(input, context, path);
}
case "array": {
return decodeArray(input, context, coder, path);
}
case "record": {
return decodeRecord(input, context, coder, path);
}
}
return resolveTypeTag(input, decodableTypeOf, context, coder, path);
}
function getShouldEscapeKey(key) {
return /^~*\$\$/.test(key);
}
function encodeInput(input, encodeContext, coder, path) {
const codableTypeOf = getCodableTypeOf(input);
switch (codableTypeOf) {
case "primitive":
return input;
case "special-number":
return $$num.encodeTag(input, encodeContext);
case "symbol":
return $$symbol.encodeTag(input, encodeContext);
case "bigint":
return $$bigInt.encodeTag(input, encodeContext);
case "undefined":
return $$undefined.encodeTag(input, encodeContext);
case "function":
return null;
}
if (encodeContext.preserveReferences) {
const alreadySeenAtPath = encodeContext.getAlreadySeenObjectPath(input);
if (alreadySeenAtPath !== null) {
return createTag("ref", alreadySeenAtPath);
}
encodeContext.registerNewSeenObject(input, path);
}
if (codableTypeOf === "custom-object") {
const matchingType = coder.getMatchingTypeFor(input);
if (!matchingType) {
switch (encodeContext.unknownMode) {
case "unchanged":
return input;
case "null":
return null;
case "throw":
throw new Error("Not able to encode - no matching type found", input);
}
}
return matchingType.createTag(
encodeInput(
matchingType.encode(input, encodeContext),
encodeContext,
coder,
addPathSegment(path, matchingType.tagKey)
)
);
}
if (codableTypeOf === "array") {
const result2 = [];
for (let i = 0; i < input.length; i++) {
result2[i] = encodeInput(input[i], encodeContext, coder, addPathSegment(path, i));
}
return result2;
}
const keys = Object.keys(input);
if (keys.length === 1 && getShouldEscapeKey(keys[0])) {
input = { [`~${keys[0]}`]: input[keys[0]] };
keys[0] = `~${keys[0]}`;
}
const result = {};
for (const key of keys) {
if (getIsForbiddenProperty(key)) continue;
result[key] = encodeInput(input[key], encodeContext, coder, addPathSegment(path, key));
}
return result;
}
function resolveDependency(dependency) {
if (getIsCodableType(dependency)) return dependency;
const codableClassType = getCodableClassType(dependency);
if (!codableClassType) return null;
return codableClassType;
}
function getDirectDependencies(dependency) {
if (getIsCodableType(dependency)) {
return resolveThunk(dependency.dependencies);
}
const codableClassType = getCodableClassType(dependency);
if (!codableClassType) return null;
return resolveThunk(codableClassType.dependencies);
}
function addToSet(source, values) {
for (const value of values) {
source.add(value);
}
}
function resolveCodableDependencies(dependency) {
const resolvedDependencies = /* @__PURE__ */ new Set();
let dependenciesToCheck = /* @__PURE__ */ new Set([dependency]);
while (true) {
if (dependenciesToCheck.size === 0) break;
for (const dependency2 of dependenciesToCheck) {
dependenciesToCheck.delete(dependency2);
if (resolvedDependencies.has(dependency2)) continue;
resolvedDependencies.add(dependency2);
const nestedDependencies = getDirectDependencies(dependency2);
if (!nestedDependencies) continue;
addToSet(resolvedDependencies, nestedDependencies);
}
}
resolvedDependencies.delete(dependency);
const resolvedTypes = Array.from(resolvedDependencies).map(resolveDependency).filter(getIsNotNull);
return new Set(resolvedTypes);
}
const DEFAULT_TYPES = [...Object.values(builtinTypesMap), $$externalReference].filter(getIsCodableType);
function getSortedTypes(types) {
return types.sort((a, b) => {
return b.priority - a.priority;
});
}
function createTypesMap(types) {
const sortedTypes = getSortedTypes(types);
const map = /* @__PURE__ */ new Map();
for (const type of sortedTypes) {
if (map.has(type.name)) {
throw new Error(`Coder type "${type.name}" already registered`);
}
map.set(type.name, type);
}
return map;
}
function updateTypesOrderByPriority(currentTypes) {
const sortedTypes = getSortedTypes([...currentTypes.values()]);
const needsReordering = sortedTypes.some((type) => type.priority !== 0);
if (!needsReordering) return;
currentTypes.clear();
for (const type of sortedTypes) {
currentTypes.set(type.name, type);
}
}
class Coder {
constructor(extraTypes = []) {
__publicField(this, "typesMap", /* @__PURE__ */ new Map());
this.typesMap = createTypesMap([...DEFAULT_TYPES]);
this.register(...extraTypes);
}
reorderTypes() {
updateTypesOrderByPriority(this.typesMap);
}
getTypeByName(name) {
return this.typesMap.get(name) ?? null;
}
getHasType(type) {
const existingType = this.getTypeByName(type.name);
return type === existingType;
}
registerSingleType(type) {
if (this.isDefault) {
throw new Error(
"Cannot register types on the default coder. Create a custom coder instance using `new Coder()` and register types on that instance."
);
}
if (this.getHasType(type)) return;
if (this.typesMap.has(type.name)) {
throw new Error(`Other codable type with name "${type.name}" already registered`);
}
this.typesMap.set(type.name, type);
const dependencies = resolveCodableDependencies(type);
for (const dependency of dependencies) {
this.registerSingleType(dependency);
}
this.reorderTypes();
}
registerType(...types) {
for (const type of types) {
this.registerSingleType(type);
}
}
registerSingle(typeOrClass) {
const typeToAdd = getIsCodableClass(typeOrClass) ? assertGet(getCodableClassType(typeOrClass), `Codable class "${typeOrClass.name}" not registered`) : typeOrClass;
return this.registerSingleType(typeToAdd);
}
register(...typesOrClasses) {
for (const typeOrClass of typesOrClasses) {
this.registerSingle(typeOrClass);
}
}
/**
* Typescript-sugar over `.registerType()` with better type inference.
*/
addType(name, canEncode, encode2, decode2, options) {
return this.registerType(createCodableType(name, canEncode, encode2, decode2, options));
}
encode(value, options) {
const encodeContext = new EncodeContext(options);
return encodeInput(value, encodeContext, this, "/");
}
decode(value, options) {
const context = new DecodeContext(value, options);
if (context.isPlainJSON) return copyJSON(value);
return decodeInput(value, context, this, "/");
}
stringify(value) {
return JSON.stringify(this.encode(value));
}
parse(value) {
return this.decode(JSON.parse(value));
}
copy(value) {
return this.decode(this.encode(value));
}
getMatchingTypeFor(input) {
for (const type of this.typesMap.values()) {
if (type.canHandle(input)) {
return type;
}
}
return null;
}
get isDefault() {
return this === defaultCoder;
}
}
function createCoder(extraTypes = []) {
return new Coder(extraTypes);
}
const defaultCoder = createCoder();
function decode(value, options) {
return defaultCoder.decode(value, options);
}
function encode(value) {
return defaultCoder.encode(value);
}
function stringify(value) {
return defaultCoder.stringify(value);
}
function parse(value) {
return defaultCoder.parse(value);
}
function getRegisteredCodableFields(Class) {
return codableClassFieldsRegistry.getFor(Class);
}
function resolveCodableOptions(options) {
if (typeof options === "string") {
return { encodeAs: options };
}
return options ?? null;
}
function codable(optionsInput) {
const options = resolveCodableOptions(optionsInput);
return function codable2(initialValue, context) {
var _a;
if (context.kind !== "accessor" && context.kind !== "field") {
throw new Error("Codable decorator can only be used on fields or accessors");
}
const isSymbolName = typeof context.name === "symbol";
if (isSymbolName) throw new Error("Symbol property names are not supported");
if ((_a = externalClassFieldsRegistry.get(context.metadata)) == null ? void 0 : _a.has(context.name)) {
throw new Error("Codable decorator cannot be used on external properties");
}
const fieldsMap = codableClassFieldsRegistry.getOrInit(context.metadata, () => /* @__PURE__ */ new Map());
fieldsMap.set(context.name, removeUndefinedProperties({ encodeAs: options == null ? void 0 : options.encodeAs }));
};
}
function* iteratePrototypeChain(Class) {
let current = Class;
while (current !== null && current !== Function.prototype && current !== Object.prototype) {
yield current;
current = Object.getPrototypeOf(current);
}
}
function getPrototypeChainLength(Class) {
return [...iteratePrototypeChain(Class)].length;
}
function collectRegisteredCodableFields(Class, keysMap) {
for (const ClassInPrototype of iteratePrototypeChain(Class)) {
const registeredKeysMap = getRegisteredCodableFields(ClassInPrototype);
if (!registeredKeysMap) continue;
for (const [key, metadata] of registeredKeysMap.entries()) {
keysMap.set(key, metadata);
}
}
if (keysMap.size === 0) return null;
return keysMap;
}
function getCodableProperties(Class) {
const keysMap = /* @__PURE__ */ new Map();
collectRegisteredCodableFields(Class, keysMap);
return keysMap;
}
function getExternalProperties(Class) {
const externalFieldsMap = externalClassFieldsRegistry.getFor(Class);
if (!externalFieldsMap) return null;
return externalFieldsMap;
}
function mergeMaps(map1, map2) {
const result = new Map(map1);
for (const [key, value] of map2.entries()) {
result.set(key, value);
}
return result;
}
function getFieldsInfo(Class, fieldsFromOptions) {
const fieldsFromProperties = getCodableProperties(Class);
return mergeMaps(fieldsFromProperties, fieldsFromOptions);
}
function createDefaultClassEncoder(Class, fieldsFromOptions) {
let fields = null;
return (instance) => {
const externalKeysMap = getExternalProperties(Class);
const data = {};
if (!fields) {
fields = getFieldsInfo(Class, fieldsFromOptions);
}
for (const [instanceKey, metadata] of fields.entries()) {
const encodeAs = metadata.encodeAs ?? instanceKey;
data[encodeAs] = instance[instanceKey];
}
if (externalKeysMap) {
for (const [key, externalRef] of externalKeysMap.entries()) {
data[key] = externalReference(externalRef.key, externalRef.isOptional);
}
}
return [data];
};
}
function createRemappingMap(fields) {
const remappingMap = /* @__PURE__ */ new Map();
for (const [classKey, fieldMeta] of fields.entries()) {
const encodedAsKey = fieldMeta.encodeAs ?? classKey;
if (encodedAsKey !== classKey) {
remappingMap.set(encodedAsKey, classKey);
}
}
return remappingMap;
}
function unmapKeys(data, remappingMap) {
const unmappedData = {};
for (const [classOrEncodedKey, value] of Object.entries(data)) {
const instanceKey = remappingMap.get(classOrEncodedKey) ?? classOrEncodedKey;
unmappedData[instanceKey] = value;
}
return unmappedData;
}
function createClassDecoder(Class, isDefaultEncoder, keysFromOptions) {
let fields = null;
let remappingMap = null;
return (data) => {
if (!isDefaultEncoder) {
return new Class(...data);
}
let [dataInput] = data;
if (!fields) {
fields = getFieldsInfo(Class, keysFromOptions);
}
if (remappingMap === null) {
remappingMap = createRemappingMap(fields);
}
if (remappingMap.size > 0) {
dataInput = unmapKeys(dataInput, remappingMap);
}
const instance = new Class(dataInput);
Object.assign(instance, dataInput);
return instance;
};
}
function resolveKeys(keys) {
if (Array.isArray(keys)) {
const entries2 = keys.map((key) => [key, {}]);
return new Map(entries2);
}
const entries = Object.entries(keys).map(([key, value]) => {
return [
//
key,
{ encodeAs: value }
];
});
return new Map(entries);
}
function codableClass(...[name, maybeOptions]) {
return (Class, context) => {
const isUsingDefaultEncoder = (maybeOptions == null ? void 0 : maybeOptions.encode) === void 0;
const keysFromOptions = resolveKeys((maybeOptions == null ? void 0 : maybeOptions.keys) ?? []);
const encoder = (maybeOptions == null ? void 0 : maybeOptions.encode) ?? createDefaultClassEncoder(Class, keysFromOptions);
const decoder = createClassDecoder(Class, isUsingDefaultEncoder, keysFromOptions);
const type = createCodableType(
name,
(value) => value instanceof Class,
encoder,
decoder,
/**
* If we have Foo and Bar extending Foo, Bar should always be the first to try to match.
* As Foo is the parent class, it will also match Bar, resulting in incorrect data being decoded.
*/
{
priority: getPrototypeChainLength(Class),
dependencies: maybeOptions == null ? void 0 : maybeOptions.dependencies
}
);
registerCodableClass(context.metadata, {
name,
codableType: type
});
const fieldsMap = codableClassFieldsRegistry.getOrInit(context.metadata, () => /* @__PURE__ */ new Map());
for (const [key, metadata] of keysFromOptions.entries()) {
if (fieldsMap.has(key)) continue;
fieldsMap.set(key, metadata);
}
};
}
exports.CodableType = CodableType;
exports.Coder = Coder;
exports.codable = codable;
exports.codableClass = codableClass;
exports.createCodableType = createCodableType;
exports.decode = decode;
exports.encode = encode;
exports.getIsCodableType = getIsCodableType;
exports.parse = parse;
exports.stringify = stringify;
//# sourceMappingURL=index.cjs.map

Sorry, the diff of this file is too big to display

var __defProp = Object.defineProperty;
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
const TYPED_ARRAY_MAP = {
uint8: Uint8Array,
uint8clamped: Uint8ClampedArray,
uint16: Uint16Array,
uint32: Uint32Array,
int8: Int8Array,
int16: Int16Array,
int32: Int32Array,
float32: Float32Array,
float64: Float64Array
};
function getIsTypedArray(value) {
for (const type of Object.values(TYPED_ARRAY_MAP)) {
if (value instanceof type) return true;
}
return false;
}
function getTypedArrayType(value) {
for (const name of Object.keys(TYPED_ARRAY_MAP)) {
if (value instanceof TYPED_ARRAY_MAP[name])
return name;
}
throw new Error(`Unknown typed array type: ${value}`);
}
function getTypedArrayConstructor(type) {
const TypedArrayClass = TYPED_ARRAY_MAP[type];
if (!TypedArrayClass) {
throw new Error(`Unknown typed array type: ${type}`);
}
return TypedArrayClass;
}
function getSpecialNumberType(value) {
switch (value) {
case Infinity:
return "Infinity";
case -Infinity:
return "-Infinity";
case 0:
if (1 / value === -Infinity) return "-0";
}
if (isNaN(value)) return "NaN";
return null;
}
function decodeSpecialNumber(value) {
switch (value) {
case "NaN":
return NaN;
case "Infinity":
return Infinity;
case "-Infinity":
return -Infinity;
case "-0":
return -0;
default:
throw new Error(`Invalid special number type: ${value}`);
}
}
function resolveThunk(thunk) {
let result = thunk;
while (typeof result === "function") {
result = result();
}
return result;
}
function getSymbolKey(symbol) {
const nativeKey = Symbol.keyFor(symbol);
if (nativeKey) return nativeKey;
const toStringResult = symbol.toString();
return toStringResult.slice(7, -1);
}
function removeUndefinedProperties(input) {
const result = {};
for (const key of Object.keys(input)) {
const value = input[key];
if (value !== void 0) {
result[key] = value;
}
}
return result;
}
function createTag(name, data) {
const tag = {};
tag[`$$${name}`] = data;
return tag;
}
const DEFAULT_PRIORITY = 10;
class CodableType {
constructor(definition) {
__publicField(this, "name");
__publicField(this, "priority");
__publicField(this, "dependencies");
__publicField(this, "tagKey");
var _a, _b;
this.definition = definition;
this.name = definition.name;
this.tagKey = `$$${this.name}`;
this.priority = ((_a = definition.options) == null ? void 0 : _a.priority) ?? DEFAULT_PRIORITY;
this.dependencies = ((_b = definition.options) == null ? void 0 : _b.dependencies) ?? null;
}
encode(value, context) {
return this.definition.encode(value, context);
}
encodeTag(value, context) {
return createTag(this.name, this.encode(value, context));
}
decode(data, context) {
return this.definition.decode(data, context);
}
canHandle(value) {
return this.definition.canHandle(value);
}
createTag(data) {
return createTag(this.name, data);
}
}
function createCodableType(name, canHandle, encode2, decode2, options) {
return new CodableType({
name,
canHandle,
encode: encode2,
decode: decode2,
options
});
}
function getIsCodableType(value) {
return value instanceof CodableType;
}
function getErrorExtraProperties(error) {
const properties = {};
let isEmpty = true;
for (const key of Object.keys(error)) {
if (key === "message" || key === "name" || key === "cause") continue;
properties[key] = error[key];
isEmpty = false;
}
if (isEmpty) return null;
return properties;
}
function getIsValidDate(date) {
return !isNaN(date.getTime());
}
const $$date = createCodableType(
"Date",
(value) => value instanceof Date,
(date) => {
if (!getIsValidDate(date)) return null;
return date.toISOString();
},
(maybeISOString) => {
if (maybeISOString === null) return /* @__PURE__ */ new Date("invalid");
return new Date(maybeISOString);
}
);
const $$set = createCodableType(
"Set",
(value) => value instanceof Set,
(set) => [...set],
(array) => new Set(array)
);
const $$map = createCodableType(
"Map",
(value) => value instanceof Map,
(map) => [...map.entries()],
(entries) => new Map(entries)
);
const $$error = createCodableType(
"Error",
(value) => value instanceof Error,
(error, context) => {
var _a;
const shouldIncludeErrorStack = ((_a = context.options) == null ? void 0 : _a.includeErrorStack) ?? false;
const extraProperties = getErrorExtraProperties(error) ?? void 0;
const name = error.name && error.name !== "Error" ? error.name : void 0;
const cause = error.cause;
const message = error.message;
const stack = shouldIncludeErrorStack ? error.stack : void 0;
if (!extraProperties && !name && !cause && !stack) {
return message;
}
return removeUndefinedProperties({
message,
name,
cause,
properties: extraProperties,
stack
});
},
(messageOrData) => {
if (typeof messageOrData === "string") return new Error(messageOrData);
const { message, name, cause, properties, stack } = messageOrData;
const error = new Error(message, { cause });
if (stack) {
error.stack = stack;
}
if (name && name !== "Error") {
error.name = name;
}
if (properties) {
Object.assign(error, properties);
}
return error;
}
);
const $$undefined = createCodableType(
"undefined",
(value) => value === void 0,
() => null,
() => void 0
);
const $$bigInt = createCodableType(
"BigInt",
(value) => typeof value === "bigint",
(bigInt) => bigInt.toString(),
(string) => BigInt(string)
);
const $$regexp = createCodableType(
"RegExp",
(value) => value instanceof RegExp,
({ source, flags }) => {
if (flags) return [source, flags];
return source;
},
(sourceOrSourceAndFlags) => {
if (typeof sourceOrSourceAndFlags === "string") {
return new RegExp(sourceOrSourceAndFlags);
}
const [source, flags] = sourceOrSourceAndFlags;
return new RegExp(source, flags);
}
);
const $$url = createCodableType(
"URL",
(value) => value instanceof URL,
(url) => url.toString(),
(string) => new URL(string)
);
const symbolsRegistry = /* @__PURE__ */ new Map();
const $$symbol = createCodableType(
"Symbol",
(value) => typeof value === "symbol",
(symbol) => {
const symbolKey = getSymbolKey(symbol);
symbolsRegistry.set(symbolKey, symbol);
return symbolKey;
},
(symbolKey) => symbolsRegistry.get(symbolKey) ?? Symbol.for(symbolKey)
);
const $$typedArray = createCodableType(
"typedArray",
getIsTypedArray,
(value) => {
return {
type: getTypedArrayType(value),
data: [...value]
};
},
({ type, data }) => {
return new (getTypedArrayConstructor(type))(data);
}
);
const $$num = createCodableType(
"num",
(value) => typeof value === "number" && !!getSpecialNumberType(value),
getSpecialNumberType,
decodeSpecialNumber
);
const $$urlSearchParams = createCodableType(
"URLSearchParams",
(value) => value instanceof URLSearchParams,
(urlSearchParams) => urlSearchParams.toString(),
(string) => new URLSearchParams(string)
);
const builtinTypesMap = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
__proto__: null,
$$bigInt,
$$date,
$$error,
$$map,
$$num,
$$regexp,
$$set,
$$symbol,
$$typedArray,
$$undefined,
$$url,
$$urlSearchParams
}, Symbol.toStringTag, { value: "Module" }));
function getTagValue(tag) {
return Object.values(tag)[0];
}
const OBJECT_PROTOTYPE = Object.prototype;
function getCodableTypeOf(input) {
if (input === null) return "primitive";
if (input === void 0) return "undefined";
const typeOfInput = typeof input;
switch (typeOfInput) {
case "boolean":
case "string":
return "primitive";
case "number":
if (getSpecialNumberType(input)) return "special-number";
return "primitive";
case "symbol":
return "symbol";
case "bigint":
return "bigint";
case "function":
return "function";
}
if (Array.isArray(input)) return "array";
const inputPrototype = Object.getPrototypeOf(input);
if (inputPrototype === OBJECT_PROTOTYPE || inputPrototype === null) {
return "record";
}
return "custom-object";
}
const ANY_TAG_KEY_REGEXP = /^~*\$\$.+/;
function getDecodableTypeOf(input, context) {
switch (typeof input) {
case "boolean":
case "string":
case "number":
return "primitive";
case "symbol":
case "bigint":
case "function":
case "undefined":
throw new Error(`undefined is not a valid JSON value`);
}
if (input === null) return "primitive";
if (Array.isArray(input)) return "array";
const keys = Object.keys(input);
if (keys.length !== 1) return "record";
const key = keys[0];
if (key.startsWith("$$")) {
if (key === "$$") return "record";
if (key === "$$ref") {
if (typeof input.$$ref === "string") return "ref-tag";
return "record";
}
return key;
}
if (ANY_TAG_KEY_REGEXP.test(key)) return "escaped-tag";
return "record";
}
function assert(condition, message) {
if (!condition) {
if (message instanceof Error) {
throw message;
}
throw new Error(message);
}
}
function assertGet(value, message) {
assert(value !== null && value !== void 0, message);
return value;
}
function analyzeEncodedData(data, context) {
const decodableTypeOf = getDecodableTypeOf(data);
switch (decodableTypeOf) {
case "primitive":
return;
case "ref-tag":
context.presentRefAliases.add(data.$$ref);
return;
case "array":
for (let i = 0; i < data.length; i++) {
analyzeEncodedData(data[i], context);
}
return;
case "record":
for (const key of Object.keys(data)) {
analyzeEncodedData(data[key], context);
}
return;
case "escaped-tag":
context.hasEscapedTags = true;
analyzeEncodedData(getTagValue(data), context);
return;
}
context.hasCustomTypes = true;
analyzeEncodedData(data[decodableTypeOf], context);
}
class DecodeContext {
constructor(data, options) {
__publicField(this, "hasEscapedTags", false);
__publicField(this, "hasCustomTypes", false);
// Is ready instantly after analyzing the data
__publicField(this, "presentRefAliases", /* @__PURE__ */ new Set());
// Will be filled while decoding the data
__publicField(this, "resolvedRefs", /* @__PURE__ */ new Map());
__publicField(this, "hasRefAliases");
__publicField(this, "externalReferencesMap");
this.options = options;
analyzeEncodedData(data, this);
this.externalReferencesMap = new Map(Object.entries((options == null ? void 0 : options.externalReferences) ?? {}));
this.hasRefAliases = this.presentRefAliases.size > 0;
}
/**
* No custom types, no ref aliases, no escaped tags. Encoded data is regular JSON-compatible.
*/
get isPlainJSON() {
return !this.hasEscapedTags && !this.hasCustomTypes && !this.hasRefAliases;
}
/**
* Some object needed by some alias (list prepared before) is ready to be used.
*/
registerRef(path, object) {
if (!this.hasRefAliases || !this.presentRefAliases.has(path)) return;
this.resolvedRefs.set(path, object);
}
/**
* Alias requested value it referenced
*/
resolveRefAlias(path) {
return this.resolvedRefs.get(path) ?? null;
}
}
class EncodeContext {
constructor(options) {
__publicField(this, "unknownMode");
__publicField(this, "preserveReferences");
__publicField(this, "refFirstSeenPath", /* @__PURE__ */ new Map());
this.options = options;
this.unknownMode = (options == null ? void 0 : options.unknownInputMode) ?? "null";
this.preserveReferences = (options == null ? void 0 : options.preserveReferences) ?? true;
}
/**
* Call it the first time some object is seen.
*/
registerNewSeenObject(object, path) {
this.refFirstSeenPath.set(object, path);
}
/**
* Returns where the object was first seen at.
*/
getAlreadySeenObjectPath(object) {
return this.refFirstSeenPath.get(object) ?? null;
}
}
function getMetadataKey(Class) {
return Class[Symbol.metadata] ?? null;
}
if (!Symbol.metadata) {
Reflect.set(Symbol, "metadata", Symbol.for("Symbol.metadata"));
}
class PrivateMetadata {
constructor() {
__publicField(this, "registry", /* @__PURE__ */ new WeakMap());
}
getFor(Class) {
const key = getMetadataKey(Class);
if (!key) return null;
return this.get(key);
}
get(key) {
return this.registry.get(key) ?? null;
}
getOrInit(key, initializer) {
if (this.registry.has(key)) return this.registry.get(key);
const value = initializer();
this.registry.set(key, value);
return value;
}
has(key) {
return this.registry.has(key);
}
set(key, value) {
this.registry.set(key, value);
}
init(key, value) {
if (this.registry.has(key)) return this.registry.get(key);
this.registry.set(key, value);
return value;
}
}
const codableClassRegistry = new PrivateMetadata();
const codableClassFieldsRegistry = new PrivateMetadata();
const externalClassFieldsRegistry = new PrivateMetadata();
function registerCodableClass(key, metadata) {
return codableClassRegistry.init(key, metadata);
}
function getIsCodableClass(Class) {
const key = getMetadataKey(Class);
if (!key) return false;
return codableClassRegistry.has(key);
}
function getCodableClassType(Class) {
var _a;
const key = getMetadataKey(Class);
if (!key) return null;
const codableType = (_a = codableClassRegistry.get(key)) == null ? void 0 : _a.codableType;
if (!codableType) return null;
return codableType;
}
class ExternalReference {
constructor(key, isOptional = false) {
this.key = key;
this.isOptional = isOptional;
}
}
function externalReference(key, isOptional = false) {
return new ExternalReference(key, isOptional);
}
const $$externalReference = createCodableType(
"external",
(value) => value instanceof ExternalReference,
(ref) => {
return { key: ref.key, isOptional: ref.isOptional };
},
({ key, isOptional }, context) => {
if (!context.externalReferencesMap.has(key)) {
if (isOptional) return void 0;
throw new Error(`External reference "${key}" not found`);
}
return context.externalReferencesMap.get(key);
}
);
function getIsForbiddenProperty(property) {
switch (property) {
case "__proto__":
case "prototype":
case "constructor":
return true;
default:
return false;
}
}
function getIsRecord(value) {
if (!value) return false;
const valuePrototype = Object.getPrototypeOf(value);
return valuePrototype === Object.prototype || valuePrototype === null;
}
function getIsObject(value) {
return typeof value === "object" && value !== null;
}
function getIsNotNull(value) {
return value !== null;
}
function copyJSON(json) {
if (Array.isArray(json)) {
const result = [];
for (let index = 0; index < json.length; index++) {
result[index] = copyJSON(json[index]);
}
return result;
} else if (getIsRecord(json)) {
const result = {};
for (const key of Object.keys(json)) {
if (getIsForbiddenProperty(key)) continue;
result[key] = copyJSON(json[key]);
}
return result;
}
return json;
}
function escapePathSegment(segment) {
if (typeof segment === "number") return `${segment}`;
return segment.replaceAll("~", "~0").replaceAll("/", "~1");
}
function addPathSegment(currentPointer, newSegment) {
switch (currentPointer) {
case "/":
case "":
return `/${escapePathSegment(newSegment)}`;
default:
return `${currentPointer}/${escapePathSegment(newSegment)}`;
}
}
function resolveRefAlias(input, context, currentPath) {
const referencedObject = context.resolveRefAlias(input.$$ref);
if (!referencedObject) {
console.warn(`Reference could not be resolved while decoding (${input.$$ref}) at ${currentPath}`);
return null;
}
return referencedObject;
}
function resolveTypeTag(tag, tagKey, context, coder, path) {
const typeName = tagKey.slice(2);
const matchingType = coder.getTypeByName(typeName);
if (!matchingType) {
console.warn(`Unknown custom type: ${typeName} at ${path}. Returning the raw value.`);
return tag[tagKey];
}
const decodedTypeInput = decodeInput(tag[tagKey], context, coder, addPathSegment(path, tagKey));
return matchingType.decode(decodedTypeInput, context);
}
function decodeArray(input, context, coder, path) {
const result = [];
context.registerRef(path, result);
for (let key = 0; key < input.length; key++) {
const fullPath = addPathSegment(path, key);
const decoded = decodeInput(input[key], context, coder, fullPath);
result[key] = decoded;
if (context.hasRefAliases && getIsObject(decoded)) {
context.registerRef(fullPath, decoded);
}
}
return result;
}
function decodeRecord(input, context, coder, path) {
const result = {};
context.registerRef(path, result);
for (const key of Object.keys(input)) {
if (getIsForbiddenProperty(key)) continue;
const fullPath = addPathSegment(path, key);
const decoded = decodeInput(input[key], context, coder, fullPath);
result[key] = decoded;
if (context.hasRefAliases && getIsObject(decoded)) {
context.registerRef(fullPath, decoded);
}
}
return result;
}
function unescapeTag(input) {
const key = Object.keys(input)[0];
return {
[key.slice(1)]: input[key]
};
}
function decodeInput(input, context, coder, path) {
let decodableTypeOf = getDecodableTypeOf(input);
switch (decodableTypeOf) {
case "escaped-tag": {
input = unescapeTag(input);
decodableTypeOf = "record";
}
case "primitive": {
return input;
}
case "ref-tag": {
return resolveRefAlias(input, context, path);
}
case "array": {
return decodeArray(input, context, coder, path);
}
case "record": {
return decodeRecord(input, context, coder, path);
}
}
return resolveTypeTag(input, decodableTypeOf, context, coder, path);
}
function getShouldEscapeKey(key) {
return /^~*\$\$/.test(key);
}
function encodeInput(input, encodeContext, coder, path) {
const codableTypeOf = getCodableTypeOf(input);
switch (codableTypeOf) {
case "primitive":
return input;
case "special-number":
return $$num.encodeTag(input, encodeContext);
case "symbol":
return $$symbol.encodeTag(input, encodeContext);
case "bigint":
return $$bigInt.encodeTag(input, encodeContext);
case "undefined":
return $$undefined.encodeTag(input, encodeContext);
case "function":
return null;
}
if (encodeContext.preserveReferences) {
const alreadySeenAtPath = encodeContext.getAlreadySeenObjectPath(input);
if (alreadySeenAtPath !== null) {
return createTag("ref", alreadySeenAtPath);
}
encodeContext.registerNewSeenObject(input, path);
}
if (codableTypeOf === "custom-object") {
const matchingType = coder.getMatchingTypeFor(input);
if (!matchingType) {
switch (encodeContext.unknownMode) {
case "unchanged":
return input;
case "null":
return null;
case "throw":
throw new Error("Not able to encode - no matching type found", input);
}
}
return matchingType.createTag(
encodeInput(
matchingType.encode(input, encodeContext),
encodeContext,
coder,
addPathSegment(path, matchingType.tagKey)
)
);
}
if (codableTypeOf === "array") {
const result2 = [];
for (let i = 0; i < input.length; i++) {
result2[i] = encodeInput(input[i], encodeContext, coder, addPathSegment(path, i));
}
return result2;
}
const keys = Object.keys(input);
if (keys.length === 1 && getShouldEscapeKey(keys[0])) {
input = { [`~${keys[0]}`]: input[keys[0]] };
keys[0] = `~${keys[0]}`;
}
const result = {};
for (const key of keys) {
if (getIsForbiddenProperty(key)) continue;
result[key] = encodeInput(input[key], encodeContext, coder, addPathSegment(path, key));
}
return result;
}
function resolveDependency(dependency) {
if (getIsCodableType(dependency)) return dependency;
const codableClassType = getCodableClassType(dependency);
if (!codableClassType) return null;
return codableClassType;
}
function getDirectDependencies(dependency) {
if (getIsCodableType(dependency)) {
return resolveThunk(dependency.dependencies);
}
const codableClassType = getCodableClassType(dependency);
if (!codableClassType) return null;
return resolveThunk(codableClassType.dependencies);
}
function addToSet(source, values) {
for (const value of values) {
source.add(value);
}
}
function resolveCodableDependencies(dependency) {
const resolvedDependencies = /* @__PURE__ */ new Set();
let dependenciesToCheck = /* @__PURE__ */ new Set([dependency]);
while (true) {
if (dependenciesToCheck.size === 0) break;
for (const dependency2 of dependenciesToCheck) {
dependenciesToCheck.delete(dependency2);
if (resolvedDependencies.has(dependency2)) continue;
resolvedDependencies.add(dependency2);
const nestedDependencies = getDirectDependencies(dependency2);
if (!nestedDependencies) continue;
addToSet(resolvedDependencies, nestedDependencies);
}
}
resolvedDependencies.delete(dependency);
const resolvedTypes = Array.from(resolvedDependencies).map(resolveDependency).filter(getIsNotNull);
return new Set(resolvedTypes);
}
const DEFAULT_TYPES = [...Object.values(builtinTypesMap), $$externalReference].filter(getIsCodableType);
function getSortedTypes(types) {
return types.sort((a, b) => {
return b.priority - a.priority;
});
}
function createTypesMap(types) {
const sortedTypes = getSortedTypes(types);
const map = /* @__PURE__ */ new Map();
for (const type of sortedTypes) {
if (map.has(type.name)) {
throw new Error(`Coder type "${type.name}" already registered`);
}
map.set(type.name, type);
}
return map;
}
function updateTypesOrderByPriority(currentTypes) {
const sortedTypes = getSortedTypes([...currentTypes.values()]);
const needsReordering = sortedTypes.some((type) => type.priority !== 0);
if (!needsReordering) return;
currentTypes.clear();
for (const type of sortedTypes) {
currentTypes.set(type.name, type);
}
}
class Coder {
constructor(extraTypes = []) {
__publicField(this, "typesMap", /* @__PURE__ */ new Map());
this.typesMap = createTypesMap([...DEFAULT_TYPES]);
this.register(...extraTypes);
}
reorderTypes() {
updateTypesOrderByPriority(this.typesMap);
}
getTypeByName(name) {
return this.typesMap.get(name) ?? null;
}
getHasType(type) {
const existingType = this.getTypeByName(type.name);
return type === existingType;
}
registerSingleType(type) {
if (this.isDefault) {
throw new Error(
"Cannot register types on the default coder. Create a custom coder instance using `new Coder()` and register types on that instance."
);
}
if (this.getHasType(type)) return;
if (this.typesMap.has(type.name)) {
throw new Error(`Other codable type with name "${type.name}" already registered`);
}
this.typesMap.set(type.name, type);
const dependencies = resolveCodableDependencies(type);
for (const dependency of dependencies) {
this.registerSingleType(dependency);
}
this.reorderTypes();
}
registerType(...types) {
for (const type of types) {
this.registerSingleType(type);
}
}
registerSingle(typeOrClass) {
const typeToAdd = getIsCodableClass(typeOrClass) ? assertGet(getCodableClassType(typeOrClass), `Codable class "${typeOrClass.name}" not registered`) : typeOrClass;
return this.registerSingleType(typeToAdd);
}
register(...typesOrClasses) {
for (const typeOrClass of typesOrClasses) {
this.registerSingle(typeOrClass);
}
}
/**
* Typescript-sugar over `.registerType()` with better type inference.
*/
addType(name, canEncode, encode2, decode2, options) {
return this.registerType(createCodableType(name, canEncode, encode2, decode2, options));
}
encode(value, options) {
const encodeContext = new EncodeContext(options);
return encodeInput(value, encodeContext, this, "/");
}
decode(value, options) {
const context = new DecodeContext(value, options);
if (context.isPlainJSON) return copyJSON(value);
return decodeInput(value, context, this, "/");
}
stringify(value) {
return JSON.stringify(this.encode(value));
}
parse(value) {
return this.decode(JSON.parse(value));
}
copy(value) {
return this.decode(this.encode(value));
}
getMatchingTypeFor(input) {
for (const type of this.typesMap.values()) {
if (type.canHandle(input)) {
return type;
}
}
return null;
}
get isDefault() {
return this === defaultCoder;
}
}
function createCoder(extraTypes = []) {
return new Coder(extraTypes);
}
const defaultCoder = createCoder();
function decode(value, options) {
return defaultCoder.decode(value, options);
}
function encode(value) {
return defaultCoder.encode(value);
}
function stringify(value) {
return defaultCoder.stringify(value);
}
function parse(value) {
return defaultCoder.parse(value);
}
function getRegisteredCodableFields(Class) {
return codableClassFieldsRegistry.getFor(Class);
}
function resolveCodableOptions(options) {
if (typeof options === "string") {
return { encodeAs: options };
}
return options ?? null;
}
function codable(optionsInput) {
const options = resolveCodableOptions(optionsInput);
return function codable2(initialValue, context) {
var _a;
if (context.kind !== "accessor" && context.kind !== "field") {
throw new Error("Codable decorator can only be used on fields or accessors");
}
const isSymbolName = typeof context.name === "symbol";
if (isSymbolName) throw new Error("Symbol property names are not supported");
if ((_a = externalClassFieldsRegistry.get(context.metadata)) == null ? void 0 : _a.has(context.name)) {
throw new Error("Codable decorator cannot be used on external properties");
}
const fieldsMap = codableClassFieldsRegistry.getOrInit(context.metadata, () => /* @__PURE__ */ new Map());
fieldsMap.set(context.name, removeUndefinedProperties({ encodeAs: options == null ? void 0 : options.encodeAs }));
};
}
function* iteratePrototypeChain(Class) {
let current = Class;
while (current !== null && current !== Function.prototype && current !== Object.prototype) {
yield current;
current = Object.getPrototypeOf(current);
}
}
function getPrototypeChainLength(Class) {
return [...iteratePrototypeChain(Class)].length;
}
function collectRegisteredCodableFields(Class, keysMap) {
for (const ClassInPrototype of iteratePrototypeChain(Class)) {
const registeredKeysMap = getRegisteredCodableFields(ClassInPrototype);
if (!registeredKeysMap) continue;
for (const [key, metadata] of registeredKeysMap.entries()) {
keysMap.set(key, metadata);
}
}
if (keysMap.size === 0) return null;
return keysMap;
}
function getCodableProperties(Class) {
const keysMap = /* @__PURE__ */ new Map();
collectRegisteredCodableFields(Class, keysMap);
return keysMap;
}
function getExternalProperties(Class) {
const externalFieldsMap = externalClassFieldsRegistry.getFor(Class);
if (!externalFieldsMap) return null;
return externalFieldsMap;
}
function mergeMaps(map1, map2) {
const result = new Map(map1);
for (const [key, value] of map2.entries()) {
result.set(key, value);
}
return result;
}
function getFieldsInfo(Class, fieldsFromOptions) {
const fieldsFromProperties = getCodableProperties(Class);
return mergeMaps(fieldsFromProperties, fieldsFromOptions);
}
function createDefaultClassEncoder(Class, fieldsFromOptions) {
let fields = null;
return (instance) => {
const externalKeysMap = getExternalProperties(Class);
const data = {};
if (!fields) {
fields = getFieldsInfo(Class, fieldsFromOptions);
}
for (const [instanceKey, metadata] of fields.entries()) {
const encodeAs = metadata.encodeAs ?? instanceKey;
data[encodeAs] = instance[instanceKey];
}
if (externalKeysMap) {
for (const [key, externalRef] of externalKeysMap.entries()) {
data[key] = externalReference(externalRef.key, externalRef.isOptional);
}
}
return [data];
};
}
function createRemappingMap(fields) {
const remappingMap = /* @__PURE__ */ new Map();
for (const [classKey, fieldMeta] of fields.entries()) {
const encodedAsKey = fieldMeta.encodeAs ?? classKey;
if (encodedAsKey !== classKey) {
remappingMap.set(encodedAsKey, classKey);
}
}
return remappingMap;
}
function unmapKeys(data, remappingMap) {
const unmappedData = {};
for (const [classOrEncodedKey, value] of Object.entries(data)) {
const instanceKey = remappingMap.get(classOrEncodedKey) ?? classOrEncodedKey;
unmappedData[instanceKey] = value;
}
return unmappedData;
}
function createClassDecoder(Class, isDefaultEncoder, keysFromOptions) {
let fields = null;
let remappingMap = null;
return (data) => {
if (!isDefaultEncoder) {
return new Class(...data);
}
let [dataInput] = data;
if (!fields) {
fields = getFieldsInfo(Class, keysFromOptions);
}
if (remappingMap === null) {
remappingMap = createRemappingMap(fields);
}
if (remappingMap.size > 0) {
dataInput = unmapKeys(dataInput, remappingMap);
}
const instance = new Class(dataInput);
Object.assign(instance, dataInput);
return instance;
};
}
function resolveKeys(keys) {
if (Array.isArray(keys)) {
const entries2 = keys.map((key) => [key, {}]);
return new Map(entries2);
}
const entries = Object.entries(keys).map(([key, value]) => {
return [
//
key,
{ encodeAs: value }
];
});
return new Map(entries);
}
function codableClass(...[name, maybeOptions]) {
return (Class, context) => {
const isUsingDefaultEncoder = (maybeOptions == null ? void 0 : maybeOptions.encode) === void 0;
const keysFromOptions = resolveKeys((maybeOptions == null ? void 0 : maybeOptions.keys) ?? []);
const encoder = (maybeOptions == null ? void 0 : maybeOptions.encode) ?? createDefaultClassEncoder(Class, keysFromOptions);
const decoder = createClassDecoder(Class, isUsingDefaultEncoder, keysFromOptions);
const type = createCodableType(
name,
(value) => value instanceof Class,
encoder,
decoder,
/**
* If we have Foo and Bar extending Foo, Bar should always be the first to try to match.
* As Foo is the parent class, it will also match Bar, resulting in incorrect data being decoded.
*/
{
priority: getPrototypeChainLength(Class),
dependencies: maybeOptions == null ? void 0 : maybeOptions.dependencies
}
);
registerCodableClass(context.metadata, {
name,
codableType: type
});
const fieldsMap = codableClassFieldsRegistry.getOrInit(context.metadata, () => /* @__PURE__ */ new Map());
for (const [key, metadata] of keysFromOptions.entries()) {
if (fieldsMap.has(key)) continue;
fieldsMap.set(key, metadata);
}
};
}
export {
CodableType,
Coder,
codable,
codableClass,
createCodableType,
decode,
encode,
getIsCodableType,
parse,
stringify
};
//# sourceMappingURL=index.mjs.map

Sorry, the diff of this file is too big to display

export declare const $$date: import("./CodableType").CodableType<Date, string | null>;
export declare const $$set: import("./CodableType").CodableType<Set<any>, any[]>;
export declare const $$map: import("./CodableType").CodableType<Map<any, any>, [any, any][]>;
export declare const $$error: import("./CodableType").CodableType<Error, string | {
message: string;
name: string | undefined;
cause: unknown;
properties: Record<string, unknown> | undefined;
stack: string | undefined;
}>;
export declare const $$undefined: import("./CodableType").CodableType<undefined, null>;
export declare const $$bigInt: import("./CodableType").CodableType<bigint, string>;
export declare const $$regexp: import("./CodableType").CodableType<RegExp, string | readonly [string, string]>;
export declare const $$url: import("./CodableType").CodableType<URL, string>;
export declare const $$symbol: import("./CodableType").CodableType<symbol, string>;
export declare const $$typedArray: import("./CodableType").CodableType<import("./utils/typedArrays").TypedArray, {
readonly type: "uint8" | "uint8clamped" | "uint16" | "uint32" | "int8" | "int16" | "int32" | "float32" | "float64";
readonly data: readonly number[];
}>;
/**
* Handles special numbers like NaN, Infinity, -Infinity, -0 that are not correctly serialized by
* regular JSON
*/
export declare const $$num: import("./CodableType").CodableType<number, "Infinity" | "-Infinity" | "-0" | "NaN" | null>;
export declare const $$urlSearchParams: import("./CodableType").CodableType<URLSearchParams, string>;
import { Tag, TagKey } from "./format";
import { CodableDependencies } from "./dependencies";
import { DecodeContext } from "./DecodeContext";
import { EncodeContext } from "./EncodeContext";
export interface CodableTypeOptions {
priority?: number;
dependencies?: CodableDependencies;
}
interface CodableTypeDefinition<Item, Data> {
name: string;
canHandle: (value: unknown) => value is Item;
encode: (data: Item, context: EncodeContext) => Data;
decode: (data: Data, context: DecodeContext) => Item;
options?: CodableTypeOptions;
}
export declare function createTag<Name extends string, Data>(name: Name, data: Data): Tag<Data, Name>;
export declare class CodableType<Item = any, Data = any> {
readonly definition: CodableTypeDefinition<Item, Data>;
constructor(definition: CodableTypeDefinition<Item, Data>);
readonly name: string;
readonly priority: number;
readonly dependencies: CodableDependencies | null;
readonly tagKey: TagKey<typeof this.name>;
encode(value: Item, context: EncodeContext): Data;
encodeTag(value: Item, context: EncodeContext): Tag<Data, typeof this.name>;
decode(data: Data, context: DecodeContext): Item;
canHandle(value: unknown): value is Item;
createTag(data: Data): Tag<Data, typeof this.name>;
}
export declare function createCodableType<Item, Data>(name: string, canHandle: (value: unknown) => value is Item, encode: (data: Item, context: EncodeContext) => Data, decode: (data: Data, context: DecodeContext) => Item, options?: CodableTypeOptions): CodableType<Item, Data>;
export declare function getIsCodableType(value: unknown): value is CodableType;
export {};
import { JSONValue } from "./types";
import { CodableType, CodableTypeOptions } from "./CodableType";
import { DecodeOptions } from "./DecodeContext";
import { EncodeOptions } from "./EncodeContext";
import { AnyClass } from "./decorators/types";
type CodableTypeOrClass = CodableType | AnyClass;
export declare class Coder {
private readonly typesMap;
constructor(extraTypes?: CodableTypeOrClass[]);
private reorderTypes;
getTypeByName(name: string): CodableType | null;
private getHasType;
private registerSingleType;
registerType(...types: Array<CodableType>): void;
private registerSingle;
register(...typesOrClasses: CodableTypeOrClass[]): void;
/**
* Typescript-sugar over `.registerType()` with better type inference.
*/
addType<Item, Data>(name: string, canEncode: (value: unknown) => value is Item, encode: (data: Item) => Data, decode: (data: Data) => Item, options?: CodableTypeOptions): void;
encode<T>(value: T, options?: EncodeOptions): JSONValue;
decode<T>(value: JSONValue, options?: DecodeOptions): T;
stringify<T>(value: T): string;
parse<T>(value: string): T;
copy<T>(value: T): T;
getMatchingTypeFor(input: unknown): CodableType | null;
get isDefault(): boolean;
}
export declare function createCoder(extraTypes?: CodableType[]): Coder;
export declare const defaultCoder: Coder;
export declare function decode<T>(value: JSONValue, options?: DecodeOptions): T;
export declare function encode<T>(value: T): JSONValue;
export declare function stringify<T>(value: T): string;
export declare function parse<T>(value: string): T;
export declare function copy<T>(value: T): T;
export {};
import { JSONValue } from "./types";
import { Coder } from "./Coder";
import { DecodeContext } from "./DecodeContext";
export declare function decodeInput<T>(input: JSONValue, context: DecodeContext, coder: Coder, path: string): T;
import { JSONValue } from "./types";
export interface DecodeOptions {
externalReferences?: Record<string, unknown>;
}
export declare function analyzeEncodedData(data: JSONValue, context: DecodeContext): void;
export declare class DecodeContext {
readonly options?: DecodeOptions | undefined;
hasEscapedTags: boolean;
hasCustomTypes: boolean;
presentRefAliases: Set<string>;
resolvedRefs: Map<string, object>;
readonly hasRefAliases: boolean;
/**
* No custom types, no ref aliases, no escaped tags. Encoded data is regular JSON-compatible.
*/
get isPlainJSON(): boolean;
/**
* Some object needed by some alias (list prepared before) is ready to be used.
*/
registerRef(path: string, object: object): void;
/**
* Alias requested value it referenced
*/
resolveRefAlias(path: string): object | null;
readonly externalReferencesMap: Map<string, unknown>;
constructor(data: JSONValue, options?: DecodeOptions | undefined);
}
import { AnyClass } from "./types";
type CodableFieldDecoratorContext<T, V> = ClassFieldDecoratorContext<T, V> | ClassAccessorDecoratorContext<T, V>;
export declare function getRegisteredCodableFields(Class: AnyClass): import("./registry").CodableClassFieldsMap<AnyClass> | null;
interface CodableOptions {
encodeAs?: string;
}
type CodableOptionsInput = string | CodableOptions;
export declare function codable<T, V>(optionsInput?: CodableOptionsInput): <T_1, V_1>(initialValue: any, context: CodableFieldDecoratorContext<T_1, V_1>) => void;
export {};
import { AnyClass, ClassDecorator, MakeRequired, MemberwiseClass } from "./types";
import { ClassEncoder } from "./encode";
import { CodableClassFieldsMap } from "./registry";
import { CodableDependencies } from "../dependencies";
export type CodableClassKeys<T extends AnyClass> = Array<keyof InstanceType<T>> | CodableClassFieldsMap<T>;
type CodableClassKeysInput<T extends AnyClass> = Array<keyof InstanceType<T>> | Record<keyof InstanceType<T>, string>;
interface CodableClassOptions<T extends AnyClass> {
dependencies?: CodableDependencies;
encode?: ClassEncoder<T>;
keys?: CodableClassKeysInput<T>;
}
type CodableClassDecoratorArgs<T extends AnyClass> = T extends MemberwiseClass<T> ? [
string,
CodableClassOptions<T> | void
] : [
string,
MakeRequired<CodableClassOptions<T>, "encode">
];
export declare function codableClass<T extends AnyClass>(...[name, maybeOptions]: CodableClassDecoratorArgs<T>): ClassDecorator<T>;
export {};
import { AnyClass } from "./types";
import { CodableClassFieldsMap } from "./registry";
export type ClassEncoder<T extends AnyClass> = (instance: InstanceType<T>) => ConstructorParameters<T>;
export declare function createDefaultClassEncoder<T extends AnyClass>(Class: T, fieldsFromOptions: CodableClassFieldsMap<T>): ClassEncoder<T>;
export type ClassDecoder<T extends AnyClass> = (data: ConstructorParameters<T>) => InstanceType<T>;
export declare function createClassDecoder<T extends AnyClass>(Class: T, isDefaultEncoder: boolean, keysFromOptions: CodableClassFieldsMap<T>): ClassDecoder<T>;
import { AnyClass } from "./types";
type CodableFieldDecoratorContext<T, V> = ClassFieldDecoratorContext<T, V> | ClassAccessorDecoratorContext<T, V>;
export declare function getRegisteredCodableFields(Class: AnyClass): import("./registry").CodableClassFieldsMap<AnyClass> | null;
export declare function external<T, V>(key: string, isOptional?: boolean): <T_1, V_1>(initialValue: any, context: CodableFieldDecoratorContext<T_1, V_1>) => void;
export {};
export * from "./codable";
export * from "./codableClass";
export declare function getMetadataKey<T extends object>(Class: T): DecoratorMetadata | null;
export declare class PrivateMetadata<T> {
private registry;
getFor(Class: object): T | null;
get(key: DecoratorMetadata): T | null;
getOrInit(key: DecoratorMetadata, initializer: () => T): T;
has(key: DecoratorMetadata): boolean;
set(key: DecoratorMetadata, value: T): void;
init(key: DecoratorMetadata, value: T): T;
}
import { CodableClassFieldsMap } from "./registry";
import { AnyClass } from "./types";
export declare function getCodableProperties(Class: AnyClass): CodableClassFieldsMap;
export declare function getExternalProperties(Class: AnyClass): Map<string, {
key: string;
isOptional: boolean;
}> | null;
import { AnyClass } from "./types";
export declare function iteratePrototypeChain(Class: object): Generator<object>;
export declare function getPrototypeChainLength(Class: AnyClass): number;
import { PrivateMetadata } from "./PrivateMetadata";
import { AnyClass } from "./types";
import { CodableType } from "../CodableType";
export interface FieldMetadata {
encodeAs?: string;
}
export type ClassCodableType<T extends AnyClass> = CodableType<InstanceType<T>, ConstructorParameters<T>>;
export type CodableClassFieldsMap<T extends AnyClass = AnyClass> = Map<keyof InstanceType<T>, FieldMetadata>;
export interface CodableClassMetadata<T extends AnyClass = AnyClass> {
name: string;
codableType: ClassCodableType<T>;
}
export declare const codableClassRegistry: PrivateMetadata<CodableClassMetadata<AnyClass>>;
export declare const codableClassFieldsRegistry: PrivateMetadata<CodableClassFieldsMap<AnyClass>>;
export declare const externalClassFieldsRegistry: PrivateMetadata<Map<string, {
key: string;
isOptional: boolean;
}>>;
export declare function registerCodableClass<T extends AnyClass>(key: DecoratorMetadata, metadata: CodableClassMetadata<T>): CodableClassMetadata<AnyClass>;
export declare function getIsCodableClass<T extends AnyClass>(Class: object): Class is AnyClass;
export declare function getCodableClassType<T extends AnyClass>(Class: T): ClassCodableType<T> | null;
export type AnyClass = new (...args: any) => any;
export type AtLeastOne<T> = [T, ...T[]];
export type AnyClassWithArgs = new (...args: AtLeastOne<any>) => any;
export type MemberwiseClass<T extends AnyClass> = ConstructorParameters<T> extends [] | [Partial<InstanceType<T>>] ? T : never;
export type IsMemberwiseClass<T extends AnyClass> = ConstructorParameters<T> extends [Partial<InstanceType<T>>] ? true : false;
export type If<Condition, True, False> = Condition extends true ? True : False;
export type ClassDecorator<T extends AnyClass> = (Class: T, context: ClassDecoratorContext<T>) => void;
export type Voidable<T> = T | void;
export type VoidableIf<T, Condition> = Condition extends true ? Voidable<T> : T;
export type MakeRequired<T, K extends keyof T> = T & Required<Pick<T, K>>;
type PropDecorator = (value: any, context: any) => any;
export declare function combineDecorators(...decorators: PropDecorator[]): PropDecorator;
export {};
import { CodableType } from "./CodableType";
import { Thunk } from "./utils/misc";
import { AnyClass } from "./decorators/types";
export type CodableDependency = AnyClass | CodableType;
export type CodableDependencies = Thunk<CodableDependency[]>;
export declare function resolveCodableDependencies(dependency: CodableDependency): Set<CodableType>;
import { JSONValue } from "./types";
import { Coder } from "./Coder";
import { EncodeContext } from "./EncodeContext";
export declare function encodeInput(input: unknown, encodeContext: EncodeContext, coder: Coder, path: string): JSONValue;
type UnknownMode = "unchanged" | "null" | "throw";
export interface EncodeOptions {
/**
* What should happen if some custom class instance is passed to encode, but no
* matching type is found.
*
* - "unchanged": Pass the input as is to the encoded output
* - "null": Replace the input with null in the encoded output
* - "throw": Throw an error if no matching type is found
*
* @default "null"
*/
unknownInputMode?: UnknownMode;
/**
* Should encoder detect if the same object is seen multiple times in the input so it can
* re-create those references later, when decoding the data?
*
* Note: if disabled, and your input contains circular references, the encoder will throw an error.
*
* @default true
*/
preserveReferences?: boolean;
/**
* @default false
*/
includeErrorStack?: boolean;
}
export declare class EncodeContext {
readonly options?: EncodeOptions | undefined;
constructor(options?: EncodeOptions | undefined);
readonly unknownMode: UnknownMode;
readonly preserveReferences: boolean;
private refFirstSeenPath;
/**
* Call it the first time some object is seen.
*/
registerNewSeenObject(object: object, path: string): void;
/**
* Returns where the object was first seen at.
*/
getAlreadySeenObjectPath(object: object): string | null;
}
export {};
export declare class ExternalReference<T> {
readonly key: string;
readonly isOptional: boolean;
constructor(key: string, isOptional?: boolean);
}
export declare function externalReference<T>(key: string, isOptional?: boolean): T;
export declare const $$externalReference: import("./CodableType").CodableType<ExternalReference<any>, {
key: string;
isOptional: boolean;
}>;
export type TagKey<T extends string = string> = `$$${T}`;
export type Tag<V = unknown, T extends string = string> = {
[key in TagKey<T>]: V;
};
export type RefAlias = Tag<string, "ref">;
export declare function getTagValue<T>(tag: Tag<T>): T;
export { Coder, encode, decode, parse, stringify } from "./Coder";
export { codableClass } from "./decorators/codableClass";
export { codable } from "./decorators/codable";
export { createCodableType, getIsCodableType, CodableType } from "./CodableType";
import { JSONPrimitive } from "./types";
export declare function getIsJSONPrimitive(value: unknown): value is JSONPrimitive;
/**
* Returns true only if the value is POJO (Plain Old JavaScript Object)
*
* Returns false for instances of classes, functions, etc.
*/
export declare function getIsRecord(value: unknown): value is Record<string, unknown>;
export declare function getIsObject(value: unknown): value is object;
export declare function getIsNotNull<T>(value: T | null): value is T;
export interface GenerateDataOptions {
sameReferences?: boolean;
i?: number;
j?: number;
}
export declare function generateData(options?: GenerateDataOptions): {
createdAt: Date | Map<string, string> | Set<Date> | Set<number> | Set<number>[];
updatedAt: Date | Map<string, string> | Set<Date> | Set<number> | Set<number>[];
nested1: {
createdAt: Date | Map<string, string> | Set<Date> | Set<number> | Set<number>[];
updatedAt: Date | Map<string, string> | Set<Date> | Set<number> | Set<number>[];
innerNested: {
createdAt: Date | Map<string, string> | Set<Date> | Set<number> | Set<number>[];
updatedAt: Date | Map<string, string> | Set<Date> | Set<number> | Set<number>[];
};
}[];
nested2: {
createdAt: Date | Map<string, string> | Set<Date> | Set<number> | Set<number>[];
updatedAt: Date | Map<string, string> | Set<Date> | Set<number> | Set<number>[];
innerNested: {
createdAt: Date | Map<string, string> | Set<Date> | Set<number> | Set<number>[];
updatedAt: Date | Map<string, string> | Set<Date> | Set<number> | Set<number>[];
};
}[];
}[];
/**
* Example:
* ```
* it("some test", () => {
* using _ = captureWarnings();
*
* // some code that will warn
* expect(console.warn).toHaveBeenCalledWith("Warning message");
* });
* ```
*/
export declare function captureWarnings(): {
[Symbol.dispose]: () => void;
};
export type JSONPrimitive = string | number | boolean | null;
export type JSONObject = {
[key: string]: JSONValue;
};
export type JSONArray = JSONValue[];
export type JSONValue = JSONPrimitive | JSONObject | JSONArray;
export type Primitive = string | number | boolean | null | undefined;
export type ClassConstructor<T> = new (...args: any) => T;
export type AtLeastOne<T> = [T, ...T[]];
/**
* A class that can be instantiated without any arguments.
*/
export type MemberwiseClass<T> = new (input?: Partial<T>) => T;
export type ClassWithoutInput<T> = new () => T;
/**
* A class that requires arguments to be known in order to be instantiated.
*/
export type ManuallyCodableClass<T> = new (...args: AtLeastOne<any>) => T;
export type AnyCodableClass<T> = MemberwiseClass<T> | ManuallyCodableClass<T>;
export declare function assert(condition: boolean, message: string | Error): asserts condition;
export declare function assertGet<T>(value: T | null | undefined, message: string | Error): T;
export declare function narrowType<T>(value: unknown): asserts value is T;
export declare function getErrorExtraProperties(error: Error): Record<string, unknown> | null;
import { JSONValue } from "../types";
export declare function copyJSON(json: JSONValue): JSONValue;
export declare function addPathSegment(currentPointer: string, newSegment: string | number): string;
export type Thunk<T> = T | (() => Thunk<T>);
export declare function resolveThunk<T>(thunk: Thunk<T>): T;
export declare function getSymbolKey(symbol: symbol): string;
export declare function removeUndefinedProperties<T extends Record<string, unknown>>(input: T): T;
export declare function getSpecialNumberType(value: number): "Infinity" | "-Infinity" | "-0" | "NaN" | null;
type SpecialNumberType = ReturnType<typeof getSpecialNumberType>;
export declare function decodeSpecialNumber(value: SpecialNumberType): number;
export {};
export declare function getIsForbiddenProperty(property: string): boolean;
declare const TYPED_ARRAY_MAP: {
readonly uint8: Uint8ArrayConstructor;
readonly uint8clamped: Uint8ClampedArrayConstructor;
readonly uint16: Uint16ArrayConstructor;
readonly uint32: Uint32ArrayConstructor;
readonly int8: Int8ArrayConstructor;
readonly int16: Int16ArrayConstructor;
readonly int32: Int32ArrayConstructor;
readonly float32: Float32ArrayConstructor;
readonly float64: Float64ArrayConstructor;
};
export type TypedArrayTypeName = keyof typeof TYPED_ARRAY_MAP;
export type TypedArray = InstanceType<(typeof TYPED_ARRAY_MAP)[TypedArrayTypeName]>;
export declare function getIsTypedArray(value: unknown): value is TypedArray;
export declare function getTypedArrayType(value: TypedArray): TypedArrayTypeName;
export declare function getTypedArrayConstructor(type: TypedArrayTypeName): Uint8ArrayConstructor | Uint8ClampedArrayConstructor | Uint16ArrayConstructor | Uint32ArrayConstructor | Int8ArrayConstructor | Int16ArrayConstructor | Int32ArrayConstructor | Float32ArrayConstructor | Float64ArrayConstructor;
export {};
import { DecodeContext } from "../DecodeContext";
import { JSONValue } from "../types";
/**
* The goal here is to geather as much information at once, so later we avoid doing unnecessary checks
* such as `typeof input` or `Array.isArray(input)` etc.
*/
export declare function getCodableTypeOf(input: unknown): "function" | "primitive" | "undefined" | "special-number" | "symbol" | "bigint" | "array" | "record" | "custom-object";
export type CodableTypeOf = ReturnType<typeof getCodableTypeOf>;
export type CodablePrimitive = boolean | string | undefined | number;
export declare function getDecodableTypeOf(input: JSONValue, context: DecodeContext): `$$${string}` | "primitive" | "array" | "record" | "ref-tag" | "escaped-tag";
export type DecodableTypeOf = ReturnType<typeof getDecodableTypeOf>;
declare const _default: import("vite").UserConfig;
export default _default;
declare const _default: import("vite").UserConfig;
export default _default;
+61
-2
{
"name": "codables",
"version": "0.0.1",
"version": "0.1.0",
"description": "High-performance, type-safe JSON serialization library that extends JSON to support complex JavaScript types including Date, BigInt, Map, Set, RegExp, Symbol, typed arrays, circular references, and custom classes.",
"type": "module",
"main": "./dist/index.cjs",
"module": "./dist/index.mjs",
"types": "./dist/types/index.d.ts",
"exports": {
".": {
"types": "./dist/types/index.d.ts",
"import": "./dist/index.mjs",
"require": "./dist/index.cjs"
}
},
"files": [
"dist"
],
"keywords": [
"json",
"serialization",
"typescript",
"date",
"bigint",
"map",
"set",
"regexp",
"symbol",
"circular-references",
"decorators",
"class-serialization",
"declarative-serialization",
"typed-arrays",
"url",
"error-serialization",
"reference-preservation",
"superjson-alternative",
"full-stack",
"nextjs",
"remix",
"api-serialization"
],
"author": {
"name": "Adam Pietrasiak",
"email": "adam@pietrasiak.com"
},
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/pie6k/codables.git"
},
"bugs": {
"url": "https://github.com/pie6k/codables/issues"
},
"homepage": "https://github.com/pie6k/codables#readme",
"scripts": {
"build": "vite build && tsc --emitDeclarationOnly",
"clean": "rm -rf dist",
"test": "vitest",
"test:watch": "vitest --watch"
"test:watch": "vitest --watch",
"bench": "vitest bench tests/benchmark.bench.ts"
},
"devDependencies": {
"@types/jsdom": "^27",
"@types/node": "^24.9.1",
"@vitest/coverage-v8": "4.0.1",
"jsdom": "^27.0.1",
"superjson": "^2.2.3",
"typescript": "^5.9.3",
"vite": "^5.0.0",
"vitest": "^4.0.1"
}
}
-173
import { createCoderType } from "./CoderType";
export const $$undefined = createCoderType(
"undefined",
(value) => value === undefined,
() => null,
() => undefined
);
function getIsValidDate(date: Date): boolean {
return !isNaN(date.getTime());
}
export const $$date = createCoderType(
"date",
(value) => value instanceof Date,
(date) => (getIsValidDate(date) ? date.toISOString() : null),
(isoString) => {
if (isoString === null) return new Date("invalid");
return new Date(isoString);
}
);
export const $$bigInt = createCoderType(
"bigInt",
(value) => typeof value === "bigint",
(bigInt) => bigInt.toString(),
(string) => BigInt(string)
);
export const $$set = createCoderType(
"set",
(value) => value instanceof Set,
(set) => Array.from(set),
(array) => new Set(array)
);
export const $$map = createCoderType(
"map",
(value) => value instanceof Map,
(map) => Array.from(map.entries()),
(entries) => new Map(entries)
);
export const $$regexp = createCoderType(
"regexp",
(value) => value instanceof RegExp,
({ source, flags }) => [source, flags] as const,
([source, flags]) => new RegExp(source, flags)
);
export const $$error = createCoderType(
"error",
(value) => value instanceof Error,
(error) => ({
message: error.message,
name: error.name,
cause: error.cause,
}),
({ message, name, cause }) => {
const error = new Error(message, { cause });
if (name) {
error.name = name;
}
return error;
}
);
export const $$url = createCoderType(
"url",
(value) => value instanceof URL,
(url) => url.toString(),
(string) => new URL(string)
);
const symbolsRegistry = new Map<string, symbol>();
function getSymbolName(symbol: symbol): string {
const nativeKey = Symbol.keyFor(symbol);
if (nativeKey) return nativeKey;
const toStringResult = symbol.toString(); // eg "Symbol(foo)"
return toStringResult.slice(7, -1); // "foo"
}
function registerSymbol(symbol: symbol) {
const name = getSymbolName(symbol);
symbolsRegistry.set(name, symbol);
}
function getSymbol(symbolKey: string): symbol {
return symbolsRegistry.get(symbolKey) ?? Symbol.for(symbolKey);
}
export const $$symbol = createCoderType(
"symbol",
(value) => typeof value === "symbol",
(symbol) => {
registerSymbol(symbol);
return getSymbolName(symbol);
},
(symbolKey) => getSymbol(symbolKey)
);
const TYPED_ARRAY_MAP = {
uint8: Uint8Array,
Uint16Array,
uint32: Uint32Array,
int8: Int8Array,
int16: Int16Array,
int32: Int32Array,
float32: Float32Array,
float64: Float64Array,
} as const;
type TypedArrayTypeName = keyof typeof TYPED_ARRAY_MAP;
type TypedArray = InstanceType<(typeof TYPED_ARRAY_MAP)[TypedArrayTypeName]>;
function getIsTypedArray(value: unknown): value is TypedArray {
for (const [name, type] of Object.entries(TYPED_ARRAY_MAP)) {
if (value instanceof type) return true;
}
return false;
}
function getTypedArrayType(value: unknown): TypedArrayTypeName | null {
for (const [name, type] of Object.entries(TYPED_ARRAY_MAP)) {
if (value instanceof type) return name as TypedArrayTypeName;
}
return null;
}
export const $$typedArray = createCoderType(
"typedArray",
getIsTypedArray,
(value) => {
const type = getTypedArrayType(value)!;
return {
type,
data: Array.from(value),
};
},
({ type, data }) => new TYPED_ARRAY_MAP[type as TypedArrayTypeName](data)
);
function getSpecialNumberType(
value: number
): "NaN" | "Infinity" | "-Infinity" | null {
if (isNaN(value)) return "NaN";
if (value === Infinity) return "Infinity";
if (value === -Infinity) return "-Infinity";
return null;
}
export const $$num = createCoderType(
"num",
(value): value is number =>
typeof value === "number" && !!getSpecialNumberType(value),
(value) => getSpecialNumberType(value),
(value) => {
if (value === "NaN") return NaN;
if (value === "Infinity") return Infinity;
if (value === "-Infinity") return -Infinity;
throw new Error(`Invalid special number type: ${value}`);
}
);
import { CoderType, createCoderType } from "./CoderType";
import { AnyCodableClass } from "./types";
const IS_CODABLE = Symbol.for("IS_CODABLE");
const CODABLE_CLASS_TYPE = Symbol.for("CODABLE_CLASS_TYPE");
interface CodableClassMetadata {
[IS_CODABLE]: boolean;
[CODABLE_CLASS_TYPE]: CoderType<any, any>;
}
if (!Symbol.metadata) {
Reflect.set(Symbol, "metadata", Symbol.for("Symbol.metadata"));
}
function getMetadata<T extends object>(Class: T): CodableClassMetadata | null {
return (
(Reflect.get(Class, Symbol.metadata) as CodableClassMetadata | null) ?? null
);
}
export function getIsCodableClass<T extends AnyCodableClass<any>>(
Class: T
): boolean {
return getMetadata(Class)?.[IS_CODABLE] ?? false;
}
export function getCodableClassType<T extends AnyCodableClass<any>>(
Class: T
): CoderType<any, any> | null {
return getMetadata(Class)?.[CODABLE_CLASS_TYPE] ?? null;
}
export function createCodableClassType<T extends AnyCodableClass<any>>(
Class: T
): CoderType<any, any> {
return createCoderType(
Class.name,
(value): value is T => value instanceof Class,
(value) => value,
(value) => value
);
}
export function codableClass<T extends AnyCodableClass<any>>(name: string) {
return (Class: T, context: ClassDecoratorContext<T>) => {
context.metadata[IS_CODABLE] = true;
const codableClassType = createCodableClassType(Class);
context.metadata[CODABLE_CLASS_TYPE] = codableClassType;
};
}
type CodableFieldDecoratorContext<T, V> =
| ClassFieldDecoratorContext<T, V>
| ClassAccessorDecoratorContext<T, V>;
export function codable<T, V>(
initialValue: any,
context: CodableFieldDecoratorContext<T, V>
) {
const isSymbolName = typeof context.name === "symbol";
if (isSymbolName) throw new Error("Symbol property names are not supported");
}
import * as builtinTypesMap from "./builtin";
import { AnyCodableClass, JSONValue } from "./types";
import { CoderType, createCoderType, getIsCoderType } from "./CoderType";
import { encodeInput, finalizeEncodeWithCircularRefs } from "./encode";
import { getCodableClassType, getIsCodableClass } from "./codableClass";
import { CircularRefsManager } from "./refs";
import { decodeInput } from "./decode";
import { parseMaybeCustomTypeWrapper } from "./parseUtils";
const DEFAULT_TYPES = [...Object.values(builtinTypesMap)].filter(
getIsCoderType
);
export class Coder {
private typesMap = new Map<string, CoderType>(
DEFAULT_TYPES.map((type) => [type.name, type])
);
getTypeByName(name: string): CoderType | null {
return this.typesMap.get(name) ?? null;
}
registerType<Item, Data>(type: CoderType<Item, Data>) {
if (this.isDefault) {
throw new Error(
"Cannot register types on the default coder. Create a custom coder instance using `new Coder()` and register types on that instance."
);
}
if (this.typesMap.has(type.name)) {
throw new Error(`Coder type "${type.name}" already registered`);
}
this.typesMap.set(type.name, type);
return type;
}
/**
* Typescript-sugar over `.registerType()` with better type inference.
*/
addType<Item, Data>(
name: string,
canEncode: (value: unknown) => value is Item,
encode: (data: Item) => Data,
decode: (data: Data) => Item
) {
return this.registerType(createCoderType(name, canEncode, encode, decode));
}
encode<T>(value: T): JSONValue {
const circularRefsManager = new CircularRefsManager();
const result = encodeInput(value, circularRefsManager, this, []);
if (!circularRefsManager.hasCircularRefs) return result;
return finalizeEncodeWithCircularRefs(result, circularRefsManager);
}
decode<T>(value: JSONValue): T {
const circularRefsMap = new Map<number, unknown>();
return decodeInput<T>(value, circularRefsMap, this, []);
}
stringify<T>(value: T): string {
return JSON.stringify(this.encode(value));
}
parse<T>(value: string): T {
return this.decode(JSON.parse(value));
}
getMatchingTypeFor(input: unknown): CoderType | null {
for (const type of this.typesMap.values()) {
if (type.canHandle(input)) {
return type;
}
}
return null;
}
parseMaybeCustomTypeWrapper(input: unknown) {
return parseMaybeCustomTypeWrapper(input, this);
}
get isDefault() {
return this === coder;
}
registerClass<T extends AnyCodableClass<any>>(Class: T) {
if (this.isDefault) {
throw new Error(
"Cannot register classes on the default coder. Create a custom coder instance using `new Coder()` and register classes on that instance."
);
}
if (!getIsCodableClass(Class)) {
throw new Error(`Class "${Class.name}" is not codable`);
}
const codableClassType = getCodableClassType(Class);
if (!codableClassType) {
throw new Error(`Class "${Class.name}" is not codable`);
}
return this.registerType(codableClassType);
}
}
export const coder = new Coder();
export function decode<T>(value: JSONValue): T {
return coder.decode(value);
}
export function encode<T>(value: T): JSONValue {
return coder.encode(value);
}
export function stringify<T>(value: T): string {
return coder.stringify(value);
}
export function parse<T>(value: string): T {
return coder.parse(value);
}
import { Coder } from "./Coder";
import { getIsRecord } from "./is";
interface CoderTypeDefinition<Item, Data> {
name: string;
encode: (data: Item) => Data;
decode: (data: Data) => Item;
canHandle: (value: unknown) => value is Item;
}
type CustomTypeKey<Name extends string> = `$$${Name}`;
type CustomTypeWrapper<Name extends string, Type> = {
[key in CustomTypeKey<Name>]: Type;
};
function wrapAsCustomType<Name extends string, Data>(
name: Name,
data: Data
): CustomTypeWrapper<Name, Data> {
const wrapper = {
[`$$${name}`]: data,
} as CustomTypeWrapper<Name, Data>;
return wrapper;
}
export class CoderType<Item = any, Data = any> {
constructor(readonly definition: CoderTypeDefinition<Item, Data>) {}
get name() {
return this.definition.name;
}
get encoder() {
return this.definition.encode;
}
get decoder() {
return this.definition.decode;
}
encode(value: Item): CustomTypeWrapper<string, Data> {
const encodedData = this.encoder(value);
return wrapAsCustomType(this.name, encodedData);
}
canHandle(value: unknown): value is Item {
return this.definition.canHandle(value);
}
}
export function createCoderType<Item, Data>(
name: string,
canHandle: (value: unknown) => value is Item,
encode: (data: Item) => Data,
decode: (data: Data) => Item
): CoderType<Item, Data> {
return new CoderType({
name,
canHandle,
encode,
decode,
});
}
export function getIsCoderType(value: unknown): value is CoderType {
return value instanceof CoderType;
}
export const CUSTOM_TYPE_INDICATOR_PREFIX = "$$";
import { Coder } from "./Coder";
import { JSONValue } from "./types";
import { getIsRecord } from "./is";
import { parseMaybeCircularRefInfo } from "./refs";
import { sanitizePath } from "./utils";
type CircularRefsMap = Map<number, unknown>;
export function decodeInput<T>(
input: JSONValue,
circularRefsMap: CircularRefsMap,
coder: Coder,
path: string[]
): T {
const maybeCustomTypeWrapper = coder.parseMaybeCustomTypeWrapper(input);
if (maybeCustomTypeWrapper) {
const decodedData = decodeInput(
maybeCustomTypeWrapper.data,
circularRefsMap,
coder,
path
);
return maybeCustomTypeWrapper.type.decoder(decodedData) as T;
}
const maybeCircularRefInfo = parseMaybeCircularRefInfo(input);
if (maybeCircularRefInfo) {
if (maybeCircularRefInfo.type === "source") {
circularRefsMap.set(maybeCircularRefInfo.id, maybeCircularRefInfo.source);
}
if (maybeCircularRefInfo.type === "alias") {
// TODO: Validate
return circularRefsMap.get(maybeCircularRefInfo.id)! as T;
}
}
if (Array.isArray(input)) {
const result: unknown[] = [];
for (const [index, item] of input.entries()) {
result[index] = decodeInput(item, circularRefsMap, coder, [
...path,
index.toString(),
]);
}
return result as T;
}
if (getIsRecord(input)) {
const result: Record<string, unknown> = {};
for (const [key, value] of Object.entries(input)) {
result[key] = decodeInput(value, circularRefsMap, coder, [...path, key]);
}
return result as T;
}
return input as T;
}
import { Coder } from "./Coder";
import { codableClass } from "./codableClass";
describe("decorators", () => {
it("should register a class as codable", () => {
@codableClass("Foo")
class Foo {
foo!: string;
}
const coder = new Coder();
coder.registerClass(Foo);
const foo = new Foo();
foo.foo = "bar";
const encoded = coder.encode(foo);
expect(encoded).toEqual({
foo: "bar",
});
const decoded = coder.decode(encoded);
expect(decoded).toEqual(foo);
});
});
import { changeInJSONByMutation, sanitizePath } from "./utils";
import { getIsJSONPrimitive, getIsObject, getIsRecord } from "./is";
import { CircularRefsManager } from "./refs";
import { Coder } from "./Coder";
import { JSONValue } from "./types";
export function finalizeEncodeWithCircularRefs(
output: JSONValue,
circularRefsManager: CircularRefsManager
): JSONValue {
for (const [
circularRefId,
path,
] of circularRefsManager.iterateCircularRefsSourcePaths()) {
output = changeInJSONByMutation(output, path, (current) => {
return {
[`$$ref:${circularRefId}`]: current,
};
});
}
return output;
}
export function encodeInput(
input: unknown,
circularRefsManager: CircularRefsManager,
coder: Coder,
path: string[]
): JSONValue {
if (getIsJSONPrimitive(input)) {
return input;
}
if (getIsObject(input)) {
circularRefsManager.handleNewRef(input, path);
const circularRefAlias = circularRefsManager.getCircularRefAlias(input);
if (circularRefAlias) return circularRefAlias;
}
if (Array.isArray(input)) {
const result: JSONValue = [];
for (const [index, item] of input.entries()) {
result[index] = encodeInput(item, circularRefsManager, coder, [
...path,
index.toString(),
]);
}
return result;
}
if (getIsRecord(input)) {
const result: JSONValue = {};
for (const [key, value] of Object.entries(input)) {
result[key] = encodeInput(value, circularRefsManager, coder, [
...path,
key,
]);
}
return result;
}
const customType = coder.getMatchingTypeFor(input);
if (customType) {
const encoded = customType.encode(input);
return encodeInput(encoded, circularRefsManager, coder, path);
}
return null;
}
import { JSONArray, JSONPrimitive, Primitive } from "./types";
export function getIsJSONPrimitive(value: unknown): value is JSONPrimitive {
if (value === null) return true;
if (typeof value === "string") return true;
if (typeof value === "boolean") return true;
if (typeof value === "number") {
if (isNaN(value)) return false;
if (value === Infinity) return false;
if (value === -Infinity) return false;
return true;
}
return false;
}
/**
* Returns true only if the value is POJO (Plain Old JavaScript Object)
*
* Returns false for instances of classes, functions, etc.
*/
export function getIsRecord(value: unknown): value is Record<string, unknown> {
if (typeof value !== "object" || value === null) {
return false;
}
return value.constructor === Object;
}
export function getIsPrimitive(value: unknown): value is Primitive {
return getIsJSONPrimitive(value) || value === undefined;
}
export function getIsObject(value: unknown): value is object {
return typeof value === "object" && value !== null;
}
import { CUSTOM_TYPE_INDICATOR_PREFIX } from "./consts";
import { Coder } from "./Coder";
import { JSONValue } from "./types";
import { getIsRecord } from "./is";
function parseCustomTypeIndicatorKey(key: string) {
if (!key.startsWith(CUSTOM_TYPE_INDICATOR_PREFIX)) {
return null;
}
return key.slice(CUSTOM_TYPE_INDICATOR_PREFIX.length);
}
export function parseMaybeCustomTypeWrapper(input: unknown, coder: Coder) {
if (!getIsRecord(input)) {
return null;
}
const key = Object.keys(input);
if (key.length !== 1) return null;
const [customTypeIndicatorKey] = key;
const customTypeName = parseCustomTypeIndicatorKey(customTypeIndicatorKey);
if (!customTypeName) return null;
const customType = coder.getTypeByName(customTypeName);
if (!customType) return null;
const data = input[customTypeIndicatorKey] as JSONValue;
return {
name: customTypeName,
type: customType,
data,
coder,
};
}
export type ParsedCustomTypeWrapper = NonNullable<
ReturnType<typeof parseMaybeCustomTypeWrapper>
>;
import { getIsRecord } from "./is";
type RefSourceKey = `$$ref:${number}`;
export type CircularRefSource<T extends object> = {
[key in RefSourceKey]: T;
};
export type CircularRefAlias = {
$$ref: number;
};
export type CircularRefInfo =
| {
type: "source";
id: number;
source: object;
}
| {
type: "alias";
id: number;
};
export function parseMaybeCircularRefInfo(
input: unknown
): CircularRefInfo | null {
if (!getIsRecord(input)) {
return null;
}
const key = Object.keys(input);
if (key.length !== 1) return null;
const [refKey] = key;
if (refKey === "$$ref") {
return {
type: "alias",
id: input[refKey] as number,
};
}
if (refKey.startsWith("$$ref:")) {
const id = parseInt(refKey.slice("$$ref:".length));
return {
type: "source",
id,
source: input[refKey] as object,
};
}
return null;
}
export class CircularRefsManager {
private refFirstSeenPath = new WeakMap<object, string[]>();
private circularRefIds = new Map<object, number>();
private circularRefIdRef = new Map<number, object>();
*iterateCircularRefsSourcePaths() {
for (const [refId, ref] of this.circularRefIdRef) {
const path = this.refFirstSeenPath.get(ref)!;
yield [refId, path] as const;
}
}
registerKnownRef(value: object, path: string[]): void {
this.refFirstSeenPath.set(value, path);
}
hasKnownRef(value: object): boolean {
return this.refFirstSeenPath.has(value);
}
getCircularRefId(value: object): number | null {
return this.circularRefIds.get(value) ?? null;
}
getIsCircularRef(value: object): boolean {
return this.circularRefIds.has(value);
}
registerCircularRef(value: object): number {
const existingRef = this.getCircularRefId(value);
if (existingRef !== null) {
return existingRef;
}
const refId = this.circularRefIds.size;
this.circularRefIds.set(value, refId);
this.circularRefIdRef.set(refId, value);
return refId;
}
handleNewRef(value: object, path: string[]) {
if (!this.refFirstSeenPath.has(value)) {
this.registerKnownRef(value, path);
return null;
}
// Did already see this ref, so it's circular
return this.registerCircularRef(value);
}
getCircularRefAlias(value: object): CircularRefAlias | null {
const ref = this.getCircularRefId(value);
if (ref === null) {
return null;
}
return { $$ref: ref };
}
get hasCircularRefs(): boolean {
return this.circularRefIds.size > 0;
}
getRefIdFirstSeenPath(refId: number): string[] | null {
const ref = this.circularRefIdRef.get(refId);
if (!ref) return null;
return this.refFirstSeenPath.get(ref) ?? null;
}
getRefId(ref: object): number | null {
return this.circularRefIds.get(ref) ?? null;
}
}
import { Coder, coder } from "./Coder";
import { describe, expect, it } from "vitest";
import { JSONValue } from "./types";
function createUInt8Array(length: number) {
const array = new Uint8Array(length);
for (let i = 0; i < length; i++) {
array[i] = i % 256;
}
return array;
}
function expectSerializeAndDeserialize(
value: unknown,
encodedShape?: JSONValue
) {
const encoded = coder.encode(value);
if (encodedShape) {
expect(encoded).toEqual(encodedShape);
}
const decoded = coder.decode(encoded);
expect(decoded).toEqual(value);
}
describe("basic", () => {
it("should encode a simple object", () => {
const date = new Date("2025-01-01T00:00:00.000Z");
const foo = coder.encode(date);
expect(coder.encode(date)).toEqual({ $$date: date.toISOString() });
expect(coder.decode({ $$date: date.toISOString() })).toEqual(date);
expect(coder.encode(/bar/g)).toEqual({ $$regexp: ["bar", "g"] });
expect(coder.decode({ $$regexp: ["bar", "g"] })).toEqual(/bar/g);
expect(coder.encode(new Map([["foo", "bar"]]))).toEqual({
$$map: [["foo", "bar"]],
});
expect(coder.decode({ $$map: [["foo", "bar"]] })).toEqual(
new Map([["foo", "bar"]])
);
expect(coder.encode(new Set(["foo", "bar"]))).toEqual({
$$set: ["foo", "bar"],
});
expect(coder.decode({ $$set: ["foo", "bar"] })).toEqual(
new Set(["foo", "bar"])
);
expect(coder.encode([undefined])).toEqual([{ $$undefined: null }]);
expect(coder.decode([{ $$undefined: null }])).toEqual([undefined]);
expect(coder.encode(createUInt8Array(10))).toEqual({
$$typedArray: { type: "uint8", data: Array.from(createUInt8Array(10)) },
});
expect(
coder.decode({
$$typedArray: { type: "uint8", data: Array.from(createUInt8Array(10)) },
})
).toEqual(createUInt8Array(10));
expect(coder.encode(NaN)).toEqual({ $$num: "NaN" });
expect(coder.decode({ $$num: "NaN" })).toEqual(NaN);
});
it("more coding examples", () => {
expectSerializeAndDeserialize(new Date("2025-01-01T00:00:00.000Z"));
expectSerializeAndDeserialize(NaN, { $$num: "NaN" });
expectSerializeAndDeserialize(new Date("invalid"), { $$date: null });
expectSerializeAndDeserialize(Infinity, { $$num: "Infinity" });
expectSerializeAndDeserialize(-Infinity, { $$num: "-Infinity" });
expectSerializeAndDeserialize(0.5);
expectSerializeAndDeserialize(123n, { $$bigInt: "123" });
expectSerializeAndDeserialize(BigInt(123), { $$bigInt: "123" });
expectSerializeAndDeserialize(Symbol.for("foo"), { $$symbol: "foo" });
expectSerializeAndDeserialize(Symbol("foo"), { $$symbol: "foo" });
expectSerializeAndDeserialize(
{ foo: undefined },
{ foo: { $$undefined: null } }
);
expectSerializeAndDeserialize(/bar/g, { $$regexp: ["bar", "g"] });
});
it("should encode nested objects", () => {
expect(coder.encode(new Set([new Date("2025-01-01T00:00:00.000Z")])))
.toMatchInlineSnapshot(`
{
"$$set": [
{
"$$date": "2025-01-01T00:00:00.000Z",
},
],
}
`);
});
it("should decode nested objects", () => {
expect(
coder.decode({
$$set: [{ $$date: "2025-01-01T00:00:00.000Z" }],
})
).toEqual(new Set([new Date("2025-01-01T00:00:00.000Z")]));
});
});
describe("circular references", () => {
it("should encode circular references (top)", () => {
const foo = { text: "foo", self: null as any };
foo.self = foo;
expect(coder.encode(foo)).toEqual({
"$$ref:0": {
text: "foo",
self: { $$ref: 0 },
},
});
});
it("should encode circular references", () => {
const foo = { foo: "foo", bar: null as any };
const bar = { foo: foo };
foo.bar = bar;
expect(coder.encode(foo)).toEqual({
"$$ref:0": {
foo: "foo",
bar: { foo: { $$ref: 0 } },
},
});
});
it.todo("should properly encode same level circular references", () => {
const foo: any = {};
const bar: any = {};
foo.bar = bar;
bar.foo = foo;
expect(coder.encode([foo, bar])).toEqual([{ $$ref: 0 }, { $$ref: 1 }]);
});
});
describe("custom types", () => {
it("should encode custom types", () => {
const coder = new Coder();
class Foo {
constructor(public name: string) {}
}
coder.addType(
"Foo",
(value) => value instanceof Foo,
(value) => value.name,
(name) => new Foo(name)
);
expect(coder.encode(new Foo("bar"))).toEqual({
$$Foo: "bar",
});
expect(coder.decode({ $$Foo: "bar" })).toEqual(new Foo("bar"));
});
it("custom types can have nested custom objects", () => {
const coder = new Coder();
class User {
constructor(readonly logins: Set<Date>) {}
}
coder.addType(
"User",
(value) => value instanceof User,
(value) => value.logins,
(logins) => new User(logins)
);
expect(
coder.encode(new User(new Set([new Date("2025-01-01T00:00:00.000Z")])))
).toEqual({
$$User: { $$set: [{ $$date: "2025-01-01T00:00:00.000Z" }] },
});
expect(
coder.decode({
$$User: { $$set: [{ $$date: "2025-01-01T00:00:00.000Z" }] },
})
).toEqual(new User(new Set([new Date("2025-01-01T00:00:00.000Z")])));
});
});
describe("plain json", () => {
it("serializes to json the same way as normal json", () => {
const a: JSONValue = [
1,
2,
3,
{ foo: "bar" },
{ baz: false, qux: null, quux: true },
{ a: 1, b: [4, 5, 6, null] },
];
expect(coder.encode(a)).toEqual(a);
expect(coder.decode(a)).toEqual(a);
expect(coder.encode(null)).toEqual(null);
expect(coder.decode(true)).toEqual(true);
expect(coder.decode(false)).toEqual(false);
expect(coder.decode(1)).toEqual(1);
expect(coder.decode(2.5)).toEqual(2.5);
expect(coder.decode("foo")).toEqual("foo");
expect(coder.encode([])).toEqual([]);
expect(coder.encode({})).toEqual({});
});
});
describe("errors", () => {
it("should throw an error if a type is registered twice", () => {
const coder = new Coder();
coder.addType(
"foo",
(value) => typeof value === "string",
(value) => value,
(value) => value
);
expect(() => {
coder.addType(
"foo",
(value) => typeof value === "string",
(value) => value,
(value) => value
);
}).toThrowErrorMatchingInlineSnapshot(
`[Error: Coder type "foo" already registered]`
);
});
});
describe("stringify/parse", () => {
it("should stringify and parse the same way as normal json", () => {
const a = [
1,
2,
3,
{ foo: "bar" },
{ baz: false, qux: null, quux: true },
{ a: 1, b: [4, 5, 6, null] },
];
expect(coder.stringify(a)).toEqual(JSON.stringify(a));
expect(coder.parse(coder.stringify(a))).toEqual(a);
});
it("should stringify and parse custom types", () => {
const coder = new Coder();
class Foo {
constructor(public name: string) {}
}
coder.addType(
"Foo",
(value) => value instanceof Foo,
(value) => value.name,
(name) => new Foo(name)
);
expect(coder.stringify(new Foo("bar"))).toEqual(
JSON.stringify({ $$Foo: "bar" })
);
expect(coder.parse(JSON.stringify({ $$Foo: "bar" }))).toEqual(
new Foo("bar")
);
});
});
describe("isDefault", () => {
it("should return true if the coder is the default coder", () => {
expect(coder.isDefault).toBe(true);
const otherCoder = new Coder();
expect(otherCoder.isDefault).toBe(false);
});
it("should throw an error if a type is registered on the default coder", () => {
expect(() => {
coder.addType(
"foo",
(value) => typeof value === "string",
(value) => value,
(value) => value
);
}).toThrowErrorMatchingInlineSnapshot(
`[Error: Cannot register types on the default coder. Create a custom coder instance using \`new Coder()\` and register types on that instance.]`
);
});
});
{
"compilerOptions": {
"target": "ESNext",
"module": "ESNext",
"moduleResolution": "node",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"declaration": true,
"outDir": "./dist",
"rootDir": "./",
"types": ["vitest/globals", "node"]
},
"include": ["**/*.ts"],
"exclude": ["node_modules", "dist"]
}
export type JSONPrimitive = string | number | boolean | null;
export type JSONObject = { [key: string]: JSONValue };
export type JSONArray = JSONValue[];
export type JSONValue = JSONPrimitive | JSONObject | JSONArray;
export type Primitive = string | number | boolean | null | undefined;
export type ClassConstructor<T> = new (...args: any) => T;
export type AtLeastOne<T> = [T, ...T[]];
/**
* A class that can be instantiated without any arguments.
*/
export type AutoCodableClass<T> = new () => T;
/**
* A class that requires arguments to be known in order to be instantiated.
*/
export type ManuallyCodableClass<T> = new (...args: AtLeastOne<any>) => T;
export type AnyCodableClass<T> = AutoCodableClass<T> | ManuallyCodableClass<T>;
import { JSONObject, JSONValue } from "./types";
type SanitizablePathSegment = string | number;
function sanitizePathSegment(segment: SanitizablePathSegment): string {
if (typeof segment === "number") {
return `${segment}`;
}
return segment;
}
export type PropertyPath = string[];
export function sanitizePath(path: SanitizablePathSegment[]): PropertyPath {
return path.map(sanitizePathSegment);
}
export function changeInJSONByMutation(
input: JSONValue,
propertyPath: PropertyPath,
valueChanger: (current: JSONValue) => JSONValue
): JSONValue {
if (propertyPath.length === 0) {
return valueChanger(input);
}
let pointer = input;
for (let i = 0; i < propertyPath.length; i++) {
const propertyName = propertyPath[i];
const isLastProperty = i === propertyPath.length - 1;
const propertyValue = Reflect.get(
pointer as JSONObject,
propertyName
) as JSONObject;
if (!isLastProperty) {
pointer = propertyValue;
continue;
}
Reflect.set(
pointer as JSONObject,
propertyName,
valueChanger(propertyValue)
);
return pointer;
}
return pointer;
}
import { defineConfig } from "vitest/config";
export default defineConfig({
test: {
globals: true,
environment: "node",
},
});