Spaces:
Paused
Paused
| import { | |
| VOID, PRIMITIVE, | |
| ARRAY, OBJECT, | |
| DATE, REGEXP, MAP, SET, | |
| ERROR, BIGINT | |
| } from './types.js'; | |
| const EMPTY = ''; | |
| const {toString} = {}; | |
| const {keys} = Object; | |
| const typeOf = value => { | |
| const type = typeof value; | |
| if (type !== 'object' || !value) | |
| return [PRIMITIVE, type]; | |
| const asString = toString.call(value).slice(8, -1); | |
| switch (asString) { | |
| case 'Array': | |
| return [ARRAY, EMPTY]; | |
| case 'Object': | |
| return [OBJECT, EMPTY]; | |
| case 'Date': | |
| return [DATE, EMPTY]; | |
| case 'RegExp': | |
| return [REGEXP, EMPTY]; | |
| case 'Map': | |
| return [MAP, EMPTY]; | |
| case 'Set': | |
| return [SET, EMPTY]; | |
| } | |
| if (asString.includes('Array')) | |
| return [ARRAY, asString]; | |
| if (asString.includes('Error')) | |
| return [ERROR, asString]; | |
| return [OBJECT, asString]; | |
| }; | |
| const shouldSkip = ([TYPE, type]) => ( | |
| TYPE === PRIMITIVE && | |
| (type === 'function' || type === 'symbol') | |
| ); | |
| const serializer = (strict, json, $, _) => { | |
| const as = (out, value) => { | |
| const index = _.push(out) - 1; | |
| $.set(value, index); | |
| return index; | |
| }; | |
| const pair = value => { | |
| if ($.has(value)) | |
| return $.get(value); | |
| let [TYPE, type] = typeOf(value); | |
| switch (TYPE) { | |
| case PRIMITIVE: { | |
| let entry = value; | |
| switch (type) { | |
| case 'bigint': | |
| TYPE = BIGINT; | |
| entry = value.toString(); | |
| break; | |
| case 'function': | |
| case 'symbol': | |
| if (strict) | |
| throw new TypeError('unable to serialize ' + type); | |
| entry = null; | |
| break; | |
| case 'undefined': | |
| return as([VOID], value); | |
| } | |
| return as([TYPE, entry], value); | |
| } | |
| case ARRAY: { | |
| if (type) | |
| return as([type, [...value]], value); | |
| const arr = []; | |
| const index = as([TYPE, arr], value); | |
| for (const entry of value) | |
| arr.push(pair(entry)); | |
| return index; | |
| } | |
| case OBJECT: { | |
| if (type) { | |
| switch (type) { | |
| case 'BigInt': | |
| return as([type, value.toString()], value); | |
| case 'Boolean': | |
| case 'Number': | |
| case 'String': | |
| return as([type, value.valueOf()], value); | |
| } | |
| } | |
| if (json && ('toJSON' in value)) | |
| return pair(value.toJSON()); | |
| const entries = []; | |
| const index = as([TYPE, entries], value); | |
| for (const key of keys(value)) { | |
| if (strict || !shouldSkip(typeOf(value[key]))) | |
| entries.push([pair(key), pair(value[key])]); | |
| } | |
| return index; | |
| } | |
| case DATE: | |
| return as([TYPE, value.toISOString()], value); | |
| case REGEXP: { | |
| const {source, flags} = value; | |
| return as([TYPE, {source, flags}], value); | |
| } | |
| case MAP: { | |
| const entries = []; | |
| const index = as([TYPE, entries], value); | |
| for (const [key, entry] of value) { | |
| if (strict || !(shouldSkip(typeOf(key)) || shouldSkip(typeOf(entry)))) | |
| entries.push([pair(key), pair(entry)]); | |
| } | |
| return index; | |
| } | |
| case SET: { | |
| const entries = []; | |
| const index = as([TYPE, entries], value); | |
| for (const entry of value) { | |
| if (strict || !shouldSkip(typeOf(entry))) | |
| entries.push(pair(entry)); | |
| } | |
| return index; | |
| } | |
| } | |
| const {message} = value; | |
| return as([TYPE, {name: type, message}], value); | |
| }; | |
| return pair; | |
| }; | |
| /** | |
| * @typedef {Array<string,any>} Record a type representation | |
| */ | |
| /** | |
| * Returns an array of serialized Records. | |
| * @param {any} value a serializable value. | |
| * @param {{json?: boolean, lossy?: boolean}?} options an object with a `lossy` or `json` property that, | |
| * if `true`, will not throw errors on incompatible types, and behave more | |
| * like JSON stringify would behave. Symbol and Function will be discarded. | |
| * @returns {Record[]} | |
| */ | |
| export const serialize = (value, {json, lossy} = {}) => { | |
| const _ = []; | |
| return serializer(!(json || lossy), !!json, new Map, _)(value), _; | |
| }; | |