| 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]; | |
|     case 'DataView': | |
|       return [ARRAY, asString]; | |
|   } | |
| 
 | |
|   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) { | |
|           let spread = value; | |
|           if (type === 'DataView') { | |
|             spread = new Uint8Array(value.buffer); | |
|           } | |
|           else if (type === 'ArrayBuffer') { | |
|             spread = new Uint8Array(value); | |
|           } | |
|           return as([type, [...spread]], 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), _; | |
| };
 |