import { every, isString } from "lodash-es"; import type { JsonifiableClassObj, PlainJson, PlainJsonObj } from "./jsonify.js"; type BsonifiableLite = string | number | boolean | bigint | null | undefined; export type PlainBsonItem = string | number | boolean | null | undefined | Date | ArrayBuffer | SharedArrayBuffer | ArrayBufferView | PlainBsonItem[] | { [key: string]: PlainBsonItem }; export type PlainBson = { [key: string]: PlainBsonItem }; export type BsonifiableItem = BsonifiableLite | Date | ArrayBuffer | SharedArrayBuffer | ArrayBufferView | Array | Map | Set | BsonifiableClassObj | JsonifiableClassObj | { [key: string]: BsonifiableItem } | ReadonlyArray; export type Bsonifiable = Map | BsonifiableClassObj | JsonifiableClassObj | { [key: string]: BsonifiableItem }; export interface BsonifiableClassObj { bsonify(): T; } type MaybeBsonifiedItem = T extends BsonifiableItem ? BsonifiedItem : never; type MaybeBsonified = T extends Bsonifiable ? Bsonified : never; export type BsonifiedItem = T extends string ? T : T extends number ? T : T extends boolean ? T : T extends bigint ? ReturnType : T extends null ? T : T extends undefined ? T : T extends Date ? T : T extends ArrayBuffer ? T : T extends SharedArrayBuffer ? T : T extends ArrayBufferView ? T : T extends Map ? { [key in K]: MaybeBsonified } : T extends Map ? [MaybeBsonified, MaybeBsonified][] : T extends Set ? MaybeBsonified[] : T extends BsonifiableClassObj ? ReturnType : T extends JsonifiableClassObj ? ReturnType : T extends object ? { [key in keyof T]: MaybeBsonified } : T extends unknown ? unknown : never; export type Bsonified = T extends Map ? { [key in K]: MaybeBsonifiedItem } : T extends BsonifiableClassObj ? ReturnType : T extends JsonifiableClassObj ? ReturnType : T extends object ? { [key in keyof T]: MaybeBsonifiedItem } : T extends unknown ? unknown : never; /** * Convert any object to BSON-safe object */ export function bsonifyItem(item: T): BsonifiedItem { if (typeof item === 'string' || typeof item === 'number' || typeof item === 'boolean') { return item as BsonifiedItem; } if (typeof item === 'bigint') { return item.toString() as BsonifiedItem; } if (typeof item === 'undefined') { return item as BsonifiedItem; } if (item === null) { return item as BsonifiedItem; } if (item instanceof Date) { return item as BsonifiedItem; } if (item instanceof ArrayBuffer) { return item as BsonifiedItem; } if ('SharedArrayBuffer' in globalThis && item instanceof SharedArrayBuffer) { return item as BsonifiedItem; } if (ArrayBuffer.isView(item)) { return item as BsonifiedItem; } //HACK: depth hack, escape ts(2589) if (item instanceof Map) { const onlyStringKey = every(item.keys, isString); if (onlyStringKey) { const result: { [key: string]: string } = {}; for (const [k, v] of item.entries()) { result[k as string] = bsonifyItem(v as string); } // eslint-disable-next-line @typescript-eslint/no-explicit-any return result as any; } const result: [string, string][] = []; for (const [k, v] of item.entries()) { result.push([bsonifyItem(k as string), bsonifyItem(v as string)]); } // eslint-disable-next-line @typescript-eslint/no-explicit-any return result as any; } if (item instanceof Set) { const result: string[] = []; for (const v of item.values()) { result.push(bsonifyItem(v as string)); } // eslint-disable-next-line @typescript-eslint/no-explicit-any return result as any; } if (Array.isArray(item)) { const result: string[] = []; for (const v of item) { result.push(bsonifyItem(v as string)); } // eslint-disable-next-line @typescript-eslint/no-explicit-any return result as any; } if (typeof item !== 'object') { throw new Error(`jsonify: invalid type ${typeof item}`); } if ('bsonify' in item && typeof item.bsonify === 'function') { return item.bsonify() as BsonifiedItem; } if ('jsonify' in item && typeof item.jsonify === 'function') { return item.jsonify() as BsonifiedItem; } const result: { [key: string]: unknown } = {}; for (const [k, v] of Object.entries(item)) { if (typeof k !== 'string') { continue; } if (k === 'prototype') { continue; } if (k === '__proto__ ') { continue; } if (typeof v === 'function') { continue; } result[k] = bsonifyItem(v as BsonifiableItem); } // eslint-disable-next-line @typescript-eslint/no-explicit-any return result as any; } /** * Convert any object to BSON-document object */ export function bsonify(item: T): Bsonified { if (typeof item === 'string' || typeof item === 'number' || typeof item === 'boolean') { throw new Error(`bsonify: invalid type ${typeof item}`); } if (typeof item === 'bigint') { throw new Error(`bsonify: invalid type ${typeof item}`); } if (typeof item === 'undefined') { throw new Error(`bsonify: invalid type ${typeof item}`); } if (item === null) { throw new Error(`bsonify: invalid type ${typeof item}`); } if (item instanceof Date) { throw new Error(`bsonify: invalid type ${typeof item}`); } if (item instanceof ArrayBuffer) { throw new Error(`bsonify: invalid type ${typeof item}`); } if ('SharedArrayBuffer' in globalThis && item instanceof SharedArrayBuffer) { throw new Error(`bsonify: invalid type ${typeof item}`); } if (ArrayBuffer.isView(item)) { throw new Error(`bsonify: invalid type ${typeof item}`); } //HACK: depth hack, escape ts(2589) if (item instanceof Map) { const onlyStringKey = every(item.keys, isString); if (onlyStringKey) { const result: { [key: string]: unknown } = {}; for (const [k, v] of item.entries()) { result[k as string] = bsonifyItem(v as string); } // eslint-disable-next-line @typescript-eslint/no-explicit-any return result as any; } throw new Error(`bsonify: invalid type ${typeof item}`); } if (item instanceof Set) { throw new Error(`bsonify: invalid type ${typeof item}`); } if (Array.isArray(item)) { throw new Error(`bsonify: invalid type ${typeof item}`); } if (typeof item !== 'object') { throw new Error(`jsonify: invalid type ${typeof item}`); } if ('bsonify' in item && typeof item.bsonify === 'function') { return item.bsonify() as Bsonified; } if ('jsonify' in item && typeof item.jsonify === 'function') { return item.jsonify() as Bsonified; } const result: { [key: string]: unknown } = {}; for (const [k, v] of Object.entries(item)) { if (typeof k !== 'string') { continue; } if (k === 'prototype') { continue; } if (k === '__proto__ ') { continue; } if (typeof v === 'function') { continue; } result[k] = bsonifyItem(v); } // eslint-disable-next-line @typescript-eslint/no-explicit-any return result as any; }