Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 60 additions & 0 deletions .github/workflows/docs.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
name: Regenerate documentation

on:
workflow_dispatch:
inputs:
tag:
description: 'Tagged version from which to generate docs'
required: false

jobs:
generate-and-commit:
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@v5
with:
fetch-depth: 0
fetch-tags: true
ref: main
- uses: actions/setup-node@v6
with:
node-version: 24.x
cache: 'npm'

- name: Set target as specified input tag
if: ${{ github.event.inputs.tag }} != ''
run: echo "TAG=${{ github.event.inputs.tag }}" >> $GITHUB_OUTPUT
id: from_input
- name: Set target as latest available tag
if: ${{ github.event.inputs.tag }} == ''
run: echo "TAG=$(git describe --tags $(git rev-list --tags --max-count=1))" >> $GITHUB_OUTPUT
id: from_git
- name: Checkout latest tag
run: git checkout ${{ steps.from_input.outputs.tag }}${{ steps.from_git.outputs.tag }}

- run: npm ci

- name: Build html docs
run: npm run docs-html
- name: Checkout docs branch
run: git checkout github-pages

- name: Remove everything in working dir other than .git and docs directories
run: ls -A -1 --ignore '.git' --ignore 'docs' | xargs rm -rf

- name: Move html jsdocs to working dir and remove docs dir
run: |
mv -f docs/* ./
rm -rf docs/

- name: Configure, add, commit and push any changes to docs branch
run: |
git config --global user.email "${{ github.actor }}"
git config --global user.name "${{ github.actor }}"
git add .
git commit -m "Docs updated -- $(date)"
git push origin github-pages
continue-on-error: true

3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ build-*.*js
lib/
dist/

# jsdoc stuff
docs/

# node testing
tests/*.ignore.test.cjs

Expand Down
97 changes: 90 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,15 @@
![npm](https://nodei.co/npm/serialize-function.png)
](https://www.npmjs.com/package/serialize-function)

---

- [Quickstart](#quickstart)
- [Deep serialization](#deep-serialization)
- [Hashing](#hashing)
- [Whitespace and comments](#whitespace-and-comments)
- [Function type support](#function-type-support)
- [Changelog](#changelog)


## Quickstart

Expand All @@ -30,7 +39,7 @@ Serializes javascript functions to a JSON-encodable object suitable for storage
```js
function doTheThing(a,b,c,d,e) { return a + b * c / d % e; }

const obj = serialize(doTheThing);
const obj = await serialize(doTheThing);
console.log(obj);
// {
// params: [ 'a', 'b', 'c', 'd', 'e' ],
Expand All @@ -42,17 +51,70 @@ console.log(obj);
Deserializes back into an invokable function:

```js
const func = deserialize(obj);
const func = await deserialize(obj);
console.log( func(1, 2, 3, 4, 5) );
// 2.5
```


## Deep serialization

You may want to deeply serialize _any_ functions nested at arbitrary levels of your data structures. The provided convenience functions will traverse and selectively clone any containing objects, while serializing any functions found:

```js
const { deepSerialize, deepDeserialize } = require('serialize-function');

const original = {
foo: () => 'something',
bar: [
function* (seed = 0) { let n = seed; while(true) { n = n * 2; yield n; } }
],
baz: new Date('2026-01-01')
}

const clone = await deepSerialize(original);
// {
// foo: {
// params: [],
// body: "return ('something');",
// type: 'ArrowFunction'
// },
// bar: [
// {
// params: [ 'seed = 0' ],
// body: 'let n = seed; while(true) { n = n * 2; yield n; }',
// type: 'Generator'
// }
// ],
// baz: 2026-01-01T00:00:00.000Z
// }

// original container and functions remain unmodified
original.foo(); // 'something'
const gen1 = original.bar[0](3.14);
gen1.next().value; // 6.28
gen1.next().value; // 12.56

const restored = await deepDeserialize(clone);
// {
// foo: [Function: anonymous],
// bar: [ [GeneratorFunction: anonymous] ],
// baz: 2026-01-01T00:00:00.000Z
// }

// deserialized functions remain invokable
restored.foo(); // 'something'
const gen2 = restored.bar[0](901364);
gen2.next().value; // 1802728
gen2.next().value; // 3605456
```


## Hashing

Optionally supports SHA256 checksum hashing to prevent MITM tampering:

```js
// note: use of hashing returns a promise
const hashedObj = await serialize(doTheThing, { hash: true });
console.log(hashedObj);
// {
Expand All @@ -69,6 +131,7 @@ const tamperedFunc = await deserialize(hashedObj, { hash: true });

> Under the hood, hashing uses the [SubtleCrypto](https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto) API.


## Whitespace and comments

Line breaks within the function body are preserved and normalized, but all other padding whitespace is removed from the function by default, along with any comments.
Expand Down Expand Up @@ -97,7 +160,7 @@ function thingNumberTwo(
return remainder;
}

const commentedObj = serialize(thingNumberTwo, { whitespace: true, comments: true });
const commentedObj = await serialize(thingNumberTwo, { whitespace: true, comments: true });
console.log(commentedObj);
// {
// params: [ '\n /* marco */\n a', ' b', '\tc', '\n d', 'e/* polo */\n' ],
Expand All @@ -120,12 +183,13 @@ console.log(commentedObj);
// }
```


## Function type support

Arrow functions, generators, and all async variants are supported (contingent on _browser support_ where relevant):

```js
serialize(
await serialize(
(i,j,k) => ({ i, j, k })
);
// {
Expand All @@ -134,7 +198,7 @@ serialize(
// type: 'ArrowFunction'
// }

serialize(
await serialize(
function* (x,y,z) {
yield x;
yield y;
Expand All @@ -147,7 +211,7 @@ serialize(
// type: 'Generator'
// }

serialize(
await serialize(
async (ms) => new Promise(
resolve => setTimeout(resolve, ms)
)
Expand All @@ -158,3 +222,22 @@ serialize(
// type: 'AsyncArrowFunction'
// }
```

> [!NOTE]
> As there is no global `Class` object constructor, there is no way to safely deserialize [ES6 classes](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes).
>
> As such, ES6 classes are _not_ currently supported for serialization.
>
> Alternatively, you can rewrite your classes as functions, or transpile them with tools like [Babel](https://babeljs.io/docs/babel-plugin-transform-classes/).


## Changelog

Any potentially breaking changes will be documented here.

- 1.1.0 - Standardized both node and web builds on SubtleCrypto API
- 1.2.0 - Refactored comment stripping, to address potential regex DOS
- 2.0.0
- Made all exported functions fully async
- Implemented named captures for format patterns
- Implemented deep de/serialization
25 changes: 25 additions & 0 deletions jsdoc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"markdown": {
"idInHeadings": true
},
"opts": {
"template": "classy-template",
"destination": "./docs",
"package": "./package.json",
"readme": "./README.md"
},
"plugins": [
"classy-template/plugin",
"plugins/markdown"
],
"source": {
"includePattern": "main.mjs$"
},
"templates": {
"classy": {
"outputSourceFiles": false,
"showGitLink": true,
"showVersion": true
}
}
}
Loading
Loading