serialize-function 2.0.0 → 2.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +1 -1
  2. package/lib/main.js +1 -1
  3. package/package.json +13 -13
package/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
  ![ci status](https://github.com/EvanK/npm-serialize-function/actions/workflows/ci.yaml/badge.svg)
5
5
  ](https://github.com/EvanK/npm-serialize-function/actions/workflows/ci.yaml)
6
6
  [
7
- ![Node.js supported and tested on v20 through v26](https://img.shields.io/badge/Node.js-v20%20--%20v26-seagreen?logo=nodedotjs "Node.js supported and tested on v20 through v26")
7
+ ![Node.js supported and tested on v20 through v26](https://img.shields.io/badge/Node.js-v22%20--%20v26-seagreen?logo=nodedotjs "Node.js supported and tested on v22 through v26")
8
8
  ](https://nodejs.org/en/about/previous-releases)
9
9
  [
10
10
  ![ECMAScript standard supported as of ES2023](https://img.shields.io/badge/ES-2023-dodgerblue "ECMAScript standard supported as of ES2023")
package/lib/main.js CHANGED
@@ -1 +1 @@
1
- class JsonError extends Error{};class CryptoError extends Error{};class SerializeError extends Error{};class DeserializeError extends Error{};class ChecksumError extends Error{};class ConstructError extends Error{};const AsyncFunction=async function(){}.constructor;const Generator=function*(){}.constructor;const AsyncGenerator=async function*(){}.constructor;const formatPatterns={"Generator":/^(?<isAsync>async\s+)?function\*\s*[^()]*\((?<params>[^)]*)\)\s*{(?<body>[\s\S]*)}$/,"Function":/^(?<isAsync>async\s+)?function\s*[^()]*\((?<params>[^)]*)\)\s*{(?<body>[\s\S]*)}$/,"ArrowFunction":/^(?<isAsync>async\s+)?(?:\((?<params>[^)]*)\)|(?<singleParam>[^=\s(]+))\s*=>\s*(?:{(?<bracedBody>[\s\S]*)}|(?<bodyExpr>[\s\S]+))$/};async function hasher(obj){let json,hashed;try{json=JSON.stringify(obj)}catch(cause){throw new JsonError("Failed to stringify serialized function structure",{cause})}try{const hashBuffer=await(globalThis?.crypto?.subtle??window?.crypto?.subtle).digest("SHA-256",new TextEncoder().encode(json));hashed=Array.from(new Uint8Array(hashBuffer)).map(item=>item.toString(16).padStart(2,"0")).join("")}catch(cause){throw new CryptoError("Failed to generate hash digest",{cause})}return hashed}function getConstructor(type){switch(type){case"Function":case"ArrowFunction":return Function;case"AsyncFunction":case"AsyncArrowFunction":return AsyncFunction;case"Generator":return Generator;case"AsyncGenerator":return AsyncGenerator;default:throw new ConstructError(`Unexpected type ${type}`)}}function removeComments(input){let[...output]=`__${input}__`;const mode={singleQuote:false,doubleQuote:false,regex:false,blockComment:false,lineComment:false};for(let i=0,l=output.length;i<l;i++){if(mode.regex){if(output[i]==="/"&&output[i-1]!=="\\")mode.regex=false;continue}if(mode.singleQuote){if(output[i]==="'"&&output[i-1]!=="\\")mode.singleQuote=false;continue}if(mode.doubleQuote){if(output[i]==="\""&&output[i-1]!=="\\")mode.doubleQuote=false;continue}if(mode.blockComment){if(output[i]==="*"&&output[i+1]==="/"){output[i+1]="";mode.blockComment=false}output[i]="";continue}if(mode.lineComment){if(output[i+1]==="\n"||output[i+1]==="\r")mode.lineComment=false;output[i]="";continue}mode.doubleQuote=output[i]==="\"";mode.singleQuote=output[i]==="'";if(output[i]==="/"){if(output[i+1]==="*"){output[i]="";mode.blockComment=true;continue}if(output[i+1]==="/"){output[i]="";mode.lineComment=true;continue}mode.regex=true}}return output.join("").slice(2,-2)}async function serialize(func,opts){const def={hash:false,comments:false,whitespace:false};opts=typeof opts==="object"&&null!==opts?Object.assign({},def,opts):Object.assign({},def);const typed=typeof func;if(typed!=="function"){throw new SerializeError("Invalid argument type, must be a function",{cause:{"typeof":typed}})}let stringified=func.toString();if(!opts.comments){stringified=removeComments(stringified)}if(!opts.whitespace){stringified=stringified.split(/[\r\n]+/).map(line=>line.trim()).filter(line=>line!=="").join("\n")}let match,serialized;for(const[type,pattern]of Object.entries(formatPatterns)){try{match=stringified.match(pattern);if(match){let async=match.groups.isAsync?"Async":"";let params=type==="ArrowFunction"?match.groups.params??match.groups.singleParam:match.groups.params;params=params.split(",").map(p=>opts.whitespace?p:p.trim()).filter(Boolean);let body=type==="ArrowFunction"?match.groups.bracedBody??`return (${match.groups.bodyExpr});`:match.groups.body;if(!opts.whitespace)body=body.trim();serialized={params,body,type:`${async}${type}`};break}}catch(cause){throw new SerializeError(`Unexpected error serializing ${type}`,{cause})}}if(!serialized){throw new SerializeError("Unsupported function format",{cause:stringified})}if(opts.hash){try{const hashed=await hasher(serialized);serialized.hash=hashed}catch(cause){throw new SerializeError("Failure hashing serialized function",{cause})}}return serialized}async function deserialize(struct){let opts=arguments.length>1&&arguments[1]!==undefined?arguments[1]:{hash:false};if(opts?.hash){if(struct?.hash===undefined){throw new DeserializeError("Deserialized function missing hash")}const test=Object.assign({},struct);delete test.hash;try{const checksum=await hasher(test);if(checksum!==struct.hash){throw new ChecksumError("Checksum failed",{cause:{a:checksum,b:struct.hash}})}}catch(cause){if(cause instanceof ChecksumError)throw cause;throw new DeserializeError("Failure generating checksum",{cause})}}try{const constructor=getConstructor(struct.type);return new constructor(...struct.params,struct.body)}catch(cause){if(cause instanceof ConstructError)throw cause;throw new DeserializeError("Failure deserializing",{cause})}}async function traverse(input,tester,converter){if(tester(input)){return await converter(input)}if(input===null||typeof input!=="object"){return input}const nonTraversables=[String,Boolean,Number,Date,RegExp];for(const constructor of nonTraversables){if(input instanceof constructor){return new constructor(input.valueOf())}}if(input instanceof Array){const cloned=[];for(let i=0;i<input.length;i++){if(tester(input[i]))cloned[i]=await converter(input[i]);else cloned[i]=await traverse(input[i],tester,converter)}return cloned}if(input instanceof Set){const cloned=new Set;for(const value of input){if(tester(value))cloned.add(await converter(value));else cloned.add(await traverse(value,tester,converter))}return cloned}if(input instanceof Map){const cloned=new Map;for(const[key,value]of input){if(tester(value))cloned.set(key,await converter(value));else cloned.set(key,await traverse(value,tester,converter))}return cloned}if(input instanceof Object){const cloned=Object.create(Object.getPrototypeOf(input));for(const key in input){if(Object.hasOwn(input,key)){if(tester(input[key]))cloned[key]=await converter(input[key]);else cloned[key]=await traverse(input[key],tester,converter)}}return cloned}return input}async function deepSerialize(value,options){try{return await traverse(value,input=>typeof input==="function",input=>serialize(input,options))}catch(cause){throw new SerializeError("Failure traversing and serializing",{cause})}}async function deepDeserialize(value,options){try{return await traverse(value,input=>typeof input==="object"&&Object.hasOwn(input,"params")&&Object.hasOwn(input,"body")&&Object.hasOwn(input,"type"),input=>deserialize(input,Object.assign({hash:Object.hasOwn(input,"hash")},options)))}catch(cause){throw new DeserializeError("Failure traversing and deserializing",{cause})}}export{serialize,deserialize,deepSerialize,deepDeserialize,JsonError,CryptoError,SerializeError,DeserializeError,ChecksumError,ConstructError};
1
+ class JsonError extends Error{};class CryptoError extends Error{};class SerializeError extends Error{};class DeserializeError extends Error{};class ChecksumError extends Error{};class ConstructError extends Error{};const AsyncFunction=async function(){}.constructor;const Generator=function*(){}.constructor;const AsyncGenerator=async function*(){}.constructor;const formatPatterns={"Generator":/^(?<isAsync>async\s+)?function\*\s*[^()]*\((?<params>[^)]*)\)\s*{(?<body>[\s\S]*)}$/,"Function":/^(?<isAsync>async\s+)?function\s*[^()]*\((?<params>[^)]*)\)\s*{(?<body>[\s\S]*)}$/,"ArrowFunction":/^(?<isAsync>async\s+)?(?:\((?<params>[^)]*)\)|(?<singleParam>[^=\s(]+))\s*=>\s*(?:{(?<bracedBody>[\s\S]*)}|(?<bodyExpr>[\s\S]+))$/};async function hasher(obj){let json,hashed;try{json=JSON.stringify(obj)}catch(cause){throw new JsonError("Failed to stringify serialized function structure",{cause})}try{const hashBuffer=await(globalThis?.crypto?.subtle??window?.crypto?.subtle).digest("SHA-256",new TextEncoder().encode(json));hashed=Array.from(new Uint8Array(hashBuffer)).map(item=>item.toString(16).padStart(2,"0")).join("")}catch(cause){throw new CryptoError("Failed to generate hash digest",{cause})}return hashed}function getConstructor(type){switch(type){case"Function":case"ArrowFunction":return Function;case"AsyncFunction":case"AsyncArrowFunction":return AsyncFunction;case"Generator":return Generator;case"AsyncGenerator":return AsyncGenerator;default:throw new ConstructError(`Unexpected type ${type}`)}}function removeComments(input){let[...output]=`__${input}__`;const mode={singleQuote:false,doubleQuote:false,regex:false,blockComment:false,lineComment:false};for(let i=0,l=output.length;i<l;i++){if(mode.regex){if(output[i]==="/"&&output[i-1]!=="\\")mode.regex=false;continue}if(mode.singleQuote){if(output[i]==="'"&&output[i-1]!=="\\")mode.singleQuote=false;continue}if(mode.doubleQuote){if(output[i]==="\""&&output[i-1]!=="\\")mode.doubleQuote=false;continue}if(mode.blockComment){if(output[i]==="*"&&output[i+1]==="/"){output[i+1]="";mode.blockComment=false}output[i]="";continue}if(mode.lineComment){if(output[i+1]==="\n"||output[i+1]==="\r")mode.lineComment=false;output[i]="";continue}mode.doubleQuote=output[i]==="\"";mode.singleQuote=output[i]==="'";if(output[i]==="/"){if(output[i+1]==="*"){output[i]="";mode.blockComment=true;continue}if(output[i+1]==="/"){output[i]="";mode.lineComment=true;continue}mode.regex=true}}return output.join("").slice(2,-2)}async function serialize(func,opts){const def={hash:false,comments:false,whitespace:false};opts=typeof opts==="object"&&null!==opts?Object.assign({},def,opts):Object.assign({},def);const typed=typeof func;if(typed!=="function"){throw new SerializeError("Invalid argument type, must be a function",{cause:{"typeof":typed}})}let stringified=func.toString();if(!opts.comments){stringified=removeComments(stringified)}if(!opts.whitespace){stringified=stringified.split(/[\r\n]+/).map(line=>line.trim()).filter(line=>line!=="").join("\n")}let match,serialized;for(const[type,pattern]of Object.entries(formatPatterns)){try{match=stringified.match(pattern);if(match){let async=match.groups.isAsync?"Async":"";let params=type==="ArrowFunction"?match.groups.params??match.groups.singleParam:match.groups.params;params=params.split(",").map(p=>opts.whitespace?p:p.trim()).filter(Boolean);let body=type==="ArrowFunction"?match.groups.bracedBody??`return (${match.groups.bodyExpr});`:match.groups.body;if(!opts.whitespace)body=body.trim();serialized={params,body,type:`${async}${type}`};break}}catch(cause){throw new SerializeError(`Unexpected error serializing ${type}`,{cause})}}if(!serialized){throw new SerializeError("Unsupported function format",{cause:stringified})}if(opts.hash){try{const hashed=await hasher(serialized);serialized.hash=hashed}catch(cause){throw new SerializeError("Failure hashing serialized function",{cause})}}return serialized}async function deserialize(struct,opts={hash:false}){if(opts?.hash){if(struct?.hash===undefined){throw new DeserializeError("Deserialized function missing hash")}const test=Object.assign({},struct);delete test.hash;try{const checksum=await hasher(test);if(checksum!==struct.hash){throw new ChecksumError("Checksum failed",{cause:{a:checksum,b:struct.hash}})}}catch(cause){if(cause instanceof ChecksumError)throw cause;throw new DeserializeError("Failure generating checksum",{cause})}}try{const constructor=getConstructor(struct.type);return new constructor(...struct.params,struct.body)}catch(cause){if(cause instanceof ConstructError)throw cause;throw new DeserializeError("Failure deserializing",{cause})}}async function traverse(input,tester,converter){if(tester(input)){return await converter(input)}if(input===null||typeof input!=="object"){return input}const nonTraversables=[String,Boolean,Number,Date,RegExp];for(const constructor of nonTraversables){if(input instanceof constructor){return new constructor(input.valueOf())}}if(input instanceof Array){const cloned=[];for(let i=0;i<input.length;i++){if(tester(input[i]))cloned[i]=await converter(input[i]);else cloned[i]=await traverse(input[i],tester,converter)}return cloned}if(input instanceof Set){const cloned=new Set;for(const value of input){if(tester(value))cloned.add(await converter(value));else cloned.add(await traverse(value,tester,converter))}return cloned}if(input instanceof Map){const cloned=new Map;for(const[key,value]of input){if(tester(value))cloned.set(key,await converter(value));else cloned.set(key,await traverse(value,tester,converter))}return cloned}if(input instanceof Object){const cloned=Object.create(Object.getPrototypeOf(input));for(const key in input){if(Object.hasOwn(input,key)){if(tester(input[key]))cloned[key]=await converter(input[key]);else cloned[key]=await traverse(input[key],tester,converter)}}return cloned}return input}async function deepSerialize(value,options){try{return await traverse(value,input=>typeof input==="function",input=>serialize(input,options))}catch(cause){throw new SerializeError("Failure traversing and serializing",{cause})}}async function deepDeserialize(value,options){try{return await traverse(value,input=>typeof input==="object"&&Object.hasOwn(input,"params")&&Object.hasOwn(input,"body")&&Object.hasOwn(input,"type"),input=>deserialize(input,Object.assign({hash:Object.hasOwn(input,"hash")},options)))}catch(cause){throw new DeserializeError("Failure traversing and deserializing",{cause})}}export{serialize,deserialize,deepSerialize,deepDeserialize,JsonError,CryptoError,SerializeError,DeserializeError,ChecksumError,ConstructError};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "serialize-function",
3
- "version": "2.0.0",
3
+ "version": "2.0.2",
4
4
  "description": "Serializes javascript functions to a JSON-friendly format",
5
5
  "author": "Evan Kaufman <evan@evanskaufman.com>",
6
6
  "license": "MIT",
@@ -45,23 +45,23 @@
45
45
  "test": "npm run clean && npm run dist && npm run prepare-test-node && npm run test-node && npm run prepare-test-browser && npm run test-browser"
46
46
  },
47
47
  "devDependencies": {
48
- "@babel/cli": "^7.28.6",
49
- "@babel/core": "^7.29.0",
50
- "@babel/plugin-transform-nullish-coalescing-operator": "^7.28.6",
51
- "@babel/preset-env": "^7.29.0",
48
+ "@babel/cli": "^8.0.4",
49
+ "@babel/core": "^8.0.1",
50
+ "@babel/plugin-transform-nullish-coalescing-operator": "^8.0.1",
51
+ "@babel/preset-env": "^8.0.2",
52
52
  "@eslint/js": "^10.0.1",
53
- "@fastify/static": "^9.0.0",
53
+ "@fastify/static": "^10.1.3",
54
54
  "@stylistic/eslint-plugin": "^5.10.0",
55
55
  "chai": "^6.2.2",
56
56
  "classy-template": "^1.5.4",
57
- "eslint": "^10.0.3",
58
- "eslint-plugin-mocha": "^11.2.0",
59
- "fastify": "^5.8.2",
60
- "globals": "^17.4.0",
57
+ "eslint": "^10.9.1",
58
+ "eslint-plugin-mocha": "^12.0.2",
59
+ "fastify": "^5.12.1",
60
+ "globals": "^17.11.0",
61
61
  "jsdoc": "^4.0.5",
62
- "mocha": "^11.7.5",
62
+ "mocha": "^11.8.0",
63
63
  "proxyquire": "^2.1.3",
64
- "puppeteer": "^24.38.0",
65
- "sinon": "^21.0.2"
64
+ "puppeteer": "^25.9.0",
65
+ "sinon": "^22.1.0"
66
66
  }
67
67
  }