import { domainOf, type inferDomain, type Primitive } from "./domains.js" import type { List } from "./lists.js" import type { BigintLiteral, NumberLiteral } from "./numericLiterals.js" import type { Dict } from "./records.js" export type SerializationOptions = { onCycle?: (value: object) => string onSymbol?: (value: symbol) => string onFunction?: (value: Function) => string onUndefined?: string } export const snapshot = ( data: t, opts: SerializationOptions = { onUndefined: "(undefined)" } ) => serializeRecurse(data, opts, []) as snapshot export type snapshot = unknown extends t ? unknown : t extends Primitive ? snapshotPrimitive : t extends Function ? `(function${string})` : t extends Date ? string : depth["length"] extends 10 ? unknown : t extends List ? List> : { [k in keyof t]: snapshot } type snapshotPrimitive = t extends undefined ? "(undefined)" : t extends bigint ? `${t}n` : t extends symbol ? `(symbol${string})` : t export const print = (data: unknown, indent?: number) => console.log(stringify(data, indent)) export const stringify = (data: unknown, indent?: number) => { switch (domainOf(data)) { case "object": return JSON.stringify( serializeRecurse(data, stringifyOpts, []), null, indent ) case "symbol": return stringifyOpts.onSymbol(data as symbol) default: return serializePrimitive(data as SerializablePrimitive) } } const stringifyOpts = { onCycle: () => "(cycle)", onSymbol: (v) => `(symbol ${v.description ?? "anonymous"})`, onFunction: (v) => `(function ${v.name ?? "anonymous"})` } satisfies SerializationOptions const serializeRecurse = ( data: unknown, opts: SerializationOptions, seen: unknown[] ): unknown => { switch (domainOf(data)) { case "object": if (typeof data === "function") { return stringifyOpts.onFunction(data) } if (seen.includes(data)) { return "(cycle)" } const nextSeen = [...seen, data] if (Array.isArray(data)) { return data.map((item) => serializeRecurse(item, opts, nextSeen) ) } if (data instanceof Date) { return data.toDateString() } const result: Record = {} for (const k in data as Dict) { result[k] = serializeRecurse((data as any)[k], opts, nextSeen) } return result case "symbol": return stringifyOpts.onSymbol(data as symbol) case "bigint": return `${data}n` case "undefined": return opts.onUndefined ?? "undefined" default: return data } } type SerializedString = `"${value}"` export type SerializedPrimitives = { string: SerializedString number: NumberLiteral bigint: BigintLiteral boolean: "true" | "false" null: "null" undefined: "undefined" } export type SerializedPrimitive = SerializedPrimitives[keyof SerializedPrimitives] export type SerializablePrimitive = inferDomain export const serializePrimitive = ( value: value ) => (typeof value === "string" ? JSON.stringify(value) : typeof value === "bigint" ? `${value}n` : `${value}`) as serializePrimitive export type serializePrimitive = value extends string ? `"${value}"` : value extends bigint ? `${value}n` : `${value}`